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 _generate_atom_potential( self, base_path: str, symbol: str, ) -> None: """ Make a dir with the atoms name,generate the input file and run the atomic program Args: symbol (str): Atom symbol base_path (str): Path to mkpotcar{symbol} """ folder_path = os.path.join(base_path, "pseudopotential") if os.path.exists(folder_path): shutil.rmtree(folder_path) os.mkdir(folder_path) input_file = InputFile.minimum_setup( symbol, self.exchange_correlation_type, self.max_iterations, self.calculation_code, ) input_file.to_file(os.path.join(folder_path, "INP")) process = Popen(['minushalf', 'run-atomic', "--quiet"], stdout=PIPE, stderr=PIPE, cwd=folder_path) _, stderr = process.communicate() if stderr: raise Exception("Call to atomic program failed")
def test_chemical_symbol(): """ Test if the chemical symbol of the predetermined will raises exception in the class construction. """ for element in PeriodicTable: InputFile("pb", "ae", element.value, "", 1, 1, [])
def test_from_file(file_path): """ Test from_file path to generate instances of the InputFile class with an INP text file """ for element in ElectronicDistribution: symbol = str(element) path = file_path(f"{symbol}/INP_COMMENTED") inp_from_file = InputFile.from_file(path) inp_minimum_setup = InputFile.minimum_setup(symbol, 'pb') from_file_string = "".join(inp_from_file.to_stringlist()) minimum_setup_string = "".join(inp_minimum_setup.to_stringlist()) assert from_file_string == minimum_setup_string
def test_occupation_with_default_params_Yb(): """ Test occupation command in the p orbital of ytterbium """ inp_oxygen = InputFile.minimum_setup("Yb", "pb") lines = inp_oxygen.to_stringlist() runner = CliRunner() with runner.isolated_filesystem(): with open("INP", "w") as file: file.writelines(lines) result = runner.invoke(occupation, ["0", "50"]) assert result.exit_code == 0 occupied_file = InputFile.from_file("INP_OCC") for orbital in occupied_file.valence_orbitals: if orbital["l"] == 0: assert np.isclose(orbital["occupation"][0], 1.75) == True
def test_exchange_and_correlation_functional_spin(): """ Test if the factor of correlation and exchange with spin factor will raises exception in the class construction. """ exchange_correlation_types = [ "cas", "wis", "hls", "gls", "bhs", "pbs", "rps", "rvs", "bls" ] for type_correlation in exchange_correlation_types: InputFile(type_correlation, "ae", "Ge", "", 1, 1, [])
def test_exchange_and_correlation_functional_relativistic(): """ Test if the factor of correlation and exchange with relativistic factor will raises exception in the class construction. """ exchange_correlation_types = [ "car", "wir", "hlr", "glr", "bhr", "pbr", "rpr", "rvr", "blr" ] for type_correlation in exchange_correlation_types: InputFile(type_correlation, "ae", "Ge", "", 1, 1, [])
def test_electron_occupation_na(): """ Test occupation in INP file for Na """ inp = InputFile.minimum_setup('Na', 'pb') inp.electron_occupation(0.5, 0) for orbital in inp.valence_orbitals: if orbital["l"] == 0: assert np.isclose(orbital["occupation"][0], 0.5)
def test_exchange_and_correlation_functional(): """ Test if the factor of correlation and exchange will raises exception in the class construction. """ exchange_correlation_types = [ "ca", "wi", "hl", "gl", "bh", "pb", "rp", "rv", "bl" ] for type_correlation in exchange_correlation_types: InputFile(type_correlation, "ae", "Ge", "", 1, 1, [])
def test_electron_occupation_au(): """ Test occupation in INP file for Au """ inp = InputFile.minimum_setup('Au', 'pb') inp.electron_occupation(0.1, 0) inp.electron_occupation(0.4, 2) for orbital in inp.valence_orbitals: if orbital["l"] == 0: assert np.isclose(orbital["occupation"][0], 0.9) elif orbital["l"] == 2: assert np.isclose(orbital["occupation"][0], 9.6)
def test_failing_occupation_with_default_params_O(): """ Test passing invalid secondary quantum number """ inp_oxygen = InputFile.minimum_setup("O", "pb") lines = inp_oxygen.to_stringlist() runner = CliRunner() with runner.isolated_filesystem(): with open("INP", "w") as file: file.writelines(lines) result = runner.invoke(occupation, ["0,-1", "*,*"]) assert result.exit_code == 1
def test_failing_occupation_with_default_params_Au(): """ Test wrong occuparion percentual passed to command """ inp_oxygen = InputFile.minimum_setup("Au", "pb") lines = inp_oxygen.to_stringlist() runner = CliRunner() with runner.isolated_filesystem(): with open("INP", "w") as file: file.writelines(lines) result = runner.invoke(occupation, ["2", "-1"]) assert result.exit_code == 2
def test_failing_occupation_with_default_params_Yb(): """ Test invalid occupation number for Yb """ inp_oxygen = InputFile.minimum_setup("Yb", "pb") lines = inp_oxygen.to_stringlist() runner = CliRunner() with runner.isolated_filesystem(): with open("INP", "w") as file: file.writelines(lines) result = runner.invoke(occupation, ["0,3", "50,101"]) assert result.exit_code == 1
def test_electron_occupation_yb(): """ Test occupation in INP file for Yb """ inp = InputFile.minimum_setup('Yb', 'pb') inp.electron_occupation(0.5, 0) inp.electron_occupation(0.5, 3) for orbital in inp.valence_orbitals: if orbital["l"] == 0: assert np.isclose(orbital["occupation"][0], 1.5) elif orbital["l"] == 3: assert np.isclose(orbital["occupation"][0], 13.5)
def test_electron_occupation_si(): """ Test occupation in INP file for Si """ inp = InputFile.minimum_setup('Si', 'pb') inp.electron_occupation(0.4, 0) inp.electron_occupation(0.3, 1) for orbital in inp.valence_orbitals: if orbital["l"] == 0: assert np.isclose(orbital["occupation"][0], 1.6) elif orbital["l"] == 1: assert np.isclose(orbital["occupation"][0], 1.7)
def test_electron_occupation_ag(): """ Test occupation in INP file for Ag """ inp = InputFile.minimum_setup('Ag', 'pb') inp.electron_occupation(0.5, 2) inp.electron_occupation(0.2, 0) for orbital in inp.valence_orbitals: if orbital["l"] == 2: assert np.isclose(orbital["occupation"][0], 9.5) elif orbital["l"] == 0: assert np.isclose(orbital["occupation"][0], 0.8)
def test_minimum_setup(file_path): """ Test minimum setup function to generate INP files with the elements symbol and the functional of exchange and exchange correlation """ for element in ElectronicDistribution: symbol = str(element) path = file_path(f"{symbol}/INP") inp = InputFile.minimum_setup(symbol, 'pb') with open(path, "r") as file: assert file.read() == "".join(inp.to_stringlist())
def test_atomic_with_all_elements(): """ Test if the atomic run produce the correct output for elements in the periodic table """ runner = CliRunner() for element in ElectronicDistribution: inp = InputFile.minimum_setup(str(element), "pb") lines = inp.to_stringlist() with runner.isolated_filesystem(): with open("INP", "w") as file: file.writelines(lines) result = runner.invoke(run_atomic, []) assert result.exit_code == 0 assert os.path.exists("VTOTAL0") == True assert os.path.exists("VTOTAL.ae") == True assert os.path.exists("VTOTAL2") == True assert os.path.exists("VTOTAL3") == True
def test_pass_wrong_exchange_functional(): """ Pass wrong correlation and exchange functional and expect to fail """ InputFile('sd', 'ae', 'Ge', "", 1, 1, [])
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 test_pass_wrong_calculation_code(): """ Pass wrong calculation code and expect to fail """ InputFile('pb', 'ee', 'Ge', "", 1, 1, [])
def test_pass_wrong_chemical_symbol(): """ Pass wrong chemical symbol and expect to fail """ InputFile('pb', 'ae', 'Ss', "", 1, 1, [])