Esempio n. 1
0
def get_spectrum(force_constants: ForceConstants,
                 *,
                 bin_width: Quantity,
                 max_energy: Quantity,
                 q: Quantity = (0.1 * ureg('1/angstrom')),
                 npts: int = 1000,
                 npts_density: bool = False,
                 sampling: str = 'golden',
                 jitter: bool = True,
                 dos: bool = False,
                 smear_width: Optional[Quantity] = (1 * ureg('meV'))):

    assert isinstance(q, Quantity)

    if npts_density:
        npts = int(np.ceil(npts * (q.to('1/angstrom').magnitude**2)))

    energy_bins = np.arange(0,
                            (max_energy.to('meV').magnitude),
                            bin_width.to('meV').magnitude) * max_energy.units

    if dos:
        sampling_function = sample_sphere_dos
    else:
        sampling_function = sample_sphere_structure_factor

    spectrum = sampling_function(force_constants, mod_q=q, npts=npts,
                                 sampling=sampling, jitter=jitter,
                                 energy_bins=energy_bins)

    if smear_width is None:
        return spectrum
    else:
        return spectrum.broaden(smear_width, shape='gauss')
Esempio n. 2
0
class TestQpointPhononModesCalculateDos:
    @pytest.mark.parametrize(
        'material, qpt_ph_modes_file, expected_dos_json, ebins',
        [('quartz', 'quartz-666-grid.phonon', 'quartz_666_dos.json',
          np.arange(0, 155, 0.5) * ureg('meV')),
         ('CaHgO2', 'CaHgO2-666-grid.yaml', 'CaHgO2_666_dos.json',
          np.arange(0, 95, 0.4) * ureg('meV'))])
    def test_calculate_dos(self, material, qpt_ph_modes_file,
                           expected_dos_json, ebins):
        if qpt_ph_modes_file.endswith('.phonon'):
            qpt_ph_modes = QpointPhononModes.from_castep(
                get_castep_path(material, qpt_ph_modes_file))
        else:
            qpt_ph_modes = QpointPhononModes.from_phonopy(
                phonon_name=get_phonopy_path(material, qpt_ph_modes_file))
        dos = qpt_ph_modes.calculate_dos(ebins)
        expected_dos = get_expected_spectrum1d(expected_dos_json)
        check_spectrum1d(dos, expected_dos)

    def test_calculate_dos_with_0_inv_cm_bin_doesnt_raise_runtime_warn(self):
        qpt_ph_modes = get_qpt_ph_modes('quartz')
        ebins = np.arange(0, 1300, 4) * ureg('1/cm')
        with pytest.warns(None) as warn_record:
            dos = qpt_ph_modes.calculate_dos(ebins)
        assert len(warn_record) == 0
Esempio n. 3
0
    def setUp(self):
        # Test creation of BandsData object (which reads NaH.bands file in
        # test/data dir). BandsData object will also read extra data (ion_r
        # and ion_type) from the NaH.castep file

        # Create trivial object so attributes can be assigned to it
        expctd_data = type('', (), {})()  # Expected data

        expctd_data.cell_vec = np.array(
            [[0.000000, 4.534397, 4.534397], [4.534397, 0.000000, 4.534397],
             [4.534397, 4.534397, 0.000000]]) * ureg('bohr')
        expctd_data.ion_r = np.array([[0.500000, 0.500000, 0.500000],
                                      [0.000000, 0.000000, 0.000000]])
        expctd_data.ion_type = np.array(['H', 'Na'])
        expctd_data.qpts = np.array([[-0.45833333, -0.37500000, -0.45833333],
                                     [-0.45833333, -0.37500000, -0.20833333]])
        expctd_data.weights = np.array([0.00347222, 0.00694444])
        expctd_data.freqs = np.array([[
            -1.83230180, -0.83321119, -0.83021854, -0.83016941, -0.04792334
        ], [-1.83229571, -0.83248269, -0.83078961, -0.83036048, -0.05738470]
                                      ]) * ureg('hartree')
        expctd_data.freq_down = np.array([]) * ureg('hartree')
        expctd_data.fermi = np.array([-0.009615]) * ureg('hartree')
        self.expctd_data = expctd_data

        seedname = 'NaH'
        path = 'data'
        data = BandsData.from_castep(seedname, path=path)
        self.data = data
