Example #1
0
 def wavelength_must_be_known(
     cls, value
 ):  # pylint:disable=no-self-argument,no-self-use
     """We only allow for anode names for which we know the wavelength"""
     if value not in list(WAVELENGTHS.keys()):
         raise ValueError(
             "Wavelength must be in {}".format(", ".join(list(WAVELENGTHS.keys())))
         )
     return value
Example #2
0
    def get_target_and_edge(cls, values: Dict):
        print("Validations")
        # Only do this if neither target not edge is defined
        if "target" not in values and "edge" not in values:
            print("Are we even getting here?")
            try:
                pymatgen_wavelength = next(
                    k
                    for k, v in WAVELENGTHS.items()
                    if np.allclose(values["wavelength"], v)
                )
                values["target"] = pymatgen_wavelength[:2]
                values["edge"] = pymatgen_wavelength[2:]

            except Exception:
                return values
        return values
Example #3
0
    def all_layouts(self):

        # Main plot
        graph = html.Div(
            [
                dcc.Graph(
                    figure=go.Figure(layout=XRayDiffractionComponent.empty_plot_style),
                    id=self.id("xrd-plot"),
                    config={"displayModeBar": False},
                )
            ]
        )

        # Radiation source selector
        rad_source = html.Div(
            [
                html.P("Radiation Source"),
                dcc.Dropdown(
                    id=self.id("rad-source"),
                    options=[{"label": i, "value": i} for i in WAVELENGTHS.keys()],
                    value=self.initial_xrdcalculator_kwargs["wavelength"],
                    placeholder="Select a source...",
                    clearable=False,
                ),
            ],
            style={"max-width": "200"},
        )

        # Shape factor input
        shape_factor = html.Div(
            [
                html.P("Shape Factor, K "),
                dcc.Input(
                    id=self.id("shape-factor"),
                    placeholder="0.94",
                    type="text",
                    value="0.94",
                ),
            ],
            style={"max-width": "200"},
        )
        # Peak profile selector (Gaussian, Lorentzian, Voigt)
        peak_profile = html.Div(
            [
                html.P("Peak Profile"),
                dcc.Dropdown(
                    id=self.id("peak-profile"),
                    options=[
                        {"label": "Gaussian", "value": "G"},
                        {"label": "Lorentzian", "value": "L"},
                        {"label": "Voigt", "value": "V"},
                    ],
                    value="G",
                    clearable=False,
                ),
            ],
            style={"max-width": "200"},
        )

        # Crystallite size selector (via Scherrer Equation)
        crystallite_size = html.Div(
            [
                html.P("Scherrer Crystallite Size (nm)"),
                html.Div(
                    [
                        dcc.Slider(
                            id=self.id("crystallite-slider"),
                            marks={i: "{}".format(10 ** i) for i in range(-1, 3)},
                            min=-1,
                            max=2,
                            value=0,
                            step=0.01,
                        )
                    ],
                    style={"max-width": "500"},
                ),
                html.Div(
                    [], id=self.id("crystallite-input"), style={"padding-top": "20px"}
                ),
            ]
        )

        return {
            "graph": graph,
            "rad_source": rad_source,
            "peak_profile": peak_profile,
            "shape_factor": shape_factor,
            "crystallite_size": crystallite_size,
        }
