Skip to content

Create an action to push some ARM builds to PyPI #877

Create an action to push some ARM builds to PyPI

Create an action to push some ARM builds to PyPI #877

GitHub Actions / Test Results failed Jul 17, 2024 in 0s

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

Check warning on line 0 in euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants

See this annotation in the file changed.

@github-actions 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

Check warning on line 0 in euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants

See this annotation in the file changed.

@github-actions 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

Check warning on line 0 in euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants

See this annotation in the file changed.

@github-actions 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

Check warning on line 0 in euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants

See this annotation in the file changed.

@github-actions 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

Check warning on line 0 in euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants

See this annotation in the file changed.

@github-actions 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

Check warning on line 0 in euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants

See this annotation in the file changed.

@github-actions 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

Check warning on line 0 in euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants

See this annotation in the file changed.

@github-actions 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

Check warning on line 0 in euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants

See this annotation in the file changed.

@github-actions 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

Check warning on line 0 in euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants

See this annotation in the file changed.

@github-actions 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

Check warning on line 0 in euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants

See this annotation in the file changed.

@github-actions 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

Check warning on line 0 in euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants

See this annotation in the file changed.

@github-actions 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

Check warning on line 0 in euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants

See this annotation in the file changed.

@github-actions 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

Check warning on line 0 in euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies

See this annotation in the file changed.

@github-actions 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

Check warning on line 0 in euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies

See this annotation in the file changed.

@github-actions 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

Check warning on line 0 in euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies

See this annotation in the file changed.

@github-actions 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

Check warning on line 0 in euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies

See this annotation in the file changed.

@github-actions 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

Check warning on line 0 in euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies

See this annotation in the file changed.

@github-actions 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

Check warning on line 0 in euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies

See this annotation in the file changed.

@github-actions 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

Check warning on line 0 in euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies

See this annotation in the file changed.

@github-actions 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

Check warning on line 0 in euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies

See this annotation in the file changed.

@github-actions 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

Check warning on line 0 in euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies

See this annotation in the file changed.

@github-actions 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

Check warning on line 0 in euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies

See this annotation in the file changed.

@github-actions 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

Check warning on line 0 in euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies

See this annotation in the file changed.

@github-actions 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

Check warning on line 0 in euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies

See this annotation in the file changed.

@github-actions 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

Check warning on line 0 in euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies

See this annotation in the file changed.

@github-actions 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