Skip to content

run_tests workflow: use parallel tox #1006

run_tests workflow: use parallel tox

run_tests workflow: use parallel tox #1006

GitHub Actions / Test Results failed Oct 23, 2024 in 0s

37 errors, 58 fail, 6 skipped, 988 pass in 39m 41s

   14 files     14 suites   39m 41s ⏱️
1 089 tests   988 ✅  6 💤 58 ❌ 37 🔥
6 514 runs  6 380 ✅ 39 💤 58 ❌ 37 🔥

Results for commit 073ac8a.

Annotations

Check warning on line 0 in script_tests.test_brille_convergence.TestRegression

See this annotation in the file changed.

@github-actions github-actions / Test Results

1 out of 8 runs failed: test_brille_interpolator_from_force_constants_kwargs_passed[brille_conv_args1-expected_kwargs1] (script_tests.test_brille_convergence.TestRegression)

artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbafedce7b0>
qpts = array([[0.        , 0.        , 0.        ],
       [0.81917251, 0.67104361, 0.54970048],
       [0.63834503, 0.342087...86, 0.03047588, 0.05137647],
       [0.53416837, 0.70151949, 0.60107695],
       [0.35334088, 0.3725631 , 0.15077742]])
weights = None, asr = 'realspace', dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = True

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
>               import euphonic._euphonic as euphonic_c
E               ModuleNotFoundError: No module named 'euphonic._euphonic'

../../euphonic/force_constants.py:638: ModuleNotFoundError

During handling of the above exception, another exception occurred:

self = <tests_and_analysis.test.script_tests.test_brille_convergence.TestRegression object at 0x7fbb118d0710>
inject_mocks = None
mocker = <pytest_mock.plugin.MockerFixture object at 0x7fbafe2cc2c0>
brille_conv_args = ['--brille-grid-type', 'mesh', '--use-c', '--n-threads', '2', '--asr', ...]
expected_kwargs = {'grid_type': 'mesh', 'interpolation_kwargs': {'asr': 'realspace', 'n_threads': 2, 'use_c': True}}

    @pytest.mark.parametrize('brille_conv_args, expected_kwargs', [
        (['--brille-npts', '25', '--disable-c'],
         {'grid_npts': 25, 'grid_type': 'trellis',
          'interpolation_kwargs': {'use_c': False}}),
        (['--brille-grid-type', 'mesh', '--use-c',
          '--n-threads', '2', '--asr', 'realspace'],
          {'grid_type': 'mesh', 'interpolation_kwargs': {'asr': 'realspace',
                                                         'use_c': True,
                                                         'n_threads': 2}})
    ])
    def test_brille_interpolator_from_force_constants_kwargs_passed(
            self, inject_mocks, mocker, brille_conv_args, expected_kwargs):
        from euphonic.brille import BrilleInterpolator
        # Stop execution once from_fc has been called - we're only
        # checking here that the correct arguments have been passed
        # through
        class MockException(Exception):
            pass
        mock = mocker.patch.object(BrilleInterpolator, 'from_force_constants',
                                   side_effect=MockException())
        try:
>           euphonic.cli.brille_convergence.main(
                [graphite_fc_file] + brille_conv_args)

script_tests/test_brille_convergence.py:144: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../euphonic/cli/brille_convergence.py:24: in main
    check_brille_settings(**params)
../../euphonic/cli/brille_convergence.py:55: in check_brille_settings
    modes = fc.calculate_qpoint_phonon_modes(qpts, **calc_modes_kwargs)
../../euphonic/force_constants.py:387: in calculate_qpoint_phonon_modes
    qpts, freqs, weights, evecs, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <euphonic.force_constants.ForceConstants object at 0x7fbafedce7b0>
qpts = array([[0.        , 0.        , 0.        ],
       [0.81917251, 0.67104361, 0.54970048],
       [0.63834503, 0.342087...86, 0.03047588, 0.05137647],
       [0.53416837, 0.70151949, 0.60107695],
       [0.35334088, 0.3725631 , 0.15077742]])
weights = None, asr = 'realspace', dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = True

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
                import euphonic._euphonic as euphonic_c
                use_c_status = True
            except ImportError:
                if use_c is None:
                    warnings.warn((
                        cext_err_msg
                        + ' Falling back to pure Python calculation.'),
                        stacklevel=3)
                else:
>                   raise ImportCError(cext_err_msg)
E                   euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.

../../euphonic/force_constants.py:647: ImportCError

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

1 out of 6 runs failed: test_calculate_structure_factor[1-quartz-fc0-qpt_ph_modes_args0-quartz_666_0K_debye_waller.json-quartz_0K_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)

artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f156a0>
qpts = array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0. ...[-1.      ,  1.      ,  1.      ],
       [-0.151515,  0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]])
weights = None, asr = None, dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = True

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
>               import euphonic._euphonic as euphonic_c
E               ModuleNotFoundError: No module named 'euphonic._euphonic'

../../euphonic/force_constants.py:638: ModuleNotFoundError

During handling of the above exception, another exception occurred:

self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7fbb11f8eae0>
material = 'quartz'
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11f156a0>
qpt_ph_modes_args = (array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0....
       [-0.151515,  0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]]), {'n_threads': 1, 'use_c': True})
dw_file = 'quartz_666_0K_debye_waller.json'
expected_sf_file = 'quartz_0K_fc_structure_factor.json', n_threads = 1

    @pytest.mark.parametrize(
        'material, fc, qpt_ph_modes_args, dw_file, expected_sf_file', [
            ('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
             'quartz_666_0K_debye_waller.json',
             'quartz_0K_fc_structure_factor.json'),
            ('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
             'quartz_666_300K_debye_waller.json',
             'quartz_300K_fc_structure_factor.json'),
            ('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
             None,
             'quartz_fc_structure_factor.json'),
            ('Si2-sc-skew', get_si2_fc(), (get_test_qpts(), {}),
             'Si2-sc-skew_666_300K_debye_waller.json',
             'Si2-sc-skew_300K_fc_structure_factor.json'),
            ('quartz', get_quartz_fc(),
             (get_test_qpts('split'), {'asr': 'reciprocal'}),
             'quartz_666_300K_debye_waller.json',
             'quartz_recip_asr_300K_fc_structure_factor.json')])
    @pytest.mark.parametrize('n_threads', [0, 1, 2])
    def test_calculate_structure_factor(self, material, fc, qpt_ph_modes_args,
                                        dw_file, expected_sf_file, n_threads):
        args, kwargs = qpt_ph_modes_args
        if n_threads == 0:
            kwargs['use_c'] = False
        else:
            kwargs['use_c'] = True
            kwargs['n_threads'] = n_threads
>       qpt_ph_modes = fc.calculate_qpoint_phonon_modes(args, **kwargs)

euphonic_test/test_calculate_structure_factor.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../euphonic/force_constants.py:387: in calculate_qpoint_phonon_modes
    qpts, freqs, weights, evecs, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f156a0>
qpts = array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0. ...[-1.      ,  1.      ,  1.      ],
       [-0.151515,  0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]])
weights = None, asr = None, dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = True

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
                import euphonic._euphonic as euphonic_c
                use_c_status = True
            except ImportError:
                if use_c is None:
                    warnings.warn((
                        cext_err_msg
                        + ' Falling back to pure Python calculation.'),
                        stacklevel=3)
                else:
>                   raise ImportCError(cext_err_msg)
E                   euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.

../../euphonic/force_constants.py:647: ImportCError

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

1 out of 6 runs failed: test_calculate_structure_factor[1-quartz-fc1-qpt_ph_modes_args1-quartz_666_300K_debye_waller.json-quartz_300K_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)

artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f3e480>
qpts = array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0. ...[-1.      ,  1.      ,  1.      ],
       [-0.151515,  0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]])
weights = None, asr = None, dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = True

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
>               import euphonic._euphonic as euphonic_c
E               ModuleNotFoundError: No module named 'euphonic._euphonic'

../../euphonic/force_constants.py:638: ModuleNotFoundError

During handling of the above exception, another exception occurred:

self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7fbb11f8ef00>
material = 'quartz'
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11f3e480>
qpt_ph_modes_args = (array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0....
       [-0.151515,  0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]]), {'n_threads': 1, 'use_c': True})
dw_file = 'quartz_666_300K_debye_waller.json'
expected_sf_file = 'quartz_300K_fc_structure_factor.json', n_threads = 1

    @pytest.mark.parametrize(
        'material, fc, qpt_ph_modes_args, dw_file, expected_sf_file', [
            ('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
             'quartz_666_0K_debye_waller.json',
             'quartz_0K_fc_structure_factor.json'),
            ('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
             'quartz_666_300K_debye_waller.json',
             'quartz_300K_fc_structure_factor.json'),
            ('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
             None,
             'quartz_fc_structure_factor.json'),
            ('Si2-sc-skew', get_si2_fc(), (get_test_qpts(), {}),
             'Si2-sc-skew_666_300K_debye_waller.json',
             'Si2-sc-skew_300K_fc_structure_factor.json'),
            ('quartz', get_quartz_fc(),
             (get_test_qpts('split'), {'asr': 'reciprocal'}),
             'quartz_666_300K_debye_waller.json',
             'quartz_recip_asr_300K_fc_structure_factor.json')])
    @pytest.mark.parametrize('n_threads', [0, 1, 2])
    def test_calculate_structure_factor(self, material, fc, qpt_ph_modes_args,
                                        dw_file, expected_sf_file, n_threads):
        args, kwargs = qpt_ph_modes_args
        if n_threads == 0:
            kwargs['use_c'] = False
        else:
            kwargs['use_c'] = True
            kwargs['n_threads'] = n_threads
>       qpt_ph_modes = fc.calculate_qpoint_phonon_modes(args, **kwargs)

euphonic_test/test_calculate_structure_factor.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../euphonic/force_constants.py:387: in calculate_qpoint_phonon_modes
    qpts, freqs, weights, evecs, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f3e480>
qpts = array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0. ...[-1.      ,  1.      ,  1.      ],
       [-0.151515,  0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]])
weights = None, asr = None, dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = True

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
                import euphonic._euphonic as euphonic_c
                use_c_status = True
            except ImportError:
                if use_c is None:
                    warnings.warn((
                        cext_err_msg
                        + ' Falling back to pure Python calculation.'),
                        stacklevel=3)
                else:
>                   raise ImportCError(cext_err_msg)
E                   euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.

../../euphonic/force_constants.py:647: ImportCError

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

1 out of 6 runs failed: test_calculate_structure_factor[1-quartz-fc2-qpt_ph_modes_args2-None-quartz_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)

artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f3ffb0>
qpts = array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0. ...[-1.      ,  1.      ,  1.      ],
       [-0.151515,  0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]])
