run_tests workflow: use parallel tox #1006
37 errors, 58 fail, 6 skipped, 988 pass in 39m 41s
14 files 14 suites 39m 41s ⏱️
1 089 tests 988 ✅ 6 💤 58 ❌ 37 🔥
6 514 runs 6 380 ✅ 39 💤 58 ❌ 37 🔥
Results for commit 073ac8a.
Annotations
Check warning on line 0 in script_tests.test_brille_convergence.TestRegression
github-actions / Test Results
1 out of 8 runs failed: test_brille_interpolator_from_force_constants_kwargs_passed[brille_conv_args1-expected_kwargs1] (script_tests.test_brille_convergence.TestRegression)
artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbafedce7b0>
qpts = array([[0. , 0. , 0. ],
[0.81917251, 0.67104361, 0.54970048],
[0.63834503, 0.342087...86, 0.03047588, 0.05137647],
[0.53416837, 0.70151949, 0.60107695],
[0.35334088, 0.3725631 , 0.15077742]])
weights = None, asr = 'realspace', dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = True
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
> import euphonic._euphonic as euphonic_c
E ModuleNotFoundError: No module named 'euphonic._euphonic'
../../euphonic/force_constants.py:638: ModuleNotFoundError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.script_tests.test_brille_convergence.TestRegression object at 0x7fbb118d0710>
inject_mocks = None
mocker = <pytest_mock.plugin.MockerFixture object at 0x7fbafe2cc2c0>
brille_conv_args = ['--brille-grid-type', 'mesh', '--use-c', '--n-threads', '2', '--asr', ...]
expected_kwargs = {'grid_type': 'mesh', 'interpolation_kwargs': {'asr': 'realspace', 'n_threads': 2, 'use_c': True}}
@pytest.mark.parametrize('brille_conv_args, expected_kwargs', [
(['--brille-npts', '25', '--disable-c'],
{'grid_npts': 25, 'grid_type': 'trellis',
'interpolation_kwargs': {'use_c': False}}),
(['--brille-grid-type', 'mesh', '--use-c',
'--n-threads', '2', '--asr', 'realspace'],
{'grid_type': 'mesh', 'interpolation_kwargs': {'asr': 'realspace',
'use_c': True,
'n_threads': 2}})
])
def test_brille_interpolator_from_force_constants_kwargs_passed(
self, inject_mocks, mocker, brille_conv_args, expected_kwargs):
from euphonic.brille import BrilleInterpolator
# Stop execution once from_fc has been called - we're only
# checking here that the correct arguments have been passed
# through
class MockException(Exception):
pass
mock = mocker.patch.object(BrilleInterpolator, 'from_force_constants',
side_effect=MockException())
try:
> euphonic.cli.brille_convergence.main(
[graphite_fc_file] + brille_conv_args)
script_tests/test_brille_convergence.py:144:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../euphonic/cli/brille_convergence.py:24: in main
check_brille_settings(**params)
../../euphonic/cli/brille_convergence.py:55: in check_brille_settings
modes = fc.calculate_qpoint_phonon_modes(qpts, **calc_modes_kwargs)
../../euphonic/force_constants.py:387: in calculate_qpoint_phonon_modes
qpts, freqs, weights, evecs, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <euphonic.force_constants.ForceConstants object at 0x7fbafedce7b0>
qpts = array([[0. , 0. , 0. ],
[0.81917251, 0.67104361, 0.54970048],
[0.63834503, 0.342087...86, 0.03047588, 0.05137647],
[0.53416837, 0.70151949, 0.60107695],
[0.35334088, 0.3725631 , 0.15077742]])
weights = None, asr = 'realspace', dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = True
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
import euphonic._euphonic as euphonic_c
use_c_status = True
except ImportError:
if use_c is None:
warnings.warn((
cext_err_msg
+ ' Falling back to pure Python calculation.'),
stacklevel=3)
else:
> raise ImportCError(cext_err_msg)
E euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
../../euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
1 out of 6 runs failed: test_calculate_structure_factor[1-quartz-fc0-qpt_ph_modes_args0-quartz_666_0K_debye_waller.json-quartz_0K_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)
artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f156a0>
qpts = array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0. ...[-1. , 1. , 1. ],
[-0.151515, 0.575758, 0.5 ],
[-1. , 1. , 1. ]])
weights = None, asr = None, dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = True
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
> import euphonic._euphonic as euphonic_c
E ModuleNotFoundError: No module named 'euphonic._euphonic'
../../euphonic/force_constants.py:638: ModuleNotFoundError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7fbb11f8eae0>
material = 'quartz'
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11f156a0>
qpt_ph_modes_args = (array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0....
[-0.151515, 0.575758, 0.5 ],
[-1. , 1. , 1. ]]), {'n_threads': 1, 'use_c': True})
dw_file = 'quartz_666_0K_debye_waller.json'
expected_sf_file = 'quartz_0K_fc_structure_factor.json', n_threads = 1
@pytest.mark.parametrize(
'material, fc, qpt_ph_modes_args, dw_file, expected_sf_file', [
('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
'quartz_666_0K_debye_waller.json',
'quartz_0K_fc_structure_factor.json'),
('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
'quartz_666_300K_debye_waller.json',
'quartz_300K_fc_structure_factor.json'),
('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
None,
'quartz_fc_structure_factor.json'),
('Si2-sc-skew', get_si2_fc(), (get_test_qpts(), {}),
'Si2-sc-skew_666_300K_debye_waller.json',
'Si2-sc-skew_300K_fc_structure_factor.json'),
('quartz', get_quartz_fc(),
(get_test_qpts('split'), {'asr': 'reciprocal'}),
'quartz_666_300K_debye_waller.json',
'quartz_recip_asr_300K_fc_structure_factor.json')])
@pytest.mark.parametrize('n_threads', [0, 1, 2])
def test_calculate_structure_factor(self, material, fc, qpt_ph_modes_args,
dw_file, expected_sf_file, n_threads):
args, kwargs = qpt_ph_modes_args
if n_threads == 0:
kwargs['use_c'] = False
else:
kwargs['use_c'] = True
kwargs['n_threads'] = n_threads
> qpt_ph_modes = fc.calculate_qpoint_phonon_modes(args, **kwargs)
euphonic_test/test_calculate_structure_factor.py:58:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../euphonic/force_constants.py:387: in calculate_qpoint_phonon_modes
qpts, freqs, weights, evecs, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f156a0>
qpts = array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0. ...[-1. , 1. , 1. ],
[-0.151515, 0.575758, 0.5 ],
[-1. , 1. , 1. ]])
weights = None, asr = None, dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = True
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
import euphonic._euphonic as euphonic_c
use_c_status = True
except ImportError:
if use_c is None:
warnings.warn((
cext_err_msg
+ ' Falling back to pure Python calculation.'),
stacklevel=3)
else:
> raise ImportCError(cext_err_msg)
E euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
../../euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
1 out of 6 runs failed: test_calculate_structure_factor[1-quartz-fc1-qpt_ph_modes_args1-quartz_666_300K_debye_waller.json-quartz_300K_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)
artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f3e480>
qpts = array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0. ...[-1. , 1. , 1. ],
[-0.151515, 0.575758, 0.5 ],
[-1. , 1. , 1. ]])
weights = None, asr = None, dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = True
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
> import euphonic._euphonic as euphonic_c
E ModuleNotFoundError: No module named 'euphonic._euphonic'
../../euphonic/force_constants.py:638: ModuleNotFoundError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7fbb11f8ef00>
material = 'quartz'
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11f3e480>
qpt_ph_modes_args = (array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0....
[-0.151515, 0.575758, 0.5 ],
[-1. , 1. , 1. ]]), {'n_threads': 1, 'use_c': True})
dw_file = 'quartz_666_300K_debye_waller.json'
expected_sf_file = 'quartz_300K_fc_structure_factor.json', n_threads = 1
@pytest.mark.parametrize(
'material, fc, qpt_ph_modes_args, dw_file, expected_sf_file', [
('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
'quartz_666_0K_debye_waller.json',
'quartz_0K_fc_structure_factor.json'),
('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
'quartz_666_300K_debye_waller.json',
'quartz_300K_fc_structure_factor.json'),
('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
None,
'quartz_fc_structure_factor.json'),
('Si2-sc-skew', get_si2_fc(), (get_test_qpts(), {}),
'Si2-sc-skew_666_300K_debye_waller.json',
'Si2-sc-skew_300K_fc_structure_factor.json'),
('quartz', get_quartz_fc(),
(get_test_qpts('split'), {'asr': 'reciprocal'}),
'quartz_666_300K_debye_waller.json',
'quartz_recip_asr_300K_fc_structure_factor.json')])
@pytest.mark.parametrize('n_threads', [0, 1, 2])
def test_calculate_structure_factor(self, material, fc, qpt_ph_modes_args,
dw_file, expected_sf_file, n_threads):
args, kwargs = qpt_ph_modes_args
if n_threads == 0:
kwargs['use_c'] = False
else:
kwargs['use_c'] = True
kwargs['n_threads'] = n_threads
> qpt_ph_modes = fc.calculate_qpoint_phonon_modes(args, **kwargs)
euphonic_test/test_calculate_structure_factor.py:58:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../euphonic/force_constants.py:387: in calculate_qpoint_phonon_modes
qpts, freqs, weights, evecs, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f3e480>
qpts = array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0. ...[-1. , 1. , 1. ],
[-0.151515, 0.575758, 0.5 ],
[-1. , 1. , 1. ]])
weights = None, asr = None, dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = True
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
import euphonic._euphonic as euphonic_c
use_c_status = True
except ImportError:
if use_c is None:
warnings.warn((
cext_err_msg
+ ' Falling back to pure Python calculation.'),
stacklevel=3)
else:
> raise ImportCError(cext_err_msg)
E euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
../../euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
1 out of 6 runs failed: test_calculate_structure_factor[1-quartz-fc2-qpt_ph_modes_args2-None-quartz_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)
artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f3ffb0>
qpts = array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0. ...[-1. , 1. , 1. ],
[-0.151515, 0.575758, 0.5 ],
[-1. , 1. , 1. ]])
weights = None, asr = None, dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = True
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
> import euphonic._euphonic as euphonic_c
E ModuleNotFoundError: No module named 'euphonic._euphonic'
../../euphonic/force_constants.py:638: ModuleNotFoundError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7fbb11f8eff0>
material = 'quartz'
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11f3ffb0>
qpt_ph_modes_args = (array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0....
[-0.151515, 0.575758, 0.5 ],
[-1. , 1. , 1. ]]), {'n_threads': 1, 'use_c': True})
dw_file = None, expected_sf_file = 'quartz_fc_structure_factor.json'
n_threads = 1
@pytest.mark.parametrize(
'material, fc, qpt_ph_modes_args, dw_file, expected_sf_file', [
('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
'quartz_666_0K_debye_waller.json',
'quartz_0K_fc_structure_factor.json'),
('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
'quartz_666_300K_debye_waller.json',
'quartz_300K_fc_structure_factor.json'),
('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
None,
'quartz_fc_structure_factor.json'),
('Si2-sc-skew', get_si2_fc(), (get_test_qpts(), {}),
'Si2-sc-skew_666_300K_debye_waller.json',
'Si2-sc-skew_300K_fc_structure_factor.json'),
('quartz', get_quartz_fc(),
(get_test_qpts('split'), {'asr': 'reciprocal'}),
'quartz_666_300K_debye_waller.json',
'quartz_recip_asr_300K_fc_structure_factor.json')])
@pytest.mark.parametrize('n_threads', [0, 1, 2])
def test_calculate_structure_factor(self, material, fc, qpt_ph_modes_args,
dw_file, expected_sf_file, n_threads):
args, kwargs = qpt_ph_modes_args
if n_threads == 0:
kwargs['use_c'] = False
else:
kwargs['use_c'] = True
kwargs['n_threads'] = n_threads
> qpt_ph_modes = fc.calculate_qpoint_phonon_modes(args, **kwargs)
euphonic_test/test_calculate_structure_factor.py:58:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../euphonic/force_constants.py:387: in calculate_qpoint_phonon_modes
qpts, freqs, weights, evecs, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f3ffb0>
qpts = array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0. ...[-1. , 1. , 1. ],
[-0.151515, 0.575758, 0.5 ],
[-1. , 1. , 1. ]])
weights = None, asr = None, dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = True
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
import euphonic._euphonic as euphonic_c
use_c_status = True
except ImportError:
if use_c is None:
warnings.warn((
cext_err_msg
+ ' Falling back to pure Python calculation.'),
stacklevel=3)
else:
> raise ImportCError(cext_err_msg)
E euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
../../euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
1 out of 6 runs failed: test_calculate_structure_factor[1-Si2-sc-skew-fc3-qpt_ph_modes_args3-Si2-sc-skew_666_300K_debye_waller.…w_300K_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)
artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8cd70>
qpts = array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0.5...[ 0.6 , 0. , 0.2 ],
[ 2. , 2. , 0.5 ],
[ 1.75 , 0.5 , 2.5 ]])
weights = None, asr = None, dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = True
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
> import euphonic._euphonic as euphonic_c
E ModuleNotFoundError: No module named 'euphonic._euphonic'
../../euphonic/force_constants.py:638: ModuleNotFoundError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7fbb11f8f110>
material = 'Si2-sc-skew'
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8cd70>
qpt_ph_modes_args = (array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0....
[ 2. , 2. , 0.5 ],
[ 1.75 , 0.5 , 2.5 ]]), {'n_threads': 1, 'use_c': True})
dw_file = 'Si2-sc-skew_666_300K_debye_waller.json'
expected_sf_file = 'Si2-sc-skew_300K_fc_structure_factor.json', n_threads = 1
@pytest.mark.parametrize(
'material, fc, qpt_ph_modes_args, dw_file, expected_sf_file', [
('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
'quartz_666_0K_debye_waller.json',
'quartz_0K_fc_structure_factor.json'),
('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
'quartz_666_300K_debye_waller.json',
'quartz_300K_fc_structure_factor.json'),
('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
None,
'quartz_fc_structure_factor.json'),
('Si2-sc-skew', get_si2_fc(), (get_test_qpts(), {}),
'Si2-sc-skew_666_300K_debye_waller.json',
'Si2-sc-skew_300K_fc_structure_factor.json'),
('quartz', get_quartz_fc(),
(get_test_qpts('split'), {'asr': 'reciprocal'}),
'quartz_666_300K_debye_waller.json',
'quartz_recip_asr_300K_fc_structure_factor.json')])
@pytest.mark.parametrize('n_threads', [0, 1, 2])
def test_calculate_structure_factor(self, material, fc, qpt_ph_modes_args,
dw_file, expected_sf_file, n_threads):
args, kwargs = qpt_ph_modes_args
if n_threads == 0:
kwargs['use_c'] = False
else:
kwargs['use_c'] = True
kwargs['n_threads'] = n_threads
> qpt_ph_modes = fc.calculate_qpoint_phonon_modes(args, **kwargs)
euphonic_test/test_calculate_structure_factor.py:58:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../euphonic/force_constants.py:387: in calculate_qpoint_phonon_modes
qpts, freqs, weights, evecs, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8cd70>
qpts = array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0.5...[ 0.6 , 0. , 0.2 ],
[ 2. , 2. , 0.5 ],
[ 1.75 , 0.5 , 2.5 ]])
weights = None, asr = None, dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = True
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
import euphonic._euphonic as euphonic_c
use_c_status = True
except ImportError:
if use_c is None:
warnings.warn((
cext_err_msg
+ ' Falling back to pure Python calculation.'),
stacklevel=3)
else:
> raise ImportCError(cext_err_msg)
E euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
../../euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
1 out of 6 runs failed: test_calculate_structure_factor[1-quartz-fc4-qpt_ph_modes_args4-quartz_666_300K_debye_waller.json-quart…r_300K_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)
artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d5b0>
qpts = array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0. ...[-1. , 1. , 1. ],
[-0.151515, 0.575758, 0.5 ],
[-1. , 1. , 1. ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = True
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
> import euphonic._euphonic as euphonic_c
E ModuleNotFoundError: No module named 'euphonic._euphonic'
../../euphonic/force_constants.py:638: ModuleNotFoundError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7fbb11f8f230>
material = 'quartz'
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d5b0>
qpt_ph_modes_args = (array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0....0.575758, 0.5 ],
[-1. , 1. , 1. ]]), {'asr': 'reciprocal', 'n_threads': 1, 'use_c': True})
dw_file = 'quartz_666_300K_debye_waller.json'
expected_sf_file = 'quartz_recip_asr_300K_fc_structure_factor.json'
n_threads = 1
@pytest.mark.parametrize(
'material, fc, qpt_ph_modes_args, dw_file, expected_sf_file', [
('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
'quartz_666_0K_debye_waller.json',
'quartz_0K_fc_structure_factor.json'),
('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
'quartz_666_300K_debye_waller.json',
'quartz_300K_fc_structure_factor.json'),
('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
None,
'quartz_fc_structure_factor.json'),
('Si2-sc-skew', get_si2_fc(), (get_test_qpts(), {}),
'Si2-sc-skew_666_300K_debye_waller.json',
'Si2-sc-skew_300K_fc_structure_factor.json'),
('quartz', get_quartz_fc(),
(get_test_qpts('split'), {'asr': 'reciprocal'}),
'quartz_666_300K_debye_waller.json',
'quartz_recip_asr_300K_fc_structure_factor.json')])
@pytest.mark.parametrize('n_threads', [0, 1, 2])
def test_calculate_structure_factor(self, material, fc, qpt_ph_modes_args,
dw_file, expected_sf_file, n_threads):
args, kwargs = qpt_ph_modes_args
if n_threads == 0:
kwargs['use_c'] = False
else:
kwargs['use_c'] = True
kwargs['n_threads'] = n_threads
> qpt_ph_modes = fc.calculate_qpoint_phonon_modes(args, **kwargs)
euphonic_test/test_calculate_structure_factor.py:58:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../euphonic/force_constants.py:387: in calculate_qpoint_phonon_modes
qpts, freqs, weights, evecs, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d5b0>
qpts = array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0. ...[-1. , 1. , 1. ],
[-0.151515, 0.575758, 0.5 ],
[-1. , 1. , 1. ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = True
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
import euphonic._euphonic as euphonic_c
use_c_status = True
except ImportError:
if use_c is None:
warnings.warn((
cext_err_msg
+ ' Falling back to pure Python calculation.'),
stacklevel=3)
else:
> raise ImportCError(cext_err_msg)
E euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
../../euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
1 out of 6 runs failed: test_calculate_structure_factor[2-quartz-fc0-qpt_ph_modes_args0-quartz_666_0K_debye_waller.json-quartz_0K_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)
artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f156a0>
qpts = array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0. ...[-1. , 1. , 1. ],
[-0.151515, 0.575758, 0.5 ],
[-1. , 1. , 1. ]])
weights = None, asr = None, dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = True
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
> import euphonic._euphonic as euphonic_c
E ModuleNotFoundError: No module named 'euphonic._euphonic'
../../euphonic/force_constants.py:638: ModuleNotFoundError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7fbb11f8f320>
material = 'quartz'
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11f156a0>
qpt_ph_modes_args = (array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0....
[-0.151515, 0.575758, 0.5 ],
[-1. , 1. , 1. ]]), {'n_threads': 2, 'use_c': True})
dw_file = 'quartz_666_0K_debye_waller.json'
expected_sf_file = 'quartz_0K_fc_structure_factor.json', n_threads = 2
@pytest.mark.parametrize(
'material, fc, qpt_ph_modes_args, dw_file, expected_sf_file', [
('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
'quartz_666_0K_debye_waller.json',
'quartz_0K_fc_structure_factor.json'),
('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
'quartz_666_300K_debye_waller.json',
'quartz_300K_fc_structure_factor.json'),
('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
None,
'quartz_fc_structure_factor.json'),
('Si2-sc-skew', get_si2_fc(), (get_test_qpts(), {}),
'Si2-sc-skew_666_300K_debye_waller.json',
'Si2-sc-skew_300K_fc_structure_factor.json'),
('quartz', get_quartz_fc(),
(get_test_qpts('split'), {'asr': 'reciprocal'}),
'quartz_666_300K_debye_waller.json',
'quartz_recip_asr_300K_fc_structure_factor.json')])
@pytest.mark.parametrize('n_threads', [0, 1, 2])
def test_calculate_structure_factor(self, material, fc, qpt_ph_modes_args,
dw_file, expected_sf_file, n_threads):
args, kwargs = qpt_ph_modes_args
if n_threads == 0:
kwargs['use_c'] = False
else:
kwargs['use_c'] = True
kwargs['n_threads'] = n_threads
> qpt_ph_modes = fc.calculate_qpoint_phonon_modes(args, **kwargs)
euphonic_test/test_calculate_structure_factor.py:58:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../euphonic/force_constants.py:387: in calculate_qpoint_phonon_modes
qpts, freqs, weights, evecs, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f156a0>
qpts = array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0. ...[-1. , 1. , 1. ],
[-0.151515, 0.575758, 0.5 ],
[-1. , 1. , 1. ]])
weights = None, asr = None, dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = True
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
import euphonic._euphonic as euphonic_c
use_c_status = True
except ImportError:
if use_c is None:
warnings.warn((
cext_err_msg
+ ' Falling back to pure Python calculation.'),
stacklevel=3)
else:
> raise ImportCError(cext_err_msg)
E euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
../../euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
1 out of 6 runs failed: test_calculate_structure_factor[2-quartz-fc1-qpt_ph_modes_args1-quartz_666_300K_debye_waller.json-quartz_300K_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)
artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f3e480>
qpts = array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0. ...[-1. , 1. , 1. ],
[-0.151515, 0.575758, 0.5 ],
[-1. , 1. , 1. ]])
weights = None, asr = None, dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = True
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
> import euphonic._euphonic as euphonic_c
E ModuleNotFoundError: No module named 'euphonic._euphonic'
../../euphonic/force_constants.py:638: ModuleNotFoundError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7fbb11f8f3b0>
material = 'quartz'
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11f3e480>
qpt_ph_modes_args = (array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0....
[-0.151515, 0.575758, 0.5 ],
[-1. , 1. , 1. ]]), {'n_threads': 2, 'use_c': True})
dw_file = 'quartz_666_300K_debye_waller.json'
expected_sf_file = 'quartz_300K_fc_structure_factor.json', n_threads = 2
@pytest.mark.parametrize(
'material, fc, qpt_ph_modes_args, dw_file, expected_sf_file', [
('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
'quartz_666_0K_debye_waller.json',
'quartz_0K_fc_structure_factor.json'),
('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
'quartz_666_300K_debye_waller.json',
'quartz_300K_fc_structure_factor.json'),
('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
None,
'quartz_fc_structure_factor.json'),
('Si2-sc-skew', get_si2_fc(), (get_test_qpts(), {}),
'Si2-sc-skew_666_300K_debye_waller.json',
'Si2-sc-skew_300K_fc_structure_factor.json'),
('quartz', get_quartz_fc(),
(get_test_qpts('split'), {'asr': 'reciprocal'}),
'quartz_666_300K_debye_waller.json',
'quartz_recip_asr_300K_fc_structure_factor.json')])
@pytest.mark.parametrize('n_threads', [0, 1, 2])
def test_calculate_structure_factor(self, material, fc, qpt_ph_modes_args,
dw_file, expected_sf_file, n_threads):
args, kwargs = qpt_ph_modes_args
if n_threads == 0:
kwargs['use_c'] = False
else:
kwargs['use_c'] = True
kwargs['n_threads'] = n_threads
> qpt_ph_modes = fc.calculate_qpoint_phonon_modes(args, **kwargs)
euphonic_test/test_calculate_structure_factor.py:58:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../euphonic/force_constants.py:387: in calculate_qpoint_phonon_modes
qpts, freqs, weights, evecs, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f3e480>
qpts = array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0. ...[-1. , 1. , 1. ],
[-0.151515, 0.575758, 0.5 ],
[-1. , 1. , 1. ]])
weights = None, asr = None, dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = True
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
import euphonic._euphonic as euphonic_c
use_c_status = True
except ImportError:
if use_c is None:
warnings.warn((
cext_err_msg
+ ' Falling back to pure Python calculation.'),
stacklevel=3)
else:
> raise ImportCError(cext_err_msg)
E euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
../../euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
1 out of 6 runs failed: test_calculate_structure_factor[2-quartz-fc2-qpt_ph_modes_args2-None-quartz_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)
artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f3ffb0>
qpts = array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0. ...[-1. , 1. , 1. ],
[-0.151515, 0.575758, 0.5 ],
[-1. , 1. , 1. ]])
weights = None, asr = None, dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = True
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
> import euphonic._euphonic as euphonic_c
E ModuleNotFoundError: No module named 'euphonic._euphonic'
../../euphonic/force_constants.py:638: ModuleNotFoundError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7fbb11f8f470>
material = 'quartz'
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11f3ffb0>
qpt_ph_modes_args = (array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0....
[-0.151515, 0.575758, 0.5 ],
[-1. , 1. , 1. ]]), {'n_threads': 2, 'use_c': True})
dw_file = None, expected_sf_file = 'quartz_fc_structure_factor.json'
n_threads = 2
@pytest.mark.parametrize(
'material, fc, qpt_ph_modes_args, dw_file, expected_sf_file', [
('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
'quartz_666_0K_debye_waller.json',
'quartz_0K_fc_structure_factor.json'),
('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
'quartz_666_300K_debye_waller.json',
'quartz_300K_fc_structure_factor.json'),
('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
None,
'quartz_fc_structure_factor.json'),
('Si2-sc-skew', get_si2_fc(), (get_test_qpts(), {}),
'Si2-sc-skew_666_300K_debye_waller.json',
'Si2-sc-skew_300K_fc_structure_factor.json'),
('quartz', get_quartz_fc(),
(get_test_qpts('split'), {'asr': 'reciprocal'}),
'quartz_666_300K_debye_waller.json',
'quartz_recip_asr_300K_fc_structure_factor.json')])
@pytest.mark.parametrize('n_threads', [0, 1, 2])
def test_calculate_structure_factor(self, material, fc, qpt_ph_modes_args,
dw_file, expected_sf_file, n_threads):
args, kwargs = qpt_ph_modes_args
if n_threads == 0:
kwargs['use_c'] = False
else:
kwargs['use_c'] = True
kwargs['n_threads'] = n_threads
> qpt_ph_modes = fc.calculate_qpoint_phonon_modes(args, **kwargs)
euphonic_test/test_calculate_structure_factor.py:58:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../euphonic/force_constants.py:387: in calculate_qpoint_phonon_modes
qpts, freqs, weights, evecs, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f3ffb0>
qpts = array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0. ...[-1. , 1. , 1. ],
[-0.151515, 0.575758, 0.5 ],
[-1. , 1. , 1. ]])
weights = None, asr = None, dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = True
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
import euphonic._euphonic as euphonic_c
use_c_status = True
except ImportError:
if use_c is None:
warnings.warn((
cext_err_msg
+ ' Falling back to pure Python calculation.'),
stacklevel=3)
else:
> raise ImportCError(cext_err_msg)
E euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
../../euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
1 out of 6 runs failed: test_calculate_structure_factor[2-Si2-sc-skew-fc3-qpt_ph_modes_args3-Si2-sc-skew_666_300K_debye_waller.…w_300K_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)
artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8cd70>
qpts = array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0.5...[ 0.6 , 0. , 0.2 ],
[ 2. , 2. , 0.5 ],
[ 1.75 , 0.5 , 2.5 ]])
weights = None, asr = None, dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = True
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
> import euphonic._euphonic as euphonic_c
E ModuleNotFoundError: No module named 'euphonic._euphonic'
../../euphonic/force_constants.py:638: ModuleNotFoundError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7fbb11f8f5c0>
material = 'Si2-sc-skew'
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8cd70>
qpt_ph_modes_args = (array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0....
[ 2. , 2. , 0.5 ],
[ 1.75 , 0.5 , 2.5 ]]), {'n_threads': 2, 'use_c': True})
dw_file = 'Si2-sc-skew_666_300K_debye_waller.json'
expected_sf_file = 'Si2-sc-skew_300K_fc_structure_factor.json', n_threads = 2
@pytest.mark.parametrize(
'material, fc, qpt_ph_modes_args, dw_file, expected_sf_file', [
('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
'quartz_666_0K_debye_waller.json',
'quartz_0K_fc_structure_factor.json'),
('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
'quartz_666_300K_debye_waller.json',
'quartz_300K_fc_structure_factor.json'),
('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
None,
'quartz_fc_structure_factor.json'),
('Si2-sc-skew', get_si2_fc(), (get_test_qpts(), {}),
'Si2-sc-skew_666_300K_debye_waller.json',
'Si2-sc-skew_300K_fc_structure_factor.json'),
('quartz', get_quartz_fc(),
(get_test_qpts('split'), {'asr': 'reciprocal'}),
'quartz_666_300K_debye_waller.json',
'quartz_recip_asr_300K_fc_structure_factor.json')])
@pytest.mark.parametrize('n_threads', [0, 1, 2])
def test_calculate_structure_factor(self, material, fc, qpt_ph_modes_args,
dw_file, expected_sf_file, n_threads):
args, kwargs = qpt_ph_modes_args
if n_threads == 0:
kwargs['use_c'] = False
else:
kwargs['use_c'] = True
kwargs['n_threads'] = n_threads
> qpt_ph_modes = fc.calculate_qpoint_phonon_modes(args, **kwargs)
euphonic_test/test_calculate_structure_factor.py:58:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../euphonic/force_constants.py:387: in calculate_qpoint_phonon_modes
qpts, freqs, weights, evecs, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8cd70>
qpts = array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0.5...[ 0.6 , 0. , 0.2 ],
[ 2. , 2. , 0.5 ],
[ 1.75 , 0.5 , 2.5 ]])
weights = None, asr = None, dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = True
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
import euphonic._euphonic as euphonic_c
use_c_status = True
except ImportError:
if use_c is None:
warnings.warn((
cext_err_msg
+ ' Falling back to pure Python calculation.'),
stacklevel=3)
else:
> raise ImportCError(cext_err_msg)
E euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
../../euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
1 out of 6 runs failed: test_calculate_structure_factor[2-quartz-fc4-qpt_ph_modes_args4-quartz_666_300K_debye_waller.json-quart…r_300K_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)
artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d5b0>
qpts = array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0. ...[-1. , 1. , 1. ],
[-0.151515, 0.575758, 0.5 ],
[-1. , 1. , 1. ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = True
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
> import euphonic._euphonic as euphonic_c
E ModuleNotFoundError: No module named 'euphonic._euphonic'
../../euphonic/force_constants.py:638: ModuleNotFoundError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7fbb11f8f6e0>
material = 'quartz'
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d5b0>
qpt_ph_modes_args = (array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0....0.575758, 0.5 ],
[-1. , 1. , 1. ]]), {'asr': 'reciprocal', 'n_threads': 2, 'use_c': True})
dw_file = 'quartz_666_300K_debye_waller.json'
expected_sf_file = 'quartz_recip_asr_300K_fc_structure_factor.json'
n_threads = 2
@pytest.mark.parametrize(
'material, fc, qpt_ph_modes_args, dw_file, expected_sf_file', [
('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
'quartz_666_0K_debye_waller.json',
'quartz_0K_fc_structure_factor.json'),
('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
'quartz_666_300K_debye_waller.json',
'quartz_300K_fc_structure_factor.json'),
('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
None,
'quartz_fc_structure_factor.json'),
('Si2-sc-skew', get_si2_fc(), (get_test_qpts(), {}),
'Si2-sc-skew_666_300K_debye_waller.json',
'Si2-sc-skew_300K_fc_structure_factor.json'),
('quartz', get_quartz_fc(),
(get_test_qpts('split'), {'asr': 'reciprocal'}),
'quartz_666_300K_debye_waller.json',
'quartz_recip_asr_300K_fc_structure_factor.json')])
@pytest.mark.parametrize('n_threads', [0, 1, 2])
def test_calculate_structure_factor(self, material, fc, qpt_ph_modes_args,
dw_file, expected_sf_file, n_threads):
args, kwargs = qpt_ph_modes_args
if n_threads == 0:
kwargs['use_c'] = False
else:
kwargs['use_c'] = True
kwargs['n_threads'] = n_threads
> qpt_ph_modes = fc.calculate_qpoint_phonon_modes(args, **kwargs)
euphonic_test/test_calculate_structure_factor.py:58:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../euphonic/force_constants.py:387: in calculate_qpoint_phonon_modes
qpts, freqs, weights, evecs, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d5b0>
qpts = array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0. ...[-1. , 1. , 1. ],
[-0.151515, 0.575758, 0.5 ],
[-1. , 1. , 1. ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = True
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
import euphonic._euphonic as euphonic_c
use_c_status = True
except ImportError:
if use_c is None:
warnings.warn((
cext_err_msg
+ ' Falling back to pure Python calculation.'),
stacklevel=3)
else:
> raise ImportCError(cext_err_msg)
E euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
../../euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
1 out of 7 runs failed: test_calculate_structure_factor_from_phonopy[1-CaHgO2-fc_kwargs0-qpt_ph_modes_args0-CaHgO2_666_300K_deb…2_300K_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)
artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb1172bda0>
qpts = array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0.5...[ 0.6 , 0. , 0.2 ],
[ 2. , 2. , 0.5 ],
[ 1.75 , 0.5 , 2.5 ]])
weights = None, asr = None, dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = True
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
> import euphonic._euphonic as euphonic_c
E ModuleNotFoundError: No module named 'euphonic._euphonic'
../../euphonic/force_constants.py:638: ModuleNotFoundError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7fbb11f8fce0>
material = 'CaHgO2'
fc_kwargs = {'path': '/home/runner/work/Euphonic/Euphonic/tests_and_analysis/test/data/phonopy_files/CaHgO2', 'summary_name': 'mp-7041-20180417.yaml'}
qpt_ph_modes_args = (array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0....
[ 2. , 2. , 0.5 ],
[ 1.75 , 0.5 , 2.5 ]]), {'n_threads': 1, 'use_c': True})
dw_file = 'CaHgO2_666_300K_debye_waller.json'
expected_sf_file = 'CaHgO2_300K_fc_structure_factor.json', n_threads = 1
@pytest.mark.phonopy_reader
@pytest.mark.parametrize(
'material, fc_kwargs, qpt_ph_modes_args, dw_file, expected_sf_file', [
('CaHgO2', {'path': get_phonopy_path('CaHgO2'),
'summary_name': 'mp-7041-20180417.yaml'},
(get_test_qpts(), {}),
'CaHgO2_666_300K_debye_waller.json',
'CaHgO2_300K_fc_structure_factor.json')])
@pytest.mark.parametrize('n_threads', [0, 1, 2])
def test_calculate_structure_factor_from_phonopy(
self, material, fc_kwargs, qpt_ph_modes_args, dw_file,
expected_sf_file, n_threads):
fc = ForceConstants.from_phonopy(**fc_kwargs)
args, kwargs = qpt_ph_modes_args
if n_threads == 0:
kwargs['use_c'] = False
else:
kwargs['use_c'] = True
kwargs['n_threads'] = n_threads
> qpt_ph_modes = fc.calculate_qpoint_phonon_modes(args, **kwargs)
euphonic_test/test_calculate_structure_factor.py:99:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../euphonic/force_constants.py:387: in calculate_qpoint_phonon_modes
qpts, freqs, weights, evecs, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <euphonic.force_constants.ForceConstants object at 0x7fbb1172bda0>
qpts = array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0.5...[ 0.6 , 0. , 0.2 ],
[ 2. , 2. , 0.5 ],
[ 1.75 , 0.5 , 2.5 ]])
weights = None, asr = None, dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = True
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
import euphonic._euphonic as euphonic_c
use_c_status = True
except ImportError:
if use_c is None:
warnings.warn((
cext_err_msg
+ ' Falling back to pure Python calculation.'),
stacklevel=3)
else:
> raise ImportCError(cext_err_msg)
E euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
../../euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
1 out of 7 runs failed: test_calculate_structure_factor_from_phonopy[2-CaHgO2-fc_kwargs0-qpt_ph_modes_args0-CaHgO2_666_300K_deb…2_300K_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)
artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb1172ae40>
qpts = array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0.5...[ 0.6 , 0. , 0.2 ],
[ 2. , 2. , 0.5 ],
[ 1.75 , 0.5 , 2.5 ]])
weights = None, asr = None, dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = True
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
> import euphonic._euphonic as euphonic_c
E ModuleNotFoundError: No module named 'euphonic._euphonic'
../../euphonic/force_constants.py:638: ModuleNotFoundError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7fbb11f8fd70>
material = 'CaHgO2'
fc_kwargs = {'path': '/home/runner/work/Euphonic/Euphonic/tests_and_analysis/test/data/phonopy_files/CaHgO2', 'summary_name': 'mp-7041-20180417.yaml'}
qpt_ph_modes_args = (array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0....
[ 2. , 2. , 0.5 ],
[ 1.75 , 0.5 , 2.5 ]]), {'n_threads': 2, 'use_c': True})
dw_file = 'CaHgO2_666_300K_debye_waller.json'
expected_sf_file = 'CaHgO2_300K_fc_structure_factor.json', n_threads = 2
@pytest.mark.phonopy_reader
@pytest.mark.parametrize(
'material, fc_kwargs, qpt_ph_modes_args, dw_file, expected_sf_file', [
('CaHgO2', {'path': get_phonopy_path('CaHgO2'),
'summary_name': 'mp-7041-20180417.yaml'},
(get_test_qpts(), {}),
'CaHgO2_666_300K_debye_waller.json',
'CaHgO2_300K_fc_structure_factor.json')])
@pytest.mark.parametrize('n_threads', [0, 1, 2])
def test_calculate_structure_factor_from_phonopy(
self, material, fc_kwargs, qpt_ph_modes_args, dw_file,
expected_sf_file, n_threads):
fc = ForceConstants.from_phonopy(**fc_kwargs)
args, kwargs = qpt_ph_modes_args
if n_threads == 0:
kwargs['use_c'] = False
else:
kwargs['use_c'] = True
kwargs['n_threads'] = n_threads
> qpt_ph_modes = fc.calculate_qpoint_phonon_modes(args, **kwargs)
euphonic_test/test_calculate_structure_factor.py:99:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../euphonic/force_constants.py:387: in calculate_qpoint_phonon_modes
qpts, freqs, weights, evecs, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <euphonic.force_constants.ForceConstants object at 0x7fbb1172ae40>
qpts = array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0.5...[ 0.6 , 0. , 0.2 ],
[ 2. , 2. , 0.5 ],
[ 1.75 , 0.5 , 2.5 ]])
weights = None, asr = None, dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = True
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
import euphonic._euphonic as euphonic_c
use_c_status = True
except ImportError:
if use_c is None:
warnings.warn((
cext_err_msg
+ ' Falling back to pure Python calculation.'),
stacklevel=3)
else:
> raise ImportCError(cext_err_msg)
E euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
../../euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
1 out of 6 runs failed: test_calculate_qpoint_frequencies[True-1-fc0-LZO-all_args0-LZO_no_asr_qpoint_frequencies.json] (euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies)
artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d4c0>
qpts = array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0.5...[ 0.6 , 0. , 0.2 ],
[ 2. , 2. , 0.5 ],
[ 1.75 , 0.5 , 2.5 ]])
weights = None, asr = None, dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = False
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
> import euphonic._euphonic as euphonic_c
E ModuleNotFoundError: No module named 'euphonic._euphonic'
../../euphonic/force_constants.py:638: ModuleNotFoundError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies object at 0x7fbb11d13560>
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d4c0>
material = 'LZO'
all_args = [array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0....2. , 0.5 ],
[ 1.75 , 0.5 , 2.5 ]]), {'n_threads': 1, 'reduce_qpts': True, 'use_c': True}]
expected_qpoint_frequencies_file = 'LZO_no_asr_qpoint_frequencies.json'
reduce_qpts = True, n_threads = 1
@pytest.mark.parametrize(
'fc, material, all_args, expected_qpoint_frequencies_file',
lzo_params + quartz_params)
@pytest.mark.parametrize(
'reduce_qpts, n_threads',
[(False, 0), (True, 0), (True, 1), (True, 2)])
def test_calculate_qpoint_frequencies(
self, fc, material, all_args, expected_qpoint_frequencies_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
> qpt_freqs = fc.calculate_qpoint_frequencies(all_args[0], **func_kwargs)
euphonic_test/test_force_constants_calculate_qpoint_frequencies.py:67:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../euphonic/force_constants.py:418: in calculate_qpoint_frequencies
qpts, freqs, weights, _, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d4c0>
qpts = array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0.5...[ 0.6 , 0. , 0.2 ],
[ 2. , 2. , 0.5 ],
[ 1.75 , 0.5 , 2.5 ]])
weights = None, asr = None, dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = False
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
import euphonic._euphonic as euphonic_c
use_c_status = True
except ImportError:
if use_c is None:
warnings.warn((
cext_err_msg
+ ' Falling back to pure Python calculation.'),
stacklevel=3)
else:
> raise ImportCError(cext_err_msg)
E euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
../../euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
1 out of 6 runs failed: test_calculate_qpoint_frequencies[True-1-fc1-LZO-all_args1-LZO_realspace_qpoint_frequencies.json] (euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies)
artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11ef7fb0>
qpts = array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0.5...[ 0.6 , 0. , 0.2 ],
[ 2. , 2. , 0.5 ],
[ 1.75 , 0.5 , 2.5 ]])
weights = None, asr = 'realspace', dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = False
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
> import euphonic._euphonic as euphonic_c
E ModuleNotFoundError: No module named 'euphonic._euphonic'
../../euphonic/force_constants.py:638: ModuleNotFoundError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies object at 0x7fbb11d135f0>
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11ef7fb0>
material = 'LZO'
all_args = [array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0....,
[ 1.75 , 0.5 , 2.5 ]]), {'asr': 'realspace', 'n_threads': 1, 'reduce_qpts': True, 'use_c': True}]
expected_qpoint_frequencies_file = 'LZO_realspace_qpoint_frequencies.json'
reduce_qpts = True, n_threads = 1
@pytest.mark.parametrize(
'fc, material, all_args, expected_qpoint_frequencies_file',
lzo_params + quartz_params)
@pytest.mark.parametrize(
'reduce_qpts, n_threads',
[(False, 0), (True, 0), (True, 1), (True, 2)])
def test_calculate_qpoint_frequencies(
self, fc, material, all_args, expected_qpoint_frequencies_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
> qpt_freqs = fc.calculate_qpoint_frequencies(all_args[0], **func_kwargs)
euphonic_test/test_force_constants_calculate_qpoint_frequencies.py:67:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../euphonic/force_constants.py:418: in calculate_qpoint_frequencies
qpts, freqs, weights, _, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11ef7fb0>
qpts = array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0.5...[ 0.6 , 0. , 0.2 ],
[ 2. , 2. , 0.5 ],
[ 1.75 , 0.5 , 2.5 ]])
weights = None, asr = 'realspace', dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = False
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
import euphonic._euphonic as euphonic_c
use_c_status = True
except ImportError:
if use_c is None:
warnings.warn((
cext_err_msg
+ ' Falling back to pure Python calculation.'),
stacklevel=3)
else:
> raise ImportCError(cext_err_msg)
E euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
../../euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
1 out of 6 runs failed: test_calculate_qpoint_frequencies[True-1-fc2-quartz-all_args2-quartz_reciprocal_qpoint_frequencies.json] (euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies)
artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d520>
qpts = array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0.5...[ 0.6 , 0. , 0.2 ],
[ 2. , 2. , 0.5 ],
[ 1.75 , 0.5 , 2.5 ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = False
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
> import euphonic._euphonic as euphonic_c
E ModuleNotFoundError: No module named 'euphonic._euphonic'
../../euphonic/force_constants.py:638: ModuleNotFoundError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies object at 0x7fbb11d13680>
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d520>
material = 'quartz'
all_args = [array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0....1.75 , 0.5 , 2.5 ]]), {'asr': 'reciprocal', 'n_threads': 1, 'reduce_qpts': True, 'splitting': False, ...}]
expected_qpoint_frequencies_file = 'quartz_reciprocal_qpoint_frequencies.json'
reduce_qpts = True, n_threads = 1
@pytest.mark.parametrize(
'fc, material, all_args, expected_qpoint_frequencies_file',
lzo_params + quartz_params)
@pytest.mark.parametrize(
'reduce_qpts, n_threads',
[(False, 0), (True, 0), (True, 1), (True, 2)])
def test_calculate_qpoint_frequencies(
self, fc, material, all_args, expected_qpoint_frequencies_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
> qpt_freqs = fc.calculate_qpoint_frequencies(all_args[0], **func_kwargs)
euphonic_test/test_force_constants_calculate_qpoint_frequencies.py:67:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../euphonic/force_constants.py:418: in calculate_qpoint_frequencies
qpts, freqs, weights, _, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d520>
qpts = array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0.5...[ 0.6 , 0. , 0.2 ],
[ 2. , 2. , 0.5 ],
[ 1.75 , 0.5 , 2.5 ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = False
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
import euphonic._euphonic as euphonic_c
use_c_status = True
except ImportError:
if use_c is None:
warnings.warn((
cext_err_msg
+ ' Falling back to pure Python calculation.'),
stacklevel=3)
else:
> raise ImportCError(cext_err_msg)
E euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
../../euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
1 out of 6 runs failed: test_calculate_qpoint_frequencies[True-1-fc3-quartz-all_args3-quartz_reciprocal_qpoint_frequencies.json] (euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies)
artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11d0bec0>
qpts = array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0.5...[ 0.6 , 0. , 0.2 ],
[ 2. , 2. , 0.5 ],
[ 1.75 , 0.5 , 2.5 ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 0.75
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = False
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
> import euphonic._euphonic as euphonic_c
E ModuleNotFoundError: No module named 'euphonic._euphonic'
../../euphonic/force_constants.py:638: ModuleNotFoundError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies object at 0x7fbb11d13710>
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11d0bec0>
material = 'quartz'
all_args = [array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0.... , 0.5 , 2.5 ]]), {'asr': 'reciprocal', 'dipole_parameter': 0.75, 'n_threads': 1, 'reduce_qpts': True, ...}]
expected_qpoint_frequencies_file = 'quartz_reciprocal_qpoint_frequencies.json'
reduce_qpts = True, n_threads = 1
@pytest.mark.parametrize(
'fc, material, all_args, expected_qpoint_frequencies_file',
lzo_params + quartz_params)
@pytest.mark.parametrize(
'reduce_qpts, n_threads',
[(False, 0), (True, 0), (True, 1), (True, 2)])
def test_calculate_qpoint_frequencies(
self, fc, material, all_args, expected_qpoint_frequencies_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
> qpt_freqs = fc.calculate_qpoint_frequencies(all_args[0], **func_kwargs)
euphonic_test/test_force_constants_calculate_qpoint_frequencies.py:67:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../euphonic/force_constants.py:418: in calculate_qpoint_frequencies
qpts, freqs, weights, _, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11d0bec0>
qpts = array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0.5...[ 0.6 , 0. , 0.2 ],
[ 2. , 2. , 0.5 ],
[ 1.75 , 0.5 , 2.5 ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 0.75
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = False
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
import euphonic._euphonic as euphonic_c
use_c_status = True
except ImportError:
if use_c is None:
warnings.warn((
cext_err_msg
+ ' Falling back to pure Python calculation.'),
stacklevel=3)
else:
> raise ImportCError(cext_err_msg)
E euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
../../euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
1 out of 6 runs failed: test_calculate_qpoint_frequencies[True-1-fc4-quartz-all_args4-quartz_split_reciprocal_qpoint_frequencies.json] (euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies)
artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11d115e0>
qpts = array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0. ...[-1. , 1. , 1. ],
[-0.151515, 0.575758, 0.5 ],
[-1. , 1. , 1. ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = False
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
> import euphonic._euphonic as euphonic_c
E ModuleNotFoundError: No module named 'euphonic._euphonic'
../../euphonic/force_constants.py:638: ModuleNotFoundError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies object at 0x7fbb11d137a0>
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11d115e0>
material = 'quartz'
all_args = [array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0.... , 1. , 1. ]]), {'asr': 'reciprocal', 'insert_gamma': False, 'n_threads': 1, 'reduce_qpts': True, ...}]
expected_qpoint_frequencies_file = 'quartz_split_reciprocal_qpoint_frequencies.json'
reduce_qpts = True, n_threads = 1
@pytest.mark.parametrize(
'fc, material, all_args, expected_qpoint_frequencies_file',
lzo_params + quartz_params)
@pytest.mark.parametrize(
'reduce_qpts, n_threads',
[(False, 0), (True, 0), (True, 1), (True, 2)])
def test_calculate_qpoint_frequencies(
self, fc, material, all_args, expected_qpoint_frequencies_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
> qpt_freqs = fc.calculate_qpoint_frequencies(all_args[0], **func_kwargs)
euphonic_test/test_force_constants_calculate_qpoint_frequencies.py:67:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../euphonic/force_constants.py:418: in calculate_qpoint_frequencies
qpts, freqs, weights, _, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11d115e0>
qpts = array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0. ...[-1. , 1. , 1. ],
[-0.151515, 0.575758, 0.5 ],
[-1. , 1. , 1. ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = False
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
import euphonic._euphonic as euphonic_c
use_c_status = True
except ImportError:
if use_c is None:
warnings.warn((
cext_err_msg
+ ' Falling back to pure Python calculation.'),
stacklevel=3)
else:
> raise ImportCError(cext_err_msg)
E euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
../../euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
1 out of 6 runs failed: test_calculate_qpoint_frequencies[True-1-fc5-quartz-all_args5-quartz_split_reciprocal_qpoint_frequencies.json] (euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies)
artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11d11d90>
qpts = array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0. ...[-1. , 1. , 1. ],
[-0.151515, 0.575758, 0.5 ],
[-1. , 1. , 1. ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = True, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = False
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
> import euphonic._euphonic as euphonic_c
E ModuleNotFoundError: No module named 'euphonic._euphonic'
../../euphonic/force_constants.py:638: ModuleNotFoundError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies object at 0x7fbb11d13830>
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11d11d90>
material = 'quartz'
all_args = [array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0.... , 1. , 1. ]]), {'asr': 'reciprocal', 'insert_gamma': True, 'n_threads': 1, 'reduce_qpts': True, ...}]
expected_qpoint_frequencies_file = 'quartz_split_reciprocal_qpoint_frequencies.json'
reduce_qpts = True, n_threads = 1
@pytest.mark.parametrize(
'fc, material, all_args, expected_qpoint_frequencies_file',
lzo_params + quartz_params)
@pytest.mark.parametrize(
'reduce_qpts, n_threads',
[(False, 0), (True, 0), (True, 1), (True, 2)])
def test_calculate_qpoint_frequencies(
self, fc, material, all_args, expected_qpoint_frequencies_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
> qpt_freqs = fc.calculate_qpoint_frequencies(all_args[0], **func_kwargs)
euphonic_test/test_force_constants_calculate_qpoint_frequencies.py:67:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../euphonic/force_constants.py:418: in calculate_qpoint_frequencies
qpts, freqs, weights, _, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11d11d90>
qpts = array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0. ...[-1. , 1. , 1. ],
[-0.151515, 0.575758, 0.5 ],
[-1. , 1. , 1. ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = True, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = False
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
import euphonic._euphonic as euphonic_c
use_c_status = True
except ImportError:
if use_c is None:
warnings.warn((
cext_err_msg
+ ' Falling back to pure Python calculation.'),
stacklevel=3)
else:
> raise ImportCError(cext_err_msg)
E euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
../../euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
1 out of 6 runs failed: test_calculate_qpoint_frequencies[True-2-fc0-LZO-all_args0-LZO_no_asr_qpoint_frequencies.json] (euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies)
artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d4c0>
qpts = array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0.5...[ 0.6 , 0. , 0.2 ],
[ 2. , 2. , 0.5 ],
[ 1.75 , 0.5 , 2.5 ]])
weights = None, asr = None, dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = False
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
> import euphonic._euphonic as euphonic_c
E ModuleNotFoundError: No module named 'euphonic._euphonic'
../../euphonic/force_constants.py:638: ModuleNotFoundError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies object at 0x7fbb11d138c0>
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d4c0>
material = 'LZO'
all_args = [array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0....2. , 0.5 ],
[ 1.75 , 0.5 , 2.5 ]]), {'n_threads': 2, 'reduce_qpts': True, 'use_c': True}]
expected_qpoint_frequencies_file = 'LZO_no_asr_qpoint_frequencies.json'
reduce_qpts = True, n_threads = 2
@pytest.mark.parametrize(
'fc, material, all_args, expected_qpoint_frequencies_file',
lzo_params + quartz_params)
@pytest.mark.parametrize(
'reduce_qpts, n_threads',
[(False, 0), (True, 0), (True, 1), (True, 2)])
def test_calculate_qpoint_frequencies(
self, fc, material, all_args, expected_qpoint_frequencies_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
> qpt_freqs = fc.calculate_qpoint_frequencies(all_args[0], **func_kwargs)
euphonic_test/test_force_constants_calculate_qpoint_frequencies.py:67:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../euphonic/force_constants.py:418: in calculate_qpoint_frequencies
qpts, freqs, weights, _, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d4c0>
qpts = array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0.5...[ 0.6 , 0. , 0.2 ],
[ 2. , 2. , 0.5 ],
[ 1.75 , 0.5 , 2.5 ]])
weights = None, asr = None, dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = False
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
import euphonic._euphonic as euphonic_c
use_c_status = True
except ImportError:
if use_c is None:
warnings.warn((
cext_err_msg
+ ' Falling back to pure Python calculation.'),
stacklevel=3)
else:
> raise ImportCError(cext_err_msg)
E euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
../../euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
1 out of 6 runs failed: test_calculate_qpoint_frequencies[True-2-fc1-LZO-all_args1-LZO_realspace_qpoint_frequencies.json] (euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies)
artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11ef7fb0>
qpts = array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0.5...[ 0.6 , 0. , 0.2 ],
[ 2. , 2. , 0.5 ],
[ 1.75 , 0.5 , 2.5 ]])
weights = None, asr = 'realspace', dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = False
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
> import euphonic._euphonic as euphonic_c
E ModuleNotFoundError: No module named 'euphonic._euphonic'
../../euphonic/force_constants.py:638: ModuleNotFoundError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies object at 0x7fbb11d13950>
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11ef7fb0>
material = 'LZO'
all_args = [array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0....,
[ 1.75 , 0.5 , 2.5 ]]), {'asr': 'realspace', 'n_threads': 2, 'reduce_qpts': True, 'use_c': True}]
expected_qpoint_frequencies_file = 'LZO_realspace_qpoint_frequencies.json'
reduce_qpts = True, n_threads = 2
@pytest.mark.parametrize(
'fc, material, all_args, expected_qpoint_frequencies_file',
lzo_params + quartz_params)
@pytest.mark.parametrize(
'reduce_qpts, n_threads',
[(False, 0), (True, 0), (True, 1), (True, 2)])
def test_calculate_qpoint_frequencies(
self, fc, material, all_args, expected_qpoint_frequencies_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
> qpt_freqs = fc.calculate_qpoint_frequencies(all_args[0], **func_kwargs)
euphonic_test/test_force_constants_calculate_qpoint_frequencies.py:67:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../euphonic/force_constants.py:418: in calculate_qpoint_frequencies
qpts, freqs, weights, _, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11ef7fb0>
qpts = array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0.5...[ 0.6 , 0. , 0.2 ],
[ 2. , 2. , 0.5 ],
[ 1.75 , 0.5 , 2.5 ]])
weights = None, asr = 'realspace', dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = False
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
import euphonic._euphonic as euphonic_c
use_c_status = True
except ImportError:
if use_c is None:
warnings.warn((
cext_err_msg
+ ' Falling back to pure Python calculation.'),
stacklevel=3)
else:
> raise ImportCError(cext_err_msg)
E euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
../../euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
1 out of 6 runs failed: test_calculate_qpoint_frequencies[True-2-fc2-quartz-all_args2-quartz_reciprocal_qpoint_frequencies.json] (euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies)
artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d520>
qpts = array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0.5...[ 0.6 , 0. , 0.2 ],
[ 2. , 2. , 0.5 ],
[ 1.75 , 0.5 , 2.5 ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = False
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
> import euphonic._euphonic as euphonic_c
E ModuleNotFoundError: No module named 'euphonic._euphonic'
../../euphonic/force_constants.py:638: ModuleNotFoundError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies object at 0x7fbb11d139e0>
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d520>
material = 'quartz'
all_args = [array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0....1.75 , 0.5 , 2.5 ]]), {'asr': 'reciprocal', 'n_threads': 2, 'reduce_qpts': True, 'splitting': False, ...}]
expected_qpoint_frequencies_file = 'quartz_reciprocal_qpoint_frequencies.json'
reduce_qpts = True, n_threads = 2
@pytest.mark.parametrize(
'fc, material, all_args, expected_qpoint_frequencies_file',
lzo_params + quartz_params)
@pytest.mark.parametrize(
'reduce_qpts, n_threads',
[(False, 0), (True, 0), (True, 1), (True, 2)])
def test_calculate_qpoint_frequencies(
self, fc, material, all_args, expected_qpoint_frequencies_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
> qpt_freqs = fc.calculate_qpoint_frequencies(all_args[0], **func_kwargs)
euphonic_test/test_force_constants_calculate_qpoint_frequencies.py:67:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../euphonic/force_constants.py:418: in calculate_qpoint_frequencies
qpts, freqs, weights, _, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d520>
qpts = array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0.5...[ 0.6 , 0. , 0.2 ],
[ 2. , 2. , 0.5 ],
[ 1.75 , 0.5 , 2.5 ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = False
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
import euphonic._euphonic as euphonic_c
use_c_status = True
except ImportError:
if use_c is None:
warnings.warn((
cext_err_msg
+ ' Falling back to pure Python calculation.'),
stacklevel=3)
else:
> raise ImportCError(cext_err_msg)
E euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
../../euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
1 out of 6 runs failed: test_calculate_qpoint_frequencies[True-2-fc3-quartz-all_args3-quartz_reciprocal_qpoint_frequencies.json] (euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies)
artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11d0bec0>
qpts = array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0.5...[ 0.6 , 0. , 0.2 ],
[ 2. , 2. , 0.5 ],
[ 1.75 , 0.5 , 2.5 ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 0.75
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = False
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
> import euphonic._euphonic as euphonic_c
E ModuleNotFoundError: No module named 'euphonic._euphonic'
../../euphonic/force_constants.py:638: ModuleNotFoundError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies object at 0x7fbb11d13a70>
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11d0bec0>
material = 'quartz'
all_args = [array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0.... , 0.5 , 2.5 ]]), {'asr': 'reciprocal', 'dipole_parameter': 0.75, 'n_threads': 2, 'reduce_qpts': True, ...}]
expected_qpoint_frequencies_file = 'quartz_reciprocal_qpoint_frequencies.json'
reduce_qpts = True, n_threads = 2
@pytest.mark.parametrize(
'fc, material, all_args, expected_qpoint_frequencies_file',
lzo_params + quartz_params)
@pytest.mark.parametrize(
'reduce_qpts, n_threads',
[(False, 0), (True, 0), (True, 1), (True, 2)])
def test_calculate_qpoint_frequencies(
self, fc, material, all_args, expected_qpoint_frequencies_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
> qpt_freqs = fc.calculate_qpoint_frequencies(all_args[0], **func_kwargs)
euphonic_test/test_force_constants_calculate_qpoint_frequencies.py:67:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../euphonic/force_constants.py:418: in calculate_qpoint_frequencies
qpts, freqs, weights, _, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11d0bec0>
qpts = array([[ 0. , 0. , 0. ],
[ 0. , 0. , 0.5 ],
[-0.25 , 0.5 , 0.5...[ 0.6 , 0. , 0.2 ],
[ 2. , 2. , 0.5 ],
[ 1.75 , 0.5 , 2.5 ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 0.75
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = False
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
import euphonic._euphonic as euphonic_c
use_c_status = True
except ImportError:
if use_c is None:
warnings.warn((
cext_err_msg
+ ' Falling back to pure Python calculation.'),
stacklevel=3)
else:
> raise ImportCError(cext_err_msg)
E euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
../../euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
1 out of 6 runs failed: test_calculate_qpoint_frequencies[True-2-fc4-quartz-all_args4-quartz_split_reciprocal_qpoint_frequencies.json] (euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies)
artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11d115e0>
qpts = array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0. ...[-1. , 1. , 1. ],
[-0.151515, 0.575758, 0.5 ],
[-1. , 1. , 1. ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = False
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
> import euphonic._euphonic as euphonic_c
E ModuleNotFoundError: No module named 'euphonic._euphonic'
../../euphonic/force_constants.py:638: ModuleNotFoundError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies object at 0x7fbb11d13b00>
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11d115e0>
material = 'quartz'
all_args = [array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0.... , 1. , 1. ]]), {'asr': 'reciprocal', 'insert_gamma': False, 'n_threads': 2, 'reduce_qpts': True, ...}]
expected_qpoint_frequencies_file = 'quartz_split_reciprocal_qpoint_frequencies.json'
reduce_qpts = True, n_threads = 2
@pytest.mark.parametrize(
'fc, material, all_args, expected_qpoint_frequencies_file',
lzo_params + quartz_params)
@pytest.mark.parametrize(
'reduce_qpts, n_threads',
[(False, 0), (True, 0), (True, 1), (True, 2)])
def test_calculate_qpoint_frequencies(
self, fc, material, all_args, expected_qpoint_frequencies_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
> qpt_freqs = fc.calculate_qpoint_frequencies(all_args[0], **func_kwargs)
euphonic_test/test_force_constants_calculate_qpoint_frequencies.py:67:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../euphonic/force_constants.py:418: in calculate_qpoint_frequencies
qpts, freqs, weights, _, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11d115e0>
qpts = array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0. ...[-1. , 1. , 1. ],
[-0.151515, 0.575758, 0.5 ],
[-1. , 1. , 1. ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = False
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
import euphonic._euphonic as euphonic_c
use_c_status = True
except ImportError:
if use_c is None:
warnings.warn((
cext_err_msg
+ ' Falling back to pure Python calculation.'),
stacklevel=3)
else:
> raise ImportCError(cext_err_msg)
E euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
../../euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
1 out of 6 runs failed: test_calculate_qpoint_frequencies[True-2-fc5-quartz-all_args5-quartz_split_reciprocal_qpoint_frequencies.json] (euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies)
artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11d11d90>
qpts = array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0. ...[-1. , 1. , 1. ],
[-0.151515, 0.575758, 0.5 ],
[-1. , 1. , 1. ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = True, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = False
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
> import euphonic._euphonic as euphonic_c
E ModuleNotFoundError: No module named 'euphonic._euphonic'
../../euphonic/force_constants.py:638: ModuleNotFoundError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies object at 0x7fbb11d13b90>
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11d11d90>
material = 'quartz'
all_args = [array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0.... , 1. , 1. ]]), {'asr': 'reciprocal', 'insert_gamma': True, 'n_threads': 2, 'reduce_qpts': True, ...}]
expected_qpoint_frequencies_file = 'quartz_split_reciprocal_qpoint_frequencies.json'
reduce_qpts = True, n_threads = 2
@pytest.mark.parametrize(
'fc, material, all_args, expected_qpoint_frequencies_file',
lzo_params + quartz_params)
@pytest.mark.parametrize(
'reduce_qpts, n_threads',
[(False, 0), (True, 0), (True, 1), (True, 2)])
def test_calculate_qpoint_frequencies(
self, fc, material, all_args, expected_qpoint_frequencies_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
> qpt_freqs = fc.calculate_qpoint_frequencies(all_args[0], **func_kwargs)
euphonic_test/test_force_constants_calculate_qpoint_frequencies.py:67:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../euphonic/force_constants.py:418: in calculate_qpoint_frequencies
qpts, freqs, weights, _, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11d11d90>
qpts = array([[ 1. , 1. , 1. ],
[ 0. , 0. , 0.5 ],
[ 0. , 0. , 0. ...[-1. , 1. , 1. ],
[-0.151515, 0.575758, 0.5 ],
[-1. , 1. , 1. ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = True, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = False
def _calculate_phonons_at_qpts(
self,
qpts: np.ndarray,
weights: Optional[np.ndarray],
asr: Optional[Literal['realspace', 'reciprocal']],
dipole: bool,
dipole_parameter: float,
splitting: bool,
insert_gamma: bool,
reduce_qpts: bool,
use_c: Optional[bool],
n_threads: Optional[int],
return_mode_gradients: bool,
return_eigenvectors: bool) -> Tuple[
np.ndarray, Quantity, Optional[np.ndarray],
Optional[np.ndarray], Optional[Quantity]]:
"""
Calculates phonon frequencies, and optionally eigenvectors and
phonon frequency gradients. See calculate_qpoint_phonon_modes
for argument details
Returns
-------
qpts
Shape (n_qpts, 3) float ndarray. The calculated q-points,
note that this may not be the same as the input q-points
if splitting and insert_gamma=True and there are gamma
points
frequencies
Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
the phonon frequencies. n_qpts may not be the same as the
number of input q-points, if splitting and
insert_gamma=True and there are gamma points
weights
Shape (n_qpts,) float ndarray. The weight of each q-point
eigenvectors
Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
gradients
Shape (n_qpts, 3*n_atoms, 3) float Quantity in
energy*length units. The phonon mode gradients
"""
# Check weights is of appropriate type and shape, to avoid doing all
# the interpolation only for it to fail creating QpointPhononModes
_check_constructor_inputs(
[weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
# Set default splitting params
if self.born is None:
dipole = False
if not dipole:
splitting = False
if splitting and insert_gamma:
# Duplicate gamma points where there is splitting
gamma_i = np.where(is_gamma(qpts))[0]
split_gamma = gamma_i[np.where(
np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
# It doesn't necessarily make sense to use both weights
# (usually used for DOS) and splitting (usually used for
# bandstructures) but we need to handle this case anyway
# Where 1 q-point splits into 2, half the weight for each
if weights is not None:
# Don't change original array
weights = np.copy(weights)
weights[split_gamma] = weights[split_gamma]/2
weights = np.insert(weights, split_gamma,
weights[split_gamma])
if reduce_qpts:
norm_qpts = qpts - np.rint(qpts)
# Ensure gamma points are exactly zero, otherwise you may
# have a case where small fp differences mean np.unique
# doesn't reduce them, yet they're all classified as gamma
# points. This causes indexing errors later when calculating
# q-directions as there are then points in reduced_qpts
# whose index isn't in qpts_i
gamma_i = np.where(is_gamma(qpts))[0]
n_gamma = len(gamma_i)
norm_qpts[gamma_i] = 0.
reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
axis=0)
qpts_i = qpts_i.flatten()
n_rqpts = len(reduced_qpts)
# Special handling of gamma points - don't reduce gamma
# points if LO-TO splitting
if splitting and n_gamma > 1:
# Replace any gamma points and their indices with new
# gamma points appended onto the reduced q-point array,
# so each gamma can have its own splitting
qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
reduced_qpts = np.append(reduced_qpts,
np.tile(np.array([0., 0., 0., ]),
(n_gamma - 1, 1)),
axis=0)
n_rqpts = len(reduced_qpts)
else:
reduced_qpts = qpts
qpts_i = np.arange(0, len(qpts), dtype=np.int32)
n_rqpts = len(qpts)
# Get q-directions for non-analytical corrections
if splitting:
split_idx = np.where(is_gamma(reduced_qpts))[0]
q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
else:
split_idx = np.array([])
q_dirs = np.array([])
n_sc_shells = 2 # How many supercells out to search for atom images
# Construct list of supercell atom images
if not hasattr(self, '_sc_image_i'):
self._calculate_supercell_images(n_sc_shells)
# Get a list of all the unique supercell image origins and cell
# origins in x, y, z and how to rebuild them to minimise
# expensive phase calculations later
sc_image_r = get_all_origins(
np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
sc_origins = np.einsum('ij,jk->ik', sc_image_r,
self.sc_matrix).astype(np.int32)
unique_sc_origins = [[] for i in range(3)]
unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
unique_cell_origins = [[] for i in range(3)]
unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
for i in range(3):
unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
sc_origins[:, i], return_inverse=True)
unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
self.cell_origins[:, i], return_inverse=True)
if return_mode_gradients:
cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
self.crystal._cell_vectors)
# Append 0. to sc_origins_cart, so that when being indexed by
# sc_image_i to get the origins for each image, an index of -1
# and a value of 0. can be used
sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
sc_origins_cart[:len(sc_origins)] = np.einsum(
'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
ax = np.newaxis
all_origins_cart = (sc_origins_cart[self._sc_image_i]
+ cell_origins_cart[:, ax, ax, ax, :])
else:
all_origins_cart = np.zeros((0, 3), dtype=np.float64)
# Precompute dynamical matrix mass weighting
atom_mass = self.crystal._atom_mass
n_atoms = self.crystal.n_atoms
masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
# Initialise dipole correction calc to FC matrix if required
if dipole and (not hasattr(self, '_dipole_init_data') or
dipole_parameter != self._dipole_init_data[
'dipole_parameter']):
self._dipole_init_data = self._dipole_correction_init(
self.crystal, self._born, self._dielectric, dipole_parameter)
force_constants = self._force_constants
if asr == 'realspace':
if not hasattr(self, '_force_constants_asr'):
self._force_constants_asr = self._enforce_realspace_asr()
force_constants = self._force_constants_asr
# Precompute fc matrix weighted by number of supercell atom
# images (for cumulant method)
n_sc_images_repeat = (
self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
fc_img_weighted = np.divide(
force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
recip_asr_correction = np.array([], dtype=np.complex128)
if asr == 'reciprocal':
# Calculate dyn mat at gamma for reciprocal ASR
q_gamma = np.array([0., 0., 0.])
dyn_mat_gamma, _ = self._calculate_dyn_mat(
q_gamma, fc_img_weighted, unique_sc_origins,
unique_sc_i, unique_cell_origins, unique_cell_i,
all_origins_cart)
if dipole:
dyn_mat_gamma += self._calculate_dipole_correction(
q_gamma, self.crystal, self._born, self._dielectric,
self._dipole_init_data)
recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
rfreqs = np.zeros((n_rqpts, 3*n_atoms))
if return_eigenvectors:
n_reigenvecs = n_rqpts
else:
# Create dummy zero-length eigenvectors so this can be
# detected in C and eigenvectors won't be saved
n_reigenvecs = 0
if return_mode_gradients:
rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
dtype=np.complex128)
else:
rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
euphonic_path = os.path.dirname(euphonic.__file__)
cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
f'from {euphonic_path}, it may not have been '
f'installed.')
# Check if C extension can be used and handle appropriately
use_c_status = False
if use_c is not False:
try:
import euphonic._euphonic as euphonic_c
use_c_status = True
except ImportError:
if use_c is None:
warnings.warn((
cext_err_msg
+ ' Falling back to pure Python calculation.'),
stacklevel=3)
else:
> raise ImportCError(cext_err_msg)
E euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
../../euphonic/force_constants.py:647: ImportCError