def create_input(chemical_symbol: str, exchange_correlation_code: str, calculation_code: str, maximum_iterations: int, filename: str, quiet: bool): """ Create the input file for the run-atomic command. Requires: CHEMICAL_SYMBOL: Chemical symbol of the atom (H, He, Na, Li...). Check the list of available atoms in the docs Returns: INP: The input file for run-atomic command """ welcome_message("minushalf") if quiet: logger.remove() logger.add(sys.stdout, level="ERROR") input_file = InputFile.minimum_setup(chemical_symbol.capitalize(), exchange_correlation_code, maximum_iterations, calculation_code) logger.info("Creating INP file") input_file.to_file(filename) end_message()
def band_gap(software: str, base_path: str) -> None: """Uses output files from softwares that perform ab initio calculations to provide the locations of VBM, CBM and the Gap value in electronvolts.The names of the files required for each software are listed below, it is worth mentioning that their names cannot be modified. VASP: PROCAR, EIGENVAL, vasprun.xml """ welcome_message("minushalf") softwares = {"VASP": Vasp()} factory = softwares[software.upper()] eigenvalues = factory.get_eigenvalues(base_path=base_path) fermi_energy = factory.get_fermi_energy(base_path=base_path) atoms_map = factory.get_atoms_map(base_path=base_path) num_bands = factory.get_number_of_bands(base_path=base_path) band_projection_file = factory.get_band_projection_class( base_path=base_path) band_structure = BandStructure(eigenvalues, fermi_energy, atoms_map, num_bands, band_projection_file) gap_report = band_structure.band_gap() click.echo(gap_report["vbm"]) click.echo(gap_report["cbm"]) click.echo("Gap: {:.3f}eV".format(gap_report["gap"])) end_message()
def cbm_character(software: str, base_path: str) -> None: """Uses output files from softwares that perform ab initio calculations to discover the first conduction band (CBM) and extract, in percentage, its character corresponding to each orbital type (s, p, d, ... ). The names of the files required for each software are listed below, it is worth mentioning that their names cannot be modified. VASP: PROCAR, EIGENVAL, vasprun.xml """ welcome_message("minushalf") softwares = {"VASP": Vasp()} factory = softwares[software.upper()] eigenvalues = factory.get_eigenvalues(base_path=base_path) fermi_energy = factory.get_fermi_energy(base_path=base_path) atoms_map = factory.get_atoms_map(base_path=base_path) num_bands = factory.get_number_of_bands(base_path=base_path) band_projection_file = factory.get_band_projection_class( base_path=base_path) band_structure = BandStructure(eigenvalues, fermi_energy, atoms_map, num_bands, band_projection_file) cbm_projection = band_structure.cbm_projection() normalized_df = projection_to_df(cbm_projection) click.echo(normalized_df.to_markdown()) end_message()
def run_atomic(quiet: bool): """ Run the atomic program. The atomic program used is a version of the ATOM modified by the professor Luiz Guimarães Ferreira to be used in the DFT -1/2 method. Requires: INP: The input file for the calculation. Returns: VTOTAL.ae: Contains the atom potential. OUT: Contains detailed information about the run. AECHARGE: Contains in four columns values of r, the “up” and “down” parts of the total charge density, and the total core charge density (the charges multiplied by 4πr 2 ). CHARGE: is exactly identical to AECHARGE and is generated for backwards compatibility. RHO: Like CHARGE, but without the 4πr 2 factor AEWFNR0...AEWFNR3: All-electron valence wavefunctions as function of radius, for s, p, d, and f valence orbitals (0, 1, 2, 3, respectively — some channels might not be available). They include a factor of r, the s orbitals also going to zero at the origin. """ welcome_message("minushalf") if quiet: logger.remove() logger.add(sys.stdout, level="ERROR") logger.info("Run atomic program") try: atomic_program.run() except: raise Exception('Problems in atomic program execution') logger.info("Atomic program finished execution.") if not os.path.exists('./VTOTAL1'): logger.warning( "VTOTAL1 not found, thus VTOTAL.ae will not be generated") else: logger.info("Changing VTOTAL1 to VTOTAL.ae") os.rename("VTOTAL1", "VTOTAL.ae") end_message()
def occupation(orbital_quantum_number: str, occupation_percentage: str, quiet: bool): """ Perform fractional occupation on the atom and generate the potential for this occupation. The occupation can subtract any fraction of the electron between 0 and 0.5, half occupation is the default. Requires: ORBITAL_QUANTUM_NUMBER: A string that defines the orbital(s) in which the occupation will be made, it can assume four values: (0: s | 1: p | 2: d | 3: f). if going to modify multiple orbitals, pass a string with numbers separated by commas : ("0,1,2,3"). OCCUPATION_PERCENTAGE: A string that defines percentual of half an electron to be used in the occupation. The default is 100%, which states for 0.5e. For multiple occupations in different orbitals, pass a string separated by commas ("100,50,40,100"). For simplicity, to avoid the excessive repetition of the number 100, just replace the number with * ("*,30,*"). If this argument is not used, the occupation of half an electron will be made for all orbitals assed as arguments. INP: Input file of the run-atomic command. Returns: INP_OCC : Input file modified for fractional occupation. INP.ae: A copy of the input file for the calculation. VTOTAL_OCC: Contains the atom potential for fractional occupation. OUT: Contains detailed information about the run. AECHARGE: Contains in four columns values of r, the “up” and “down” parts of the total charge density, and the total core charge density (the charges multiplied by 4πr 2 ). CHARGE: is exactly identical to AECHARGE and is generated for backwards compatibility. RHO: Like CHARGE, but without the 4πr 2 factor AEWFNR0...AEWFNR3: All-electron valence wavefunctions as function of radius, for s, p, d, and f valence orbitals (0, 1, 2, 3, respectively — some channels might not be available). They include a factor of r, the s orbitals also going to zero at the origin. """ welcome_message("minushalf") if quiet: logger.remove() logger.add(sys.stdout, level="ERROR") input_file = InputFile.from_file() logger.info("Adding minus one half electron correction on INP") try: quantum_numbers = np.array(orbital_quantum_number.split(","), dtype=int) except ValueError as wrong_input: raise ValueError( "Invalid value for secondary quantum number") from wrong_input for quantum_number in quantum_numbers: if quantum_number < 0 or quantum_number > 3: raise ValueError("Invalid value for secondary quantum number") percentuals = np.ones(len(quantum_numbers)) * 100 for index, percentual in enumerate(occupation_percentage.split(",")): if percentual != "*": if float(percentual) < 0 or float(percentual) > 100: raise ValueError("Invalid value for occupation percentual") percentuals[index] = float(percentual) for quantum_number, percentual in zip(quantum_numbers, percentuals): input_file.electron_occupation(0.5 * (percentual / 100), quantum_number) os.rename('INP', 'INP.ae') input_file.to_file() logger.info("Run atomic program") try: atomic_program.run() except Exception as program_fail: raise Exception('Problems in atomic program') from program_fail logger.info("Atomic program finished execution.") if not os.path.exists('./VTOTAL1'): raise FileNotFoundError("Problems in INP file generation") logger.info("Changing VTOTAL1 to VTOTAL_OCC") os.rename("VTOTAL1", "VTOTAL_OCC") logger.info("Changing INP to INP_OCC") os.rename('INP', 'INP_OCC') end_message()
def correct_potfile( quiet: bool, base_potfile_path: str, vtotal_path: str, vtotal_occupied_path: str, software: str, correction: str, cut: str, amplitude: float, ) -> None: """Generate the occupied atomic potential file used for ab initio calculations. Requires: VTOTAL.ae: potential of the atom with all electrons VTOTAL_OCC: potential of the occupied atom INP_OCC: Input file for the run-atomic command of the occupied atom The command also needs the potential files used by the chosen software: VASP: POTCAR (This name can't be changed) Generates: POTFILEcut${CUT_VALUE} (If amplitude is equal to 1.0) POTFILEcut${CUT_VALUE}A${AMPLITUDE_VALUE} (If amplitude is different from 1.0) """ welcome_message("minushalf") if quiet: logger.remove() logger.add(sys.stdout, level="ERROR") softwares = {"VASP": Vasp()} factory = softwares[software.upper()] vtotal = Vtotal.from_file(vtotal_path) vtotal_occ = Vtotal.from_file(vtotal_occupied_path) potential_file = factory.get_potential_class(base_path=base_potfile_path) atomic_potential = AtomicPotential(vtotal, vtotal_occ, potential_file) cut_numbers = parse_cut(cut) is_conduction = False if correction.upper() == "CONDUCTION": is_conduction = True for new_cut in cut_numbers: logger.info("Correcting POTFILE for cut = {:.2f} a.u".format(new_cut)) new_potential = atomic_potential.correct_potential( new_cut, amplitude, is_conduction) atomic_potential.correct_file(new_potential, new_cut, amplitude, is_conduction) end_message()
def execute(quiet: bool): """ Uses the Nelder-Mead method to find the optimal values for the CUT(S) and, finally, find the corrected Gap value. This command uses external software to perform ab initio calculations, so it must be installed in order to perform the command. Check the docs for an list of the softwares supported by the CLI. Requires: minushalf.yaml : Parameters file. Check the docs for a more detailed description. ab_initio_files: Files needed to perform the ab initio calculations. They must be in the same directory as the input file minushalf.yaml potential_folder: Folder with the potential files for each atom in the crystal. The files must be named in the following pattern ${POTENTIAL_FILE_NAME}.${LOWERCASE_CHEMICAL_SYMBOL} Returns: minushalf_results.dat : File that contains the optimal values of the cuts and the final value of the Gap. corrected_valence_potfiles: Potential files corrected with opti-mum valence cuts. corrected_conduction_potfiles: Potential files corrected with optimum conduction cuts. """ welcome_message("minushalf") if quiet: logger.remove() logger.add(sys.stdout, level="ERROR") ## Read yaml file logger.info("Reading minushalf.yaml file") minushalf_yaml = MinushalfYaml.from_file() correction_factory_chooser = {Softwares.vasp.value: DFTCorrection} software_factory_chooser = {Softwares.vasp.value: Vasp()} software_name = minushalf_yaml.get_software_name() correction = correction_factory_chooser[software_name] software_factory = software_factory_chooser[software_name] ## Makes abinition calculation logger.info("Running ab initio calculations") software_configurations = minushalf_yaml.get_software_configurations_params( ) runner = software_factory.get_runner(**software_configurations) runner.run() ## Makes root folder logger.info("Make potfiles folder") root_folder = "mkpotfiles" if os.path.exists(root_folder): shutil.rmtree(root_folder) os.mkdir(root_folder) ## get atoms list logger.info("Get atoms list") atoms = get_atoms_list(software_factory) ## amplitude logger if not np.isclose(minushalf_yaml.get_amplitude(), CorrectionDefaultParams.amplitude.value): logger.warning( "Amplitude value is different from 1.0. This is not recommended unless you know exactly what you are doing." ) valence_options = get_valence_correction_params(minushalf_yaml, software_factory, atoms=atoms, runner=runner, root_folder=root_folder) conduction_options = get_conduction_correction_params( minushalf_yaml, software_factory, atoms=atoms, runner=runner, root_folder=root_folder) correction_code = minushalf_yaml.get_correction_code() logger.info("Doing corrections") gap = None valence_cuts = None conduction_cuts = None if "v" in correction_code: valence_correction = correction(**valence_options) valence_cuts, valence_gap = valence_correction.execute() gap = valence_gap make_minushalf_results(valence_cuts=valence_cuts, gap=valence_gap) if "c" in correction_code: conduction_correction = correction(**conduction_options) conduction_cuts, conduction_gap = conduction_correction.execute() gap = conduction_gap make_minushalf_results(valence_cuts=valence_cuts, gap=gap, conduction_cuts=conduction_cuts) end_message()