weights = None, asr = None, dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = True

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
>               import euphonic._euphonic as euphonic_c
E               ModuleNotFoundError: No module named 'euphonic._euphonic'

../../euphonic/force_constants.py:638: ModuleNotFoundError

During handling of the above exception, another exception occurred:

self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7fbb11f8eff0>
material = 'quartz'
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11f3ffb0>
qpt_ph_modes_args = (array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0....
       [-0.151515,  0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]]), {'n_threads': 1, 'use_c': True})
dw_file = None, expected_sf_file = 'quartz_fc_structure_factor.json'
n_threads = 1

    @pytest.mark.parametrize(
        'material, fc, qpt_ph_modes_args, dw_file, expected_sf_file', [
            ('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
             'quartz_666_0K_debye_waller.json',
             'quartz_0K_fc_structure_factor.json'),
            ('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
             'quartz_666_300K_debye_waller.json',
             'quartz_300K_fc_structure_factor.json'),
            ('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
             None,
             'quartz_fc_structure_factor.json'),
            ('Si2-sc-skew', get_si2_fc(), (get_test_qpts(), {}),
             'Si2-sc-skew_666_300K_debye_waller.json',
             'Si2-sc-skew_300K_fc_structure_factor.json'),
            ('quartz', get_quartz_fc(),
             (get_test_qpts('split'), {'asr': 'reciprocal'}),
             'quartz_666_300K_debye_waller.json',
             'quartz_recip_asr_300K_fc_structure_factor.json')])
    @pytest.mark.parametrize('n_threads', [0, 1, 2])
    def test_calculate_structure_factor(self, material, fc, qpt_ph_modes_args,
                                        dw_file, expected_sf_file, n_threads):
        args, kwargs = qpt_ph_modes_args
        if n_threads == 0:
            kwargs['use_c'] = False
        else:
            kwargs['use_c'] = True
            kwargs['n_threads'] = n_threads
>       qpt_ph_modes = fc.calculate_qpoint_phonon_modes(args, **kwargs)

euphonic_test/test_calculate_structure_factor.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../euphonic/force_constants.py:387: in calculate_qpoint_phonon_modes
    qpts, freqs, weights, evecs, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f3ffb0>
qpts = array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0. ...[-1.      ,  1.      ,  1.      ],
       [-0.151515,  0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]])
weights = None, asr = None, dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = True

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
                import euphonic._euphonic as euphonic_c
                use_c_status = True
            except ImportError:
                if use_c is None:
                    warnings.warn((
                        cext_err_msg
                        + ' Falling back to pure Python calculation.'),
                        stacklevel=3)
                else:
>                   raise ImportCError(cext_err_msg)
E                   euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.

../../euphonic/force_constants.py:647: ImportCError

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

1 out of 6 runs failed: test_calculate_structure_factor[1-Si2-sc-skew-fc3-qpt_ph_modes_args3-Si2-sc-skew_666_300K_debye_waller.…w_300K_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)

artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8cd70>
qpts = array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0.5...[ 0.6     ,  0.      ,  0.2     ],
       [ 2.      ,  2.      ,  0.5     ],
       [ 1.75    ,  0.5     ,  2.5     ]])
weights = None, asr = None, dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = True

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
>               import euphonic._euphonic as euphonic_c
E               ModuleNotFoundError: No module named 'euphonic._euphonic'

../../euphonic/force_constants.py:638: ModuleNotFoundError

During handling of the above exception, another exception occurred:

self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7fbb11f8f110>
material = 'Si2-sc-skew'
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8cd70>
qpt_ph_modes_args = (array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0....
       [ 2.      ,  2.      ,  0.5     ],
       [ 1.75    ,  0.5     ,  2.5     ]]), {'n_threads': 1, 'use_c': True})
dw_file = 'Si2-sc-skew_666_300K_debye_waller.json'
expected_sf_file = 'Si2-sc-skew_300K_fc_structure_factor.json', n_threads = 1

    @pytest.mark.parametrize(
        'material, fc, qpt_ph_modes_args, dw_file, expected_sf_file', [
            ('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
             'quartz_666_0K_debye_waller.json',
             'quartz_0K_fc_structure_factor.json'),
            ('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
             'quartz_666_300K_debye_waller.json',
             'quartz_300K_fc_structure_factor.json'),
            ('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
             None,
             'quartz_fc_structure_factor.json'),
            ('Si2-sc-skew', get_si2_fc(), (get_test_qpts(), {}),
             'Si2-sc-skew_666_300K_debye_waller.json',
             'Si2-sc-skew_300K_fc_structure_factor.json'),
            ('quartz', get_quartz_fc(),
             (get_test_qpts('split'), {'asr': 'reciprocal'}),
             'quartz_666_300K_debye_waller.json',
             'quartz_recip_asr_300K_fc_structure_factor.json')])
    @pytest.mark.parametrize('n_threads', [0, 1, 2])
    def test_calculate_structure_factor(self, material, fc, qpt_ph_modes_args,
                                        dw_file, expected_sf_file, n_threads):
        args, kwargs = qpt_ph_modes_args
        if n_threads == 0:
            kwargs['use_c'] = False
        else:
            kwargs['use_c'] = True
            kwargs['n_threads'] = n_threads
>       qpt_ph_modes = fc.calculate_qpoint_phonon_modes(args, **kwargs)

euphonic_test/test_calculate_structure_factor.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../euphonic/force_constants.py:387: in calculate_qpoint_phonon_modes
    qpts, freqs, weights, evecs, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8cd70>
qpts = array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0.5...[ 0.6     ,  0.      ,  0.2     ],
       [ 2.      ,  2.      ,  0.5     ],
       [ 1.75    ,  0.5     ,  2.5     ]])
weights = None, asr = None, dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = True

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
                import euphonic._euphonic as euphonic_c
                use_c_status = True
            except ImportError:
                if use_c is None:
                    warnings.warn((
                        cext_err_msg
                        + ' Falling back to pure Python calculation.'),
                        stacklevel=3)
                else:
>                   raise ImportCError(cext_err_msg)
E                   euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.

../../euphonic/force_constants.py:647: ImportCError

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

1 out of 6 runs failed: test_calculate_structure_factor[1-quartz-fc4-qpt_ph_modes_args4-quartz_666_300K_debye_waller.json-quart…r_300K_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)

artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d5b0>
qpts = array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0. ...[-1.      ,  1.      ,  1.      ],
       [-0.151515,  0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = True

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
>               import euphonic._euphonic as euphonic_c
E               ModuleNotFoundError: No module named 'euphonic._euphonic'

../../euphonic/force_constants.py:638: ModuleNotFoundError

During handling of the above exception, another exception occurred:

self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7fbb11f8f230>
material = 'quartz'
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d5b0>
qpt_ph_modes_args = (array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0....0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]]), {'asr': 'reciprocal', 'n_threads': 1, 'use_c': True})
dw_file = 'quartz_666_300K_debye_waller.json'
expected_sf_file = 'quartz_recip_asr_300K_fc_structure_factor.json'
n_threads = 1

    @pytest.mark.parametrize(
        'material, fc, qpt_ph_modes_args, dw_file, expected_sf_file', [
            ('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
             'quartz_666_0K_debye_waller.json',
             'quartz_0K_fc_structure_factor.json'),
            ('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
             'quartz_666_300K_debye_waller.json',
             'quartz_300K_fc_structure_factor.json'),
            ('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
             None,
             'quartz_fc_structure_factor.json'),
            ('Si2-sc-skew', get_si2_fc(), (get_test_qpts(), {}),
             'Si2-sc-skew_666_300K_debye_waller.json',
             'Si2-sc-skew_300K_fc_structure_factor.json'),
            ('quartz', get_quartz_fc(),
             (get_test_qpts('split'), {'asr': 'reciprocal'}),
             'quartz_666_300K_debye_waller.json',
             'quartz_recip_asr_300K_fc_structure_factor.json')])
    @pytest.mark.parametrize('n_threads', [0, 1, 2])
    def test_calculate_structure_factor(self, material, fc, qpt_ph_modes_args,
                                        dw_file, expected_sf_file, n_threads):
        args, kwargs = qpt_ph_modes_args
        if n_threads == 0:
            kwargs['use_c'] = False
        else:
            kwargs['use_c'] = True
            kwargs['n_threads'] = n_threads
>       qpt_ph_modes = fc.calculate_qpoint_phonon_modes(args, **kwargs)

euphonic_test/test_calculate_structure_factor.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../euphonic/force_constants.py:387: in calculate_qpoint_phonon_modes
    qpts, freqs, weights, evecs, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d5b0>
qpts = array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0. ...[-1.      ,  1.      ,  1.      ],
       [-0.151515,  0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = True

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
                import euphonic._euphonic as euphonic_c
                use_c_status = True
            except ImportError:
                if use_c is None:
                    warnings.warn((
                        cext_err_msg
                        + ' Falling back to pure Python calculation.'),
                        stacklevel=3)
                else:
>                   raise ImportCError(cext_err_msg)
E                   euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.

../../euphonic/force_constants.py:647: ImportCError

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

1 out of 6 runs failed: test_calculate_structure_factor[2-quartz-fc0-qpt_ph_modes_args0-quartz_666_0K_debye_waller.json-quartz_0K_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)

artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f156a0>
qpts = array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0. ...[-1.      ,  1.      ,  1.      ],
       [-0.151515,  0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]])
weights = None, asr = None, dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = True

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
>               import euphonic._euphonic as euphonic_c
E               ModuleNotFoundError: No module named 'euphonic._euphonic'

../../euphonic/force_constants.py:638: ModuleNotFoundError

During handling of the above exception, another exception occurred:

self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7fbb11f8f320>
material = 'quartz'
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11f156a0>
qpt_ph_modes_args = (array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0....
       [-0.151515,  0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]]), {'n_threads': 2, 'use_c': True})
dw_file = 'quartz_666_0K_debye_waller.json'
expected_sf_file = 'quartz_0K_fc_structure_factor.json', n_threads = 2

    @pytest.mark.parametrize(
        'material, fc, qpt_ph_modes_args, dw_file, expected_sf_file', [
            ('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
             'quartz_666_0K_debye_waller.json',
             'quartz_0K_fc_structure_factor.json'),
            ('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
             'quartz_666_300K_debye_waller.json',
             'quartz_300K_fc_structure_factor.json'),
            ('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
             None,
             'quartz_fc_structure_factor.json'),
            ('Si2-sc-skew', get_si2_fc(), (get_test_qpts(), {}),
             'Si2-sc-skew_666_300K_debye_waller.json',
             'Si2-sc-skew_300K_fc_structure_factor.json'),
            ('quartz', get_quartz_fc(),
             (get_test_qpts('split'), {'asr': 'reciprocal'}),
             'quartz_666_300K_debye_waller.json',
             'quartz_recip_asr_300K_fc_structure_factor.json')])
    @pytest.mark.parametrize('n_threads', [0, 1, 2])
    def test_calculate_structure_factor(self, material, fc, qpt_ph_modes_args,
                                        dw_file, expected_sf_file, n_threads):
        args, kwargs = qpt_ph_modes_args
        if n_threads == 0:
            kwargs['use_c'] = False
        else:
            kwargs['use_c'] = True
            kwargs['n_threads'] = n_threads
