Create an action to push some ARM builds to PyPI #877
6 errors, 56 fail, 6 skipped, 982 pass in 2h 0m 4s
25 files 25 suites 2h 0m 4s ⏱️
1 050 tests 982 ✅ 6 💤 56 ❌ 6 🔥
26 100 runs 25 268 ✅ 150 💤 616 ❌ 66 🔥
Results for commit 8dd48f3.
Annotations
github-actions / Test Results
11 out of 25 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 macos-13/junit_report_1721211581.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212282.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212875.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721210876.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721211142.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211174.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211599.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721212018.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211441.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211990.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721212547.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7f708a839d10>
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 ImportError: numpy.core.multiarray failed to import
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:638: ImportError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7f708aa81f90>
material = 'quartz'
fc = <euphonic.force_constants.ForceConstants object at 0x7f708a839d10>
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:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../.tox/pypi-py311/lib/python3.11/site-packages/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 0x7f708a839d10>
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/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
11 out of 25 runs failed: test_calculate_structure_factor[1-quartz-fc1-qpt_ph_modes_args1-quartz_666_300K_debye_waller.json-qua…z_300K_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)
artifacts/Unit test results macos-13/junit_report_1721211581.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212282.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212875.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721210876.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721211142.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211174.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211599.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721212018.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211441.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211990.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721212547.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7f708a7a3d50>
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 ImportError: numpy.core.multiarray failed to import
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:638: ImportError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7f708aa83450>
material = 'quartz'
fc = <euphonic.force_constants.ForceConstants object at 0x7f708a7a3d50>
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:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../.tox/pypi-py311/lib/python3.11/site-packages/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 0x7f708a7a3d50>
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/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
11 out of 25 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 macos-13/junit_report_1721211581.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212282.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212875.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721210876.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721211142.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211174.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211599.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721212018.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211441.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211990.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721212547.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7f708a7a0950>
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 ImportError: numpy.core.multiarray failed to import
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:638: ImportError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7f708aa80f50>
material = 'quartz'
fc = <euphonic.force_constants.ForceConstants object at 0x7f708a7a0950>
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:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../.tox/pypi-py311/lib/python3.11/site-packages/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 0x7f708a7a0950>
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/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
11 out of 25 runs failed: test_calculate_structure_factor[1-Si2-sc-skew-fc3-qpt_ph_modes_args3-Si2-sc-skew_666_300K_debye_walle…w_300K_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)
artifacts/Unit test results macos-13/junit_report_1721211581.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212282.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212875.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721210876.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721211142.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211174.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211599.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721212018.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211441.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211990.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721212547.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7f708a83af10>
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 ImportError: numpy.core.multiarray failed to import
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:638: ImportError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7f708aa80110>
material = 'Si2-sc-skew'
fc = <euphonic.force_constants.ForceConstants object at 0x7f708a83af10>
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:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../.tox/pypi-py311/lib/python3.11/site-packages/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 0x7f708a83af10>
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/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
11 out of 25 runs failed: test_calculate_structure_factor[1-quartz-fc4-qpt_ph_modes_args4-quartz_666_300K_debye_waller.json-qua…r_300K_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)
artifacts/Unit test results macos-13/junit_report_1721211581.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212282.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212875.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721210876.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721211142.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211174.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211599.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721212018.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211441.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211990.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721212547.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7f708a7a3ed0>
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 ImportError: numpy.core.multiarray failed to import
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:638: ImportError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7f708aa82790>
material = 'quartz'
fc = <euphonic.force_constants.ForceConstants object at 0x7f708a7a3ed0>
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:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../.tox/pypi-py311/lib/python3.11/site-packages/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 0x7f708a7a3ed0>
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/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
11 out of 25 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 macos-13/junit_report_1721211581.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212282.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212875.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721210876.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721211142.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211174.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211599.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721212018.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211441.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211990.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721212547.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7f708a839d10>
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 ImportError: numpy.core.multiarray failed to import
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:638: ImportError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7f708aa8bcd0>
material = 'quartz'
fc = <euphonic.force_constants.ForceConstants object at 0x7f708a839d10>
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:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../.tox/pypi-py311/lib/python3.11/site-packages/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 0x7f708a839d10>
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/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
11 out of 25 runs failed: test_calculate_structure_factor[2-quartz-fc1-qpt_ph_modes_args1-quartz_666_300K_debye_waller.json-qua…z_300K_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)
artifacts/Unit test results macos-13/junit_report_1721211581.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212282.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212875.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721210876.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721211142.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211174.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211599.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721212018.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211441.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211990.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721212547.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7f708a7a3d50>
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 ImportError: numpy.core.multiarray failed to import
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:638: ImportError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7f708aa89250>
material = 'quartz'
fc = <euphonic.force_constants.ForceConstants object at 0x7f708a7a3d50>
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:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../.tox/pypi-py311/lib/python3.11/site-packages/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 0x7f708a7a3d50>
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/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
11 out of 25 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 macos-13/junit_report_1721211581.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212282.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212875.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721210876.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721211142.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211174.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211599.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721212018.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211441.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211990.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721212547.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7f708a7a0950>
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 ImportError: numpy.core.multiarray failed to import
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:638: ImportError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7f708aa88290>
material = 'quartz'
fc = <euphonic.force_constants.ForceConstants object at 0x7f708a7a0950>
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:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../.tox/pypi-py311/lib/python3.11/site-packages/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 0x7f708a7a0950>
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/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
11 out of 25 runs failed: test_calculate_structure_factor[2-Si2-sc-skew-fc3-qpt_ph_modes_args3-Si2-sc-skew_666_300K_debye_walle…w_300K_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)
artifacts/Unit test results macos-13/junit_report_1721211581.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212282.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212875.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721210876.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721211142.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211174.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211599.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721212018.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211441.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211990.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721212547.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7f708a83af10>
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 ImportError: numpy.core.multiarray failed to import
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:638: ImportError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7f708aa89d90>
material = 'Si2-sc-skew'
fc = <euphonic.force_constants.ForceConstants object at 0x7f708a83af10>
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:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../.tox/pypi-py311/lib/python3.11/site-packages/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 0x7f708a83af10>
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/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
11 out of 25 runs failed: test_calculate_structure_factor[2-quartz-fc4-qpt_ph_modes_args4-quartz_666_300K_debye_waller.json-qua…r_300K_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)
artifacts/Unit test results macos-13/junit_report_1721211581.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212282.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212875.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721210876.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721211142.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211174.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211599.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721212018.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211441.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211990.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721212547.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7f708a7a3ed0>
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 ImportError: numpy.core.multiarray failed to import
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:638: ImportError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7f708aa89150>
material = 'quartz'
fc = <euphonic.force_constants.ForceConstants object at 0x7f708a7a3ed0>
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:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../.tox/pypi-py311/lib/python3.11/site-packages/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 0x7f708a7a3ed0>
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/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
11 out of 25 runs failed: test_calculate_structure_factor_from_phonopy[1-CaHgO2-fc_kwargs0-qpt_ph_modes_args0-CaHgO2_666_300K_d…2_300K_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)
artifacts/Unit test results macos-13/junit_report_1721211581.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212282.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212875.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721210876.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721211142.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211174.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211599.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721212018.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211441.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211990.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721212547.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7f7084f088d0>
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 ImportError: numpy.core.multiarray failed to import
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:638: ImportError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7f708a850a50>
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:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../.tox/pypi-py311/lib/python3.11/site-packages/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 0x7f7084f088d0>
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/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
11 out of 25 runs failed: test_calculate_structure_factor_from_phonopy[2-CaHgO2-fc_kwargs0-qpt_ph_modes_args0-CaHgO2_666_300K_d…2_300K_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)
artifacts/Unit test results macos-13/junit_report_1721211581.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212282.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212875.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721210876.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721211142.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211174.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211599.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721212018.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211441.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211990.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721212547.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7f7084eb4090>
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 ImportError: numpy.core.multiarray failed to import
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:638: ImportError
During handling of the above exception, another exception occurred:
self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7f708a82e0d0>
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:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../.tox/pypi-py311/lib/python3.11/site-packages/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 0x7f7084eb4090>
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/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
11 out of 25 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 macos-13/junit_report_1721211581.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212282.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212875.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721210876.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721211142.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211174.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211599.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721212018.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211441.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211990.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721212547.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7f708a82f5d0>
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 ImportError: numpy.core.multiarray failed to import
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:638: ImportError
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 0x7f708a87a090>
fc = <euphonic.force_constants.ForceConstants object at 0x7f708a82f5d0>
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:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../.tox/pypi-py311/lib/python3.11/site-packages/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 0x7f708a82f5d0>
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/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
11 out of 25 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 macos-13/junit_report_1721211581.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212282.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212875.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721210876.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721211142.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211174.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211599.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721212018.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211441.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211990.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721212547.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7f708aa63c10>
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 ImportError: numpy.core.multiarray failed to import
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:638: ImportError
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 0x7f708a87a9d0>
fc = <euphonic.force_constants.ForceConstants object at 0x7f708aa63c10>
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:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../.tox/pypi-py311/lib/python3.11/site-packages/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 0x7f708aa63c10>
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/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
11 out of 25 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 macos-13/junit_report_1721211581.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212282.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212875.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721210876.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721211142.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211174.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211599.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721212018.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211441.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211990.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721212547.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7f708a86bc50>
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 ImportError: numpy.core.multiarray failed to import
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:638: ImportError
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 0x7f708a87a5d0>
fc = <euphonic.force_constants.ForceConstants object at 0x7f708a86bc50>
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:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../.tox/pypi-py311/lib/python3.11/site-packages/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 0x7f708a86bc50>
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/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
11 out of 25 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 macos-13/junit_report_1721211581.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212282.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212875.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721210876.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721211142.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211174.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211599.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721212018.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211441.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211990.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721212547.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7f708aa61e10>
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 ImportError: numpy.core.multiarray failed to import
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:638: ImportError
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 0x7f708a87a7d0>
fc = <euphonic.force_constants.ForceConstants object at 0x7f708aa61e10>
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:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../.tox/pypi-py311/lib/python3.11/site-packages/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 0x7f708aa61e10>
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/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
11 out of 25 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 macos-13/junit_report_1721211581.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212282.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212875.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721210876.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721211142.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211174.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211599.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721212018.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211441.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211990.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721212547.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7f708a7fad10>
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 ImportError: numpy.core.multiarray failed to import
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:638: ImportError
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 0x7f708a87b010>
fc = <euphonic.force_constants.ForceConstants object at 0x7f708a7fad10>
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:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../.tox/pypi-py311/lib/python3.11/site-packages/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 0x7f708a7fad10>
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/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
11 out of 25 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 macos-13/junit_report_1721211581.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212282.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212875.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721210876.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721211142.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211174.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211599.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721212018.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211441.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211990.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721212547.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7f708a869210>
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 ImportError: numpy.core.multiarray failed to import
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:638: ImportError
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 0x7f708a87b690>
fc = <euphonic.force_constants.ForceConstants object at 0x7f708a869210>
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:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../.tox/pypi-py311/lib/python3.11/site-packages/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 0x7f708a869210>
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/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
11 out of 25 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 macos-13/junit_report_1721211581.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212282.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212875.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721210876.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721211142.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211174.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211599.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721212018.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211441.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211990.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721212547.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7f708a82f5d0>
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 ImportError: numpy.core.multiarray failed to import
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:638: ImportError
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 0x7f708a87b150>
fc = <euphonic.force_constants.ForceConstants object at 0x7f708a82f5d0>
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:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../.tox/pypi-py311/lib/python3.11/site-packages/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 0x7f708a82f5d0>
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/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
11 out of 25 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 macos-13/junit_report_1721211581.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212282.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212875.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721210876.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721211142.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211174.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211599.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721212018.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211441.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211990.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721212547.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7f708aa63c10>
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 ImportError: numpy.core.multiarray failed to import
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:638: ImportError
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 0x7f708a87b810>
fc = <euphonic.force_constants.ForceConstants object at 0x7f708aa63c10>
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:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../.tox/pypi-py311/lib/python3.11/site-packages/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 0x7f708aa63c10>
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/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
11 out of 25 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 macos-13/junit_report_1721211581.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212282.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212875.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721210876.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721211142.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211174.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211599.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721212018.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211441.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211990.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721212547.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7f708a86bc50>
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 ImportError: numpy.core.multiarray failed to import
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:638: ImportError
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 0x7f708a87be90>
fc = <euphonic.force_constants.ForceConstants object at 0x7f708a86bc50>
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:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../.tox/pypi-py311/lib/python3.11/site-packages/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 0x7f708a86bc50>
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/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
11 out of 25 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 macos-13/junit_report_1721211581.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212282.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212875.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721210876.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721211142.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211174.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211599.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721212018.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211441.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211990.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721212547.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7f708aa61e10>
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 ImportError: numpy.core.multiarray failed to import
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:638: ImportError
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 0x7f708a87abd0>
fc = <euphonic.force_constants.ForceConstants object at 0x7f708aa61e10>
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:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../.tox/pypi-py311/lib/python3.11/site-packages/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 0x7f708aa61e10>
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/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
11 out of 25 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 macos-13/junit_report_1721211581.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212282.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212875.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721210876.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721211142.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211174.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211599.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721212018.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211441.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211990.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721212547.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7f708a7fad10>
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 ImportError: numpy.core.multiarray failed to import
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:638: ImportError
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 0x7f708a878310>
fc = <euphonic.force_constants.ForceConstants object at 0x7f708a7fad10>
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:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../.tox/pypi-py311/lib/python3.11/site-packages/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 0x7f708a7fad10>
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/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
11 out of 25 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 macos-13/junit_report_1721211581.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212282.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212875.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721210876.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721211142.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211174.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211599.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721212018.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211441.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211990.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721212547.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7f708a869210>
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 ImportError: numpy.core.multiarray failed to import
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:638: ImportError
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 0x7f708a8787d0>
fc = <euphonic.force_constants.ForceConstants object at 0x7f708a869210>
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:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../.tox/pypi-py311/lib/python3.11/site-packages/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 0x7f708a869210>
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/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:647: ImportCError
github-actions / Test Results
11 out of 25 runs failed: test_calculate_qpt_freqs_with_mode_gradients[2-fc0-quartz-all_args0-quartz_554_full_qpoint_frequencie…_gradients.json] (euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies)
artifacts/Unit test results macos-13/junit_report_1721211581.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212282.xml [took 0s]
artifacts/Unit test results macos-13/junit_report_1721212875.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721210876.xml [took 0s]
artifacts/Unit test results macos-latest/junit_report_1721211142.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211174.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721211599.xml [took 0s]
artifacts/Unit test results ubuntu-latest/junit_report_1721212018.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211441.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721211990.xml [took 0s]
artifacts/Unit test results windows-latest/junit_report_1721212547.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7f708a868790>
qpts = array([[-0.4 , -0.4 , -0.375],
[-0.4 , -0.4 , -0.125],
[-0.4 , -0.4 , 0.125],
[-0.4 , -0.... , 0.4 , -0.375],
[ 0.4 , 0.4 , -0.125],
[ 0.4 , 0.4 , 0.125],
[ 0.4 , 0.4 , 0.375]])
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 = True, 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 ImportError: numpy.core.multiarray failed to import
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:638: ImportError
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 0x7f708a897f10>
fc = <euphonic.force_constants.ForceConstants object at 0x7f708a868790>
material = 'quartz'
all_args = [array([[-0.4 , -0.4 , -0.375],
[-0.4 , -0.4 , -0.125],
[-0.4 , -0.4 , 0.125],
[-0.4 , -0... , 0.4 , 0.125],
[ 0.4 , 0.4 , 0.375]]), {'n_threads': 2, 'return_mode_gradients': True, 'use_c': True}]
expected_qpoint_frequencies_file = 'quartz_554_full_qpoint_frequencies.json'
expected_modg_file = 'quartz_554_full_mode_gradients.json', n_threads = 2
@pytest.mark.parametrize(
('fc, material, all_args, expected_qpoint_frequencies_file, '
'expected_modg_file'),
[(get_quartz_fc(),
'quartz',
[mp_grid([5, 5, 4]),
{'return_mode_gradients': True}],
'quartz_554_full_qpoint_frequencies.json',
'quartz_554_full_mode_gradients.json'),
(get_lzo_fc(),
'LZO',
[mp_grid([2, 2, 2]),
{'asr': 'reciprocal', 'return_mode_gradients': True}],
'lzo_222_full_qpoint_frequencies.json',
'lzo_222_full_mode_gradients.json')])
@pytest.mark.parametrize(
'n_threads',
[0, 2])
def test_calculate_qpt_freqs_with_mode_gradients(
self, fc, material, all_args, expected_qpoint_frequencies_file,
expected_modg_file, n_threads):
func_kwargs = all_args[1]
if n_threads == 0:
func_kwargs['use_c'] = False
else:
func_kwargs['use_c'] = True
func_kwargs['n_threads'] = n_threads
> qpt_freqs, modg = fc.calculate_qpoint_frequencies(
all_args[0], **func_kwargs)
euphonic_test/test_force_constants_calculate_qpoint_frequencies.py:111:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../../.tox/pypi-py311/lib/python3.11/site-packages/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 0x7f708a868790>
qpts = array([[-0.4 , -0.4 , -0.375],
[-0.4 , -0.4 , -0.125],
[-0.4 , -0.4 , 0.125],
[-0.4 , -0.... , 0.4 , -0.375],
[ 0.4 , 0.4 , -0.125],
[ 0.4 , 0.4 , 0.125],
[ 0.4 , 0.4 , 0.375]])
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 = True, 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/.tox/pypi-py311/lib/python3.11/site-packages/euphonic, it may not have been installed.
../../.tox/pypi-py311/lib/python3.11/site-packages/euphonic/force_constants.py:647: ImportCError