Exemple #1
0
    def test_custom_file_no_units(self, tmpdir):
        test_data_no_units = {'physical_property':
                              {'some_property': {'H': 1}}}
        filename = self._dump_data(test_data_no_units, tmpdir,
                                   'data_no_units.json')

        with pytest.raises(ValueError):
            get_reference_data(collection=filename,
                               physical_property='some_property')
Exemple #2
0
    def test_custom_file_no_physical_property(self, tmpdir):
        test_data_no_physical_property = {
            'not_physical_property': {
                'size': {'cat': 1, '__units__': 'meter'},
                'weight': {'cat': 4, '__units__': 'kg'}}}
        filename = self._dump_data(test_data_no_physical_property, tmpdir,
                                   'no_physical_property_data.json')

        with pytest.raises(AttributeError):
            get_reference_data(collection=filename,
                               physical_property='size')
Exemple #3
0
    def test_custom_file_wrong_property(self, tmpdir):
        test_data_wrong_property = {
            'physical_property': {
                'size': {'cat': 1, '__units__': 'meter'},
                'weight': {'cat': 4, '__units__': 'kg'}}}
        filename = self._dump_data(test_data_wrong_property, tmpdir,
                                   'data_with_size_and_weight.json')

        with pytest.raises(ValueError):
            get_reference_data(collection=filename,
                               physical_property='wrong_property')
Exemple #4
0
    def test_ref_scattering_length(self):
        data = get_reference_data(
            collection='Sears1992',
            physical_property='coherent_scattering_length')

        assert data['Ba'].units == ureg['fm']
        assert data['Hg'].magnitude == pytest.approx(12.692)
        assert data['Sm'].magnitude == pytest.approx(complex(0.80, -1.65))
Exemple #5
0
    def test_custom_file(self, tmpdir, animal_data, physical_property):

        filename = self._dump_data(animal_data, tmpdir, 'good_data.json')

        loaded_data = get_reference_data(collection=filename,
                                         physical_property=physical_property)

        animal_properties = animal_data['physical_property']
        for animal in 'cat', 'dog':
            assert loaded_data[animal].magnitude == pytest.approx(
                animal_properties[physical_property][animal])
            assert (loaded_data[animal].units
                    == animal_properties[physical_property]['__units__'])
Exemple #6
0
    def test_ref_cross_section(self):
        data = get_reference_data(collection='BlueBook',
                                  physical_property='coherent_cross_section')

        assert data['Ca'].units == ureg['barn']
        assert data['Cl'].magnitude == pytest.approx(11.5257)
Exemple #7
0
 def test_bad_physical_property(self):
     with pytest.raises(ValueError):
         get_reference_data(physical_property='not-a-real-property')