>       qpt_ph_modes = fc.calculate_qpoint_phonon_modes(args, **kwargs)

euphonic_test/test_calculate_structure_factor.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../euphonic/force_constants.py:387: in calculate_qpoint_phonon_modes
    qpts, freqs, weights, evecs, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f156a0>
qpts = array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0. ...[-1.      ,  1.      ,  1.      ],
       [-0.151515,  0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]])
weights = None, asr = None, dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = True

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
                import euphonic._euphonic as euphonic_c
                use_c_status = True
            except ImportError:
                if use_c is None:
                    warnings.warn((
                        cext_err_msg
                        + ' Falling back to pure Python calculation.'),
                        stacklevel=3)
                else:
>                   raise ImportCError(cext_err_msg)
E                   euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.

../../euphonic/force_constants.py:647: ImportCError

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

1 out of 6 runs failed: test_calculate_structure_factor[2-quartz-fc1-qpt_ph_modes_args1-quartz_666_300K_debye_waller.json-quartz_300K_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)

artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f3e480>
qpts = array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0. ...[-1.      ,  1.      ,  1.      ],
       [-0.151515,  0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]])
weights = None, asr = None, dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = True

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
>               import euphonic._euphonic as euphonic_c
E               ModuleNotFoundError: No module named 'euphonic._euphonic'

../../euphonic/force_constants.py:638: ModuleNotFoundError

During handling of the above exception, another exception occurred:

self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7fbb11f8f3b0>
material = 'quartz'
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11f3e480>
qpt_ph_modes_args = (array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0....
       [-0.151515,  0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]]), {'n_threads': 2, 'use_c': True})
dw_file = 'quartz_666_300K_debye_waller.json'
expected_sf_file = 'quartz_300K_fc_structure_factor.json', n_threads = 2

    @pytest.mark.parametrize(
        'material, fc, qpt_ph_modes_args, dw_file, expected_sf_file', [
            ('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
             'quartz_666_0K_debye_waller.json',
             'quartz_0K_fc_structure_factor.json'),
            ('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
             'quartz_666_300K_debye_waller.json',
             'quartz_300K_fc_structure_factor.json'),
            ('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
             None,
             'quartz_fc_structure_factor.json'),
            ('Si2-sc-skew', get_si2_fc(), (get_test_qpts(), {}),
             'Si2-sc-skew_666_300K_debye_waller.json',
             'Si2-sc-skew_300K_fc_structure_factor.json'),
            ('quartz', get_quartz_fc(),
             (get_test_qpts('split'), {'asr': 'reciprocal'}),
             'quartz_666_300K_debye_waller.json',
             'quartz_recip_asr_300K_fc_structure_factor.json')])
    @pytest.mark.parametrize('n_threads', [0, 1, 2])
    def test_calculate_structure_factor(self, material, fc, qpt_ph_modes_args,
                                        dw_file, expected_sf_file, n_threads):
        args, kwargs = qpt_ph_modes_args
        if n_threads == 0:
            kwargs['use_c'] = False
        else:
            kwargs['use_c'] = True
            kwargs['n_threads'] = n_threads
>       qpt_ph_modes = fc.calculate_qpoint_phonon_modes(args, **kwargs)

euphonic_test/test_calculate_structure_factor.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../euphonic/force_constants.py:387: in calculate_qpoint_phonon_modes
    qpts, freqs, weights, evecs, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f3e480>
qpts = array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0. ...[-1.      ,  1.      ,  1.      ],
       [-0.151515,  0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]])
weights = None, asr = None, dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = True

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
                import euphonic._euphonic as euphonic_c
                use_c_status = True
            except ImportError:
                if use_c is None:
                    warnings.warn((
                        cext_err_msg
                        + ' Falling back to pure Python calculation.'),
                        stacklevel=3)
                else:
>                   raise ImportCError(cext_err_msg)
E                   euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.

../../euphonic/force_constants.py:647: ImportCError

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

1 out of 6 runs failed: test_calculate_structure_factor[2-quartz-fc2-qpt_ph_modes_args2-None-quartz_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)

artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f3ffb0>
qpts = array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0. ...[-1.      ,  1.      ,  1.      ],
       [-0.151515,  0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]])
weights = None, asr = None, dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = True

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
>               import euphonic._euphonic as euphonic_c
E               ModuleNotFoundError: No module named 'euphonic._euphonic'

../../euphonic/force_constants.py:638: ModuleNotFoundError

During handling of the above exception, another exception occurred:

self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7fbb11f8f470>
material = 'quartz'
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11f3ffb0>
qpt_ph_modes_args = (array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0....
       [-0.151515,  0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]]), {'n_threads': 2, 'use_c': True})
dw_file = None, expected_sf_file = 'quartz_fc_structure_factor.json'
n_threads = 2

    @pytest.mark.parametrize(
        'material, fc, qpt_ph_modes_args, dw_file, expected_sf_file', [
            ('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
             'quartz_666_0K_debye_waller.json',
             'quartz_0K_fc_structure_factor.json'),
            ('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
             'quartz_666_300K_debye_waller.json',
             'quartz_300K_fc_structure_factor.json'),
            ('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
             None,
             'quartz_fc_structure_factor.json'),
            ('Si2-sc-skew', get_si2_fc(), (get_test_qpts(), {}),
             'Si2-sc-skew_666_300K_debye_waller.json',
             'Si2-sc-skew_300K_fc_structure_factor.json'),
            ('quartz', get_quartz_fc(),
             (get_test_qpts('split'), {'asr': 'reciprocal'}),
             'quartz_666_300K_debye_waller.json',
             'quartz_recip_asr_300K_fc_structure_factor.json')])
    @pytest.mark.parametrize('n_threads', [0, 1, 2])
    def test_calculate_structure_factor(self, material, fc, qpt_ph_modes_args,
                                        dw_file, expected_sf_file, n_threads):
        args, kwargs = qpt_ph_modes_args
        if n_threads == 0:
            kwargs['use_c'] = False
        else:
            kwargs['use_c'] = True
            kwargs['n_threads'] = n_threads
>       qpt_ph_modes = fc.calculate_qpoint_phonon_modes(args, **kwargs)

euphonic_test/test_calculate_structure_factor.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../euphonic/force_constants.py:387: in calculate_qpoint_phonon_modes
    qpts, freqs, weights, evecs, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f3ffb0>
qpts = array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0. ...[-1.      ,  1.      ,  1.      ],
       [-0.151515,  0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]])
weights = None, asr = None, dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = True

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
                import euphonic._euphonic as euphonic_c
                use_c_status = True
            except ImportError:
                if use_c is None:
                    warnings.warn((
                        cext_err_msg
                        + ' Falling back to pure Python calculation.'),
                        stacklevel=3)
                else:
>                   raise ImportCError(cext_err_msg)
E                   euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.

../../euphonic/force_constants.py:647: ImportCError

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

1 out of 6 runs failed: test_calculate_structure_factor[2-Si2-sc-skew-fc3-qpt_ph_modes_args3-Si2-sc-skew_666_300K_debye_waller.…w_300K_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)

artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8cd70>
qpts = array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0.5...[ 0.6     ,  0.      ,  0.2     ],
       [ 2.      ,  2.      ,  0.5     ],
       [ 1.75    ,  0.5     ,  2.5     ]])
weights = None, asr = None, dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = True

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
>               import euphonic._euphonic as euphonic_c
E               ModuleNotFoundError: No module named 'euphonic._euphonic'

../../euphonic/force_constants.py:638: ModuleNotFoundError

During handling of the above exception, another exception occurred:

self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7fbb11f8f5c0>
material = 'Si2-sc-skew'
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8cd70>
qpt_ph_modes_args = (array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0....
       [ 2.      ,  2.      ,  0.5     ],
       [ 1.75    ,  0.5     ,  2.5     ]]), {'n_threads': 2, 'use_c': True})
dw_file = 'Si2-sc-skew_666_300K_debye_waller.json'
expected_sf_file = 'Si2-sc-skew_300K_fc_structure_factor.json', n_threads = 2

    @pytest.mark.parametrize(
        'material, fc, qpt_ph_modes_args, dw_file, expected_sf_file', [
            ('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
             'quartz_666_0K_debye_waller.json',
             'quartz_0K_fc_structure_factor.json'),
            ('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
             'quartz_666_300K_debye_waller.json',
             'quartz_300K_fc_structure_factor.json'),
            ('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
             None,
             'quartz_fc_structure_factor.json'),
            ('Si2-sc-skew', get_si2_fc(), (get_test_qpts(), {}),
             'Si2-sc-skew_666_300K_debye_waller.json',
             'Si2-sc-skew_300K_fc_structure_factor.json'),
            ('quartz', get_quartz_fc(),
             (get_test_qpts('split'), {'asr': 'reciprocal'}),
             'quartz_666_300K_debye_waller.json',
             'quartz_recip_asr_300K_fc_structure_factor.json')])
    @pytest.mark.parametrize('n_threads', [0, 1, 2])
    def test_calculate_structure_factor(self, material, fc, qpt_ph_modes_args,
                                        dw_file, expected_sf_file, n_threads):
        args, kwargs = qpt_ph_modes_args
        if n_threads == 0:
            kwargs['use_c'] = False
        else:
            kwargs['use_c'] = True
            kwargs['n_threads'] = n_threads
>       qpt_ph_modes = fc.calculate_qpoint_phonon_modes(args, **kwargs)

euphonic_test/test_calculate_structure_factor.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../euphonic/force_constants.py:387: in calculate_qpoint_phonon_modes
    qpts, freqs, weights, evecs, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8cd70>
qpts = array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0.5...[ 0.6     ,  0.      ,  0.2     ],
       [ 2.      ,  2.      ,  0.5     ],
       [ 1.75    ,  0.5     ,  2.5     ]])
weights = None, asr = None, dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = True

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
                import euphonic._euphonic as euphonic_c
                use_c_status = True
            except ImportError:
                if use_c is None:
                    warnings.warn((
                        cext_err_msg
                        + ' Falling back to pure Python calculation.'),
                        stacklevel=3)
                else:
>                   raise ImportCError(cext_err_msg)
E                   euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.

../../euphonic/force_constants.py:647: ImportCError

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

1 out of 6 runs failed: test_calculate_structure_factor[2-quartz-fc4-qpt_ph_modes_args4-quartz_666_300K_debye_waller.json-quart…r_300K_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)

artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d5b0>
qpts = array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0. ...[-1.      ,  1.      ,  1.      ],
       [-0.151515,  0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = True

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
>               import euphonic._euphonic as euphonic_c
E               ModuleNotFoundError: No module named 'euphonic._euphonic'

../../euphonic/force_constants.py:638: ModuleNotFoundError

During handling of the above exception, another exception occurred:

self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7fbb11f8f6e0>
material = 'quartz'
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d5b0>
qpt_ph_modes_args = (array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0....0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]]), {'asr': 'reciprocal', 'n_threads': 2, 'use_c': True})
dw_file = 'quartz_666_300K_debye_waller.json'
expected_sf_file = 'quartz_recip_asr_300K_fc_structure_factor.json'
n_threads = 2

    @pytest.mark.parametrize(
        'material, fc, qpt_ph_modes_args, dw_file, expected_sf_file', [
            ('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
             'quartz_666_0K_debye_waller.json',
             'quartz_0K_fc_structure_factor.json'),
            ('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
             'quartz_666_300K_debye_waller.json',
             'quartz_300K_fc_structure_factor.json'),
            ('quartz', get_quartz_fc(), (get_test_qpts('split'), {}),
             None,
             'quartz_fc_structure_factor.json'),
            ('Si2-sc-skew', get_si2_fc(), (get_test_qpts(), {}),
             'Si2-sc-skew_666_300K_debye_waller.json',
             'Si2-sc-skew_300K_fc_structure_factor.json'),
            ('quartz', get_quartz_fc(),
             (get_test_qpts('split'), {'asr': 'reciprocal'}),
             'quartz_666_300K_debye_waller.json',
             'quartz_recip_asr_300K_fc_structure_factor.json')])
    @pytest.mark.parametrize('n_threads', [0, 1, 2])
    def test_calculate_structure_factor(self, material, fc, qpt_ph_modes_args,
                                        dw_file, expected_sf_file, n_threads):
        args, kwargs = qpt_ph_modes_args
        if n_threads == 0:
            kwargs['use_c'] = False
        else:
            kwargs['use_c'] = True
            kwargs['n_threads'] = n_threads
>       qpt_ph_modes = fc.calculate_qpoint_phonon_modes(args, **kwargs)

euphonic_test/test_calculate_structure_factor.py:58: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../euphonic/force_constants.py:387: in calculate_qpoint_phonon_modes
    qpts, freqs, weights, evecs, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d5b0>
qpts = array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0. ...[-1.      ,  1.      ,  1.      ],
       [-0.151515,  0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = True

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
                import euphonic._euphonic as euphonic_c
                use_c_status = True
            except ImportError:
                if use_c is None:
                    warnings.warn((
                        cext_err_msg
                        + ' Falling back to pure Python calculation.'),
                        stacklevel=3)
                else:
>                   raise ImportCError(cext_err_msg)
E                   euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.

../../euphonic/force_constants.py:647: ImportCError

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

1 out of 7 runs failed: test_calculate_structure_factor_from_phonopy[1-CaHgO2-fc_kwargs0-qpt_ph_modes_args0-CaHgO2_666_300K_deb…2_300K_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)

artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb1172bda0>
qpts = array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0.5...[ 0.6     ,  0.      ,  0.2     ],
       [ 2.      ,  2.      ,  0.5     ],
       [ 1.75    ,  0.5     ,  2.5     ]])
weights = None, asr = None, dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = True

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
>               import euphonic._euphonic as euphonic_c
E               ModuleNotFoundError: No module named 'euphonic._euphonic'

../../euphonic/force_constants.py:638: ModuleNotFoundError

During handling of the above exception, another exception occurred:

self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7fbb11f8fce0>
material = 'CaHgO2'
fc_kwargs = {'path': '/home/runner/work/Euphonic/Euphonic/tests_and_analysis/test/data/phonopy_files/CaHgO2', 'summary_name': 'mp-7041-20180417.yaml'}
qpt_ph_modes_args = (array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0....
       [ 2.      ,  2.      ,  0.5     ],
       [ 1.75    ,  0.5     ,  2.5     ]]), {'n_threads': 1, 'use_c': True})
dw_file = 'CaHgO2_666_300K_debye_waller.json'
expected_sf_file = 'CaHgO2_300K_fc_structure_factor.json', n_threads = 1

    @pytest.mark.phonopy_reader
    @pytest.mark.parametrize(
        'material, fc_kwargs, qpt_ph_modes_args, dw_file, expected_sf_file', [
            ('CaHgO2', {'path': get_phonopy_path('CaHgO2'),
                        'summary_name': 'mp-7041-20180417.yaml'},
             (get_test_qpts(), {}),
             'CaHgO2_666_300K_debye_waller.json',
             'CaHgO2_300K_fc_structure_factor.json')])
    @pytest.mark.parametrize('n_threads', [0, 1, 2])
    def test_calculate_structure_factor_from_phonopy(
            self, material, fc_kwargs, qpt_ph_modes_args, dw_file,
            expected_sf_file, n_threads):
        fc = ForceConstants.from_phonopy(**fc_kwargs)
        args, kwargs = qpt_ph_modes_args
        if n_threads == 0:
            kwargs['use_c'] = False
        else:
            kwargs['use_c'] = True
            kwargs['n_threads'] = n_threads
>       qpt_ph_modes = fc.calculate_qpoint_phonon_modes(args, **kwargs)

euphonic_test/test_calculate_structure_factor.py:99: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../euphonic/force_constants.py:387: in calculate_qpoint_phonon_modes
    qpts, freqs, weights, evecs, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <euphonic.force_constants.ForceConstants object at 0x7fbb1172bda0>
qpts = array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0.5...[ 0.6     ,  0.      ,  0.2     ],
       [ 2.      ,  2.      ,  0.5     ],
       [ 1.75    ,  0.5     ,  2.5     ]])
weights = None, asr = None, dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = True

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
                import euphonic._euphonic as euphonic_c
                use_c_status = True
            except ImportError:
                if use_c is None:
                    warnings.warn((
                        cext_err_msg
                        + ' Falling back to pure Python calculation.'),
                        stacklevel=3)
                else:
>                   raise ImportCError(cext_err_msg)
E                   euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.

../../euphonic/force_constants.py:647: ImportCError

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

1 out of 7 runs failed: test_calculate_structure_factor_from_phonopy[2-CaHgO2-fc_kwargs0-qpt_ph_modes_args0-CaHgO2_666_300K_deb…2_300K_fc_structure_factor.json] (euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants)

artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb1172ae40>
qpts = array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0.5...[ 0.6     ,  0.      ,  0.2     ],
       [ 2.      ,  2.      ,  0.5     ],
       [ 1.75    ,  0.5     ,  2.5     ]])
weights = None, asr = None, dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = True

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
>               import euphonic._euphonic as euphonic_c
E               ModuleNotFoundError: No module named 'euphonic._euphonic'

../../euphonic/force_constants.py:638: ModuleNotFoundError

During handling of the above exception, another exception occurred:

self = <tests_and_analysis.test.euphonic_test.test_calculate_structure_factor.TestCalculateStructureFactorFromForceConstants object at 0x7fbb11f8fd70>
material = 'CaHgO2'
fc_kwargs = {'path': '/home/runner/work/Euphonic/Euphonic/tests_and_analysis/test/data/phonopy_files/CaHgO2', 'summary_name': 'mp-7041-20180417.yaml'}
qpt_ph_modes_args = (array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0....
       [ 2.      ,  2.      ,  0.5     ],
       [ 1.75    ,  0.5     ,  2.5     ]]), {'n_threads': 2, 'use_c': True})
dw_file = 'CaHgO2_666_300K_debye_waller.json'
expected_sf_file = 'CaHgO2_300K_fc_structure_factor.json', n_threads = 2

    @pytest.mark.phonopy_reader
    @pytest.mark.parametrize(
        'material, fc_kwargs, qpt_ph_modes_args, dw_file, expected_sf_file', [
            ('CaHgO2', {'path': get_phonopy_path('CaHgO2'),
                        'summary_name': 'mp-7041-20180417.yaml'},
             (get_test_qpts(), {}),
             'CaHgO2_666_300K_debye_waller.json',
             'CaHgO2_300K_fc_structure_factor.json')])
    @pytest.mark.parametrize('n_threads', [0, 1, 2])
    def test_calculate_structure_factor_from_phonopy(
            self, material, fc_kwargs, qpt_ph_modes_args, dw_file,
            expected_sf_file, n_threads):
        fc = ForceConstants.from_phonopy(**fc_kwargs)
        args, kwargs = qpt_ph_modes_args
        if n_threads == 0:
            kwargs['use_c'] = False
        else:
            kwargs['use_c'] = True
            kwargs['n_threads'] = n_threads
>       qpt_ph_modes = fc.calculate_qpoint_phonon_modes(args, **kwargs)

euphonic_test/test_calculate_structure_factor.py:99: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../euphonic/force_constants.py:387: in calculate_qpoint_phonon_modes
    qpts, freqs, weights, evecs, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <euphonic.force_constants.ForceConstants object at 0x7fbb1172ae40>
qpts = array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0.5...[ 0.6     ,  0.      ,  0.2     ],
       [ 2.      ,  2.      ,  0.5     ],
       [ 1.75    ,  0.5     ,  2.5     ]])
weights = None, asr = None, dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = True

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
                import euphonic._euphonic as euphonic_c
                use_c_status = True
            except ImportError:
                if use_c is None:
                    warnings.warn((
                        cext_err_msg
                        + ' Falling back to pure Python calculation.'),
                        stacklevel=3)
                else:
>                   raise ImportCError(cext_err_msg)
E                   euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.

../../euphonic/force_constants.py:647: ImportCError

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

1 out of 6 runs failed: test_calculate_qpoint_frequencies[True-1-fc0-LZO-all_args0-LZO_no_asr_qpoint_frequencies.json] (euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies)

artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d4c0>
qpts = array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0.5...[ 0.6     ,  0.      ,  0.2     ],
       [ 2.      ,  2.      ,  0.5     ],
       [ 1.75    ,  0.5     ,  2.5     ]])
weights = None, asr = None, dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = False

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
>               import euphonic._euphonic as euphonic_c
E               ModuleNotFoundError: No module named 'euphonic._euphonic'