Esempio n. 4
0
def check_unit_conversion(obj, attr, unit):
    """
    Utility function to check unit conversions in Euphonic objects

    Parameters
    ----------
    obj : Object
        The object to check
    attr : str
        The name of the attribute to change the units of
    unit : str
        The unit to change attr to
    """
    unit_attr = attr + '_unit'
    original_attr_value = np.copy(getattr(obj, attr).magnitude) * getattr(
        obj, attr).units
    setattr(obj, unit_attr, unit)

    # Check unit value (e.g. 'frequencies_unit') has changed
    assert getattr(obj, unit_attr) == unit
    # Check when returning the attrbute (e.g. 'frequencies') it is in
    # the desired unit and has the correct magnitude
    if attr == 'temperature':
        assert str(getattr(obj, attr).units) == str(ureg(unit).units)
    else:
        assert getattr(obj, attr).units == ureg(unit)
    npt.assert_allclose(
        getattr(obj, attr).magnitude,
        original_attr_value.to(unit).magnitude)
Esempio n. 5
0
def test_get_default_bins(mocker, units):
    qpm = mocker.MagicMock()
    qpm.frequencies = np.array([-1., 0.3, (10 / 1.05), 4.2]) * ureg(units)

    default_bins = _get_default_bins(qpm, nbins=10)
    assert default_bins.units == ureg(units)
    npt.assert_almost_equal(default_bins.magnitude,
                            [0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10.])
class TestSpectrum1DCollectionMethods:
    @pytest.mark.parametrize(
        'spectrum, split_kwargs, expected_spectra',
        [(get_spectrum1dcollection('methane_pdos.json'), {
            'indices': [50]
        }, [
            get_spectrum1dcollection(f'methane_pdos_split50_{i}.json')
            for i in range(2)
        ])])
    def test_split(self, spectrum, split_kwargs, expected_spectra):
        spectra = spectrum.split(**split_kwargs)
        for split, expected_split in zip(spectra, expected_spectra):
            check_spectrum1dcollection(split, expected_split)

    @pytest.mark.parametrize(
        'spectrum_file, expected_bin_edges_file, expected_units',
        [('gan_bands.json', 'gan_bands_bin_edges.npy', ureg('1/angstrom')),
         ('quartz_dos_collection.json', 'quartz_dos_collection_bin_edges.npy',
          ureg('meV'))])
    def test_get_bin_edges(self, spectrum_file, expected_bin_edges_file,
                           expected_units):
        spec = get_spectrum1dcollection(spectrum_file)
        bin_edges = spec.get_bin_edges()
        expected_bin_edges = np.load(
            os.path.join(get_spectrum_dir(), expected_bin_edges_file))
        assert bin_edges.units == expected_units
        assert_allclose(bin_edges.magnitude, expected_bin_edges)

    @pytest.mark.parametrize(
        'spectrum_file, expected_bin_centres_file, expected_units',
        [('gan_bands.json', 'gan_bands_bin_centres.npy', ureg('1/angstrom')),
         ('quartz_dos_collection.json',
          'quartz_dos_collection_bin_centres.npy', ureg('meV'))])
    def test_get_bin_centres(self, spectrum_file, expected_bin_centres_file,
                             expected_units):
        spec = get_spectrum1dcollection(spectrum_file)
        bin_centres = spec.get_bin_centres()
        expected_bin_centres = np.load(
            os.path.join(get_spectrum_dir(), expected_bin_centres_file))
        assert bin_centres.units == expected_units
        assert_allclose(bin_centres.magnitude, expected_bin_centres)

    def test_get_bin_edges_with_invalid_data_shape_raises_value_error(self):
        spec = get_spectrum1dcollection('gan_bands.json')
        spec._y_data = spec._y_data[:, :51]
        with pytest.raises(ValueError):
            spec.get_bin_edges()

    def test_get_bin_centres_with_invalid_data_shape_raises_value_error(self):
        spec = get_spectrum1dcollection('quartz_dos_collection.json')
        spec._x_data = spec._x_data[:31]
        with pytest.raises(ValueError):
            spec.get_bin_centres()
 def test_calculate_qpoint_frequencies_with_mode_widths(
         self, fc, material, all_args, expected_qpoint_frequencies_file,
         expected_modw_file, n_threads):
     func_kwargs = all_args[1]
     if n_threads == 0:
         func_kwargs['use_c'] = False
     else:
         func_kwargs['use_c'] = True
         func_kwargs['n_threads'] = n_threads
     qpt_freqs, modw = fc.calculate_qpoint_frequencies(
         all_args[0], **func_kwargs)
     with open(os.path.join(get_fc_dir(), expected_modw_file), 'r') as fp:
         modw_dict = json.load(fp)
     expected_modw = modw_dict['mode_widths'] * ureg(
         modw_dict['mode_widths_unit'])
     expected_qpt_freqs = get_expected_qpt_freqs(
         material, expected_qpoint_frequencies_file)
     # Only give gamma-acoustic modes special treatment if the acoustic
     # sum rule has been applied
     if not 'asr' in func_kwargs.keys():
         gamma_atol = None
     else:
         gamma_atol = 0.5
     check_qpt_freqs(qpt_freqs,
                     expected_qpt_freqs,
                     frequencies_atol=1e-4,
                     frequencies_rtol=2e-5,
                     acoustic_gamma_atol=gamma_atol)
     assert modw.units == expected_modw.units
     npt.assert_allclose(modw.magnitude,
                         expected_modw.magnitude,
                         atol=2e-4,
                         rtol=5e-5)
Esempio n. 8
0
    def get_mp_grid_spec(
        self, spacing: Quantity = 0.1 * ureg('1/angstrom')
    ) -> Tuple[int, int, int]:
        """
        Get suggested divisions for Monkhorst-Pack grid

        Determine a mesh for even Monkhorst-Pack sampling of the reciprocal
        cell

        Parameters
        ----------
        spacing
            Scalar float quantity in 1/length units. Maximum
            reciprocal-space distance between q-point samples

        Returns
        -------
        grid_spec
            The number of divisions for each reciprocal lattice vector
        """

        recip_length_unit = spacing.units
        lattice = self.reciprocal_cell().to(recip_length_unit)
        grid_spec = np.linalg.norm(lattice.magnitude,
                                   axis=1) / spacing.magnitude
        # math.ceil is better than np.ceil because it returns ints
        return tuple([ceil(x) for x in grid_spec])
Esempio n. 9
0
 def test_calculate_dos_with_0_inv_cm_bin_doesnt_raise_runtime_warn(self):
     qpt_freqs = get_qpt_freqs('quartz',
                               'quartz_666_qpoint_frequencies.json')
     ebins = np.arange(0, 1300, 4) * ureg('1/cm')
     with pytest.warns(None) as warn_record:
         dos = qpt_freqs.calculate_dos(ebins)
     assert len(warn_record) == 0
Esempio n. 10
0
 def temperature(self):
     if self._temperature is not None:
         # See https://pint.readthedocs.io/en/latest/nonmult.html
         return Quantity(self._temperature,
                         ureg('K')).to(self.temperature_unit)
     else:
         return None
def diff_1d(spectrum: Spectrum1D,
            reference: Spectrum1D,
            fractional: bool = False,
            threshold: float = 1e-12) -> Quantity:
    """Compare the values of two Spectrum1D objects, returning an array

    Args:
        spectrum, reference: Spectra with same x-values for comparison
        fractional:
            Calculate relative difference by formula (S - R)/R. If False,
            use the unscaled difference between values instead.
        threshold:
            Ignore error from values smaller than this threshold in reference
            spectrum when fractional=True

    Returns:
        array Quantity:
            Difference between spectra
    """

    assert spectrum.x_data_unit == reference.x_data_unit
    assert allclose(spectrum.x_data.magnitude, reference.x_data.magnitude)

    diff = (spectrum.y_data.to(reference.y_data_unit).magnitude
            - reference.y_data.magnitude)

    if fractional:
        ref_values = reference.y_data.magnitude
        mask = absolute(ref_values) > threshold
        diff = diff[mask] / ref_values[mask] * ureg(None)

    else:
        diff *= spectrum.y_data.units

    return diff
Esempio n. 12
0
def _check_unit_conversion(obj: object, attr_name: str, attr_value: Any,
                           unit_attrs: List[str]) -> None:
    """
    If setting an attribute on an object that relates to the units of a
    Quantity (e.g. 'frequencies_unit' in QpointPhononModes) check that
    the unit conversion is valid before allowing the value to be set

    Parameters
    ----------
    obj
        The object to check
    attr_name
        The name of the attribute that is being set
    attr_value
        The new value of the attribute
    unit_attrs
        Only check the unit conversion if the attribute is one of
        unit_attrs

    Raises
    ------
    ValueError
        If the unit conversion is not valid
    """
    if hasattr(obj, attr_name):
        if attr_name in unit_attrs:
            try:
                _ = ureg(getattr(obj, attr_name)).to(attr_value)
            except DimensionalityError:
                raise ValueError(
                    (f'"{attr_value}" is not a known dimensionally-consistent '
                     f'unit for "{attr_name}"'))
Esempio n. 13
0
    def setUp(self):
        data = type('', (), {})()
        # Input values
        data._e_units = 'E_h'
        data.dos = np.array([
            2.30e-01, 1.82e-01, 8.35e-02, 3.95e-02, 2.68e-02, 3.89e-02,
            6.15e-02, 6.75e-02, 6.55e-02, 5.12e-02, 3.60e-02, 2.80e-02,
            5.22e-02, 1.12e-01, 1.52e-01, 1.37e-01, 9.30e-02, 6.32e-02,
            7.92e-02, 1.32e-01, 1.53e-01, 8.88e-02, 2.26e-02, 2.43e-03,
            1.08e-04, 2.00e-06, 8.11e-07, 4.32e-05, 9.63e-04, 8.85e-03,
            3.35e-02, 5.22e-02, 3.35e-02, 8.85e-03, 9.63e-04, 4.32e-05,
            7.96e-07, 6.81e-09, 9.96e-08, 5.40e-06, 1.21e-04, 1.13e-03,
            4.71e-03, 1.19e-02, 2.98e-02, 6.07e-02, 6.91e-02, 3.79e-02,
            9.33e-03, 9.85e-04, 4.40e-05, 2.24e-05, 4.82e-04, 4.43e-03,
            1.67e-02, 2.61e-02, 1.67e-02, 4.43e-03, 4.82e-04, 2.16e-05,
            3.98e-07
        ])
        data.dos_down = np.array([
            6.05e-09, 7.97e-07, 4.33e-05, 9.71e-04, 9.08e-03, 3.72e-02,
            8.06e-02, 1.37e-01, 1.84e-01, 1.47e-01, 7.37e-02, 3.84e-02,
            2.67e-02, 3.80e-02, 5.36e-02, 4.24e-02, 4.28e-02, 5.76e-02,
            5.03e-02, 3.55e-02, 2.32e-02, 3.15e-02, 7.39e-02, 1.24e-01,
            1.40e-01, 1.11e-01, 7.48e-02, 5.04e-02, 5.22e-02, 8.75e-02,
            1.37e-01, 1.30e-01, 6.37e-02, 1.47e-02, 1.51e-03, 1.09e-04,
            9.64e-04, 8.85e-03, 3.35e-02, 5.22e-02, 3.35e-02, 8.85e-03,
            9.63e-04, 4.33e-05, 6.19e-06, 1.21e-04, 1.13e-03, 4.71e-03,
            1.19e-02, 2.98e-02, 6.07e-02, 6.91e-02, 3.79e-02, 9.33e-03,
            9.85e-04, 4.40e-05, 2.24e-05, 4.82e-04, 4.43e-03, 1.67e-02,
            2.61e-02
        ])
        data.dos_bins = np.array([
            0.58, 0.78, 0.98, 1.18, 1.38, 1.58, 1.78, 1.98, 2.18, 2.38, 2.58,
            2.78, 2.98, 3.18, 3.38, 3.58, 3.78, 3.98, 4.18, 4.38, 4.58, 4.78,
            4.98, 5.18, 5.38, 5.58, 5.78, 5.98, 6.18, 6.38, 6.58, 6.78, 6.98,
            7.18, 7.38, 7.58, 7.78, 7.98, 8.18, 8.38, 8.58, 8.78, 8.98, 9.18,
            9.38, 9.58, 9.78, 9.98, 10.18, 10.38, 10.58, 10.78, 10.98, 11.18,
            11.38, 11.58, 11.78, 11.98, 12.18, 12.38, 12.58, 12.78
        ]) * ureg('E_h')
        data.fermi = np.array([4.71, 4.71]) * ureg('E_h')
        self.data = data
        self.title = 'Iron'
        self.mirror = False

        # Results
        self.fig = plot_dos(self.data, self.title, self.mirror)
        self.ax = self.fig.axes[0]
Esempio n. 14
0
    def test_qpts_cart_to_frac_trivial(trivial_crystal, trivial_qpts):
        """Check internal method for q-point conversion with trivial example"""
        cart_qpts = np.asarray(trivial_qpts['cart'], dtype=float
                               ) * ureg('1 / angstrom')
        frac_qpts = np.asarray(trivial_qpts['frac'], dtype=float)
        calc_frac_qpts = _qpts_cart_to_frac(cart_qpts, trivial_crystal)

        npt.assert_almost_equal(calc_frac_qpts, frac_qpts)
Esempio n. 15
0
    def from_castep_phonon_dos(cls: SC, filename: str) -> SC:
        """
        Reads total DOS and per-element PDOS from a CASTEP
        .phonon_dos file

        Parameters
        ----------
        filename
            The path and name of the .phonon_dos file to read
        """
        data = read_phonon_dos_data(filename)
        items = data['dos'].items()
        metadata = {'line_data': [{'label': item[0]} for item in items]}
        y_data = np.stack([item[1] for item in items])
        return Spectrum1DCollection(data['dos_bins'] *
                                    ureg(data['dos_bins_unit']),
                                    y_data * ureg('dimensionless'),
                                    metadata=metadata)
Esempio n. 16
0
    def test_qpts_cart_to_frac_roundtrip(random_qpts_array,
                                         nontrivial_crystal):
        frac_qpts = random_qpts_array
        cart_qpts = frac_qpts.dot(nontrivial_crystal.reciprocal_cell()
                                  .to('1 / angstrom').magnitude
                                  ) * ureg('1 / angstrom')
        calc_frac_qpts = _qpts_cart_to_frac(cart_qpts, nontrivial_crystal)

        npt.assert_almost_equal(calc_frac_qpts, frac_qpts)
Esempio n. 17
0
 def mock_crystal(self, mocker):
     crystal = mocker.MagicMock()
     crystal.configure_mock(
         **{'reciprocal_cell.return_value': np.array([[1, 0, 0],
                                                      [0, 1, 0],
                                                      [0, 0, 1]])
            * ureg('1 / angstrom'),
            'get_mp_grid_spec.return_value': (2, 2, 2)})
     return crystal
Esempio n. 18
0
    def test_sample_sphere_structure_factor(self, mocker, mock_crystal,
                                            mock_fc, mock_qpm,
                                            mock_s, mock_dw, random_qpts_array,
                                            options):
        # Make sure the same instance of mock DebyeWaller is used everywhere
        if options['dw'] == 'mock_dw':
            options['dw'] = mock_dw

        # Fixed return values for dummy functions
        return_bins = self._energy_bins
        return_scattering_lengths = self._scattering_lengths

        # Dummy out functions called by sample_sphere_structure_factor
        # that are tested elsewhere
        self.mock_get_default_bins(mocker, return_bins)
        get_qpts_sphere = self.mock_get_qpts_sphere(mocker, random_qpts_array)
        get_ref_data = self.mock_get_reference_data(mocker,
                                                    return_scattering_lengths)

        assert (sample_sphere_structure_factor(
            mock_fc, **options)
            == 'calculate_1d_average_return_value')

        # Check scattering lengths were looked up as expected
        if isinstance(options['scattering_lengths'], str):
            assert get_ref_data.call_args == (
                (), {'physical_property': 'coherent_scattering_length',
                     'collection': 'Sears1992'})
        else:
            assert isinstance(options['scattering_lengths'], dict)

        # Check qpts sphere called as expected
        assert get_qpts_sphere.call_args == ((options['npts'],),
                                             {'sampling': options['sampling'],
                                              'jitter': options['jitter']})

        # Check expected list of qpoints was passed to forceconstants
        # (fractional q = cart q because the lattice vectors are unit cube)
        npt.assert_almost_equal(
            random_qpts_array * options['mod_q'].magnitude,
            mock_fc.calculate_qpoint_phonon_modes.call_args[0][0])

        # Check structure factor args were as expected
        assert (mock_qpm.calculate_structure_factor.call_args
                == (tuple(), {'scattering_lengths': self._scattering_lengths,
                              'dw': mock_dw}))

        # Check auto grid was used if temperature given
        if options.get('temperature') is not None:
            assert (mock_qpm.calculate_debye_waller.call_args[0][0]
                    == options['temperature'])
            assert (mock_crystal.get_mp_grid_spec.call_args[0][0]
                    == 0.025 * ureg('1/angstrom'))

        # Check expected bins set for 1d averaging
        assert mock_s.calculate_1d_average.call_args == ((return_bins,),)
Esempio n. 19
0
def _grid_spec_from_args(crystal: Crystal,
                           grid: Optional[Sequence[int]] = None,
                           grid_spacing: Quantity = 0.1 * ureg('1/angstrom')
                           ) -> Sequence[int]:
    """Get Monkorst-Pack mesh divisions from user arguments"""
    if grid:
        grid_spec = grid
    else:
        grid_spec = crystal.get_mp_grid_spec(spacing=grid_spacing)
    return grid_spec
Esempio n. 20
0
    def _bose_corrected_structure_factor(
            self,
            e_bins: Quantity,
            calc_bose: bool = True,
            temperature: Optional[Quantity] = None) -> Quantity:
        """Bin structure factor in energy, return (Bose-populated) array

        Parameters
        ----------
        e_bins
            Shape (n_e_bins + 1,) float Quantity. The energy bin edges
        calc_bose
            Whether to calculate and apply the Bose population factor
        temperature
            Temperature used to calculate the Bose factor. This is only
            required if StructureFactor.temperature = None, otherwise
            the temperature stored in StructureFactor will be used.

        Returns
        -------
        intensities
            Scattering intensities as array over (qpt, energy)
        """
        # Convert units
        freqs = self._frequencies
        e_bins_internal = e_bins.to('hartree').magnitude

        # Create initial sqw_map with an extra an energy bin either
        # side, for any branches that fall outside the energy bin range
        sqw_map = np.zeros((self.n_qpts, len(e_bins) + 1))
        sf = self._structure_factors

        p_intensity = sf
        n_intensity = sf
        if calc_bose:
            try:
                bose = self._bose_factor(temperature)
                p_intensity = (1 + bose) * p_intensity
                n_intensity = bose * n_intensity
            except NoTemperatureError:
                pass

        p_bin = np.digitize(freqs, e_bins_internal)
        n_bin = np.digitize(-freqs, e_bins_internal)

        # Sum intensities into bins
        first_index = np.transpose(
            np.tile(range(self.n_qpts), (3 * self.crystal.n_atoms, 1)))
        np.add.at(sqw_map, (first_index, p_bin), p_intensity)
        np.add.at(sqw_map, (first_index, n_bin), n_intensity)
        # Exclude values outside ebin range
        sqw_map = sqw_map[:, 1:-1] * ureg('bohr**2').to(
            self.structure_factors_unit)

        return sqw_map
Esempio n. 21
0
    def calculate_dos(self, dos_bins: Quantity,
                      mode_widths: Optional[np.ndarray] = None,
                      mode_widths_min: Quantity = Quantity(0.01, 'meV')
                      ) -> Spectrum1D:
        """
        Calculates a density of states

        Parameters
        ----------
        dos_bins
            Shape (n_e_bins + 1,) float Quantity. The energy bin edges
            to use for calculating the DOS
        mode_widths
            Shape (n_qpts, n_branches) float Quantity in energy units.
            The broadening width for each mode at each q-point, for
            adaptive broadening
        mode_widths_min
            Scalar float Quantity in energy units. Sets a lower limit on
            the mode widths, as mode widths of zero will result in
            infinitely sharp peaks

        Returns
        -------
        dos
            A spectrum containing the energy bins on the x-axis and dos
            on the y-axis
        """
        freqs = self._frequencies
        n_modes = self.frequencies.shape[1]
        # dos_bins commonly contains a 0 bin, and converting from 0 1/cm
        # to 0 hartree causes a RuntimeWarning, so suppress it
        with warnings.catch_warnings():
            warnings.filterwarnings('ignore', category=RuntimeWarning)
            dos_bins_calc = dos_bins.to('hartree').magnitude
        if mode_widths is not None:
            from scipy.stats import norm
            dos_bins_calc = Spectrum1D._bin_edges_to_centres(dos_bins_calc)
            dos = np.zeros(len(dos_bins_calc))
            mode_widths = mode_widths.to('hartree').magnitude
            mode_widths = np.maximum(mode_widths,
                                     mode_widths_min.to('hartree').magnitude)
            for q in range(self.n_qpts):
                for m in range(n_modes):
                    pdf = norm.pdf(dos_bins_calc, loc=freqs[q,m],
                                   scale=mode_widths[q,m])
                    dos += pdf*self.weights[q]/n_modes
        else:
            weights = np.repeat(self.weights[:, np.newaxis],
                                n_modes,
                                axis=1)
            dos, _ = np.histogram(freqs, dos_bins_calc, weights=weights, density=True)

        return Spectrum1D(
            dos_bins,
            dos*ureg('dimensionless'))
Esempio n. 22
0
    def from_castep_phonon_dos(cls: S1D,
                               filename: str,
                               element: Optional[str] = None) -> S1D:
        """
        Reads DOS from a CASTEP .phonon_dos file

        Parameters
        ----------
        filename
            The path and name of the .phonon_dos file to read
        element
            Which element's PDOS to read. If not supplied reads
            the total DOS.
        """
        data = read_phonon_dos_data(filename)
        if element is None:
            element = 'Total'
        return Spectrum1D(data['dos_bins'] * ureg(data['dos_bins_unit']),
                          data['dos'][element] * ureg('dimensionless'),
                          metadata={'label': element})
Esempio n. 23
0
 def test_calculate_dos_with_mode_widths(self, material, qpt_freqs_json,
                                         mode_widths_json,
                                         expected_dos_json, ebins):
     qpt_freqs = get_qpt_freqs(material, qpt_freqs_json)
     with open(os.path.join(get_fc_dir(), mode_widths_json), 'r') as fp:
         modw_dict = json.load(fp)
     mode_widths = modw_dict['mode_widths'] * ureg(
         modw_dict['mode_widths_unit'])
     dos = qpt_freqs.calculate_dos(ebins, mode_widths=mode_widths)
     expected_dos = get_expected_spectrum1d(expected_dos_json)
     check_spectrum1d(dos, expected_dos)
Esempio n. 24
0
    def cell_volume(self) -> Quantity:
        """
        Calculates the cell volume

        Returns
        -------
        volume
            Scalar float quantity in length**3 units. The cell volume
        """
        vol = self._cell_volume() * ureg.bohr**3
        return vol.to(ureg(self.cell_vectors_unit)**3)
Esempio n. 25
0
    def broaden(self: S1D, x_width: Quantity, shape: str = 'gauss') -> S1D:
        """
        Broaden y_data and return a new broadened spectrum object

        Parameters
        ----------
        x_width
            Scalar float Quantity. The broadening FWHM
        shape
            One of {'gauss', 'lorentz'}. The broadening shape

        Returns
        -------
        broadened_spectrum
            A new Spectrum1D object with broadened y_data

        Raises
        ------
        ValueError
            If shape is not one of the allowed strings
        """
        if shape == 'gauss':
            xsigma = self._gfwhm_to_sigma(x_width, self.get_bin_centres())
            y_broadened = gaussian_filter1d(
                self.y_data.magnitude, xsigma, mode='constant') * ureg(
                    self.y_data_unit)
        elif shape == 'lorentz':
            broadening = _distribution_1d(self.get_bin_centres().magnitude,
                                          x_width.to(
                                              self.x_data_unit).magnitude,
                                          shape=shape)
            y_broadened = correlate1d(self.y_data.magnitude,
                                      broadening,
                                      mode='constant') * ureg(self.y_data_unit)
        else:
            raise ValueError(f"Distribution shape '{shape}' not recognised")

        return Spectrum1D(
            np.copy(self.x_data.magnitude) * ureg(self.x_data_unit),
            y_broadened, deepcopy(
                (self.x_tick_labels)), deepcopy(self.metadata))
Esempio n. 26
0
def _get_q_distance(length_unit_string: str, q_distance: float) -> Quantity:
    """
    Parse user arguments to obtain reciprocal-length spacing Quantity
    """
    try:
        length_units = ureg(length_unit_string)
    except UndefinedUnitError:
        raise ValueError("Length unit not known. Euphonic uses Pint for units."
                         " Try 'angstrom' or 'bohr'. Metric prefixes "
                         "are also allowed, e.g 'nm'.")
    recip_length_units = 1 / length_units
    return q_distance * recip_length_units
Esempio n. 27
0
def calculate_dos_map(modes: euphonic.QpointPhononModes,
                      ebins: euphonic.Quantity) -> euphonic.Spectrum2D:
    from euphonic.util import _calc_abscissa
    q_bins = _calc_abscissa(modes.crystal.reciprocal_cell(), modes.qpts)

    bin_indices = np.digitize(modes.frequencies.magnitude, ebins.magnitude)
    intensity_map = np.zeros((modes.n_qpts, len(ebins) + 1))
    first_index = np.tile(range(modes.n_qpts),
                          (3 * modes.crystal.n_atoms, 1)).transpose()
    np.add.at(intensity_map, (first_index, bin_indices), 1)

    return euphonic.Spectrum2D(q_bins, ebins,
                               intensity_map[:, :-1] * ureg('dimensionless'))
Esempio n. 28
0
class TestStructureFactorCalculate1dAverage:
    @pytest.mark.parametrize(
        'material, sf_json, expected_1d_json, ebins, kwargs',
        [('quartz', 'quartz_666_300K_structure_factor.json',
          'quartz_666_300K_sf_1d_average_with_weights.json',
          np.arange(0, 156) * ureg('meV'), {}),
         ('quartz', 'quartz_666_300K_structure_factor_noweights.json',
          'quartz_666_300K_sf_1d_average_with_weights.json',
          np.arange(0, 156) * ureg('meV'), {
              'weights':
              np.load(
                  os.path.join(get_sf_dir('quartz'), 'quartz_666_weights.npy'))
          }),
         ('quartz', 'quartz_666_300K_structure_factor_noweights.json',
          'quartz_666_300K_sf_1d_average_noweights.json',
          np.arange(0, 156) * ureg('meV'), {})])
    def test_calculate_1d_average(self, material, sf_json, expected_1d_json,
                                  ebins, kwargs):
        sf = get_sf(material, sf_json)
        sw = sf.calculate_1d_average(ebins, **kwargs)
        expected_sw = get_expected_spectrum1d(expected_1d_json)
        check_spectrum1d(sw, expected_sw)
Esempio n. 29
0
def _qpts_cart_to_frac(qpts: Quantity,
                       crystal: Crystal) -> np.ndarray:
    """Convert set of q-points from Cartesian to fractional coordinates

    Parameters
    ----------

    qpts
        Array of q-points in Cartesian coordinates.
    crystal
        Crystal structure determining reciprocal lattice

    Returns
    -------
    np.ndarray
        Dimensionless array of q-points in fractional coordinates
    """
    lattice = crystal.reciprocal_cell()

    return np.linalg.solve(lattice.to(ureg('1/bohr')).magnitude.T,
                           qpts.to(ureg('1/bohr')).magnitude.T
                           ).T
Esempio n. 30
0
    def setUp(self):
        # Test creation of BandsData object (which reads Fe.bands file in
        # test/data dir). There is no Fe.castep file in test/data so the ion_r
        # and ion_pos attributes shouldn't exist

        # Create trivial function object so attributes can be assigned to it
        expctd_data = type('', (), {})()  # Expected data

        expctd_data.cell_vec = np.array(
            [[-2.708355, 2.708355, 2.708355], [2.708355, -2.708355, 2.708355],
             [2.708355, 2.708355, -2.708355]]) * ureg('bohr')

        expctd_data.qpts = np.array([[-0.37500000, -0.45833333, 0.29166667],
                                     [-0.37500000, -0.37500000, 0.29166667]])
        expctd_data.weights = np.array([0.01388889, 0.01388889])
        expctd_data.freqs = np.array([[
            0.02278248, 0.02644693, 0.12383402, 0.15398152, 0.17125020,
            0.43252010
        ],
                                      [
                                          0.02760952, 0.02644911, 0.12442671,
                                          0.14597457, 0.16728951, 0.35463529
                                      ]]) * ureg('hartree')
        expctd_data.freq_down = np.array(
            [[
                0.08112495, 0.08345039, 0.19185076, 0.22763689, 0.24912308,
                0.46511567
            ],
             [
                 0.08778721, 0.08033338, 0.19288937, 0.21817779, 0.24476910,
                 0.39214129
             ]]) * ureg('hartree')
        expctd_data.fermi = [0.173319, 0.173319] * ureg('hartree')
        self.expctd_data = expctd_data

        seedname = 'Fe'
        path = 'data'
        data = BandsData.from_castep(seedname, path=path)
        self.data = data