Exemple #8
0
 def test_bad_collection(self):
     with pytest.raises(ValueError):
         get_reference_data(collection='not-a-real-label')
    def calculate_structure_factor(
        self,
        scattering_lengths: Union[str, Dict[str, Quantity]] = 'Sears1992',
        dw: Optional[DebyeWaller] = None,
    ) -> StructureFactor:
        """
        Calculate the one phonon inelastic scattering for neutrons at
        each q-point

        Parameters
        ----------
        scattering_lengths
            Dataset of coherent scattering length for each
            element in the structure. This may be provided in 3 ways:

            - A string naming an appropriate data collection packaged with
              Euphonic (including the default value 'Sears1992'). This will be
              passed to the ``collection`` argument of
              :obj:`euphonic.util.get_reference_data()`.

            - A string filename for a user's customised data file in the same
              format as those packaged with Euphonic.

            - An explicit dictionary of float Quantity, giving spin- and
              isotope-averaged coherent scattering length for each element in
              the structure, e.g.::

                {'O': 5.803*ureg('fm'), 'Zn': 5.680*ureg('fm')}

        dw
            Data for thermal motion effects. Typically this is computed over a
            converged Monkhort-Pack grid, which need not correspond to the
            q-points of this QpointPhononModes object.

        Returns
        -------
        sf
            An object containing the structure factor for each q-point
            and phonon mode

        Notes
        -----

        This function calculates :math:`|F(Q, \\nu)|^2` per unit cell, where
        :math:`F(Q, \\nu)` is defined as [1]_:

        .. math::

          F(Q, \\nu) = \\frac{b_\\kappa}{M_{\\kappa}^{1/2}\\omega_{q\\nu}^{1/2}} \\
          [Q\\cdot\\epsilon_{q\\nu\\kappa\\alpha}]e^{iQ{\\cdot}r_\\kappa}e^{-W}

        Where :math:`\\nu` runs over phonon modes, :math:`\\kappa` runs
        over atoms, :math:`\\alpha` runs over the Cartesian directions,
        :math:`b_\\kappa` is the coherent neutron scattering length,
        :math:`M_{\\kappa}` is the atom mass, :math:`r_{\\kappa}` is the
        vector to atom :math:`\\kappa` in the unit cell,
        :math:`\\epsilon_{q\\nu\\kappa\\alpha}` are the eigevectors,
        :math:`\\omega_{q\\nu}` are the frequencies and :math:`e^{-W}`
        is the Debye-Waller factor. Note that a factor N for the
        number of unit cells in the sample hasn't been included, so the
        returned structure factor is per unit cell.

        .. [1] M.T. Dove, Structure and Dynamics, Oxford University Press, Oxford, 2003, 225-226

        """
        if isinstance(scattering_lengths, str):
            scattering_length_data = get_reference_data(
                collection=scattering_lengths,
                physical_property='coherent_scattering_length')
        elif isinstance(scattering_lengths, dict):
            scattering_length_data = scattering_lengths

        sl = [
            scattering_length_data[x].to('bohr').magnitude
            for x in self.crystal.atom_type
        ]

        # Calculate normalisation factor
        norm_factor = sl / np.sqrt(self.crystal._atom_mass)

        # Calculate the exp factor for all atoms and qpts. atom_r is in
        # fractional coords, so Qdotr = 2pi*qh*rx + 2pi*qk*ry...
        exp_factor = np.exp(
            1J * 2 * math.pi *
            np.einsum('ij,kj->ik', self.qpts, self.crystal.atom_r))

        # Eigenvectors are in Cartesian so need to convert hkl to
        # Cartesian by computing dot with hkl and reciprocal lattice
        recip = self.crystal.reciprocal_cell().to('1/bohr').magnitude
        Q = np.einsum('ij,jk->ik', self.qpts, recip)

        # Calculate dot product of Q and eigenvectors for all branches
        # atoms and q-points
        eigenv_dot_q = np.einsum('ijkl,il->ijk', np.conj(self.eigenvectors), Q)

        # Calculate Debye-Waller factors
        temperature = None
        if dw:
            temperature = dw.temperature
            if dw.crystal.n_atoms != self.crystal.n_atoms:
                raise ValueError(
                    ('The DebyeWaller object used as dw is not '
                     'compatible with the QPointPhononModes object (they'
                     ' have a different number of atoms)'))
            dw_factor = np.exp(
                -np.einsum('jkl,ik,il->ij', dw._debye_waller, Q, Q))
            exp_factor *= dw_factor

        # Multiply Q.eigenvector, exp factor and normalisation factor
        term = np.einsum('ijk,ik,k->ij', eigenv_dot_q, exp_factor, norm_factor)

        # Take mod squared and divide by frequency to get intensity
        sf = np.real(
            np.absolute(term * np.conj(term)) / np.absolute(self._frequencies))

        return StructureFactor(
            self.crystal,
            self.qpts,
            self.frequencies,
            sf * ureg('bohr**2').to(self.crystal.cell_vectors.units**2),
            temperature=temperature)