../../euphonic/force_constants.py:638: ModuleNotFoundError

During handling of the above exception, another exception occurred:

self = <tests_and_analysis.test.euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies object at 0x7fbb11d13560>
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d4c0>
material = 'LZO'
all_args = [array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0....2.      ,  0.5     ],
       [ 1.75    ,  0.5     ,  2.5     ]]), {'n_threads': 1, 'reduce_qpts': True, 'use_c': True}]
expected_qpoint_frequencies_file = 'LZO_no_asr_qpoint_frequencies.json'
reduce_qpts = True, n_threads = 1

    @pytest.mark.parametrize(
        'fc, material, all_args, expected_qpoint_frequencies_file',
        lzo_params + quartz_params)
    @pytest.mark.parametrize(
        'reduce_qpts, n_threads',
        [(False, 0), (True, 0), (True, 1), (True, 2)])
    def test_calculate_qpoint_frequencies(
            self, fc, material, all_args, expected_qpoint_frequencies_file,
            reduce_qpts, n_threads):
        func_kwargs = all_args[1]
        func_kwargs['reduce_qpts'] = reduce_qpts
        if n_threads == 0:
            func_kwargs['use_c'] = False
        else:
            func_kwargs['use_c'] = True
            func_kwargs['n_threads'] = n_threads
>       qpt_freqs = fc.calculate_qpoint_frequencies(all_args[0], **func_kwargs)

euphonic_test/test_force_constants_calculate_qpoint_frequencies.py:67: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../euphonic/force_constants.py:418: in calculate_qpoint_frequencies
    qpts, freqs, weights, _, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d4c0>
qpts = array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0.5...[ 0.6     ,  0.      ,  0.2     ],
       [ 2.      ,  2.      ,  0.5     ],
       [ 1.75    ,  0.5     ,  2.5     ]])
weights = None, asr = None, dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = False

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
                import euphonic._euphonic as euphonic_c
                use_c_status = True
            except ImportError:
                if use_c is None:
                    warnings.warn((
                        cext_err_msg
                        + ' Falling back to pure Python calculation.'),
                        stacklevel=3)
                else:
>                   raise ImportCError(cext_err_msg)
E                   euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.

../../euphonic/force_constants.py:647: ImportCError

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

1 out of 6 runs failed: test_calculate_qpoint_frequencies[True-1-fc1-LZO-all_args1-LZO_realspace_qpoint_frequencies.json] (euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies)

artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11ef7fb0>
qpts = array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0.5...[ 0.6     ,  0.      ,  0.2     ],
       [ 2.      ,  2.      ,  0.5     ],
       [ 1.75    ,  0.5     ,  2.5     ]])
weights = None, asr = 'realspace', dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = False

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
>               import euphonic._euphonic as euphonic_c
E               ModuleNotFoundError: No module named 'euphonic._euphonic'

../../euphonic/force_constants.py:638: ModuleNotFoundError

During handling of the above exception, another exception occurred:

self = <tests_and_analysis.test.euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies object at 0x7fbb11d135f0>
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11ef7fb0>
material = 'LZO'
all_args = [array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0....,
       [ 1.75    ,  0.5     ,  2.5     ]]), {'asr': 'realspace', 'n_threads': 1, 'reduce_qpts': True, 'use_c': True}]
expected_qpoint_frequencies_file = 'LZO_realspace_qpoint_frequencies.json'
reduce_qpts = True, n_threads = 1

    @pytest.mark.parametrize(
        'fc, material, all_args, expected_qpoint_frequencies_file',
        lzo_params + quartz_params)
    @pytest.mark.parametrize(
        'reduce_qpts, n_threads',
        [(False, 0), (True, 0), (True, 1), (True, 2)])
    def test_calculate_qpoint_frequencies(
            self, fc, material, all_args, expected_qpoint_frequencies_file,
            reduce_qpts, n_threads):
        func_kwargs = all_args[1]
        func_kwargs['reduce_qpts'] = reduce_qpts
        if n_threads == 0:
            func_kwargs['use_c'] = False
        else:
            func_kwargs['use_c'] = True
            func_kwargs['n_threads'] = n_threads
>       qpt_freqs = fc.calculate_qpoint_frequencies(all_args[0], **func_kwargs)

euphonic_test/test_force_constants_calculate_qpoint_frequencies.py:67: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../euphonic/force_constants.py:418: in calculate_qpoint_frequencies
    qpts, freqs, weights, _, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <euphonic.force_constants.ForceConstants object at 0x7fbb11ef7fb0>
qpts = array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0.5...[ 0.6     ,  0.      ,  0.2     ],
       [ 2.      ,  2.      ,  0.5     ],
       [ 1.75    ,  0.5     ,  2.5     ]])
weights = None, asr = 'realspace', dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = False

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
                import euphonic._euphonic as euphonic_c
                use_c_status = True
            except ImportError:
                if use_c is None:
                    warnings.warn((
                        cext_err_msg
                        + ' Falling back to pure Python calculation.'),
                        stacklevel=3)
                else:
>                   raise ImportCError(cext_err_msg)
E                   euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.

../../euphonic/force_constants.py:647: ImportCError

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

1 out of 6 runs failed: test_calculate_qpoint_frequencies[True-1-fc2-quartz-all_args2-quartz_reciprocal_qpoint_frequencies.json] (euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies)

artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d520>
qpts = array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0.5...[ 0.6     ,  0.      ,  0.2     ],
       [ 2.      ,  2.      ,  0.5     ],
       [ 1.75    ,  0.5     ,  2.5     ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = False

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
>               import euphonic._euphonic as euphonic_c
E               ModuleNotFoundError: No module named 'euphonic._euphonic'

../../euphonic/force_constants.py:638: ModuleNotFoundError

During handling of the above exception, another exception occurred:

self = <tests_and_analysis.test.euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies object at 0x7fbb11d13680>
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d520>
material = 'quartz'
all_args = [array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0....1.75    ,  0.5     ,  2.5     ]]), {'asr': 'reciprocal', 'n_threads': 1, 'reduce_qpts': True, 'splitting': False, ...}]
expected_qpoint_frequencies_file = 'quartz_reciprocal_qpoint_frequencies.json'
reduce_qpts = True, n_threads = 1

    @pytest.mark.parametrize(
        'fc, material, all_args, expected_qpoint_frequencies_file',
        lzo_params + quartz_params)
    @pytest.mark.parametrize(
        'reduce_qpts, n_threads',
        [(False, 0), (True, 0), (True, 1), (True, 2)])
    def test_calculate_qpoint_frequencies(
            self, fc, material, all_args, expected_qpoint_frequencies_file,
            reduce_qpts, n_threads):
        func_kwargs = all_args[1]
        func_kwargs['reduce_qpts'] = reduce_qpts
        if n_threads == 0:
            func_kwargs['use_c'] = False
        else:
            func_kwargs['use_c'] = True
            func_kwargs['n_threads'] = n_threads
>       qpt_freqs = fc.calculate_qpoint_frequencies(all_args[0], **func_kwargs)

euphonic_test/test_force_constants_calculate_qpoint_frequencies.py:67: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../euphonic/force_constants.py:418: in calculate_qpoint_frequencies
    qpts, freqs, weights, _, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d520>
qpts = array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0.5...[ 0.6     ,  0.      ,  0.2     ],
       [ 2.      ,  2.      ,  0.5     ],
       [ 1.75    ,  0.5     ,  2.5     ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = False

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
                import euphonic._euphonic as euphonic_c
                use_c_status = True
            except ImportError:
                if use_c is None:
                    warnings.warn((
                        cext_err_msg
                        + ' Falling back to pure Python calculation.'),
                        stacklevel=3)
                else:
>                   raise ImportCError(cext_err_msg)
E                   euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.

../../euphonic/force_constants.py:647: ImportCError

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

1 out of 6 runs failed: test_calculate_qpoint_frequencies[True-1-fc3-quartz-all_args3-quartz_reciprocal_qpoint_frequencies.json] (euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies)

artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11d0bec0>
qpts = array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0.5...[ 0.6     ,  0.      ,  0.2     ],
       [ 2.      ,  2.      ,  0.5     ],
       [ 1.75    ,  0.5     ,  2.5     ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 0.75
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = False

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
>               import euphonic._euphonic as euphonic_c
E               ModuleNotFoundError: No module named 'euphonic._euphonic'

../../euphonic/force_constants.py:638: ModuleNotFoundError

During handling of the above exception, another exception occurred:

self = <tests_and_analysis.test.euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies object at 0x7fbb11d13710>
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11d0bec0>
material = 'quartz'
all_args = [array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0....  ,  0.5     ,  2.5     ]]), {'asr': 'reciprocal', 'dipole_parameter': 0.75, 'n_threads': 1, 'reduce_qpts': True, ...}]
expected_qpoint_frequencies_file = 'quartz_reciprocal_qpoint_frequencies.json'
reduce_qpts = True, n_threads = 1

    @pytest.mark.parametrize(
        'fc, material, all_args, expected_qpoint_frequencies_file',
        lzo_params + quartz_params)
    @pytest.mark.parametrize(
        'reduce_qpts, n_threads',
        [(False, 0), (True, 0), (True, 1), (True, 2)])
    def test_calculate_qpoint_frequencies(
            self, fc, material, all_args, expected_qpoint_frequencies_file,
            reduce_qpts, n_threads):
        func_kwargs = all_args[1]
        func_kwargs['reduce_qpts'] = reduce_qpts
        if n_threads == 0:
            func_kwargs['use_c'] = False
        else:
            func_kwargs['use_c'] = True
            func_kwargs['n_threads'] = n_threads
>       qpt_freqs = fc.calculate_qpoint_frequencies(all_args[0], **func_kwargs)

euphonic_test/test_force_constants_calculate_qpoint_frequencies.py:67: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../euphonic/force_constants.py:418: in calculate_qpoint_frequencies
    qpts, freqs, weights, _, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <euphonic.force_constants.ForceConstants object at 0x7fbb11d0bec0>
