diff --git a/src/koopmans/calculators/_koopmans_cp.py b/src/koopmans/calculators/_koopmans_cp.py index 3617d26ec..169756969 100644 --- a/src/koopmans/calculators/_koopmans_cp.py +++ b/src/koopmans/calculators/_koopmans_cp.py @@ -339,6 +339,7 @@ def read_ham_xml_files(self, bare=False) -> List[np.ndarray]: return ham_matrix def _ham_pkl_file(self, bare: bool = False) -> Path: + assert self.directory is not None if bare: suffix = '.bare_ham.pkl' else: @@ -549,7 +550,7 @@ def files_to_convert_with_spin2_to_spin1(self): prefix, suffix = f.split('.') nspin_2_files.append(f'{prefix}1.{suffix}') - parent = self.parameters.outdir / f'{self.parameters.prefix}_{self.parameters.ndr}.save/K00001' + parent = self.read_directory / 'K00001' return {'spin_2_files': [(self, parent / f1) for f1 in nspin_2_files], 'spin_1_files': nspin_1_files} @property @@ -570,14 +571,14 @@ def files_to_convert_with_spin1_to_spin2(self): nspin_2up_files.append(f'{prefix}1.{suffix}') nspin_2dw_files.append(f'{prefix}2.{suffix}') - parent = self.parameters.outdir / f'{self.parameters.prefix}_{self.parameters.ndr}.save/K00001' + parent = self.read_directory / 'K00001' return {'spin_1_files': [(self, parent / f) for f in nspin_1_files], 'spin_2_up_files': nspin_2up_files, 'spin_2_down_files': nspin_2dw_files} def convert_wavefunction_2to1(self): - nspin2_tmpdir = self.parameters.outdir / f'{self.parameters.prefix}_{self.parameters.ndr}.save/K00001' + nspin2_tmpdir = self.read_directory / 'K00001' nspin1_tmpdir = self.parameters.outdir / f'{self.parameters.prefix}_98.save/K00001' for directory in [nspin2_tmpdir, nspin1_tmpdir]: @@ -643,6 +644,16 @@ def convert_wavefunction_1to2(self): with open(file_out, 'wb') as fd: fd.write(contents) + @property + def read_directory(self) -> Path: + assert isinstance(self.parameters.outdir, Path) + return self.parameters.outdir / f'{self.parameters.prefix}_{self.parameters.ndr}.save' + + @property + def write_directory(self): + assert isinstance(self.parameters.outdir, Path) + return self.parameters.outdir / f'{self.parameters.prefix}_{self.parameters.ndw}.save' + def convert_flat_alphas_for_kcp(flat_alphas: List[float], parameters: settings.KoopmansCPSettingsDict) -> List[List[float]]: diff --git a/src/koopmans/references.bib b/src/koopmans/references.bib index 915b470dd..1e8fe8ac2 100644 --- a/src/koopmans/references.bib +++ b/src/koopmans/references.bib @@ -112,8 +112,9 @@ @misc{Dabo2009 author = {Dabo, I. and Cococcioni, M. and Marzari, N.}, year = {2009}, month = jan, + eprint = {0901.2637}, abstract = {In effective single-electron theories, self-interaction manifests itself through the unphysical dependence of the energy of an electronic state as a function of its occupation, which results in important deviations from the ideal Koopmans trend and strongly affects the accuracy of electronic-structure predictions. Here, we study the non-Koopmans behavior of local and semilocal density-functional theory (DFT) total energy methods as a means to quantify and to correct self-interaction errors. We introduce a non-Koopmans self-interaction correction that generalizes the Perdew-Zunger scheme, and demonstrate its considerably improved performance in correcting the deficiencies of DFT approximations for self-interaction problems of fundamental and practical relevance.}, - archiveprefix = {arxiv} + archiveprefix = {arXiv} } @article{Dabo2010, diff --git a/src/koopmans/workflows/_convergence.py b/src/koopmans/workflows/_convergence.py index b513cf1b5..cf8363593 100644 --- a/src/koopmans/workflows/_convergence.py +++ b/src/koopmans/workflows/_convergence.py @@ -21,6 +21,7 @@ import numpy as np from koopmans import cell, utils +from koopmans.outputs import OutputModel from ._workflow import Workflow @@ -215,8 +216,16 @@ def ConvergenceVariableFactory(conv_var, **kwargs) -> ConvergenceVariable: 'construct your ConvergenceWorkflow using the ConvergenceWorkflowFactory') +class ConvergenceOutputs(OutputModel): + + converged_values: Dict[str, Any] + + class ConvergenceWorkflow(Workflow): + output_model = ConvergenceOutputs # type: ignore + outputs: ConvergenceOutputs + ''' A Workflow class that wraps another workflow in a convergence procedure in order to converge the observable within the specified tolerance with respect to the variables ''' @@ -297,22 +306,19 @@ def _run(self) -> None: subwf = self._subworkflow_class.fromparent(self) # For each parameter we're converging wrt... - header = '' - subdir = Path() + label = '' for index, variable in zip(indices, self.variables): value = variable.values[index] if isinstance(value, float): value_str = f'{value:.1f}' else: value_str = str(value) - header += f'{variable.name} = {value_str}, ' if isinstance(value, list): value_str = ''.join([str(x) for x in value]) # Create new working directory - subdir /= f'{variable.name}_{value_str}'.replace( - ' ', '_').replace('.', 'd') + label += f' {variable.name} {value_str}'.replace('.', '_') # Set the value variable.set_value(subwf, value) @@ -329,9 +335,8 @@ def _run(self) -> None: # for _ in range(extra_orbitals)] # utils.write_alpha_file(directory=Path(), alphas=alphas, filling=filling) - self.print(header.rstrip(', '), style='subheading') - # Perform calculation + subwf.name += label subwf.run() # Store the result @@ -371,6 +376,8 @@ def _run(self) -> None: self.print('\n Converged variables are ' + ', '.join([f'{p.name} = {p.converged_value}' for p in self.variables])) + self.outputs = self.output_model(converged_values={v.name: v.converged_value for v in self.variables}) + return else: # Work out which variables are yet to converge, and line up more calculations @@ -379,7 +386,7 @@ def _run(self) -> None: new_array_shape = list(np.shape(results)) new_array_slice: List[Union[int, slice]] = [ slice(None) for _ in indices] - self.print('Progress update', style='heading') + self.print('\nProgress update', style='heading') for index, var in enumerate(self.variables): subarray_slice = [slice(None) for _ in self.variables] subarray_slice[index] = slice(0, -1) @@ -403,6 +410,7 @@ def _run(self) -> None: var.extend() new_array_shape[index] += 1 new_array_slice[index] = slice(None, -1) + self.print() new_results = np.empty(new_array_shape) new_results[:] = np.nan diff --git a/src/koopmans/workflows/_dft.py b/src/koopmans/workflows/_dft.py index 4d5e95dc2..ee7fb3c4d 100644 --- a/src/koopmans/workflows/_dft.py +++ b/src/koopmans/workflows/_dft.py @@ -27,16 +27,18 @@ def __init__(self, *args, **kwargs): self.parameters.functional = 'dft' +class DFTCPOutput(OutputModel): + pass + + class DFTCPWorkflow(DFTWorkflow): + output_model = DFTCPOutput # type: ignore + outputs: DFTCPOutput def _run(self): calc = self.new_calculator('kcp') - # Removing old directories - if self.parameters.from_scratch: - utils.system_call(f'rm -r {calc.parameters.outdir} 2>/dev/null', False) - calc.prefix = 'dft' calc.directory = '.' calc.parameters.ndr = 50 @@ -59,8 +61,15 @@ def _run(self): return calc +class DFTPWOutput(OutputModel): + pass + + class DFTPWWorkflow(DFTWorkflow): + output_model = DFTPWOutput # type: ignore + outputs: DFTPWOutput + def _run(self): # Create the calculator diff --git a/src/koopmans/workflows/_koopmans_dscf.py b/src/koopmans/workflows/_koopmans_dscf.py index 4ecd5290d..03fffb3a5 100644 --- a/src/koopmans/workflows/_koopmans_dscf.py +++ b/src/koopmans/workflows/_koopmans_dscf.py @@ -27,7 +27,12 @@ class KoopmansDSCFOutputs(OutputModel): ''' Outputs for the KoopmansDSCFWorkflow ''' - pass + variational_orbital_files: Dict[str, FilePointer] + final_calc: calculators.KoopmansCPCalculator + wannier_hamiltonian_files: Dict[Tuple[str, str | None], FilePointer] | None = None + + class Config: + arbitrary_types_allowed = True class KoopmansDSCFWorkflow(Workflow): @@ -35,7 +40,7 @@ class KoopmansDSCFWorkflow(Workflow): output_model = KoopmansDSCFOutputs # type: ignore outputs: KoopmansDSCFOutputs - def __init__(self, *args, redo_smooth_dft: Optional[bool] = None, restart_from_old_ki: bool = False, **kwargs) -> None: + def __init__(self, *args, redo_smooth_dft: Optional[bool] = None, initial_variational_orbital_files: Dict[str, FilePointer] | None = None, previous_ki_calc: calculators.KoopmansCPCalculator | None = None, **kwargs) -> None: super().__init__(*args, **kwargs) # The following two additional keywords allow for some tweaking of the workflow when running a singlepoint @@ -46,7 +51,10 @@ def __init__(self, *args, redo_smooth_dft: Optional[bool] = None, restart_from_o self._redo_smooth_dft = redo_smooth_dft # For the KIPZ calculation we restart from the old KI calculation - self._restart_from_old_ki = restart_from_old_ki + self._initial_variational_orbital_files = initial_variational_orbital_files + + # For workflows where we have already performed initialization elsewhere, we can restart using that directory + self._previous_ki_calc = previous_ki_calc # If periodic, convert the kcp calculation into a Γ-only supercell calculation kcp_params = self.calculator_parameters['kcp'] @@ -227,15 +235,21 @@ def _run(self) -> None: This function runs a KI/pKIPZ/KIPZ workflow from start to finish ''' - init_wf = InitializationWorkflow.fromparent(self, restart_from_old_ki=self._restart_from_old_ki) - init_wf.run() + init_wf: Optional[InitializationWorkflow] = None + if self._initial_variational_orbital_files is None: + init_wf = InitializationWorkflow.fromparent(self) + init_wf.run() + self._initial_variational_orbital_files = init_wf.outputs.variational_orbital_files + initial_cp_calculation = init_wf.outputs.final_calc + else: + assert self._previous_ki_calc is not None + initial_cp_calculation = self._previous_ki_calc self.primitive_to_supercell() if self.parameters.calculate_alpha: - screening_wf = CalculateScreeningViaDSCF.fromparent(self, initial_variational_orbital_files=init_wf.outputs.variational_orbital_files, - initial_cp_calculation=init_wf.outputs.final_calc, - restart_from_old_ki=self._restart_from_old_ki) + screening_wf = CalculateScreeningViaDSCF.fromparent(self, initial_variational_orbital_files=self._initial_variational_orbital_files, + initial_cp_calculation=initial_cp_calculation) screening_wf.run() # Store the files which will be needed for the final calculation @@ -249,10 +263,39 @@ def _run(self) -> None: self.bands.print_history(indent=self.print_indent + 1) # In this case the final calculation will restart from the initialization calculations - n_electron_restart_dir = init_wf.outputs.final_calc + if self._previous_ki_calc is None: + assert init_wf is not None + n_electron_restart_dir = FilePointer(init_wf.outputs.final_calc, + init_wf.outputs.final_calc.write_directory) + else: + n_electron_restart_dir = FilePointer(self._previous_ki_calc, self._previous_ki_calc.write_directory) # Final calculation - self.perform_final_calculations(restart_dir=n_electron_restart_dir) + if self.parameters.functional == 'pkipz': + if self._previous_ki_calc is None: + final_calc_types = ['ki', 'pkipz'] + else: + final_calc_types = ['pkipz'] + else: + final_calc_types = [self.parameters.functional] + + for final_calc_type in final_calc_types: + + final_calc_type += '_final' + + calc = internal_new_kcp_calculator(self, final_calc_type, write_hr=True) + + if self.parameters.functional == 'ki' and self.parameters.init_orbitals in ['mlwfs', 'projwfs'] \ + and not self.parameters.calculate_alpha: + calc.parameters.restart_from_wannier_pwscf = True + + self.link(*n_electron_restart_dir, calc, n_electron_restart_dir.name, symlink=True) + self.run_calculator(calc) + + final_calc = calc + variational_orbital_files = {f: FilePointer(final_calc, final_calc.read_directory / 'K00001' / f) + for f in ['evc01.dat', 'evc02.dat', 'evc0_empty1.dat', 'evc0_empty2.dat']} + self.outputs = self.output_model(variational_orbital_files=variational_orbital_files, final_calc=final_calc) # Postprocessing if all(self.atoms.pbc): @@ -269,6 +312,7 @@ def _run(self) -> None: else: koopmans_ham_files = {('occ', None): FilePointer(final_koopmans_calc, 'ham_occ_1.dat'), ('emp', None): FilePointer(final_koopmans_calc, 'ham_emp_1.dat')} + assert init_wf is not None dft_ham_files = init_wf.outputs.wannier_hamiltonian_files ui_workflow = workflows.UnfoldAndInterpolateWorkflow.fromparent( self, @@ -299,34 +343,6 @@ def _overwrite_canonical_with_variational_orbitals(self, calc: calculators.Koopm if calc.has_empty_states(ispin): shutil.copy(savedir / f'evc_empty{ispin + 1}.dat', savedir / f'evc0_empty{ispin + 1}.dat') - def perform_final_calculations(self, restart_dir: FilePointer) -> None: - - if self.parameters.functional == 'pkipz': - final_calc_types = ['ki', 'pkipz'] - else: - final_calc_types = [self.parameters.functional] - - for final_calc_type in final_calc_types: - - final_calc_type += '_final' - - # For pKIPZ, the appropriate ndr can change but it is always ndw of the previous - # KI calculation - if final_calc_type == 'pkipz_final': - raise NotImplementedError() - ndr = [c.parameters.ndw for c in self.calculations if c.prefix in [ - 'ki', 'ki_final'] and hasattr(c.parameters, 'ndw')][-1] - calc = internal_new_kcp_calculator(self, final_calc_type, ndr=ndr, write_hr=True) - else: - calc = internal_new_kcp_calculator(self, final_calc_type, write_hr=True) - if self.parameters.functional == 'ki' and self.parameters.init_orbitals in ['mlwfs', 'projwfs'] \ - and not self.parameters.calculate_alpha: - calc.parameters.restart_from_wannier_pwscf = True - - assert isinstance(restart_dir[0], utils.HasDirectoryAttr) - self.link(*restart_dir, calc, restart_dir.name, symlink=True) - self.run_calculator(calc) - class CalculateScreeningViaDSCFOutput(OutputModel): n_electron_restart_dir: FilePointer @@ -341,12 +357,10 @@ class CalculateScreeningViaDSCF(Workflow): outputs: CalculateScreeningViaDSCFOutput def __init__(self, *args, initial_variational_orbital_files: Dict[str, Tuple[Workflow, str]], - initial_cp_calculation: calculators.KoopmansCPCalculator, - restart_from_old_ki: bool = False, **kwargs) -> None: + initial_cp_calculation: calculators.KoopmansCPCalculator, **kwargs) -> None: super().__init__(*args, **kwargs) self._initial_variational_orbital_files = initial_variational_orbital_files self._initial_cp_calculation = initial_cp_calculation - self._restart_from_old_ki = restart_from_old_ki def _run(self) -> None: converged = False @@ -362,14 +376,10 @@ def _run(self) -> None: iteration_wf = DeltaSCFIterationWorkflow.fromparent(self, variational_orbital_files=variational_orbital_files, previous_n_electron_calculation=n_electron_calc, - i_sc=i_sc, alpha_indep_calcs=alpha_indep_calcs, - restart_from_old_ki=self._restart_from_old_ki) + i_sc=i_sc, alpha_indep_calcs=alpha_indep_calcs) iteration_wf.name = f'Iteration {i_sc}' if i_sc == 1: - if self.parameters.functional == 'kipz' and not all(self.atoms.pbc): - # For the first KIPZ trial calculation, do the innerloop - iteration_wf.calculator_parameters['kcp'].do_innerloop = True # For the first iteration, the spin contamination has already been addressed during the initialization iteration_wf.parameters.fix_spin_contamination = False @@ -413,22 +423,19 @@ class DeltaSCFIterationWorkflow(Workflow): def __init__(self, *args, variational_orbital_files: Dict[str, Tuple[calculators.KoopmansCPCalculator, str]], previous_n_electron_calculation=calculators.KoopmansCPCalculator, i_sc: int, - alpha_indep_calcs: List[calculators.KoopmansCPCalculator], restart_from_old_ki: bool = False, - **kwargs) -> None: + alpha_indep_calcs: List[calculators.KoopmansCPCalculator], **kwargs) -> None: super().__init__(*args, **kwargs) self._variational_orbital_files = variational_orbital_files self._previous_n_electron_calculation = previous_n_electron_calculation self._i_sc = i_sc self._alpha_indep_calcs = alpha_indep_calcs - self._restart_from_old_ki = restart_from_old_ki # Set a more instructive name self.name = 'Iteration_' + str(i_sc) def _run(self): # Do a KI/KIPZ calculation with the updated alpha values - restart_from_wannier_pwscf = True if self.parameters.init_orbitals in [ - 'mlwfs', 'projwfs'] and not self._restart_from_old_ki and self._i_sc == 1 else None + restart_from_wannier_pwscf = 'evc_occupied1.dat' in self._variational_orbital_files if self.parameters.task in ['trajectory', 'convergence_ml'] and self.ml.input_data_for_ml_model == 'orbital_density': print_real_space_density = True else: @@ -441,13 +448,17 @@ def _run(self): # Link the temporary files from the previous calculation previous_calc = self._previous_n_electron_calculation assert isinstance(previous_calc, utils.HasDirectoryAttr) - self.link(previous_calc, ndw_directory(previous_calc), trial_calc, - ndr_directory(trial_calc), recursive_symlink=True) + self.link(previous_calc, previous_calc.write_directory, trial_calc, + trial_calc.read_directory, recursive_symlink=True) for filename, src_tuple in self._variational_orbital_files.items(): - self.link(*src_tuple, trial_calc, ndr_directory(trial_calc) / + self.link(*src_tuple, trial_calc, trial_calc.read_directory / 'K00001' / filename, symlink=True, overwrite=True) + if self.parameters.functional == 'kipz' and not all(self.atoms.pbc): + # For the first KIPZ trial calculation, do the innerloop + trial_calc.parameters.do_innerloop = True + # Run the calculation self.run_calculator(trial_calc, enforce_spin_symmetry=self.parameters.fix_spin_contamination) alpha_dep_calcs = [trial_calc] @@ -487,7 +498,7 @@ def _run(self): else: orb_range = f'{skipped_orbitals[0]}-{skipped_orbitals[-1]}' if print_headings: - self.print(f'Orbitals {orb_range}', style='subheading') + self.print(f'Orbitals {orb_range}', style='heading') if print_headings: self.print(f'Skipping; will use the screening parameter of an equivalent orbital') skipped_orbitals = [] @@ -641,18 +652,18 @@ def _run(self): add_to_spin_up=(self.band.spin == 0)) if calc.parameters.ndr == self._trial_calc.parameters.ndw: - self.link(self._trial_calc, ndw_directory(self._trial_calc), - calc, ndr_directory(calc), recursive_symlink=True) + self.link(self._trial_calc, self._trial_calc.write_directory, + calc, calc.read_directory, recursive_symlink=True) if calc_type in ['dft_n+1', 'kipz_n+1']: - dummy_calc = self.calculations[-1] - print_calc = self.calculations[-2] + dummy_calc: calculators.KoopmansCPCalculator = self.calculations[-1] + print_calc: calculators.KoopmansCPCalculator = self.calculations[-2] self.link(dummy_calc, dummy_calc.parameters.outdir, calc, calc.parameters.outdir, recursive_symlink=True) # Copying of evcfixed_empty.dat to evc_occupied.dat for ispin in range(1, 3): - self.link(print_calc, ndw_directory(print_calc) / f'K00001/evcfixed_empty{ispin}.dat', - calc, ndr_directory(calc) / f'K00001/evc_occupied{ispin}.dat', symlink=True, overwrite=True) + self.link(print_calc, print_calc.write_directory / f'K00001/evcfixed_empty{ispin}.dat', + calc, calc.read_directory / f'K00001/evc_occupied{ispin}.dat', symlink=True, overwrite=True) # Run kcp.x self.run_calculator(calc) @@ -1066,23 +1077,10 @@ def internal_new_kcp_calculator(workflow, return calc -class InitializationOutput(OutputModel): - variational_orbital_files: Dict[str, Tuple[calculators.KoopmansCPCalculator, str]] - final_calc: calculators.KoopmansCPCalculator - wannier_hamiltonian_files: Dict[Tuple[str, str | None], FilePointer] | None = None - - class Config: - arbitrary_types_allowed = True - - class InitializationWorkflow(Workflow): - output_model = InitializationOutput - outputs: InitializationOutput - - def __init__(self, restart_from_old_ki: bool = False, **kwargs): - self._restart_from_old_ki = restart_from_old_ki - super().__init__(**kwargs) + output_model = KoopmansDSCFOutputs # type: ignore + outputs: KoopmansDSCFOutputs def _run(self) -> None: # Import these here so that if these have been monkey-patched, we get the monkey-patched version @@ -1093,51 +1091,7 @@ def _run(self) -> None: wannier_hamiltonian_files: Dict[Tuple[str, str | None], FilePointer] | None = None - if self._restart_from_old_ki: - - raise NotImplementedError( - 'Restarting from an old KI calculation has not yet been converted to the new mode of operating') - self.print('Copying the density and orbitals from a pre-existing KI calculation') - - # Read the .cpi file to work out the value for ndw - calc = calculators.KoopmansCPCalculator.fromfile('init/ki_init') - - # Move the old save directory to correspond to ndw_final, using pz_innerloop_init to work out where the - # code will expect the tmp files to be - old_savedir = ndw_directory(calc) - savedir = Path(f'{self.new_kcp_calculator("pz_innerloop_init").parameters.outdir}') / \ - f'{calc.parameters.prefix}_{ndw_final}.save' - if not old_savedir.is_dir(): - raise ValueError(f'{old_savedir} does not exist; a previous ' - 'and complete KI calculation is required ' - 'to restart from an old KI calculation"') - if savedir.is_dir(): - shutil.rmtree(savedir.as_posix()) - - # Use the chdir construct in order to create the directory savedir if it does not already exist and is - # nested - with utils.chdir(savedir): - utils.system_call(f'rsync -a {old_savedir}/ .') - - # Check that the files defining the variational orbitals exist - savedir /= 'K00001' - files_to_check = [Path('init/ki_init.cpo'), savedir / 'evc01.dat', savedir / 'evc02.dat'] - - for ispin in range(2): - if calc.has_empty_states(ispin): - files_to_check.append(savedir / f'evc0_empty{ispin + 1}.dat') - - for fname in files_to_check: - if not fname.is_file(): - raise ValueError(f'Could not find {fname}') - - if not calc.is_complete(): - raise ValueError('init/ki_init.cpo is incomplete so cannot be used ' - 'to initialize the density and orbitals') - - self.calculations.append(calc) - - elif self.parameters.init_orbitals in ['mlwfs', 'projwfs'] or \ + if self.parameters.init_orbitals in ['mlwfs', 'projwfs'] or \ (all(self.atoms.pbc) and self.parameters.init_orbitals == 'kohn-sham'): # Wannier functions using pw.x, wannier90.x and pw2wannier90.x (pw.x only for Kohn-Sham states) wannier_workflow = workflows.WannierizeWorkflow.fromparent(self) @@ -1187,7 +1141,7 @@ def _run(self) -> None: self.link(dummy_calc, dummy_calc.parameters.outdir, calc, calc.parameters.outdir) for filename, filepointer in fold_workflow.outputs.kcp_files.items(): self.link(filepointer.parent, filepointer.name, calc, - ndr_directory(calc) / 'K00001' / filename, symlink=True) + calc.read_directory / 'K00001' / filename, symlink=True) self.run_calculator(calc) # Check the consistency between the PW and CP band gaps @@ -1262,8 +1216,8 @@ def _run(self) -> None: prefix = 'evc' else: prefix = 'evc0' - ndw_dir = ndw_directory(calc) / 'K00001' - ndr_dir = ndr_directory(calc) / 'K00001' + ndw_dir = calc.write_directory / 'K00001' + ndr_dir = calc.read_directory / 'K00001' for ispin in range(1, 3): if self.parameters.init_orbitals in ['mlwfs', 'projwfs'] or \ (all(self.atoms.pbc) and self.parameters.init_orbitals == 'kohn-sham'): @@ -1274,7 +1228,7 @@ def _run(self) -> None: variational_orbitals[f'evc0_empty{ispin}.dat'] = (calc, f'{ndw_dir}/{prefix}_empty{ispin}.dat') # If fixing spin contamination, copy the spin-up variational orbitals to the spin-down channel - if not self._restart_from_old_ki and self.parameters.fix_spin_contamination: + if self.parameters.fix_spin_contamination: for spin_up_file in ['evc01.dat', 'evc0_empty1.dat', 'evc_occupied1.dat']: if spin_up_file in variational_orbitals: variational_orbitals[spin_up_file.replace('1', '2')] = variational_orbitals[spin_up_file] @@ -1283,11 +1237,3 @@ def _run(self) -> None: wannier_hamiltonian_files=wannier_hamiltonian_files) return - - -def ndr_directory(calc: calculators.KoopmansCPCalculator) -> Path: - return Path(f'{calc.parameters.outdir}/{calc.parameters.prefix}_{calc.parameters.ndr}.save') - - -def ndw_directory(calc: calculators.KoopmansCPCalculator) -> Path: - return Path(f'{calc.parameters.outdir}/{calc.parameters.prefix}_{calc.parameters.ndw}.save') diff --git a/src/koopmans/workflows/_singlepoint.py b/src/koopmans/workflows/_singlepoint.py index d3baacf0f..a0220e26b 100644 --- a/src/koopmans/workflows/_singlepoint.py +++ b/src/koopmans/workflows/_singlepoint.py @@ -15,6 +15,7 @@ from koopmans import utils from koopmans.calculators import ProjwfcCalculator +from koopmans.files import FilePointer from koopmans.outputs import OutputModel from ._workflow import Workflow @@ -88,19 +89,12 @@ def _run(self) -> None: # if 'all', create subdirectories and run functionals = ['ki', 'pkipz', 'kipz'] - # Make separate directories for KI, pKIPZ, and KIPZ - for functional in functionals: - if self.parameters.from_scratch and os.path.isdir(functional): - utils.system_call(f'rm -r {functional}') - if not os.path.isdir(functional): - utils.system_call(f'mkdir {functional}') - if self.parameters.alpha_from_file: - utils.system_call('cp file_alpharef*.txt ki/') + utils.warn('Need to make sure alpharef files are copied over') + # utils.system_call('cp file_alpharef*.txt ki/') + ki_workflow = None for functional in functionals: - self.print(f'\n{functional.upper().replace("PKIPZ", "pKIPZ")} calculation', style='heading') - # For pKIPZ/KIPZ, use KI as a starting point restart_from_old_ki = (functional == 'kipz') @@ -114,56 +108,61 @@ def _run(self) -> None: calculate_alpha = self.parameters.calculate_alpha # Create a KC workflow for this particular functional + if functional != 'ki': + assert ki_workflow is not None + variational_orbital_files = ki_workflow.outputs.variational_orbital_files + previous_ki_calc = ki_workflow.outputs.final_calc + else: + variational_orbital_files = None + previous_ki_calc = None + kc_workflow = KoopmansDSCFWorkflow.fromparent(self, functional=functional, - restart_from_old_ki=restart_from_old_ki, + initial_variational_orbital_files=variational_orbital_files, redo_smooth_dft=redo_smooth_dft, + previous_ki_calc=previous_ki_calc, calculate_alpha=calculate_alpha) + kc_workflow.name += ' ' + functional.upper().replace("PKIPZ", "pKIPZ") + + if functional == 'ki': + # Save the KI workflow for later + ki_workflow = kc_workflow # Transform to the supercell if functional == 'kipz': kc_workflow.primitive_to_supercell() # Run the workflow - if functional == 'pkipz' and self.parameters.from_scratch: - # We want to run pKIPZ with from_scratch = False, but don't want this to be inherited - kc_workflow.run(subdirectory=functional, from_scratch=False) - else: - kc_workflow.run(subdirectory=functional) + kc_workflow.run(subdirectory=functional) # Provide the pKIPZ and KIPZ calculations with a KI starting point - if functional == 'ki': - if self.parameters.from_scratch: - for directory in ['pkipz', 'kipz']: - if os.listdir(directory): - utils.system_call(f'rm -rf {directory}') - - # pKIPZ - for dir in ['init', 'calc_alpha', 'TMP-CP']: - src = Path(f'ki/{dir}/') - if src.is_dir(): - utils.system_call(f'rsync -a {src} pkipz/') - for f in ['ki_final.cpi', 'ki_final.cpo', 'ki_final.ham.pkl', 'ki_final.bare_ham.pkl']: - file = Path(f'ki/final/{f}') - if file.is_file(): - utils.system_call(f'rsync -a {file} pkipz/final/') - if all(self.atoms.pbc) and self.calculator_parameters['ui'].do_smooth_interpolation: - if not Path('pkipz/postproc').is_dir(): - utils.system_call('mkdir pkipz/postproc') - for dir in ['wannier', 'TMP', 'pdos']: - if Path(f'ki/postproc/{dir}').exists(): - utils.system_call(f'rsync -a ki/postproc/{dir} pkipz/postproc/') - - # KIPZ - utils.system_call('rsync -a ki/final/ kipz/init/') - utils.system_call('mv kipz/init/ki_final.cpi kipz/init/ki_init.cpi') - utils.system_call('mv kipz/init/ki_final.cpo kipz/init/ki_init.cpo') - if self.parameters.init_orbitals in ['mlwfs', 'projwfs']: - utils.system_call('rsync -a ki/init/wannier kipz/init/') - if all(self.atoms.pbc) and self.calculator_parameters['ui'].do_smooth_interpolation: - # Copy over the smooth PBE calculation from KI for KIPZ to use - for dir in ['wannier', 'TMP', 'pdos']: - if Path(f'ki/postproc/{dir}').exists(): - utils.system_call(f'rsync -a ki/postproc/{dir} kipz/postproc/') + # if functional == 'ki': + # # pKIPZ + # for dir in ['init', 'calc_alpha', 'TMP-CP']: + # src = Path(f'ki/{dir}/') + # if src.is_dir(): + # utils.system_call(f'rsync -a {src} pkipz/') + # for f in ['ki_final.cpi', 'ki_final.cpo', 'ki_final.ham.pkl', 'ki_final.bare_ham.pkl']: + # file = Path(f'ki/final/{f}') + # if file.is_file(): + # utils.system_call(f'rsync -a {file} pkipz/final/') + # if all(self.atoms.pbc) and self.calculator_parameters['ui'].do_smooth_interpolation: + # if not Path('pkipz/postproc').is_dir(): + # utils.system_call('mkdir pkipz/postproc') + # for dir in ['wannier', 'TMP', 'pdos']: + # if Path(f'ki/postproc/{dir}').exists(): + # utils.system_call(f'rsync -a ki/postproc/{dir} pkipz/postproc/') + + # # KIPZ + # utils.system_call('rsync -a ki/final/ kipz/init/') + # utils.system_call('mv kipz/init/ki_final.cpi kipz/init/ki_init.cpi') + # utils.system_call('mv kipz/init/ki_final.cpo kipz/init/ki_init.cpo') + # if self.parameters.init_orbitals in ['mlwfs', 'projwfs']: + # utils.system_call('rsync -a ki/init/wannier kipz/init/') + # if all(self.atoms.pbc) and self.calculator_parameters['ui'].do_smooth_interpolation: + # # Copy over the smooth PBE calculation from KI for KIPZ to use + # for dir in ['wannier', 'TMP', 'pdos']: + # if Path(f'ki/postproc/{dir}').exists(): + # utils.system_call(f'rsync -a ki/postproc/{dir} kipz/postproc/') else: # self.functional != all and self.method != 'dfpt' diff --git a/src/koopmans/workflows/_workflow.py b/src/koopmans/workflows/_workflow.py index 3bdfd276c..ba2048eee 100644 --- a/src/koopmans/workflows/_workflow.py +++ b/src/koopmans/workflows/_workflow.py @@ -376,9 +376,8 @@ def run(self, subdirectory: Optional[str] = None, from_scratch: Optional[bool] = if self.parent: with self._parent_context(subdirectory, from_scratch): - self.print(f'{self.name}', style='heading') + self.print(f'- {self.name}', style='heading') self._run() - self.print() else: self._run() @@ -839,7 +838,7 @@ def _pre_run_calculator(self, qe_calc: calculators.Calc) -> bool: is_complete = self.load_old_calculator(qe_calc) if is_complete: if not self.silent: - self.print(f'Not running {os.path.relpath(qe_calc.directory)} as it is already complete') + self.print(f'- Not running {os.path.relpath(qe_calc.directory)} as it is already complete') # Check the convergence of the calculation qe_calc.check_convergence() @@ -880,7 +879,7 @@ def _run_calculators(self, calcs: List[calculators.Calc]) -> None: for calc in calcs: if not self.silent: dir_str = os.path.relpath(calc.directory) - self.print(f'Running {dir_str}...', end='', flush=True) + self.print(f'- Running {dir_str}...', end='', flush=True) try: calc.calculate() @@ -933,7 +932,7 @@ def run_process(self, process: processes.Process): self._step_counter += 1 process.directory = Path(f'{self._step_counter:02}-{process.name}').resolve() - self.print('Running ' + os.path.relpath(process.directory) + '...', end='', flush=True) + self.print('- Running ' + os.path.relpath(process.directory) + '...', end='', flush=True) process.run() self.print(' done') self.processes.append(process) @@ -991,20 +990,16 @@ def link(self, src_calc: utils.HasDirectoryAttr | None, src_path: Path | str, de dest_calc.link_file(src_calc, src_path, dest_path, symlink=symlink, # type: ignore recursive_symlink=recursive_symlink, overwrite=overwrite) - def print(self, text: str = '', style: str = 'body', **kwargs: Any): + def print(self, text: str = '', style='body', bold=False, **kwargs: Any): + if bold or style == 'heading': + text = '\033[1m' + text + '\033[0m' if style == 'body': - utils.indented_print(str(text), self.print_indent + 2, **kwargs) - else: - if style == 'heading': - underline = '=' - elif style == 'subheading': - underline = '-' - else: - raise ValueError(f'Invalid choice "{style}" for style; must be heading/subheading/body') + utils.indented_print(text, self.print_indent + 2, **kwargs) + elif style == 'heading': assert kwargs.get('end', '\n') == '\n' - utils.indented_print() - utils.indented_print(str(text), self.print_indent, **kwargs) - utils.indented_print(underline * len(text), self.print_indent, **kwargs) + utils.indented_print(text, self.print_indent, **kwargs) + else: + raise ValueError(f'Invalid choice "{style}" for style; must be heading/subheading/body') @contextmanager def _parent_context(self, subdirectory: Optional[str] = None, @@ -1129,6 +1124,7 @@ def fromjson(cls, fname: str, override: Dict[str, Any] = {}): # Define the name of the workflow using the name of the json file wf.name = fname.replace('.json', '') + return wf @classmethod @@ -1255,7 +1251,7 @@ def _fromjsondct(cls, bigdct: Dict[str, Any], override: Dict[str, Any] = {}): return wf def print_header(self): - print(header()) + print("\033[1m" + header() + "\033[0m") def print_bib(self): relevant_references = BibliographyData() @@ -1306,6 +1302,8 @@ def print_preamble(self): self.print_bib() + self.print() + def print_conclusion(self): from koopmans.io import write @@ -1316,7 +1314,7 @@ def print_conclusion(self): write(self, self.name + '.kwf') # Print farewell message - print('\n Workflow complete') + print('\n \033[1mWorkflow complete\033[0m') def toinputjson(self) -> Dict[str, Dict[str, Any]]: