def _bands_from_force_constants(data: ForceConstants, q_distance: Quantity, insert_gamma: bool = True, frequencies_only: bool = False, **calc_modes_kwargs ) -> Tuple[Union[QpointPhononModes, QpointFrequencies], XTickLabels, SplitArgs]: structure = data.crystal.to_spglib_cell() bandpath = seekpath.get_explicit_k_path( structure, reference_distance=q_distance.to('1 / angstrom').magnitude) if insert_gamma: _insert_gamma(bandpath) x_tick_labels = _get_tick_labels(bandpath) split_args = {'indices': _get_break_points(bandpath)} print( "Computing phonon modes: {n_modes} modes across {n_qpts} q-points" .format(n_modes=(data.crystal.n_atoms * 3), n_qpts=len(bandpath["explicit_kpoints_rel"]))) qpts = bandpath["explicit_kpoints_rel"] if frequencies_only: modes = data.calculate_qpoint_frequencies(qpts, reduce_qpts=False, **calc_modes_kwargs) else: modes = data.calculate_qpoint_phonon_modes(qpts, reduce_qpts=False, **calc_modes_kwargs) return modes, x_tick_labels, split_args
def test_create_from_phonopy_without_installed_modules_raises_err( self, phonopy_args, mocker): # Mock import of yaml, h5py to raise ModuleNotFoundError import builtins real_import = builtins.__import__ def mocked_import(name, *args, **kwargs): if name == 'h5py' or name == 'yaml': raise ModuleNotFoundError return real_import(name, *args, **kwargs) mocker.patch('builtins.__import__', side_effect=mocked_import) with pytest.raises(ImportPhonopyReaderError): ForceConstants.from_phonopy(**phonopy_args)
def getForceConstants(material): docs_dir = os.path.dirname(os.path.abspath(__file__)) try: return ForceConstants.from_castep(str(Path(docs_dir, material + ".castep_bin"))) except FileNotFoundError: print('{} not found in {}. Fetching remote content.'.format(material, docs_dir)) return fetchForceConstants(material)
def fetchForceConstants(material): base_url = "https://raw.githubusercontent.com/g5t/brille/master/docs/tutorials" file_to_fetch = material + ".castep_bin" with tempfile.TemporaryDirectory() as tmp_dir: r = requests.get(base_url + "/" + file_to_fetch) if not r.ok: raise Exception("Fetching {} failed with reason '{}'".format(file_to_fetch, r.reason)) out_path = Path(tmp_dir, file_to_fetch) open(str(out_path), 'wb').write(r.content) idata = ForceConstants.from_castep(str(out_path)) return idata
def force_constants_from_file(filename: Union[str, os.PathLike] ) -> ForceConstants: """ Load force constants data from file Parameters ---------- filename Data file Returns ------- ForceConstants """ path = pathlib.Path(filename) if path.suffix == '.hdf5': if (path.parent / 'phonopy.yaml').is_file(): return ForceConstants.from_phonopy(path=path.parent, summary_name='phonopy.yaml', fc_name=path.name) raise ValueError("Phonopy force_constants.hdf5 file " "must be accompanied by phonopy.yaml") elif path.suffix == '.yaml': # Assume this is a (renamed?) phonopy.yaml file if (path.parent / 'force_constants.hdf5').is_file(): fc_name = 'force_constants.hdf5' else: fc_name = 'FORCE_CONSTANTS' return ForceConstants.from_phonopy(path=path.parent, fc_name=fc_name, summary_name=path.name) elif path.suffix in ('.castep_bin', '.check'): return ForceConstants.from_castep(filename) elif path.suffix == '.json': return ForceConstants.from_json_file(filename) else: raise ValueError("File not recognised. Filename should be " "*.yaml or force_constants.hdf5 (phonopy), " "*.castep_bin or *.check " "(castep) or *.json (JSON from Euphonic).")
def _get_debye_waller(temperature: Quantity, fc: ForceConstants, grid: Optional[Sequence[int]] = None, grid_spacing: Quantity = 0.1 * ureg('1/angstrom'), **calc_modes_kwargs ) -> DebyeWaller: """Generate Debye-Waller data from force constants and grid specification """ mp_grid_spec = _grid_spec_from_args(fc.crystal, grid=grid, grid_spacing=grid_spacing) print("Calculating Debye-Waller factor on {} q-point grid" .format(' x '.join(map(str, mp_grid_spec)))) dw_phonons = fc.calculate_qpoint_phonon_modes( euphonic.util.mp_grid(mp_grid_spec), **calc_modes_kwargs) return dw_phonons.calculate_debye_waller(temperature)
def test_create_from_phonopy_without_cloader_is_ok(self, material, phonopy_args, mocker): # Mock 'from yaml import CLoader as Loader' to raise ImportError import builtins real_import = builtins.__import__ def mocked_import(name, globals, locals, fromlist, level): if name == 'yaml': if fromlist is not None and fromlist[0] == 'CSafeLoader': raise ImportError return real_import(name, globals, locals, fromlist, level) mocker.patch('builtins.__import__', side_effect=mocked_import) phonopy_args['path'] = get_phonopy_path(material, '') fc = ForceConstants.from_phonopy(**phonopy_args) expected_fc = get_expected_fc(material) check_force_constants(fc, expected_fc)
def setup_euphonic(self): from euphonic import ForceConstants from euphonic_sqw_models import CoherentCrystal fc = ForceConstants.from_castep('demo/datafiles/quartz.castep_bin') self.euobj = CoherentCrystal(fc, debye_waller_grid=[6, 6, 6], temperature=100, negative_e=True, asr=True, chunk=10000, use_c=True) self.scalefac = 200 self.effective_fwhm = 1 self.intrinsic_fwhm = 0.1 self.wsc = self.m.cut_sqw('demo/datafiles/quartz_cut.sqw', [-3.02, -2.98], [5, 0.5, 38])
def test_calculate_qpoint_phonon_modes(seedname, use_c, n_threads, benchmark): # Set up fc = ForceConstants.from_castep( os.path.join(get_data_path(), f'{seedname}.castep_bin')) qpts = get_qpts() # Benchmark if use_c: benchmark(fc.calculate_qpoint_phonon_modes, qpts, use_c=True, fall_back_on_python=False, n_threads=n_threads, asr='reciprocal', eta_scale=0.75) elif n_threads == 1: benchmark(fc.calculate_qpoint_phonon_modes, qpts, use_c=False, asr='reciprocal', eta_scale=0.75)
def test_calculate_structure_factor(seedname, benchmark): # Set up qpts = get_qpts() fc = ForceConstants.from_castep( os.path.join(get_data_path(), f'{seedname}.castep_bin')) phonons = fc.calculate_qpoint_phonon_modes(qpts, use_c=True, fall_back_on_python=False, n_threads=5) fm = ureg('fm') scattering_lengths = { 'La': 8.24 * fm, 'Zr': 7.16 * fm, 'O': 5.803 * fm, 'C': 6.646 * fm, 'Si': 4.1491 * fm, 'H': -3.7390 * fm, 'N': 9.36 * fm, 'S': 2.847 * fm, 'Nb': 7.054 * fm } # Benchmark benchmark(phonons.calculate_structure_factor, scattering_lengths=scattering_lengths)
def get_quartz_fc(): return ForceConstants.from_castep( get_castep_path('quartz', 'quartz.castep_bin'))
def get_si2_fc(): return ForceConstants.from_castep( get_castep_path('Si2-sc-skew', 'Si2-sc-skew.castep_bin'))
def test_calc_qpt_ph_mds_asr_with_nonsense_fc_raises_warning(self, asr): fc = ForceConstants.from_json_file( os.path.join(get_fc_dir(), 'quartz_random_force_constants.json')) with pytest.warns(UserWarning): fc.calculate_qpoint_phonon_modes(get_test_qpts(), asr=asr)
def get_lzo_fc(): return ForceConstants.from_castep( get_castep_path('LZO', 'La2Zr2O7.castep_bin'))
def test_create_from_castep_with_no_fc_raises_runtime_error(self): with pytest.raises(RuntimeError): ForceConstants.from_castep( get_castep_path('h-BN', 'h-BN_no_force_constants.castep_bin'))
def create_from_json(self, request): material = request.param expected_fc = get_expected_fc(material) fc = ForceConstants.from_json_file(get_json_file(material)) return fc, expected_fc
def get_si2_qpt_ph_modes(self, request): fc = ForceConstants.from_castep( get_castep_path('Si2-sc-skew', 'Si2-sc-skew.castep_bin')) kwargs = self.get_multithreaded_kwargs(request.param) return fc.calculate_qpoint_phonon_modes(get_test_qpts(), **kwargs)
def create_from_dict(self, request): material = request.param expected_fc = get_expected_fc(material) fc = ForceConstants.from_dict(expected_fc.to_dict()) return fc, expected_fc
def create_from_phonopy(self, request): material, phonopy_args = request.param phonopy_args['path'] = get_phonopy_path(material, '') fc = ForceConstants.from_phonopy(**phonopy_args) expected_fc = get_expected_fc(material) return fc, expected_fc
def serialise_to_dict(self, request): fc, expected_fc = request.param # Convert to dict, then back to object to test fc_dict = fc.to_dict() fc_from_dict = ForceConstants.from_dict(fc_dict) return fc_from_dict, expected_fc
def test_serialise_to_json_file(self, fc, tmpdir): output_file = str(tmpdir.join('tmp.test')) fc.to_json_file(output_file) check_json_metadata(output_file, 'ForceConstants') deserialised_fc = ForceConstants.from_json_file(output_file) check_force_constants(fc, deserialised_fc)
def test_faulty_object_creation(self, inject_faulty_elements): faulty_args, faulty_kwargs, expected_exception = inject_faulty_elements with pytest.raises(expected_exception): ForceConstants(*faulty_args, **faulty_kwargs)
def calculate_optimum_eta(filename: str, eta_min: float = 0.25, eta_max: float = 1.5, eta_step: float = 0.25, n: int = 100, print_to_terminal: bool = False): """ Calculate the optimum eta and other etas from the filename castep_bin file Parameters ---------- filename : str The path and name of the .castep_bin/.check to read from eta_min : float, optional, Default: 0.25 The minimum value of eta to test eta_max : float, optional, Default: 1.5 The maximum value of eta to test eta_step : float, optional, Default: 0.25 The difference between each eta to test n : int, optional, Default: 100 The number of times to loop over q-points. A higher value will get a more reliable timing, but will take longer print_to_terminal : bool, optional, Default: False Whether to print the outcome to terminal or not Returns ------- Tuple[float, float, float, np.array, np.array, np.array]: A tuple of the optimal eta, the time it took to initialise the optimal eta, the time per qpoint for the optimal eta, other etas, their initialisation times and their times per qpoint. """ etas = np.arange(eta_min, eta_max + eta_step / 100, eta_step) t_init = np.zeros(len(etas), dtype=np.float64) t_tot = np.zeros(len(etas), dtype=np.float64) idata = ForceConstants.from_castep(filename) sfmt = '{:20s}' tfmt = '{: 3.2f}' etafmt = '{: 2.2f}' for i, eta in enumerate(etas): if print_to_terminal: print(('Results for eta ' + etafmt).format(eta)) # Time Ewald sum initialisation start = time.time() idata._dipole_correction_init(eta_scale=eta) end = time.time() t_init[i] = end - start if print_to_terminal: print((sfmt + ': ' + tfmt + ' s').format('Ewald init time', t_init[i])) # Time per qpt start = time.time() for n in range(n): idata._calculate_dipole_correction(np.array([0.5, 0.5, 0.5])) end = time.time() t_tot[i] = end - start if print_to_terminal: print( (sfmt + ': ' + tfmt + ' ms\n').format('Ewald time/qpt', t_tot[i] * 1000 / n)) opt = np.argmin(t_tot) if print_to_terminal: print('******************************') print(('Suggested optimum eta is ' + etafmt).format(etas[opt])) print((sfmt + ': ' + tfmt + ' s').format('init time', t_init[opt])) print((sfmt + ': ' + tfmt + ' ms\n').format('time/qpt', t_tot[opt] * 1000 / n)) return etas[opt], t_init[opt], t_tot[opt], etas, t_init, t_tot
def sample_sphere_dos(fc: ForceConstants, mod_q: Quantity, sampling: str = 'golden', npts: int = 1000, jitter: bool = False, energy_bins: Quantity = None, **calc_modes_args ) -> Spectrum1D: """Sample the phonon DOS, averaging over a sphere of constant |q| Parameters ---------- fc Force constant data for system mod_q radius of sphere from which vector q samples are taken (in units of inverse length; usually 1/angstrom). 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] **calc_modes_args other keyword arguments (e.g. 'use_c') will be passed to ForceConstants.calculate_qpoint_phonon_modes() Returns ------- Spectrum1D """ 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_frequencies(qpts_frac, **calc_modes_args ) # type: QpointFrequencies if energy_bins is None: energy_bins = _get_default_bins(phonons) return phonons.calculate_dos(energy_bins)
def get_quartz_qpt_ph_modes(self, request): fc = ForceConstants.from_castep( get_castep_path('quartz', 'quartz.castep_bin')) kwargs = self.get_multithreaded_kwargs(request.param) return fc.calculate_qpoint_phonon_modes( get_test_qpts('split'), **kwargs)
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)
def get_cahgo2_qpt_ph_modes(self, request): fc = ForceConstants.from_phonopy( path=get_phonopy_path('CaHgO2', ''), summary_name='mp-7041-20180417.yaml') kwargs = self.get_multithreaded_kwargs(request.param) return fc.calculate_qpoint_phonon_modes(get_test_qpts(), **kwargs)
class TestForceConstantsCalculateQPointPhononModes: def get_lzo_fc(): return ForceConstants.from_castep( get_castep_path('LZO', 'La2Zr2O7.castep_bin')) lzo_params = [ (get_lzo_fc(), 'LZO', [get_test_qpts(), {}], 'LZO_no_asr_qpoint_phonon_modes.json'), (get_lzo_fc(), 'LZO', [get_test_qpts(), { 'asr': 'realspace' }], 'LZO_realspace_qpoint_phonon_modes.json'), (get_lzo_fc(), 'LZO', [get_test_qpts(), { 'asr': 'reciprocal' }], 'LZO_reciprocal_qpoint_phonon_modes.json') ] def get_si2_fc(): return ForceConstants.from_castep( get_castep_path('Si2-sc-skew', 'Si2-sc-skew.castep_bin')) si2_params = [ (get_si2_fc(), 'Si2-sc-skew', [get_test_qpts(), {}], 'Si2-sc-skew_no_asr_qpoint_phonon_modes.json'), (get_si2_fc(), 'Si2-sc-skew', [get_test_qpts(), { 'asr': 'realspace' }], 'Si2-sc-skew_realspace_qpoint_phonon_modes.json'), (get_si2_fc(), 'Si2-sc-skew', [get_test_qpts(), { 'asr': 'reciprocal' }], 'Si2-sc-skew_reciprocal_qpoint_phonon_modes.json') ] def get_quartz_fc(): return ForceConstants.from_castep( get_castep_path('quartz', 'quartz.castep_bin')) quartz_params = [ (get_quartz_fc(), 'quartz', [get_test_qpts(), { 'asr': 'reciprocal', 'splitting': False }], 'quartz_reciprocal_qpoint_phonon_modes.json'), (get_quartz_fc(), 'quartz', [ get_test_qpts(), { 'asr': 'reciprocal', 'splitting': False, 'eta_scale': 0.75 } ], 'quartz_reciprocal_qpoint_phonon_modes.json'), (get_quartz_fc(), 'quartz', [ get_test_qpts('split'), { 'asr': 'reciprocal', 'splitting': True, 'insert_gamma': False } ], 'quartz_split_reciprocal_qpoint_phonon_modes.json'), (get_quartz_fc(), 'quartz', [ get_test_qpts('split_insert_gamma'), { 'asr': 'reciprocal', 'splitting': True, 'insert_gamma': True } ], 'quartz_split_reciprocal_qpoint_phonon_modes.json') ] nacl_params = [ (ForceConstants.from_phonopy(path=get_phonopy_path('NaCl', ''), summary_name='phonopy_nacl.yaml'), 'NaCl', [get_test_qpts(), { 'asr': 'reciprocal' }], 'NaCl_reciprocal_qpoint_phonon_modes.json') ] cahgo2_params = [ (ForceConstants.from_phonopy(path=get_phonopy_path('CaHgO2', ''), summary_name='mp-7041-20180417.yaml'), 'CaHgO2', [get_test_qpts(), { 'asr': 'reciprocal' }], 'CaHgO2_reciprocal_qpoint_phonon_modes.json') ] @pytest.mark.parametrize( 'fc, material, all_args, expected_qpoint_phonon_modes_file', lzo_params + quartz_params + nacl_params + si2_params + cahgo2_params) @pytest.mark.parametrize('reduce_qpts, n_threads', [(False, 0), (True, 0), (True, 1), (True, 2)]) def test_calculate_qpoint_phonon_modes(self, fc, material, all_args, expected_qpoint_phonon_modes_file, reduce_qpts, n_threads): func_kwargs = all_args[1] func_kwargs['reduce_qpts'] = reduce_qpts if n_threads == 0: func_kwargs['use_c'] = False else: func_kwargs['use_c'] = True func_kwargs['n_threads'] = n_threads qpoint_phonon_modes = fc.calculate_qpoint_phonon_modes( all_args[0], **func_kwargs) expected_qpoint_phonon_modes = ExpectedQpointPhononModes( os.path.join(get_qpt_ph_modes_dir(material), expected_qpoint_phonon_modes_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_ph_modes(qpoint_phonon_modes, expected_qpoint_phonon_modes, frequencies_atol=1e-4, frequencies_rtol=2e-5, acoustic_gamma_atol=gamma_atol) @pytest.mark.parametrize( ('fc, material, all_args, expected_qpoint_phonon_modes_file, ' 'expected_modw_file'), [(get_quartz_fc(), 'quartz', [ mp_grid([5, 5, 4]), { 'asr': 'reciprocal', 'return_mode_widths': True } ], 'quartz_554_full_qpoint_phonon_modes.json', 'quartz_554_full_mode_widths.json'), (get_lzo_fc(), 'LZO', [ mp_grid([2, 2, 2]), { 'asr': 'reciprocal', 'return_mode_widths': True } ], 'lzo_222_full_qpoint_phonon_modes.json', 'lzo_222_full_mode_widths.json')]) @pytest.mark.parametrize('n_threads', [0, 2]) def test_calculate_qpoint_phonon_modes_with_mode_widths( self, fc, material, all_args, expected_qpoint_phonon_modes_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 qpoint_phonon_modes, modw = fc.calculate_qpoint_phonon_modes( 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_qpoint_phonon_modes = ExpectedQpointPhononModes( os.path.join(get_qpt_ph_modes_dir(material), expected_qpoint_phonon_modes_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_ph_modes(qpoint_phonon_modes, expected_qpoint_phonon_modes, 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) # ForceConstants stores some values (supercell image list, vectors # for the Ewald sum) so check repeated calculations give the same # result def test_repeated_calculate_qpoint_phonon_modes_doesnt_change_result(self): fc = get_fc('quartz') qpt_ph_modes1 = fc.calculate_qpoint_phonon_modes(get_test_qpts(), asr='realspace') qpt_ph_modes2 = fc.calculate_qpoint_phonon_modes(get_test_qpts(), asr='realspace') check_qpt_ph_modes(qpt_ph_modes1, qpt_ph_modes2) @pytest.mark.parametrize( 'fc, material, qpt, kwargs, expected_qpt_ph_modes_file', [(get_fc('quartz'), 'quartz', np.array([[1., 1., 1.]]), { 'splitting': True }, 'quartz_single_qpoint_phonon_modes.json')]) def test_calculate_qpoint_phonon_modes_single_qpt( self, fc, material, qpt, kwargs, expected_qpt_ph_modes_file): qpoint_phonon_modes = fc.calculate_qpoint_phonon_modes(qpt, **kwargs) expected_qpoint_phonon_modes = ExpectedQpointPhononModes( os.path.join(get_qpt_ph_modes_dir(material), expected_qpt_ph_modes_file)) check_qpt_ph_modes(qpoint_phonon_modes, expected_qpoint_phonon_modes, frequencies_atol=1e-4, frequencies_rtol=2e-5) weights = np.array([0.1, 0.05, 0.05, 0.2, 0.2, 0.15, 0.15, 0.2, 0.1]) weights_output_split_gamma = np.array([ 0.1, 0.05, 0.025, 0.025, 0.2, 0.1, 0.1, 0.075, 0.075, 0.075, 0.075, 0.2, 0.1 ]) @pytest.mark.parametrize( 'fc, qpts, weights, expected_weights, kwargs', [(get_fc('quartz'), get_test_qpts(), weights, weights, {}), (get_fc('quartz'), get_test_qpts('split_insert_gamma'), weights, weights_output_split_gamma, { 'insert_gamma': True })]) def test_calculate_qpoint_phonon_modes_with_weights_sets_weights( self, fc, qpts, weights, expected_weights, kwargs): qpt_ph_modes_weighted = fc.calculate_qpoint_phonon_modes( qpts, weights=weights, **kwargs) npt.assert_allclose(qpt_ph_modes_weighted.weights, expected_weights) @pytest.mark.parametrize( 'fc, qpts, weights, expected_weights, kwargs', [(get_fc('quartz'), get_test_qpts(), weights, weights, {}), (get_fc('quartz'), get_test_qpts('split_insert_gamma'), weights, weights_output_split_gamma, { 'insert_gamma': True })]) def test_calculate_qpoint_phonon_modes_with_weights_doesnt_change_result( self, fc, qpts, weights, expected_weights, kwargs): qpt_ph_modes_weighted = fc.calculate_qpoint_phonon_modes( qpts, weights=weights, **kwargs) qpt_ph_modes_unweighted = fc.calculate_qpoint_phonon_modes( qpts, **kwargs) qpt_ph_modes_unweighted.weights = expected_weights check_qpt_ph_modes(qpt_ph_modes_weighted, qpt_ph_modes_unweighted) @pytest.mark.parametrize('asr', ['realspace', 'reciprocal']) def test_calc_qpt_ph_mds_asr_with_nonsense_fc_raises_warning(self, asr): fc = ForceConstants.from_json_file( os.path.join(get_fc_dir(), 'quartz_random_force_constants.json')) with pytest.warns(UserWarning): fc.calculate_qpoint_phonon_modes(get_test_qpts(), asr=asr)
# For some reason we _have_ to load euphonic before Matlab from euphonic import ForceConstants from pace_neutrons import EuphonicWrapper # Setting (os.RTLD_NOW | os.RTLD_DEEPBIND) flags causes a crash here... #import sys; sys.setdlopenflags(10) from pace_neutrons import Matlab m = Matlab() import numpy as np from matlab import double as md fc = ForceConstants.from_castep('quartz/quartz.castep_bin') euobj = EuphonicWrapper(fc, debye_waller_grid=[6, 6, 6], temperature=100, negative_e=True, asr=True, chunk=10000, use_c=True) scalefac = 1e12 effective_fwhm = 1 intrinsic_fwhm = 0.1 wsc = m.cut_sqw('quartz/2ph_m4_0_ECut.sqw', [-3.02, -2.98], [5, 0.5, 38]) # Calculate spectra with simple energy convolution (fixed width Gaussian) wsim = m.disp2sqw_eval(wsc, euobj.horace_disp, (scalefac), effective_fwhm) # Calculate spectra with full instrument resolution convolution
def test_create_from_phonopy_with_bad_inputs_raises_err( self, phonopy_args, err): with pytest.raises(err): ForceConstants.from_phonopy(**phonopy_args)