qpts = array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0.5...[ 0.6     ,  0.      ,  0.2     ],
       [ 2.      ,  2.      ,  0.5     ],
       [ 1.75    ,  0.5     ,  2.5     ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 0.75
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = False

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
                import euphonic._euphonic as euphonic_c
                use_c_status = True
            except ImportError:
                if use_c is None:
                    warnings.warn((
                        cext_err_msg
                        + ' Falling back to pure Python calculation.'),
                        stacklevel=3)
                else:
>                   raise ImportCError(cext_err_msg)
E                   euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.

../../euphonic/force_constants.py:647: ImportCError

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

1 out of 6 runs failed: test_calculate_qpoint_frequencies[True-1-fc4-quartz-all_args4-quartz_split_reciprocal_qpoint_frequencies.json] (euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies)

artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11d115e0>
qpts = array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0. ...[-1.      ,  1.      ,  1.      ],
       [-0.151515,  0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = False

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
>               import euphonic._euphonic as euphonic_c
E               ModuleNotFoundError: No module named 'euphonic._euphonic'

../../euphonic/force_constants.py:638: ModuleNotFoundError

During handling of the above exception, another exception occurred:

self = <tests_and_analysis.test.euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies object at 0x7fbb11d137a0>
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11d115e0>
material = 'quartz'
all_args = [array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0....     ,  1.      ,  1.      ]]), {'asr': 'reciprocal', 'insert_gamma': False, 'n_threads': 1, 'reduce_qpts': True, ...}]
expected_qpoint_frequencies_file = 'quartz_split_reciprocal_qpoint_frequencies.json'
reduce_qpts = True, n_threads = 1

    @pytest.mark.parametrize(
        'fc, material, all_args, expected_qpoint_frequencies_file',
        lzo_params + quartz_params)
    @pytest.mark.parametrize(
        'reduce_qpts, n_threads',
        [(False, 0), (True, 0), (True, 1), (True, 2)])
    def test_calculate_qpoint_frequencies(
            self, fc, material, all_args, expected_qpoint_frequencies_file,
            reduce_qpts, n_threads):
        func_kwargs = all_args[1]
        func_kwargs['reduce_qpts'] = reduce_qpts
        if n_threads == 0:
            func_kwargs['use_c'] = False
        else:
            func_kwargs['use_c'] = True
            func_kwargs['n_threads'] = n_threads
>       qpt_freqs = fc.calculate_qpoint_frequencies(all_args[0], **func_kwargs)

euphonic_test/test_force_constants_calculate_qpoint_frequencies.py:67: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../euphonic/force_constants.py:418: in calculate_qpoint_frequencies
    qpts, freqs, weights, _, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <euphonic.force_constants.ForceConstants object at 0x7fbb11d115e0>
qpts = array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0. ...[-1.      ,  1.      ,  1.      ],
       [-0.151515,  0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = False

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
                import euphonic._euphonic as euphonic_c
                use_c_status = True
            except ImportError:
                if use_c is None:
                    warnings.warn((
                        cext_err_msg
                        + ' Falling back to pure Python calculation.'),
                        stacklevel=3)
                else:
>                   raise ImportCError(cext_err_msg)
E                   euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.

../../euphonic/force_constants.py:647: ImportCError

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

1 out of 6 runs failed: test_calculate_qpoint_frequencies[True-1-fc5-quartz-all_args5-quartz_split_reciprocal_qpoint_frequencies.json] (euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies)

artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11d11d90>
qpts = array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0. ...[-1.      ,  1.      ,  1.      ],
       [-0.151515,  0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = True, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = False

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
>               import euphonic._euphonic as euphonic_c
E               ModuleNotFoundError: No module named 'euphonic._euphonic'

../../euphonic/force_constants.py:638: ModuleNotFoundError

During handling of the above exception, another exception occurred:

self = <tests_and_analysis.test.euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies object at 0x7fbb11d13830>
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11d11d90>
material = 'quartz'
all_args = [array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0....      ,  1.      ,  1.      ]]), {'asr': 'reciprocal', 'insert_gamma': True, 'n_threads': 1, 'reduce_qpts': True, ...}]
expected_qpoint_frequencies_file = 'quartz_split_reciprocal_qpoint_frequencies.json'
reduce_qpts = True, n_threads = 1

    @pytest.mark.parametrize(
        'fc, material, all_args, expected_qpoint_frequencies_file',
        lzo_params + quartz_params)
    @pytest.mark.parametrize(
        'reduce_qpts, n_threads',
        [(False, 0), (True, 0), (True, 1), (True, 2)])
    def test_calculate_qpoint_frequencies(
            self, fc, material, all_args, expected_qpoint_frequencies_file,
            reduce_qpts, n_threads):
        func_kwargs = all_args[1]
        func_kwargs['reduce_qpts'] = reduce_qpts
        if n_threads == 0:
            func_kwargs['use_c'] = False
        else:
            func_kwargs['use_c'] = True
            func_kwargs['n_threads'] = n_threads
>       qpt_freqs = fc.calculate_qpoint_frequencies(all_args[0], **func_kwargs)

euphonic_test/test_force_constants_calculate_qpoint_frequencies.py:67: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../euphonic/force_constants.py:418: in calculate_qpoint_frequencies
    qpts, freqs, weights, _, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <euphonic.force_constants.ForceConstants object at 0x7fbb11d11d90>
qpts = array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0. ...[-1.      ,  1.      ,  1.      ],
       [-0.151515,  0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = True, reduce_qpts = True, use_c = True
n_threads = 1, return_mode_gradients = False, return_eigenvectors = False

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
                import euphonic._euphonic as euphonic_c
                use_c_status = True
            except ImportError:
                if use_c is None:
                    warnings.warn((
                        cext_err_msg
                        + ' Falling back to pure Python calculation.'),
                        stacklevel=3)
                else:
>                   raise ImportCError(cext_err_msg)
E                   euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.

../../euphonic/force_constants.py:647: ImportCError

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

1 out of 6 runs failed: test_calculate_qpoint_frequencies[True-2-fc0-LZO-all_args0-LZO_no_asr_qpoint_frequencies.json] (euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies)

artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d4c0>
qpts = array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0.5...[ 0.6     ,  0.      ,  0.2     ],
       [ 2.      ,  2.      ,  0.5     ],
       [ 1.75    ,  0.5     ,  2.5     ]])
weights = None, asr = None, dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = False

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
>               import euphonic._euphonic as euphonic_c
E               ModuleNotFoundError: No module named 'euphonic._euphonic'

../../euphonic/force_constants.py:638: ModuleNotFoundError

During handling of the above exception, another exception occurred:

self = <tests_and_analysis.test.euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies object at 0x7fbb11d138c0>
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d4c0>
material = 'LZO'
all_args = [array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0....2.      ,  0.5     ],
       [ 1.75    ,  0.5     ,  2.5     ]]), {'n_threads': 2, 'reduce_qpts': True, 'use_c': True}]
expected_qpoint_frequencies_file = 'LZO_no_asr_qpoint_frequencies.json'
reduce_qpts = True, n_threads = 2

    @pytest.mark.parametrize(
        'fc, material, all_args, expected_qpoint_frequencies_file',
        lzo_params + quartz_params)
    @pytest.mark.parametrize(
        'reduce_qpts, n_threads',
        [(False, 0), (True, 0), (True, 1), (True, 2)])
    def test_calculate_qpoint_frequencies(
            self, fc, material, all_args, expected_qpoint_frequencies_file,
            reduce_qpts, n_threads):
        func_kwargs = all_args[1]
        func_kwargs['reduce_qpts'] = reduce_qpts
        if n_threads == 0:
            func_kwargs['use_c'] = False
        else:
            func_kwargs['use_c'] = True
            func_kwargs['n_threads'] = n_threads
>       qpt_freqs = fc.calculate_qpoint_frequencies(all_args[0], **func_kwargs)

euphonic_test/test_force_constants_calculate_qpoint_frequencies.py:67: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../euphonic/force_constants.py:418: in calculate_qpoint_frequencies
    qpts, freqs, weights, _, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d4c0>
qpts = array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0.5...[ 0.6     ,  0.      ,  0.2     ],
       [ 2.      ,  2.      ,  0.5     ],
       [ 1.75    ,  0.5     ,  2.5     ]])
weights = None, asr = None, dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = False

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
                import euphonic._euphonic as euphonic_c
                use_c_status = True
            except ImportError:
                if use_c is None:
                    warnings.warn((
                        cext_err_msg
                        + ' Falling back to pure Python calculation.'),
                        stacklevel=3)
                else:
>                   raise ImportCError(cext_err_msg)
E                   euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.

../../euphonic/force_constants.py:647: ImportCError

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

1 out of 6 runs failed: test_calculate_qpoint_frequencies[True-2-fc1-LZO-all_args1-LZO_realspace_qpoint_frequencies.json] (euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies)

artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11ef7fb0>
qpts = array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0.5...[ 0.6     ,  0.      ,  0.2     ],
       [ 2.      ,  2.      ,  0.5     ],
       [ 1.75    ,  0.5     ,  2.5     ]])
weights = None, asr = 'realspace', dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = False

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
>               import euphonic._euphonic as euphonic_c
E               ModuleNotFoundError: No module named 'euphonic._euphonic'

../../euphonic/force_constants.py:638: ModuleNotFoundError

During handling of the above exception, another exception occurred:

self = <tests_and_analysis.test.euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies object at 0x7fbb11d13950>
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11ef7fb0>
material = 'LZO'
all_args = [array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0....,
       [ 1.75    ,  0.5     ,  2.5     ]]), {'asr': 'realspace', 'n_threads': 2, 'reduce_qpts': True, 'use_c': True}]
expected_qpoint_frequencies_file = 'LZO_realspace_qpoint_frequencies.json'
reduce_qpts = True, n_threads = 2

    @pytest.mark.parametrize(
        'fc, material, all_args, expected_qpoint_frequencies_file',
        lzo_params + quartz_params)
    @pytest.mark.parametrize(
        'reduce_qpts, n_threads',
        [(False, 0), (True, 0), (True, 1), (True, 2)])
    def test_calculate_qpoint_frequencies(
            self, fc, material, all_args, expected_qpoint_frequencies_file,
            reduce_qpts, n_threads):
        func_kwargs = all_args[1]
        func_kwargs['reduce_qpts'] = reduce_qpts
        if n_threads == 0:
            func_kwargs['use_c'] = False
        else:
            func_kwargs['use_c'] = True
            func_kwargs['n_threads'] = n_threads
>       qpt_freqs = fc.calculate_qpoint_frequencies(all_args[0], **func_kwargs)

euphonic_test/test_force_constants_calculate_qpoint_frequencies.py:67: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../euphonic/force_constants.py:418: in calculate_qpoint_frequencies
    qpts, freqs, weights, _, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <euphonic.force_constants.ForceConstants object at 0x7fbb11ef7fb0>
qpts = array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0.5...[ 0.6     ,  0.      ,  0.2     ],
       [ 2.      ,  2.      ,  0.5     ],
       [ 1.75    ,  0.5     ,  2.5     ]])
weights = None, asr = 'realspace', dipole = False, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = False

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
                import euphonic._euphonic as euphonic_c
                use_c_status = True
            except ImportError:
                if use_c is None:
                    warnings.warn((
                        cext_err_msg
                        + ' Falling back to pure Python calculation.'),
                        stacklevel=3)
                else:
>                   raise ImportCError(cext_err_msg)
E                   euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.

../../euphonic/force_constants.py:647: ImportCError

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

1 out of 6 runs failed: test_calculate_qpoint_frequencies[True-2-fc2-quartz-all_args2-quartz_reciprocal_qpoint_frequencies.json] (euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies)

artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d520>
qpts = array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0.5...[ 0.6     ,  0.      ,  0.2     ],
       [ 2.      ,  2.      ,  0.5     ],
       [ 1.75    ,  0.5     ,  2.5     ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = False

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
>               import euphonic._euphonic as euphonic_c
E               ModuleNotFoundError: No module named 'euphonic._euphonic'

../../euphonic/force_constants.py:638: ModuleNotFoundError

During handling of the above exception, another exception occurred:

self = <tests_and_analysis.test.euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies object at 0x7fbb11d139e0>
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d520>
material = 'quartz'
all_args = [array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0....1.75    ,  0.5     ,  2.5     ]]), {'asr': 'reciprocal', 'n_threads': 2, 'reduce_qpts': True, 'splitting': False, ...}]
expected_qpoint_frequencies_file = 'quartz_reciprocal_qpoint_frequencies.json'
reduce_qpts = True, n_threads = 2

    @pytest.mark.parametrize(
        'fc, material, all_args, expected_qpoint_frequencies_file',
        lzo_params + quartz_params)
    @pytest.mark.parametrize(
        'reduce_qpts, n_threads',
        [(False, 0), (True, 0), (True, 1), (True, 2)])
    def test_calculate_qpoint_frequencies(
            self, fc, material, all_args, expected_qpoint_frequencies_file,
            reduce_qpts, n_threads):
        func_kwargs = all_args[1]
        func_kwargs['reduce_qpts'] = reduce_qpts
        if n_threads == 0:
            func_kwargs['use_c'] = False
        else:
            func_kwargs['use_c'] = True
            func_kwargs['n_threads'] = n_threads
>       qpt_freqs = fc.calculate_qpoint_frequencies(all_args[0], **func_kwargs)

euphonic_test/test_force_constants_calculate_qpoint_frequencies.py:67: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../euphonic/force_constants.py:418: in calculate_qpoint_frequencies
    qpts, freqs, weights, _, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <euphonic.force_constants.ForceConstants object at 0x7fbb11f8d520>
qpts = array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0.5...[ 0.6     ,  0.      ,  0.2     ],
       [ 2.      ,  2.      ,  0.5     ],
       [ 1.75    ,  0.5     ,  2.5     ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = False

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
                import euphonic._euphonic as euphonic_c
                use_c_status = True
            except ImportError:
                if use_c is None:
                    warnings.warn((
                        cext_err_msg
                        + ' Falling back to pure Python calculation.'),
                        stacklevel=3)
                else:
>                   raise ImportCError(cext_err_msg)
E                   euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.

../../euphonic/force_constants.py:647: ImportCError

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

1 out of 6 runs failed: test_calculate_qpoint_frequencies[True-2-fc3-quartz-all_args3-quartz_reciprocal_qpoint_frequencies.json] (euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies)

artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11d0bec0>
qpts = array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0.5...[ 0.6     ,  0.      ,  0.2     ],
       [ 2.      ,  2.      ,  0.5     ],
       [ 1.75    ,  0.5     ,  2.5     ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 0.75
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = False

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
>               import euphonic._euphonic as euphonic_c
E               ModuleNotFoundError: No module named 'euphonic._euphonic'

../../euphonic/force_constants.py:638: ModuleNotFoundError

During handling of the above exception, another exception occurred:

self = <tests_and_analysis.test.euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies object at 0x7fbb11d13a70>
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11d0bec0>
material = 'quartz'
all_args = [array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0....  ,  0.5     ,  2.5     ]]), {'asr': 'reciprocal', 'dipole_parameter': 0.75, 'n_threads': 2, 'reduce_qpts': True, ...}]
expected_qpoint_frequencies_file = 'quartz_reciprocal_qpoint_frequencies.json'
reduce_qpts = True, n_threads = 2

    @pytest.mark.parametrize(
        'fc, material, all_args, expected_qpoint_frequencies_file',
        lzo_params + quartz_params)
    @pytest.mark.parametrize(
        'reduce_qpts, n_threads',
        [(False, 0), (True, 0), (True, 1), (True, 2)])
    def test_calculate_qpoint_frequencies(
            self, fc, material, all_args, expected_qpoint_frequencies_file,
            reduce_qpts, n_threads):
        func_kwargs = all_args[1]
        func_kwargs['reduce_qpts'] = reduce_qpts
        if n_threads == 0:
            func_kwargs['use_c'] = False
        else:
            func_kwargs['use_c'] = True
            func_kwargs['n_threads'] = n_threads
>       qpt_freqs = fc.calculate_qpoint_frequencies(all_args[0], **func_kwargs)

euphonic_test/test_force_constants_calculate_qpoint_frequencies.py:67: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../euphonic/force_constants.py:418: in calculate_qpoint_frequencies
    qpts, freqs, weights, _, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <euphonic.force_constants.ForceConstants object at 0x7fbb11d0bec0>
