diff --git a/docs/conf.py b/docs/conf.py index 851e0e484..4836b151a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -8,6 +8,7 @@ # isort: off import locale + locale.setlocale(locale.LC_ALL, 'C.UTF-8') # noqa # isort: on diff --git a/docs/input_file.rst b/docs/input_file.rst index 09fa923ef..5f5314524 100644 --- a/docs/input_file.rst +++ b/docs/input_file.rst @@ -158,6 +158,12 @@ The kcp subblock ~~~~~~~~~~~~~~~~ This subblock contains keywords specific to ``kcp.x``, a modified version of ``cp.x`` for performing Koopmans calculations. In addition to `the keywords associated with cp.x `_ there are several new keywords associated with the Koopmans implementation in ``kcp.x``. Non-experts will never need to change these. + +The kcw subblock +~~~~~~~~~~~~~~~~ +This subblock contains keywords specific to ``kcw.x`` (see the `list of valid kcw.x keywords `_). Non-experts will never need to change these keywords. + + The ui subblock ~~~~~~~~~~~~~~~ This subblock controls the unfolding and interpolation procedure for generating band structures and densities of states from Γ-only supercell calculations. diff --git a/quantum_espresso/q-e b/quantum_espresso/q-e index b4c5e8dee..edb08f1e7 160000 --- a/quantum_espresso/q-e +++ b/quantum_espresso/q-e @@ -1 +1 @@ -Subproject commit b4c5e8deeb7d1863e7553a87b45f8d22c21926a5 +Subproject commit edb08f1e7289a66318aa5b1ea316b7ff25fa2907 diff --git a/src/koopmans/calculators/_koopmans_ham.py b/src/koopmans/calculators/_koopmans_ham.py index 37363f59a..688ccce11 100644 --- a/src/koopmans/calculators/_koopmans_ham.py +++ b/src/koopmans/calculators/_koopmans_ham.py @@ -25,7 +25,7 @@ class KoopmansHamCalculator(KCWannCalculator, KoopmansHam, ReturnsBandStructure, ext_in = '.khi' ext_out = '.kho' - def __init__(self, atoms: Atoms, alphas: Optional[List[int]] = None, *args, **kwargs): + def __init__(self, atoms: Atoms, alphas: Optional[List[float]] = None, *args, **kwargs): # Define the valid settings self.parameters = settings.KoopmansHamSettingsDict() @@ -39,15 +39,10 @@ def __init__(self, atoms: Atoms, alphas: Optional[List[int]] = None, *args, **kw self.alphas = alphas def write_alphas(self): - # self.alphas is a list of alpha values indexed by spin index and then band index. Meanwhile, kcw.x takes a - # single file for the alphas (rather than splitting between filled/empty) and does not have two columns for - # spin up then spin down - assert self.alphas is not None, 'You have not provided screening parameters to this calculator' - if not len(self.alphas) == 1: - raise NotImplementedError('`KoopmansHamCalculator` yet to be implemented for spin-polarized systems') - [alphas] = self.alphas - filling = [True for _ in range(len(alphas))] - utils.write_alpha_file(self.directory, alphas, filling) + # self.alphas is a list of alpha values indexed by band index. Meanwhile, kcw.x takes a + # single file for the alphas (rather than splitting between filled/empty) + fake_filling = [True for _ in self.alphas] + utils.write_alpha_file(self.directory, self.alphas, fake_filling) def _pre_calculate(self): super()._pre_calculate() diff --git a/src/koopmans/settings/_koopmans_ham.py b/src/koopmans/settings/_koopmans_ham.py index 2d6346a01..f1ab63951 100644 --- a/src/koopmans/settings/_koopmans_ham.py +++ b/src/koopmans/settings/_koopmans_ham.py @@ -3,14 +3,14 @@ from ase.dft.kpoints import BandPath from ase.io.espresso import kch_keys -from ._utils import SettingsDict, kc_wann_defaults +from ._utils import SettingsDict, kcw_defaults class KoopmansHamSettingsDict(SettingsDict): def __init__(self, **kwargs) -> None: super().__init__(valid=[k for block in kch_keys.values() for k in block], defaults={'calculation': 'ham', 'do_bands': True, 'use_ws_distance': True, 'write_hr': True, - 'l_alpha_corr': False, **kc_wann_defaults}, + 'l_alpha_corr': False, **kcw_defaults}, are_paths=['outdir', 'pseudo_dir'], **kwargs) diff --git a/src/koopmans/settings/_koopmans_screen.py b/src/koopmans/settings/_koopmans_screen.py index 10e7d7474..ec65b3acb 100644 --- a/src/koopmans/settings/_koopmans_screen.py +++ b/src/koopmans/settings/_koopmans_screen.py @@ -2,14 +2,14 @@ from ase.io.espresso import kcs_keys -from ._utils import SettingsDict, kc_wann_defaults +from ._utils import SettingsDict, kcw_defaults class KoopmansScreenSettingsDict(SettingsDict): def __init__(self, **kwargs) -> None: super().__init__(valid=[k for block in kcs_keys.values() for k in block], defaults={'calculation': 'screen', 'tr2': 1.0e-18, 'nmix': 4, 'niter': 33, - 'check_spread': True, **kc_wann_defaults}, + 'check_spread': True, **kcw_defaults}, are_paths=['outdir', 'pseudo_dir'], ** kwargs) diff --git a/src/koopmans/settings/_utils.py b/src/koopmans/settings/_utils.py index 2a89d6d51..89fc77f85 100644 --- a/src/koopmans/settings/_utils.py +++ b/src/koopmans/settings/_utils.py @@ -267,12 +267,12 @@ def __setitem__(self, key: str, value: Any) -> None: return super().__setitem__(key, value) # type: ignore -kc_wann_defaults = {'outdir': 'TMP', - 'kcw_iverbosity': 1, - 'kcw_at_ks': False, - 'homo_only': False, - 'read_unitary_matrix': True, - 'lrpa': False, - 'check_ks': True, - 'have_empty': True, - 'has_disentangle': True} +kcw_defaults = {'outdir': 'TMP', + 'kcw_iverbosity': 1, + 'kcw_at_ks': False, + 'homo_only': False, + 'read_unitary_matrix': True, + 'lrpa': False, + 'check_ks': True, + 'have_empty': True, + 'has_disentangle': True} diff --git a/src/koopmans/settings/_wann2kc.py b/src/koopmans/settings/_wann2kc.py index 971f79174..0ee34ead7 100644 --- a/src/koopmans/settings/_wann2kc.py +++ b/src/koopmans/settings/_wann2kc.py @@ -2,7 +2,7 @@ from ase.io.espresso import w2kcw_keys -from ._utils import SettingsDict, kc_wann_defaults +from ._utils import SettingsDict, kcw_defaults class Wann2KCSettingsDict(SettingsDict): @@ -17,7 +17,7 @@ def __init__(self, **kwargs) -> None: flattened_kwargs[k] = v super().__init__(valid=[k for block in w2kcw_keys.values() for k in block], - defaults={'calculation': 'wann2kcw', **kc_wann_defaults}, + defaults={'calculation': 'wann2kcw', **kcw_defaults}, are_paths=['outdir', 'pseudo_dir'], **flattened_kwargs) diff --git a/src/koopmans/settings/_wann2kcp.py b/src/koopmans/settings/_wann2kcp.py index 8cdc53bd5..924606cae 100644 --- a/src/koopmans/settings/_wann2kcp.py +++ b/src/koopmans/settings/_wann2kcp.py @@ -5,7 +5,7 @@ class Wann2KCPSettingsDict(SettingsDict): def __init__(self, **kwargs) -> None: super().__init__(valid=['outdir', 'prefix', 'seedname', 'wan_mode', 'spin_component', 'gamma_trick', 'print_rho', 'wannier_plot', 'wannier_plot_list'], - defaults={'outdir': 'TMP', 'prefix': 'kc', 'seedname': 'wann', + defaults={'outdir': 'TMP', 'prefix': 'kc', 'seedname': 'wannier90', 'wan_mode': 'wannier2kcp'}, are_paths=['outdir'], **kwargs) diff --git a/src/koopmans/workflows/_koopmans_dfpt.py b/src/koopmans/workflows/_koopmans_dfpt.py index 0d7121db1..867780ac0 100644 --- a/src/koopmans/workflows/_koopmans_dfpt.py +++ b/src/koopmans/workflows/_koopmans_dfpt.py @@ -10,6 +10,8 @@ from pathlib import Path from typing import Dict +import numpy as np + from koopmans import pseudopotentials, utils from koopmans.bands import Bands from koopmans.calculators import (KoopmansHamCalculator, PWCalculator, @@ -17,6 +19,9 @@ from koopmans.files import FilePointer from koopmans.outputs import OutputModel +from ._dft import DFTPWWorkflow +from ._unfold_and_interp import UnfoldAndInterpolateWorkflow +from ._wannierize import WannierizeWorkflow from ._workflow import Workflow @@ -33,9 +38,6 @@ def __init__(self, scf_kgrid=None, *args, **kwargs): super().__init__(*args, **kwargs) # Check the consistency of keywords - if self.parameters.spin_polarized: - raise NotImplementedError( - 'Calculating screening parameters with DFPT is not yet possible for spin-polarized systems') if self.parameters.functional != 'ki': raise NotImplementedError( 'Calculating screening parameters with DFPT is not yet possible with functionals other than KI') @@ -95,28 +97,60 @@ def __init__(self, scf_kgrid=None, *args, **kwargs): f'`assume_isolated = {params.assume_isolated}` is incompatible with `mt_correction = True`') # Initialize the bands - nocc = pseudopotentials.nelec_from_pseudos( - self.atoms, self.pseudopotentials, self.parameters.pseudo_directory) // 2 - if all(self.atoms.pbc): - exclude_bands = self.calculator_parameters['w90'].get('exclude_bands', []) - nocc -= len(exclude_bands) - ntot = self.projections.num_wann() + if self.parameters.spin_polarized: + nelec = pseudopotentials.nelec_from_pseudos( + self.atoms, self.pseudopotentials, self.parameters.pseudo_directory) + tot_mag = self.calculator_parameters['pw'].tot_magnetization + nocc_up = (nelec + tot_mag) // 2 + nocc_dw = (nelec - tot_mag) // 2 + if all(self.atoms.pbc): + # Using Wannier functions + ntot_up = self.projections.num_wann(spin='up') + ntot_dw = self.projections.num_wann(spin='down') + else: + # Using KS bands + ntot_up = self.calculator_parameters['pw'].nbnd + ntot_dw = self.calculator_parameters['pw'].nbnd + nemp_up = ntot_up - nocc_up + nemp_dw = ntot_dw - nocc_dw + filling = [[True for _ in range(nocc_up)] + [False for _ in range(nemp_up)], + [True for _ in range(nocc_dw)] + [False for _ in range(nemp_dw)]] + if self.parameters.orbital_groups is None: + self.parameters.orbital_groups = [ + list(range(nocc_up + nemp_up)), list(range(nocc_dw + nemp_dw))] + tols: Dict[str, float] = {} + for key in ['self_hartree', 'spread']: + val = self.parameters.get(f'orbital_groups_{key}_tol', None) + if val is not None: + tols[key] = val + self.bands = Bands(n_bands=[len(f) for f in filling], n_spin=2, + spin_polarized=self.parameters.spin_polarized, + filling=filling, groups=self.parameters.orbital_groups, tolerances=tols) else: - ntot = self.calculator_parameters['pw'].nbnd - nemp = ntot - nocc - if self.parameters.orbital_groups is None: - self.parameters.orbital_groups = [list(range(nocc + nemp))] - tols: Dict[str, float] = {} - for key in ['self_hartree', 'spread']: - val = self.parameters.get(f'orbital_groups_{key}_tol', None) - if val is not None: - tols[key] = val - self.bands = Bands(n_bands=nocc + nemp, filling=[[True] * nocc + [False] * nemp], - groups=self.parameters.orbital_groups, tolerances=tols) + nocc = pseudopotentials.nelec_from_pseudos( + self.atoms, self.pseudopotentials, self.parameters.pseudo_directory) // 2 + if all(self.atoms.pbc): + exclude_bands = self.calculator_parameters['w90'].get( + 'exclude_bands', []) + nocc -= len(exclude_bands) + ntot = self.projections.num_wann() + else: + ntot = self.calculator_parameters['pw'].nbnd + nemp = ntot - nocc + filling = [[True] * nocc + [False] * nemp] + if self.parameters.orbital_groups is None: + self.parameters.orbital_groups = [list(range(nocc + nemp))] + tols: Dict[str, float] = {} + for key in ['self_hartree', 'spread']: + val = self.parameters.get(f'orbital_groups_{key}_tol', None) + if val is not None: + tols[key] = val + self.bands = Bands(n_bands=nocc + nemp, filling=filling, + groups=self.parameters.orbital_groups, tolerances=tols) # Populating kpoints if absent if not all(self.atoms.pbc): - for key in ['pw', 'kc_screen']: + for key in ['pw', 'kcw_screen']: self.calculator_parameters[key].kpts = [1, 1, 1] self._perform_ham_calc: bool = True @@ -128,9 +162,6 @@ def _run(self): This function runs the workflow from start to finish ''' - # Import these here so that if they have been monkey-patched, we get the monkey-patched version - from koopmans.workflows import DFTPWWorkflow, WannierizeWorkflow - if self.parameters.from_scratch: for output_directory in [self.calculator_parameters['pw'].outdir, 'wannier', 'init', 'screening', 'hamiltonian', 'postproc']: @@ -140,14 +171,15 @@ def _run(self): if self.parameters.dfpt_coarse_grid is not None: self.print('Coarse grid calculations', style='heading') - coarse_wf = self.__class__.fromparent(self, scf_kgrid=self.kpoints.grid) + coarse_wf = self.__class__.fromparent( + self, scf_kgrid=self.kpoints.grid) coarse_wf.parameters.dfpt_coarse_grid = None coarse_wf.kpoints.grid = self.parameters.dfpt_coarse_grid coarse_wf._perform_ham_calc = False coarse_wf.run(subdirectory='coarse_grid') self.print('Regular grid calculations', style='heading') - wannier_files_to_link = {} + wannier_files_to_link_by_spin = [] dft_ham_files = {} if all(self.atoms.pbc): # Run PW and Wannierization @@ -163,23 +195,22 @@ def _run(self): c, PWCalculator) and c.parameters.calculation == 'nscf'][-1] # Populate a list of files to link to subsequent calculations - for f in [wf_workflow.outputs.u_matrices_files["occ"], - wf_workflow.outputs.hr_files["occ"], - wf_workflow.outputs.centers_files["occ"]]: - assert f is not None - wannier_files_to_link[f.name] = f - - for f in [wf_workflow.outputs.u_matrices_files["emp"], - wf_workflow.outputs.u_dis_file, - wf_workflow.outputs.hr_files["emp"], - wf_workflow.outputs.centers_files["emp"]]: - assert f is not None - wannier_files_to_link[f.parent.prefix + '_emp' + str(f.name)[len(f.parent.prefix):]] = f - - if self.parameters.spin_polarized: - raise NotImplementedError('Need to adapt the following code for spin-polarized calculations') - for label in ['occ', 'emp']: - dft_ham_files[(label, None)] = wf_workflow.outputs.hr_files[label] + suffixes = ['_up', '_down'] if self.parameters.spin_polarized else [''] + for suffix in suffixes: + wannier_files_to_link_by_spin.append({}) + for filling in ['occ', 'emp']: + key = filling + suffix + for f in [wf_workflow.outputs.u_matrices_files[key], + wf_workflow.outputs.hr_files[key], + wf_workflow.outputs.centers_files[key]]: + assert f is not None + + if filling == 'occ': + wannier_files_to_link_by_spin[-1][f.name] = f + else: + wannier_files_to_link_by_spin[-1]['wannier90_emp' + str(f.name)[9:]] = f + + dft_ham_files[(filling, suffix[1:] if suffix else None)] = wf_workflow.outputs.hr_files[key] else: # Run PW @@ -191,64 +222,68 @@ def _run(self): # Update settings pw_params = pw_workflow.calculator_parameters['pw'] pw_params.nspin = 2 - pw_params.tot_magnetization = 0 # Run the subworkflow pw_workflow.run() init_pw = pw_workflow.calculations[0] - # Convert from wannier to KC - wann2kc_calc = self.new_calculator('wann2kc') - self.link(init_pw, init_pw.parameters.outdir, wann2kc_calc, wann2kc_calc.parameters.outdir) - for dst, f in wannier_files_to_link.items(): - self.link(f.parent, f.name, wann2kc_calc, dst, symlink=True) - self.run_calculator(wann2kc_calc) - - # Calculate screening parameters - if self.parameters.calculate_alpha: - if self.parameters.dfpt_coarse_grid is None: - screen_wf = ComputeScreeningViaDFPTWorkflow.fromparent( - self, wannier_files_to_link=wannier_files_to_link) - screen_wf.run(subdirectory='screening') - else: - self.bands.alphas = coarse_wf.bands.alphas - else: - # Load the alphas - if self.parameters.alpha_from_file: - self.bands.alphas = [utils.read_alpha_file(Path())] - else: - self.bands.alphas = self.parameters.alpha_guess + spin_components = [1, 2] if self.parameters.spin_polarized else [1] - # Calculate the Hamiltonian - if self._perform_ham_calc: - kc_ham_calc = self.new_calculator('kc_ham', kpts=self.kpoints.path) - self.link(wann2kc_calc, wann2kc_calc.parameters.outdir / (wann2kc_calc.parameters.prefix + '.save'), - kc_ham_calc, kc_ham_calc.parameters.outdir / (kc_ham_calc.parameters.prefix + '.save'), symlink=True) - self.link(wann2kc_calc, wann2kc_calc.parameters.outdir / (wann2kc_calc.parameters.prefix + '.xml'), - kc_ham_calc, kc_ham_calc.parameters.outdir / (kc_ham_calc.parameters.prefix + '.xml'), symlink=True) - self.link(wann2kc_calc, wann2kc_calc.parameters.outdir / 'kcw', kc_ham_calc, - kc_ham_calc.parameters.outdir / 'kcw', recursive_symlink=True) + for spin_component, wannier_files_to_link in zip(spin_components, wannier_files_to_link_by_spin): + spin_suffix = f'_spin_{spin_component}' if self.parameters.spin_polarized else '' + + # Convert from wannier to KC + wann2kc_calc = self.new_calculator('kcw_wannier', spin_component=spin_component) + self.link(init_pw, init_pw.parameters.outdir, wann2kc_calc, wann2kc_calc.parameters.outdir) for dst, f in wannier_files_to_link.items(): - self.link(f.parent, f.name, kc_ham_calc, dst, symlink=True) - self.run_calculator(kc_ham_calc) - - # Postprocessing - if all(self.atoms.pbc) and self.projections and self.kpoints.path is not None \ - and self.calculator_parameters['ui'].do_smooth_interpolation: - from koopmans.workflows import UnfoldAndInterpolateWorkflow - - # Assemble the Hamiltonian files required by the UI workflow - koopmans_ham_files = {("occ", None): FilePointer(kc_ham_calc, f'{kc_ham_calc.parameters.prefix}.kcw_hr_occ.dat'), - ("emp", None): FilePointer(kc_ham_calc, f'{kc_ham_calc.parameters.prefix}.kcw_hr_emp.dat')} - if not dft_ham_files: - raise ValueError( - 'The DFT Hamiltonian files have not been generated but are required for the UI workflow') - ui_workflow = UnfoldAndInterpolateWorkflow.fromparent( - self, dft_ham_files=dft_ham_files, koopmans_ham_files=koopmans_ham_files) - ui_workflow.run(subdirectory='postproc') - - # Plotting + self.link(f.parent, f.name, wann2kc_calc, dst, symlink=True) + wann2kc_calc.prefix += spin_suffix + self.run_calculator(wann2kc_calc) + + # Calculate screening parameters + if self.parameters.calculate_alpha: + if self.parameters.dfpt_coarse_grid is None: + screen_wf = ComputeScreeningViaDFPTWorkflow.fromparent( + self, wannier_files_to_link=wannier_files_to_link, spin_component=spin_component) + screen_wf.run(subdirectory='screening' + spin_suffix) + else: + # Load the alphas + if self.parameters.alpha_from_file: + self.bands.alphas = [utils.read_alpha_file(Path())] + else: + self.bands.alphas = self.parameters.alpha_guess + + # Calculate the Hamiltonian + if self._perform_ham_calc: + kc_ham_calc = self.new_calculator('kcw_ham', kpts=self.kpoints.path, spin_component=spin_component) + self.link(wann2kc_calc, wann2kc_calc.parameters.outdir / (wann2kc_calc.parameters.prefix + '.save'), + kc_ham_calc, kc_ham_calc.parameters.outdir / (kc_ham_calc.parameters.prefix + '.save'), symlink=True) + self.link(wann2kc_calc, wann2kc_calc.parameters.outdir / (wann2kc_calc.parameters.prefix + '.xml'), + kc_ham_calc, kc_ham_calc.parameters.outdir / (kc_ham_calc.parameters.prefix + '.xml'), symlink=True) + self.link(wann2kc_calc, wann2kc_calc.parameters.outdir / 'kcw', kc_ham_calc, + kc_ham_calc.parameters.outdir / 'kcw', recursive_symlink=True) + for dst, f in wannier_files_to_link.items(): + self.link(f.parent, f.name, kc_ham_calc, dst, symlink=True) + kc_ham_calc.prefix += spin_suffix + self.run_calculator(kc_ham_calc) + + # Postprocessing + if all(self.atoms.pbc) and self.projections and self.kpoints.path is not None \ + and self.calculator_parameters['ui'].do_smooth_interpolation: + + # Assemble the Hamiltonian files required by the UI workflow + koopmans_ham_files = {("occ", None): FilePointer(kc_ham_calc, f'{kc_ham_calc.parameters.prefix}.kcw_hr_occ.dat'), + ("emp", None): FilePointer(kc_ham_calc, f'{kc_ham_calc.parameters.prefix}.kcw_hr_emp.dat')} + if not dft_ham_files: + raise ValueError( + 'The DFT Hamiltonian files have not been generated but are required for the UI workflow') + ui_workflow = UnfoldAndInterpolateWorkflow.fromparent( + self, dft_ham_files=dft_ham_files, koopmans_ham_files=koopmans_ham_files) + ui_workflow.run(subdirectory='postproc' + spin_suffix) + + # Plotting + if self._perform_ham_calc: self.plot_bandstructure() def plot_bandstructure(self): @@ -256,13 +291,20 @@ def plot_bandstructure(self): return # Identify the relevant calculators - wann2kc_calc = [c for c in self.calculations if isinstance(c, Wann2KCCalculator)][-1] - kc_ham_calc = [c for c in self.calculations if isinstance(c, KoopmansHamCalculator)][-1] + n_calc = 2 if self.parameters.spin_polarized else 1 + kc_ham_calcs = [c for c in self.calculations if isinstance(c, KoopmansHamCalculator)][-n_calc:] # Plot the bandstructure if the band path has been specified - bs = kc_ham_calc.results['band structure'] - if bs.path.path: - super().plot_bandstructure(bs.subtract_reference()) + bandstructures = [c.results['band structure'] for c in kc_ham_calcs] + ref = max([b.reference for b in bandstructures]) + bs_to_plot = bandstructures[0].subtract_reference(ref) + if not bs_to_plot.path.path: + return + + if self.parameters.spin_polarized: + bs_to_plot._energies = np.append(bs_to_plot._energies, bandstructures[1]._energies - ref, axis=0) + + super().plot_bandstructure(bs_to_plot) def new_calculator(self, calc_presets, **kwargs): return internal_new_calculator(self, calc_presets, **kwargs) @@ -276,10 +318,11 @@ class ComputeScreeningViaDFPTWorkflow(Workflow): output_model = ComputeScreeningViaDFPTOutputs outputs: ComputeScreeningViaDFPTOutputs - def __init__(self, *args, wannier_files_to_link: Dict[str, FilePointer], **kwargs): + def __init__(self, *args, spin_component: int, wannier_files_to_link: Dict[str, FilePointer], **kwargs): super().__init__(*args, **kwargs) self._wannier_files_to_link = wannier_files_to_link + self._spin_component = spin_component def _run(self): # Group the bands by spread @@ -289,7 +332,7 @@ def _run(self): # If there is no orbital grouping, do all orbitals in one calculation # 1) Create the calculator - kc_screen_calc = self.new_calculator('kc_screen') + kc_screen_calc = self.new_calculator('kcw_screen', self._spin_component) # 2) Run the calculator self.run_calculator(kc_screen_calc) @@ -302,8 +345,9 @@ def _run(self): kc_screen_calcs = [] # 1) Create the calculators for band in self.bands.to_solve: - kc_screen_calc = self.new_calculator('kc_screen', i_orb=band.index) - kc_screen_calc.prefix += f'_orbital_{band.index}' + kc_screen_calc = self.new_calculator('kcw_screen', i_orb=band.index, + spin_component=self._spin_component) + kc_screen_calc.prefix += f'_orbital_{band.index}_spin_{self._spin_component}' kc_screen_calcs.append(kc_screen_calc) # 2) Run the calculators (possibly in parallel) @@ -313,11 +357,11 @@ def _run(self): for band, kc_screen_calc in zip(self.bands.to_solve, kc_screen_calcs): for b in self.bands: if b.group == band.group: - alpha = kc_screen_calc.results['alphas'][band.spin] - b.alpha = alpha[band.spin] + [[alpha]] = kc_screen_calc.results['alphas'] + b.alpha = alpha def new_calculator(self, calc_type: str, *args, **kwargs): - assert calc_type == 'kc_screen', 'Only the "kc_screen" calculator is supported in DFPTScreeningWorkflow' + assert calc_type == 'kcw_screen', 'Only the "kcw_screen" calculator is supported in DFPTScreeningWorkflow' calc = internal_new_calculator(self, calc_type, *args, **kwargs) @@ -337,7 +381,7 @@ def new_calculator(self, calc_type: str, *args, **kwargs): def internal_new_calculator(workflow, calc_presets, **kwargs): - if calc_presets not in ['kc_ham', 'kc_screen', 'wann2kc']: + if calc_presets not in ['kcw_ham', 'kcw_screen', 'kcw_wannier']: raise ValueError( f'Invalid choice `calc_presets={calc_presets}` in `{workflow.__class__.__name__}.new_calculator()`') @@ -348,28 +392,47 @@ def internal_new_calculator(workflow, calc_presets, **kwargs): calc.parameters.outdir = 'TMP' if all(workflow.atoms.pbc): calc.parameters.seedname = [c for c in workflow.calculations if isinstance(c, Wannier90Calculator)][-1].prefix - calc.parameters.spin_component = 1 + calc.parameters.spin_component = kwargs['spin_component'] if 'spin_component' in kwargs else 1 calc.parameters.kcw_at_ks = not all(workflow.atoms.pbc) calc.parameters.read_unitary_matrix = all(workflow.atoms.pbc) - if calc_presets == 'wann2kc': + if calc_presets == 'kcw_wannier': pass - elif calc_presets == 'kc_screen': + elif calc_presets == 'kcw_screen': # If eps_inf is not provided in the kc_wann:screen subdictionary but there is a value provided in the # workflow parameters, adopt that value if workflow.parameters.eps_inf is not None and calc.parameters.eps_inf is None and all(workflow.atoms.pbc): calc.parameters.eps_inf = workflow.parameters.eps_inf else: calc.parameters.do_bands = all(workflow.atoms.pbc) - calc.alphas = workflow.bands.alphas + if not workflow.parameters.spin_polarized and len(workflow.bands.alphas) != 1: + raise ValueError('The list of screening parameters should be length 1 for spin-unpolarized calculations') + calc.alphas = workflow.bands.alphas[calc.parameters.spin_component - 1] if all(workflow.atoms.pbc): - nocc = workflow.bands.num(filled=True) - nemp = workflow.bands.num(filled=False) + if workflow.parameters.spin_polarized: + if calc.parameters.spin_component == 1: + ntot = workflow.projections.num_wann(spin='up') + nocc = workflow.calculator_parameters['kcp'].nelup + nemp = workflow.projections.num_wann(spin='up') - nocc + else: + ntot = workflow.projections.num_wann(spin='down') + nocc = workflow.calculator_parameters['kcp'].neldw + nemp = workflow.projections.num_wann(spin='down') - nocc + else: + nocc = workflow.bands.num(filled=True) + nemp = workflow.bands.num(filled=False) + nemp_pw = workflow.calculator_parameters['pw'].nbnd - nocc have_empty = (nemp > 0) - has_disentangle = (workflow.projections.num_bands() != nocc + nemp) + has_disentangle = (nemp != nemp_pw) else: - nocc = workflow.calculator_parameters['kcp'].nelec // 2 + if workflow.parametetrs.spin_polarized: + if calc.parameters.spin_component == 1: + nocc = workflow.calculator_parameters['kcp'].nelup + else: + nocc = workflow.calculator_parameters['kcp'].neldw + else: + nocc = workflow.calculator_parameters['kcp'].nelec // 2 nemp = workflow.calculator_parameters['pw'].nbnd - nocc have_empty = (nemp > 0) has_disentangle = False @@ -378,7 +441,7 @@ def internal_new_calculator(workflow, calc_presets, **kwargs): calc.parameters.have_empty = have_empty calc.parameters.has_disentangle = has_disentangle - # Apply any additional calculator keywords passed as kwargs + # Ensure that any additional calculator keywords passed as kwargs are still that value for k, v in kwargs.items(): setattr(calc.parameters, k, v) diff --git a/src/koopmans/workflows/_workflow.py b/src/koopmans/workflows/_workflow.py index 260f47dbf..c0aca08e7 100644 --- a/src/koopmans/workflows/_workflow.py +++ b/src/koopmans/workflows/_workflow.py @@ -711,11 +711,11 @@ def new_calculator(self, calc_class = calculators.PW2WannierCalculator elif calc_type == 'wann2kcp': calc_class = calculators.Wann2KCPCalculator - elif calc_type == 'wann2kc': + elif calc_type == 'kcw_wannier': calc_class = calculators.Wann2KCCalculator - elif calc_type == 'kc_screen': + elif calc_type == 'kcw_screen': calc_class = calculators.KoopmansScreenCalculator - elif calc_type == 'kc_ham': + elif calc_type == 'kcw_ham': calc_class = calculators.KoopmansHamCalculator elif calc_type == 'ph': calc_class = calculators.PhCalculator @@ -1259,9 +1259,9 @@ def _fromjsondct(cls, bigdct: Dict[str, Any], override: Dict[str, Any] = {}, **k # Duplicate the UI block calcdict[f'ui_{key}'] = calcdict['ui'] - # Third, flatten the kc_wann subdicts - kc_wann_blocks = calcdict.pop('kc_wann', {'kc_ham': {}, 'kc_screen': {}, 'wann2kc': {}}) - calcdict.update(**kc_wann_blocks) + # Third, flatten the kcw subdicts + kcw_blocks = calcdict.pop('kcw', {'ham': {}, 'screen': {}, 'wannier': {}}) + calcdict.update(**{'kcw_' + k: v for k, v in kcw_blocks.items()}) # Finally, generate a SettingsDict for every single kind of calculator, regardless of whether or not there was # a corresponding block in the json file @@ -1473,7 +1473,7 @@ def toinputjson(self) -> Dict[str, Dict[str, Any]]: calcdct[code][key] = {k: v for k, v in dict( block).items() if v is not None} - elif code in ['pw2wannier', 'wann2kc', 'kc_screen', 'kc_ham', 'projwfc', 'wann2kcp', 'ph']: + elif code in ['pw2wannier', 'kcw_wannier', 'kcw_screen', 'kcw_ham', 'projwfc', 'wann2kcp', 'ph']: calcdct[code] = params_dict elif code.startswith('ui_'): calcdct['ui'][code.split('_')[-1]] = params_dict @@ -1529,7 +1529,7 @@ def plot_bandstructure(self, if isinstance(bs, BandStructure): bs = [bs] if isinstance(bsplot_kwargs, dict): - bsplot_kwargs = [bsplot_kwargs] + bsplot_kwargs = [bsplot_kwargs for _ in bs] if len(bs) != len(bsplot_kwargs): raise ValueError('The `bs` and `bsplot_kwargs` arguments to `plot_bandstructure()` should be the same ' 'length') @@ -1724,8 +1724,8 @@ def generate_default_calculator_parameters() -> Dict[str, settings.SettingsDict] # Dictionary to be used as the default value for 'calculator_parameters' when initializing a workflow # We create this dynamically in order for the .directory attributes to make sense return {'kcp': settings.KoopmansCPSettingsDict(), - 'kc_ham': settings.KoopmansHamSettingsDict(), - 'kc_screen': settings.KoopmansScreenSettingsDict(), + 'kcw_ham': settings.KoopmansHamSettingsDict(), + 'kcw_screen': settings.KoopmansScreenSettingsDict(), 'ph': settings.PhSettingsDict(), 'projwfc': settings.ProjwfcSettingsDict(), 'pw': settings.PWSettingsDict(), @@ -1734,7 +1734,7 @@ def generate_default_calculator_parameters() -> Dict[str, settings.SettingsDict] 'ui': settings.UnfoldAndInterpolateSettingsDict(), 'ui_occ': settings.UnfoldAndInterpolateSettingsDict(), 'ui_emp': settings.UnfoldAndInterpolateSettingsDict(), - 'wann2kc': settings.Wann2KCSettingsDict(), + 'kcw_wannier': settings.Wann2KCSettingsDict(), 'w90': settings.Wannier90SettingsDict(), 'w90_up': settings.Wannier90SettingsDict(), 'w90_down': settings.Wannier90SettingsDict(), @@ -1743,9 +1743,9 @@ def generate_default_calculator_parameters() -> Dict[str, settings.SettingsDict] # Define which function to use to read each block settings_classes = {'kcp': settings.KoopmansCPSettingsDict, - 'kc_ham': settings.KoopmansHamSettingsDict, - 'kc_screen': settings.KoopmansScreenSettingsDict, - 'wann2kc': settings.Wann2KCSettingsDict, + 'kcw_ham': settings.KoopmansHamSettingsDict, + 'kcw_screen': settings.KoopmansScreenSettingsDict, + 'kcw_wannier': settings.Wann2KCSettingsDict, 'ph': settings.PhSettingsDict, 'projwfc': settings.ProjwfcSettingsDict, 'pw': settings.PWSettingsDict,