Example #4
0
    def _sub_layouts(self):

        state = {
            "peak_profile": "G",
            "shape_factor": 0.94,
            "rad_source": "CuKa",
            "x_axis": "twotheta",
            "crystallite_size": 0.1,
        }

        # Main plot
        graph = Loading([
            dcc.Graph(
                figure=go.Figure(
                    layout=XRayDiffractionComponent.empty_plot_style),
                id=self.id("xrd-plot"),
                config={
                    "displayModeBar": False,  # or "hover",
                    "plotGlPixelRatio": 2,
                    "displaylogo": False,
                    # "modeBarButtons": [["toImage"]],  # to only add an image download button
                    "toImageButtonOptions": {
                        "format": "png",
                        "filename": "xrd",
                        "scale": 4,
                        "width": 600,
                        "height": 400,
                    },
                    "editable": True,
                },
                responsive=True,
                animate=False,
            )
        ])

        # Radiation source selector
        rad_source = self.get_choice_input(
            kwarg_label="rad_source",
            state=state,
            label="Radiation source",
            help_str=
            "This defines the wavelength of the incident X-ray radiation.",
            options=[{
                "label":
                f'{name.replace("a", "α").replace("b", "β")} ({wavelength:.3f} Å)',
                "value": name,
            } for name, wavelength in WAVELENGTHS.items()],
            style={"width": "10rem"},
        )

        # Shape factor input
        shape_factor = self.get_numerical_input(
            kwarg_label="shape_factor",
            state=state,
            label="Shape Factor",
            help_str=
            """The peak profile determines what distribute characterizes the broadening of an XRD pattern. 
Two extremes are Gaussian distributions, which are useful for peaks with more rounded tops (typically due to strain 
broadening) and Lorentzian distributions, which are useful for peaks with sharper top (typically due to size 
distributions and dislocations). In reality, peak shapes usually follow a Voigt distribution, which is a convolution of 
Gaussian and Lorentzian peak shapes, with the contribution to both Gaussian and Lorentzian components sample and instrument 
dependent. Here, both contributions are equally weighted if Voigt is chosen.""",
        )

        # Peak profile selector (Gaussian, Lorentzian, Voigt)
        peak_profile = self.get_choice_input(
            kwarg_label="peak_profile",
            state=state,
            label="Peak Profile",
            help_str=
            """The shape factor K, also known as the “Scherrer constant” is a dimensionless 
        quantity to obtain an actual particle size from an apparent particle size determined from XRD. The discrepancy is 
        because the shape of an individual crystallite will change the resulting diffraction broadening. Commonly, a value 
        of 0.94 for isotropic crystals in a spherical shape is used. However, in practice K can vary from 0.62 to 2.08.""",
            options=[
                {
                    "label": "Gaussian",
                    "value": "G"
                },
                {
                    "label": "Lorentzian",
                    "value": "L"
                },
                {
                    "label": "Voigt",
                    "value": "V"
                },
            ],
            style={"width": "10rem"},
        )

        # 2Theta or Q for x-axis
        x_axis_choice = html.Div(
            [
                self.get_choice_input(
                    kwarg_label="x_axis",
                    state=state,
                    label="Choice of 𝑥 axis",
                    help_str=
                    "Can choose between 2𝜃 or Q, where Q is the magnitude of the reciprocal lattice and "
                    "independent of radiation source.",  # TODO: improve
                    options=[
                        {
                            "label": "2𝜃",
                            "value": "twotheta"
                        },
                        {
                            "label": "Q",
                            "value": "Q"
                        },
                    ],
                )
            ],
            style={"display": "none"
                   },  # TODO: this is buggy! let's fix it before we share
        )

        # Crystallite size selector (via Scherrer Equation)
        crystallite_size = self.get_slider_input(
            kwarg_label="crystallite_size",
            label="Scherrer crystallite size / nm",
            state=state,
            help_str=
            "Simulate a real diffraction pattern by applying Scherrer broadening, which estimates the "
            "full width at half maximum (FWHM) resulting from a finite, rather than infinite, crystallite "
            "size.",
            domain=[-1, 2],
            step=0.01,
            isLogScale=True)

        static_image = self.get_figure_placeholder("xrd-plot")

        return {
            "x_axis": x_axis_choice,
            "graph": graph,
            "rad_source": rad_source,
            "peak_profile": peak_profile,
            "shape_factor": shape_factor,
            "crystallite_size": crystallite_size,
            "static_image": static_image,
        }