qpts = array([[ 0.      ,  0.      ,  0.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [-0.25    ,  0.5     ,  0.5...[ 0.6     ,  0.      ,  0.2     ],
       [ 2.      ,  2.      ,  0.5     ],
       [ 1.75    ,  0.5     ,  2.5     ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 0.75
splitting = False, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = False

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
                import euphonic._euphonic as euphonic_c
                use_c_status = True
            except ImportError:
                if use_c is None:
                    warnings.warn((
                        cext_err_msg
                        + ' Falling back to pure Python calculation.'),
                        stacklevel=3)
                else:
>                   raise ImportCError(cext_err_msg)
E                   euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.

../../euphonic/force_constants.py:647: ImportCError

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

1 out of 6 runs failed: test_calculate_qpoint_frequencies[True-2-fc4-quartz-all_args4-quartz_split_reciprocal_qpoint_frequencies.json] (euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies)

artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11d115e0>
qpts = array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0. ...[-1.      ,  1.      ,  1.      ],
       [-0.151515,  0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = False

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
>               import euphonic._euphonic as euphonic_c
E               ModuleNotFoundError: No module named 'euphonic._euphonic'

../../euphonic/force_constants.py:638: ModuleNotFoundError

During handling of the above exception, another exception occurred:

self = <tests_and_analysis.test.euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies object at 0x7fbb11d13b00>
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11d115e0>
material = 'quartz'
all_args = [array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0....     ,  1.      ,  1.      ]]), {'asr': 'reciprocal', 'insert_gamma': False, 'n_threads': 2, 'reduce_qpts': True, ...}]
expected_qpoint_frequencies_file = 'quartz_split_reciprocal_qpoint_frequencies.json'
reduce_qpts = True, n_threads = 2

    @pytest.mark.parametrize(
        'fc, material, all_args, expected_qpoint_frequencies_file',
        lzo_params + quartz_params)
    @pytest.mark.parametrize(
        'reduce_qpts, n_threads',
        [(False, 0), (True, 0), (True, 1), (True, 2)])
    def test_calculate_qpoint_frequencies(
            self, fc, material, all_args, expected_qpoint_frequencies_file,
            reduce_qpts, n_threads):
        func_kwargs = all_args[1]
        func_kwargs['reduce_qpts'] = reduce_qpts
        if n_threads == 0:
            func_kwargs['use_c'] = False
        else:
            func_kwargs['use_c'] = True
            func_kwargs['n_threads'] = n_threads
>       qpt_freqs = fc.calculate_qpoint_frequencies(all_args[0], **func_kwargs)

euphonic_test/test_force_constants_calculate_qpoint_frequencies.py:67: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../euphonic/force_constants.py:418: in calculate_qpoint_frequencies
    qpts, freqs, weights, _, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <euphonic.force_constants.ForceConstants object at 0x7fbb11d115e0>
qpts = array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0. ...[-1.      ,  1.      ,  1.      ],
       [-0.151515,  0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = False, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = False

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
                import euphonic._euphonic as euphonic_c
                use_c_status = True
            except ImportError:
                if use_c is None:
                    warnings.warn((
                        cext_err_msg
                        + ' Falling back to pure Python calculation.'),
                        stacklevel=3)
                else:
>                   raise ImportCError(cext_err_msg)
E                   euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.

../../euphonic/force_constants.py:647: ImportCError

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

1 out of 6 runs failed: test_calculate_qpoint_frequencies[True-2-fc5-quartz-all_args5-quartz_split_reciprocal_qpoint_frequencies.json] (euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies)

artifacts/Unit test results ubuntu-latest/junit_report_1729673922.xml [took 0s]
Raw output
euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.
self = <euphonic.force_constants.ForceConstants object at 0x7fbb11d11d90>
qpts = array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0. ...[-1.      ,  1.      ,  1.      ],
       [-0.151515,  0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = True, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = False

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
>               import euphonic._euphonic as euphonic_c
E               ModuleNotFoundError: No module named 'euphonic._euphonic'

../../euphonic/force_constants.py:638: ModuleNotFoundError

During handling of the above exception, another exception occurred:

self = <tests_and_analysis.test.euphonic_test.test_force_constants_calculate_qpoint_frequencies.TestForceConstantsCalculateQPointFrequencies object at 0x7fbb11d13b90>
fc = <euphonic.force_constants.ForceConstants object at 0x7fbb11d11d90>
material = 'quartz'
all_args = [array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0....      ,  1.      ,  1.      ]]), {'asr': 'reciprocal', 'insert_gamma': True, 'n_threads': 2, 'reduce_qpts': True, ...}]
expected_qpoint_frequencies_file = 'quartz_split_reciprocal_qpoint_frequencies.json'
reduce_qpts = True, n_threads = 2

    @pytest.mark.parametrize(
        'fc, material, all_args, expected_qpoint_frequencies_file',
        lzo_params + quartz_params)
    @pytest.mark.parametrize(
        'reduce_qpts, n_threads',
        [(False, 0), (True, 0), (True, 1), (True, 2)])
    def test_calculate_qpoint_frequencies(
            self, fc, material, all_args, expected_qpoint_frequencies_file,
            reduce_qpts, n_threads):
        func_kwargs = all_args[1]
        func_kwargs['reduce_qpts'] = reduce_qpts
        if n_threads == 0:
            func_kwargs['use_c'] = False
        else:
            func_kwargs['use_c'] = True
            func_kwargs['n_threads'] = n_threads
>       qpt_freqs = fc.calculate_qpoint_frequencies(all_args[0], **func_kwargs)

euphonic_test/test_force_constants_calculate_qpoint_frequencies.py:67: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../euphonic/force_constants.py:418: in calculate_qpoint_frequencies
    qpts, freqs, weights, _, grads = self._calculate_phonons_at_qpts(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <euphonic.force_constants.ForceConstants object at 0x7fbb11d11d90>
qpts = array([[ 1.      ,  1.      ,  1.      ],
       [ 0.      ,  0.      ,  0.5     ],
       [ 0.      ,  0.      ,  0. ...[-1.      ,  1.      ,  1.      ],
       [-0.151515,  0.575758,  0.5     ],
       [-1.      ,  1.      ,  1.      ]])
weights = None, asr = 'reciprocal', dipole = True, dipole_parameter = 1.0
splitting = True, insert_gamma = True, reduce_qpts = True, use_c = True
n_threads = 2, return_mode_gradients = False, return_eigenvectors = False

    def _calculate_phonons_at_qpts(
            self,
            qpts: np.ndarray,
            weights: Optional[np.ndarray],
            asr: Optional[Literal['realspace', 'reciprocal']],
            dipole: bool,
            dipole_parameter: float,
            splitting: bool,
            insert_gamma: bool,
            reduce_qpts: bool,
            use_c: Optional[bool],
            n_threads: Optional[int],
            return_mode_gradients: bool,
            return_eigenvectors: bool) -> Tuple[
                np.ndarray, Quantity, Optional[np.ndarray],
                Optional[np.ndarray], Optional[Quantity]]:
        """
        Calculates phonon frequencies, and optionally eigenvectors and
        phonon frequency gradients. See calculate_qpoint_phonon_modes
        for argument details
    
        Returns
        -------
        qpts
            Shape (n_qpts, 3) float ndarray. The calculated q-points,
            note that this may not be the same as the input q-points
            if splitting and insert_gamma=True and there are gamma
            points
        frequencies
            Shape (n_qpts, 3*n_atoms) float Quantity in energy units,
            the phonon frequencies. n_qpts may not be the same as the
            number of input q-points, if splitting and
            insert_gamma=True and there are gamma points
        weights
            Shape (n_qpts,) float ndarray. The weight of each q-point
        eigenvectors
            Shape (n_qpts, 3*n_atoms, n_atoms, 3) complex ndarray
        gradients
            Shape (n_qpts, 3*n_atoms, 3) float Quantity in
            energy*length units. The phonon mode gradients
        """
        # Check weights is of appropriate type and shape, to avoid doing all
        # the interpolation only for it to fail creating QpointPhononModes
        _check_constructor_inputs(
            [weights], [[np.ndarray, type(None)]], [(len(qpts),)], ['weights'])
    
        # Set default splitting params
        if self.born is None:
            dipole = False
        if not dipole:
            splitting = False
    
        if splitting and insert_gamma:
            # Duplicate gamma points where there is splitting
            gamma_i = np.where(is_gamma(qpts))[0]
            split_gamma = gamma_i[np.where(
                np.logical_and(gamma_i > 0, gamma_i < len(qpts) - 1))]
            qpts = np.insert(qpts, split_gamma, qpts[split_gamma], axis=0)
            # It doesn't necessarily make sense to use both weights
            # (usually used for DOS) and splitting (usually used for
            # bandstructures) but we need to handle this case anyway
            # Where 1 q-point splits into 2, half the weight for each
            if weights is not None:
                # Don't change original array
                weights = np.copy(weights)
                weights[split_gamma] = weights[split_gamma]/2
                weights = np.insert(weights, split_gamma,
                                    weights[split_gamma])
    
        if reduce_qpts:
            norm_qpts = qpts - np.rint(qpts)
            # Ensure gamma points are exactly zero, otherwise you may
            # have a case where small fp differences mean np.unique
            # doesn't reduce them, yet they're all classified as gamma
            # points. This causes indexing errors later when calculating
            # q-directions as there are then points in reduced_qpts
            # whose index isn't in qpts_i
            gamma_i = np.where(is_gamma(qpts))[0]
            n_gamma = len(gamma_i)
            norm_qpts[gamma_i] = 0.
            reduced_qpts, qpts_i = np.unique(norm_qpts, return_inverse=True,
                                             axis=0)
            qpts_i = qpts_i.flatten()
    
            n_rqpts = len(reduced_qpts)
            # Special handling of gamma points - don't reduce gamma
            # points if LO-TO splitting
            if splitting and n_gamma > 1:
                # Replace any gamma points and their indices with new
                # gamma points appended onto the reduced q-point array,
                # so each gamma can have its own splitting
                qpts_i[gamma_i[1:]] = range(n_rqpts, n_rqpts + n_gamma - 1)
                reduced_qpts = np.append(reduced_qpts,
                                         np.tile(np.array([0., 0., 0., ]),
                                                 (n_gamma - 1, 1)),
                                         axis=0)
                n_rqpts = len(reduced_qpts)
        else:
            reduced_qpts = qpts
            qpts_i = np.arange(0, len(qpts), dtype=np.int32)
            n_rqpts = len(qpts)
    
        # Get q-directions for non-analytical corrections
        if splitting:
            split_idx = np.where(is_gamma(reduced_qpts))[0]
            q_dirs = self._get_q_dirs(qpts, qpts_i, split_idx)
        else:
            split_idx = np.array([])
            q_dirs = np.array([])
    
        n_sc_shells = 2  # How many supercells out to search for atom images
        # Construct list of supercell atom images
        if not hasattr(self, '_sc_image_i'):
            self._calculate_supercell_images(n_sc_shells)
    
        # Get a list of all the unique supercell image origins and cell
        # origins in x, y, z and how to rebuild them to minimise
        # expensive phase calculations later
        sc_image_r = get_all_origins(
            np.repeat(n_sc_shells, 3) + 1, min_xyz=-np.repeat(n_sc_shells, 3))
        sc_origins = np.einsum('ij,jk->ik', sc_image_r,
                               self.sc_matrix).astype(np.int32)
        unique_sc_origins = [[] for i in range(3)]
        unique_sc_i = np.zeros((len(sc_origins), 3), dtype=np.int32)
        unique_cell_origins = [[] for i in range(3)]
        unique_cell_i = np.zeros((len(self.cell_origins), 3), dtype=np.int32)
        for i in range(3):
            unique_sc_origins[i], unique_sc_i[:, i] = np.unique(
                sc_origins[:, i], return_inverse=True)
            unique_cell_origins[i], unique_cell_i[:, i] = np.unique(
                self.cell_origins[:, i], return_inverse=True)
        if return_mode_gradients:
            cell_origins_cart = np.einsum('ij,jk->ik', self.cell_origins,
                                          self.crystal._cell_vectors)
            # Append 0. to sc_origins_cart, so that when being indexed by
            # sc_image_i to get the origins for each image, an index of -1
            # and a value of 0. can be used
            sc_origins_cart = np.zeros((len(sc_origins) + 1, 3))
            sc_origins_cart[:len(sc_origins)] = np.einsum(
                'ij,jk->ik', sc_origins, self.crystal._cell_vectors)
            ax = np.newaxis
            all_origins_cart = (sc_origins_cart[self._sc_image_i]
                                + cell_origins_cart[:, ax, ax, ax, :])
        else:
            all_origins_cart = np.zeros((0, 3), dtype=np.float64)
    
        # Precompute dynamical matrix mass weighting
        atom_mass = self.crystal._atom_mass
        n_atoms = self.crystal.n_atoms
        masses = np.tile(np.repeat(atom_mass, 3), (3*n_atoms, 1))
        dyn_mat_weighting = 1/np.sqrt(masses*np.transpose(masses))
    
        # Initialise dipole correction calc to FC matrix if required
        if dipole and (not hasattr(self, '_dipole_init_data') or
                       dipole_parameter != self._dipole_init_data[
                           'dipole_parameter']):
            self._dipole_init_data = self._dipole_correction_init(
                self.crystal, self._born, self._dielectric, dipole_parameter)
    
        force_constants = self._force_constants
        if asr == 'realspace':
            if not hasattr(self, '_force_constants_asr'):
                self._force_constants_asr = self._enforce_realspace_asr()
            force_constants = self._force_constants_asr
        # Precompute fc matrix weighted by number of supercell atom
        # images (for cumulant method)
        n_sc_images_repeat = (
            self._n_sc_images.repeat(3, axis=2).repeat(3, axis=1))
        fc_img_weighted = np.divide(
            force_constants, n_sc_images_repeat, where=n_sc_images_repeat != 0)
    
        recip_asr_correction = np.array([], dtype=np.complex128)
        if asr == 'reciprocal':
            # Calculate dyn mat at gamma for reciprocal ASR
            q_gamma = np.array([0., 0., 0.])
            dyn_mat_gamma, _ = self._calculate_dyn_mat(
                q_gamma, fc_img_weighted, unique_sc_origins,
                unique_sc_i, unique_cell_origins, unique_cell_i,
                all_origins_cart)
            if dipole:
                dyn_mat_gamma += self._calculate_dipole_correction(
                    q_gamma, self.crystal, self._born, self._dielectric,
                    self._dipole_init_data)
            recip_asr_correction = self._enforce_reciprocal_asr(dyn_mat_gamma)
    
    
        rfreqs = np.zeros((n_rqpts, 3*n_atoms))
        if return_eigenvectors:
            n_reigenvecs = n_rqpts
        else:
            # Create dummy zero-length eigenvectors so this can be
            # detected in C and eigenvectors won't be saved
            n_reigenvecs = 0
    
        if return_mode_gradients:
            rmode_gradients = np.zeros((n_rqpts, 3*n_atoms, 3),
                                       dtype=np.complex128)
        else:
            rmode_gradients = np.zeros((0, 3*n_atoms, 3), dtype=np.complex128)
    
        euphonic_path = os.path.dirname(euphonic.__file__)
        cext_err_msg = (f'Euphonic\'s C extension couldn\'t be imported '
                        f'from {euphonic_path}, it may not have been '
                        f'installed.')
    
        # Check if C extension can be used and handle appropriately
        use_c_status = False
        if use_c is not False:
            try:
                import euphonic._euphonic as euphonic_c
                use_c_status = True
            except ImportError:
                if use_c is None:
                    warnings.warn((
                        cext_err_msg
                        + ' Falling back to pure Python calculation.'),
                        stacklevel=3)
                else:
>                   raise ImportCError(cext_err_msg)
E                   euphonic.force_constants.ImportCError: Euphonic's C extension couldn't be imported from /home/runner/work/Euphonic/Euphonic/euphonic, it may not have been installed.

../../euphonic/force_constants.py:647: ImportCError