Exemple #10
0
def sample_sphere_structure_factor(
    fc: ForceConstants,
    mod_q: Quantity,
    dw: DebyeWaller = None,
    dw_spacing: Quantity = 0.025 * ureg('1/angstrom'),
    temperature: Optional[Quantity] = 273. * ureg['K'],
    sampling: str = 'golden',
    npts: int = 1000, jitter: bool = False,
    energy_bins: Quantity = None,
    scattering_lengths: Union[dict, str] = 'Sears1992',
    **calc_modes_args
) -> Spectrum1D:
    """Sample structure factor, averaging over a sphere of constant |q|

    (Specifically, this is the one-phonon inelastic-scattering structure
    factor as implemented in
    QpointPhononModes.calculate_structure_factor().)

    Parameters
    ----------

    fc
        Force constant data for system

    mod_q
        scalar radius of sphere from which vector q samples are taken

    dw
        Debye-Waller exponent used for evaluation of scattering
        function. If not provided, this is generated automatically over
        Monkhorst-Pack q-point mesh determined by ``dw_spacing``.

    dw_spacing
        Maximum distance between q-points in automatic q-point mesh (if used)
        for Debye-Waller calculation.

    temperature
        Temperature for Debye-Waller calculation. If both temperature and dw
        are set to None, Debye-Waller factor will be omitted.

    sampling
        Sphere-sampling scheme. (Case-insensitive) options are:
            - 'golden':
                Fibonnaci-like sampling that steps regularly along one
                spherical coordinate while making irrational steps in
                the other

            - 'sphere-projected-grid':
                Regular 2-D square mesh projected onto sphere. npts will
                be distributed as evenly as possible (i.e. using twice
                as many 'longitude' as 'lattitude' lines), rounding up
                if necessary.

            - 'spherical-polar-grid':
                Mesh over regular subdivisions in spherical polar
                coordinates.  npts will be rounded up as necessary in
                the same scheme as for sphere-projected-grid. 'Latitude'
                lines are evenly-spaced in z

            - 'spherical-polar-improved':
                npts distributed as regularly as possible using
                spherical polar coordinates: 'latitude' lines are
                evenly-spaced in z and points are distributed among
                these rings to obtain most even spacing possible.

            - 'random-sphere':
                Points are distributed randomly in unit square and
                projected onto sphere.

    npts
        Number of samples. Note that some sampling methods have
            constraints on valid values and will round up as
            appropriate.

    jitter
        For non-random sampling schemes, apply an additional random
            displacement to each point.

    energy_bins
        Preferred energy bin edges. If not provided, will setup 1000
        bins (1001 bin edges) from 0 to 1.05 * [max energy]

    scattering_lengths
        Dict of neutron scattering lengths labelled by element. If a
        string is provided, this selects coherent scattering lengths
        from reference data by setting the 'label' argument of the
        euphonic.util.get_reference_data() function.

    **calc_modes_args
        other keyword arguments (e.g. 'use_c') will be passed to
        ForceConstants.calculate_qpoint_phonon_modes()

    Returns
    -------
    Spectrum1D

    """

    if isinstance(scattering_lengths, str):
        scattering_lengths = get_reference_data(
            physical_property='coherent_scattering_length',
            collection=scattering_lengths)  # type: dict

    if temperature is not None:
        if (dw is None):
            dw_qpts = mp_grid(fc.crystal.get_mp_grid_spec(dw_spacing))
            dw_phonons = fc.calculate_qpoint_phonon_modes(dw_qpts,
                                                          **calc_modes_args)
            dw = dw_phonons.calculate_debye_waller(temperature
                                                   )  # type: DebyeWaller
        else:
            if not np.isclose(dw.temperature.to('K').magnitude,
                              temperature.to('K').magnitude):
                raise ValueError('Temperature argument is not consistent with '
                                 'temperature stored in DebyeWaller object.')

    qpts_cart = _get_qpts_sphere(npts, sampling=sampling, jitter=jitter
                                 ) * mod_q

    qpts_frac = _qpts_cart_to_frac(qpts_cart, fc.crystal)

    phonons = fc.calculate_qpoint_phonon_modes(qpts_frac, **calc_modes_args
                                               )  # type: QpointPhononModes

    if energy_bins is None:
        energy_bins = _get_default_bins(phonons)

    s = phonons.calculate_structure_factor(
        scattering_lengths=scattering_lengths, dw=dw)

    return s.calculate_1d_average(energy_bins)