Example #5
0
    def all_layouts(self):

        # Main plot
        graph = html.Div([
            dcc.Graph(
                figure=go.Figure(
                    layout=XRayDiffractionComponent.empty_plot_style),
                id=self.id("xrd-plot"),
                config={"displayModeBar": False},
            )
        ])

        # Radiation source selector
        rad_source = html.Div([
            html.P("Radiation Source"),
            dcc.Dropdown(id=self.id("rad-source"),
                         options=[{
                             "label": i,
                             "value": i
                         } for i in WAVELENGTHS.keys()],
                         value="CuKa",
                         placeholder="Select a source...",
                         clearable=False)
        ],
                              style={'max-width': '200'})

        # Shape factor input
        shape_factor = html.Div([
            html.P("Shape Factor, K "),
            dcc.Input(id=self.id("shape-factor"),
                      placeholder='0.94',
                      type='text',
                      value='0.94')
        ],
                                style={'max-width': '200'})
        # Peak profile selector (Gaussian, Lorentzian, Voigt)
        peak_profile = html.Div([
            html.P("Peak Profile"),
            dcc.Dropdown(id=self.id("peak-profile"),
                         options=[{
                             "label": 'Gaussian',
                             "value": 'G'
                         }, {
                             "label": 'Lorentzian',
                             "value": 'L'
                         }, {
                             "label": 'Voigt',
                             "value": 'V'
                         }],
                         value="G",
                         clearable=False)
        ],
                                style={'max-width': '200'})

        # Crystallite size selector (via Scherrer Equation)
        crystallite_size = html.Div([
            html.P("Scherrer Crystallite Size (nm)"),
            html.Div([
                dcc.Slider(id=self.id("crystallite-slider"),
                           marks={i: '{}'.format(10**i)
                                  for i in range(-1, 3)},
                           min=-1,
                           max=2,
                           value=0,
                           step=0.01),
            ],
                     style={'max-width': '500'}),
            html.Div([],
                     id=self.id("crystallite-input"),
                     style={"padding-top": "20px"})
        ])

        return {
            "graph": graph,
            "rad_source": rad_source,
            "peak_profile": peak_profile,
            "shape_factor": shape_factor,
            "crystallite_size": crystallite_size
        }
Example #6
0
from emmet.core.spectrum import SpectrumDoc
from emmet.core.structure import StructureMetadata
from emmet.core.symmetry import CrystalSystem, SymmetryData
from emmet.core.xrd import Edge, XRDDoc


@pytest.fixture
def structure():
    test_latt = Lattice.cubic(3.0)
    test_struc = Structure(lattice=test_latt,
                           species=["Fe"],
                           coords=[[0, 0, 0]])
    return test_struc


@pytest.mark.parametrize("target", list(WAVELENGTHS.keys()))
def test_target_detection(structure, target):
    doc = XRDDoc.from_structure(
        structure=structure,
        spectrum_id="test-1",
        material_id="test-1",
        wavelength=WAVELENGTHS[target],
    )

    target_element = Element(target[:2])
    target_edge = Edge(target[2:])
    assert doc.target == target_element
    assert doc.edge == target_edge


@pytest.mark.parametrize("target", list(WAVELENGTHS.keys()))
Example #7
0
class LatticeXRDCalculator(AbstractDiffractionPatternCalculator):
    r"""
    Computes the XRD pattern of a crystal structure.

    This code is implemented by Shyue Ping Ong as part of UCSD's NANO106 -
    Crystallography of Materials. The formalism for this code is based on
    that given in Chapters 11 and 12 of Structure of Materials by Marc De
    Graef and Michael E. McHenry. This takes into account the atomic
    scattering factors and the Lorentz polarization factor, but not
    the Debye-Waller (temperature) factor (for which data is typically not
    available). Note that the multiplicity correction is not needed since
    this code simply goes through all reciprocal points within the limiting
    sphere, which includes all symmetrically equivalent facets. The algorithm
    is as follows

    1. Calculate reciprocal lattice of structure. Find all reciprocal points
       within the limiting sphere given by :math:`\\frac{2}{\\lambda}`.

    2. For each reciprocal point :math:`\\mathbf{g_{hkl}}` corresponding to
       lattice plane :math:`(hkl)`, compute the Bragg condition
       :math:`\\sin(\\theta) = \\frac{\\lambda}{2d_{hkl}}`

    3. Compute the structure factor as the sum of the atomic scattering
       factors. The atomic scattering factors are given by

       .. math::

           f(s) = Z - 41.78214 \\times s^2 \\times \\sum\\limits_{i=1}^n a_i \
           \\exp(-b_is^2)

       where :math:`s = \\frac{\\sin(\\theta)}{\\lambda}` and :math:`a_i`
       and :math:`b_i` are the fitted parameters for each element. The
       structure factor is then given by

       .. math::

           F_{hkl} = \\sum\\limits_{j=1}^N f_j \\exp(2\\pi i \\mathbf{g_{hkl}}
           \\cdot \\mathbf{r})

    4. The intensity is then given by the modulus square of the structure
       factor.

       .. math::

           I_{hkl} = F_{hkl}F_{hkl}^*

    5. Finally, the Lorentz polarization correction factor is applied. This
       factor is given by:

       .. math::

           P(\\theta) = \\frac{1 + \\cos^2(2\\theta)}
           {\\sin^2(\\theta)\\cos(\\theta)}
    """

    # Tuple of available radiation keywords.
    AVAILABLE_RADIATION = tuple(WAVELENGTHS.keys())

    def __init__(self, wavelength="CuKa", symprec=0, debye_waller_factors=None):
        """
        Initializes the XRD calculator with a given radiation.

        Args:
            wavelength (str/float): The wavelength can be specified as either a
                float or a string. If it is a string, it must be one of the
                supported definitions in the AVAILABLE_RADIATION class
                variable, which provides useful commonly used wavelengths.
                If it is a float, it is interpreted as a wavelength in
                angstroms. Defaults to "CuKa", i.e, Cu K_alpha radiation.
            symprec (float): Symmetry precision for structure refinement. If
                set to 0, no refinement is done. Otherwise, refinement is
                performed using spglib with provided precision.
            debye_waller_factors ({element symbol: float}): Allows the
                specification of Debye-Waller factors. Note that these
                factors are temperature dependent.
        """
        if isinstance(wavelength, float):
            self.wavelength = wavelength
        else:
            self.radiation = wavelength
            self.wavelength = WAVELENGTHS[wavelength]
        self.symprec = symprec
        self.debye_waller_factors = debye_waller_factors or {}

    def get_pattern(self, structure, scaled=True, two_theta_range=(0, 90)):
        """
        Calculates the diffraction pattern for a structure.

        Args:
            structure (Structure): Input structure
            scaled (bool): Whether to return scaled intensities. The maximum
                peak is set to a value of 100. Defaults to True. Use False if
                you need the absolute values to combine XRD plots.
            two_theta_range ([float of length 2]): Tuple for range of
                two_thetas to calculate in degrees. Defaults to (0, 90). Set to
                None if you want all diffracted beams within the limiting
                sphere of radius 2 / wavelength.

        Returns:
            (XRDPattern)
        """
        if self.symprec:
            finder = SpacegroupAnalyzer(structure, symprec=self.symprec)
            structure = finder.get_refined_structure()

        wavelength = self.wavelength
        latt = structure.lattice
        is_hex = latt.is_hexagonal()

        # Obtained from Bragg condition. Note that reciprocal lattice
        # vector length is 1 / d_hkl.
        min_r, max_r = (
            (0, 2 / wavelength)
            if two_theta_range is None
            else [2 * sin(radians(t / 2)) / wavelength for t in two_theta_range]
        )

        # Obtain crystallographic reciprocal lattice points within range
        recip_latt = latt.reciprocal_lattice_crystallographic
        recip_pts = recip_latt.get_points_in_sphere([[0, 0, 0]], [0, 0, 0], max_r)
        if min_r:
            recip_pts = [pt for pt in recip_pts if pt[1] >= min_r]

        peaks = {}
        two_thetas = []

        for hkl, g_hkl, ind, _ in sorted(
            recip_pts, key=lambda i: (i[1], -i[0][0], -i[0][1], -i[0][2])
        ):
            # Force miller indices to be integers.
            hkl = [int(round(i)) for i in hkl]
            if g_hkl != 0:

                d_hkl = 1 / g_hkl

                # Bragg condition
                theta = asin(wavelength * g_hkl / 2)

                two_theta = degrees(2 * theta)

                if is_hex:
                    # Use Miller-Bravais indices for hexagonal lattices.
                    hkl = (hkl[0], hkl[1], -hkl[0] - hkl[1], hkl[2])
                # Deal with floating point precision issues.
                ind = np.where(
                    np.abs(np.subtract(two_thetas, two_theta))
                    < AbstractDiffractionPatternCalculator.TWO_THETA_TOL
                )
                if len(ind[0]) > 0:
                    peaks[two_thetas[ind[0][0]]][0] += 1.0
                    peaks[two_thetas[ind[0][0]]][1].append(tuple(hkl))
                else:
                    peaks[two_theta] = [1.0, [tuple(hkl)], d_hkl]
                    two_thetas.append(two_theta)

        x = []
        y = []
        hkls = []
        d_hkls = []
        for k in sorted(peaks.keys()):
            v = peaks[k]
            fam = get_unique_families(v[1])

            x.append(k)
            y.append(v[0])
            hkls.append(
                [{"hkl": hkl, "multiplicity": mult} for hkl, mult in fam.items()]
            )
            d_hkls.append(v[2])
        xrd = DiffractionPattern(x, y, hkls, d_hkls)

        return xrd
Example #8
0
    def _sub_layouts(self):

        state = {
            "mode": "powder",
            "peak_profile": "G",
            "shape_factor": 0.94,
            "rad_source": "CuKa",
            "x_axis": "twotheta",
            "crystallite_size": 0.1,
        }

        # mode selector
        mode = self.get_choice_input(
            kwarg_label="mode",
            state=state,
            label="Mode",
            help_str="""Select whether to generate a powder diffraction pattern 
(a pattern averaged over all orientations of a polycrystalline material) 
or a single crystal diffraction pattern (a diffraction pattern generated 
from a single crystal structure.""",
            options=[
                {"value": "powder", "label": "Powder"},
                {"value": "single", "label": "Single Crystal"},
            ],
        )

        # download

        # Main plot
        graph = Loading(
            [
                dcc.Graph(
                    figure=go.Figure(layout=XRayDiffractionComponent.empty_plot_style),
                    id=self.id("xrd-plot"),
                    config={
                        "displayModeBar": False,  # or "hover",
                        "plotGlPixelRatio": 2,
                        "displaylogo": False,
                        # "modeBarButtons": [["toImage"]],  # to only add an image download button
                        "toImageButtonOptions": {
                            "format": "png",
                            "filename": "xrd",
                            "scale": 4,
                            "width": 600,
                            "height": 400,
                        },
                        "editable": True,
                    },
                    responsive=True,
                    animate=False,
                )
            ]
        )

        # Broaden peaks
        broadening_toggle = ...

        # Radiation source selector
        rad_source = self.get_choice_input(
            kwarg_label="rad_source",
            state=state,
            label="Radiation source",
            help_str="...",
            options=[
                {"label": wav.replace("a", "α").replace("b", "β"), "value": wav}
                for wav in WAVELENGTHS.keys()
            ],
        )

        # Shape factor input
        shape_factor = self.get_numerical_input(
            kwarg_label="shape_factor",
            state=state,
            label="Shape Factor",
            help_str="""The peak profile determines what distribute characterizes the broadening of an XRD pattern. 
Two extremes are Gaussian distributions, which are useful for peaks with more rounded tops (typically due to strain 
broadening) and Lorentzian distributions, which are useful for peaks with sharper top (typically due to size 
distributions and dislocations). In reality, peak shapes usually follow a Voigt distribution, which is a convolution of 
Gaussian and Lorentzian peak shapes, with the contribution to both Gaussian and Lorentzian components sample and instrument 
dependent. Here, both contributions are equally weighted if Voigt is chosen.""",
        )

        # Peak profile selector (Gaussian, Lorentzian, Voigt)
        peak_profile = self.get_choice_input(
            kwarg_label="peak_profile",
            state=state,
            label="Peak Profile",
            help_str="""The shape factor K, also known as the “Scherrer constant” is a dimensionless 
        quantity to obtain an actual particle size from an apparent particle size determined from XRD. The discrepancy is 
        because the shape of an individual crystallite will change the resulting diffraction broadening. Commonly, a value 
        of 0.94 for isotropic crystals in a spherical shape is used. However, in practice K can vary from 0.62 to 2.08.""",
            options=[
                {"label": "Gaussian", "value": "G"},
                {"label": "Lorentzian", "value": "L"},
                {"label": "Voigt", "value": "V"},
            ],
        )

        # 2Theta or Q for x-axis
        x_axis_choice = self.get_choice_input(
            kwarg_label="x_axis",
            state=state,
            label="Choice of 𝑥 axis",
            help_str="Can choose between 2Θ or Q, where Q is the magnitude of the reciprocal lattice and "
            "independent of radiation source.",  # TODO: improve
            options=[
                {"label": "2Θ", "value": "twotheta"},
                {"label": "Q", "value": "Q"},
            ],
        )

        # Crystallite size selector (via Scherrer Equation)
        crystallite_size = self.get_slider_input(
            kwarg_label="crystallite_size",
            label="Scherrer crystallite size / nm",
            state=state,
            help_str="...",
            marks={i: "{}".format(10 ** i) for i in range(-1, 3)},
            min=-1,
            max=2,
            step=0.01,
        )

        return {
            "mode": mode,
            "x_axis": x_axis_choice,
            "graph": graph,
            "rad_source": rad_source,
            "peak_profile": peak_profile,
            "shape_factor": shape_factor,
            "crystallite_size": crystallite_size,
        }