'
+ )
+ new_inputschema_file = Path("input.xsd")
+ new_inputschema_file.write_text("\n".join(content))
+
+ file_list = [inputschemaextentions_path, new_inputschema_file]
+
+ # handling of all other included schema files
+ for schema_name in schema_files:
+ schema_root = root / "../../xml/schema"
+ schema_reference = schema_root / f"{schema_name}.xsd"
+ content = schema_reference.read_text().split("\n")
+
+ content[1] = f' xmlns:ex="{inputschemaextentions_name}.xsd"'
+ content[3] = f' xsi:schemaLocation="{inputschemaextentions_name}.xsd {inputschemaextentions_name}.xsd">'
+ content = "\n".join(content)
+
+ new_schema_file = Path(f"{schema_name}.xsd")
+ new_schema_file.write_text(content)
+ file_list.append(new_schema_file)
+
+ return file_list
+
+
+def read_schema_to_dict(name: str) -> dict:
+ """Read schema and transform to sensible dictionary.
+
+ Note: This could be done with an external library, such as `xmltodict` or `xmljson`
+ but as this module should be run infrequently, a custom implementation reduces
+ dependencies.
+
+ :param name: name of the schema file and tag of the xml element
+ :return: a dictionary with the need information about children/parents and valid attributes.
+ """
+ schema = xmlschema.XMLSchema(f"{name}.xsd")
+
+ tag_info = {}
+ xsd_elements = filter(lambda x: isinstance(x, xmlschema.XsdElement) and x.ref is None, schema.iter_components())
+
+ for xsd_element in xsd_elements:
+ attributes = xsd_element.attributes
+ mandatory_attributes = set([k for k, v in attributes.items() if v.use == "required"])
+ children = [x.name for x in xsd_element.iterchildren() if not isinstance(x, XsdAnyElement)]
+ mandatory_children = set([x.name for x in xsd_element.iterchildren() if x.min_occurs > 0])
+ multiple_childs = set([x.name for x in xsd_element.iterchildren() if x.max_occurs is None or x.max_occurs > 1])
+
+ tag_info[xsd_element.name] = {
+ "attribs": filter(lambda x: x is not None, attributes),
+ "children": children,
+ "mandatory_attribs": mandatory_attributes | mandatory_children,
+ "multiple_children": multiple_childs,
+ }
+
+ # special handling for the plan
+ if xsd_element.name == "doonly":
+ tag_info["doonly"]["plan"] = attributes["task"].type.validators[0].enumeration
+
+ # exclude special structure attributes (already explicitly specified in the __init__ of ExcitingStructure class)
+ if name == "structure":
+ tag_info["structure"]["mandatory_attribs"].remove("crystal")
+ tag_info["crystal"]["mandatory_attribs"].remove("basevect")
+ tag_info["species"]["mandatory_attribs"].remove("atom")
+
+ return tag_info
+
+
+def write_schema_info(super_tag: str, schema_dict: dict) -> str:
+ """Converts dict representation of the schema to string.
+
+ :param super_tag: name of the top-level element
+ :param schema_dict: contains all the information read from the schema
+ :return info_string: string of python-formatted code
+ """
+ info_string = f"\n# {super_tag} information \n"
+ for tag in schema_dict:
+ valid_attributes = sorted(schema_dict[tag]["attribs"])
+ valid_subtrees = schema_dict[tag]["children"]
+ mandatory_attributes = sorted(schema_dict[tag]["mandatory_attribs"])
+ multiple_childs = sorted(schema_dict[tag]["multiple_children"])
+
+ if not (valid_attributes or valid_subtrees or mandatory_attributes):
+ continue
+
+ if valid_attributes:
+ info_string += list_string_line_limit(f"{tag}_valid_attributes", valid_attributes) + " \n"
+ if valid_subtrees:
+ info_string += list_string_line_limit(f"{tag}_valid_subtrees", valid_subtrees) + " \n"
+ if mandatory_attributes:
+ info_string += list_string_line_limit(f"{tag}_mandatory_attributes", mandatory_attributes) + " \n"
+ if multiple_childs:
+ info_string += list_string_line_limit(f"{tag}_multiple_children", multiple_childs) + " \n"
+ info_string += "\n"
+ return info_string
+
+
+def list_string_line_limit(name: str, content: list, max_length: int = 120) -> str:
+ """Given a list of unique items, produces a python formatted string containing the definition of the list
+ given by the name:
+ name = ['entry1', 'entry2', ...]
+ Inserts a line break every time the string gets longer then the limit.
+
+ :param name: the name of the set in the final string representing the definition
+ :param content: the list to write to string as a set (list because of consistent ordering)
+ :param max_length: the maximum line length
+ :return: the formatted string with fixed line length
+ """
+ formatted_string = ""
+ current_line_string = name + " = ["
+ start_len = len(current_line_string)
+ entry_break_string = '", '
+
+ for entry in content:
+ if len(str(entry) + current_line_string + entry_break_string) > max_length:
+ formatted_string += current_line_string + "\n"
+ current_line_string = start_len * " "
+ current_line_string += f'"{entry}{entry_break_string}'
+
+ formatted_string += current_line_string[:-2] + "]"
+ return formatted_string
+
+
+def get_all_include_files() -> list:
+ """Gets a list of all included files in the input.xsd file."""
+ input_schema_file = (get_excitingtools_root() / "../../xml/schema/input.xsd").resolve()
+ if not input_schema_file.exists():
+ raise ValueError(
+ "Couldn't find exciting schema. Most likely you are using excitingtools outside of exciting."
+ "To fix this, try installing excitingtools from source in editable (-e) mode."
+ )
+ return re.findall(r'', input_schema_file.read_text())
+
+
+def main():
+ """Main function to read the schema and write it to python readable file."""
+ filename = Path(__file__).parent / "valid_attributes.py"
+ info = (
+ '""" Automatically generated file with the valid attributes from the schema. \n'
+ 'Do not manually change. Instead, run "utils/schema_parsing.py" to regenerate. """ \n'
+ )
+
+ schemas = get_all_include_files()
+ tmp_files = copy_schema_files_for_parsing(schemas)
+
+ input_schema_dict = {"input": read_schema_to_dict("input")["input"]}
+ info += write_schema_info("input", input_schema_dict)
+
+ for schema in schemas:
+ schema_dict = read_schema_to_dict(schema)
+ info += write_schema_info(schema, schema_dict)
+
+ # Handle special case for 'xs' to handle the valid plan entries
+ xs_schema_dict = read_schema_to_dict("xs")
+ info += "\n# valid entries for the xs subtree 'plan'\n"
+ info += list_string_line_limit("valid_plan_entries", sorted(xs_schema_dict["doonly"]["plan"])) + " \n"
+
+ with open(filename, "w") as fid:
+ fid.write(info)
+
+ for file in tmp_files:
+ file.unlink()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/excitingtools/utils/test_utils.py b/excitingtools/utils/test_utils.py
index 819c808..d9d230c 100644
--- a/excitingtools/utils/test_utils.py
+++ b/excitingtools/utils/test_utils.py
@@ -1,10 +1,10 @@
-""" Classes and functions to aid unit testing.
-"""
+"""Classes and functions to aid unit testing."""
+
import pathlib
class MockFile:
- """ Single class for testing parsers that require either:
+ """Single class for testing parsers that require either:
* File.
* String contents of file.
@@ -15,6 +15,7 @@ def file_mock(tmp_path):
file.write_text(string_contents)
return MockFile(file, string_contents)
"""
+
def __init__(self, file: pathlib.Path, string: str):
# File object
self.file = file
diff --git a/excitingtools/utils/utils.py b/excitingtools/utils/utils.py
index ed7c7bb..99d05b9 100644
--- a/excitingtools/utils/utils.py
+++ b/excitingtools/utils/utils.py
@@ -1,7 +1,13 @@
-"""General utility functions. Typically conversion/type-checking.
-"""
-from typing import Union, List, Optional, Callable
+"""General utility functions. Typically, conversion/type-checking."""
+
+import pathlib
import re
+from typing import Any, Callable, Iterable, Iterator, List, Optional, Union
+
+
+def get_excitingtools_root() -> pathlib.Path:
+ """Get the root directory of excitingtools."""
+ return pathlib.Path(__file__).parents[2]
def can_be_float(value) -> bool:
@@ -58,30 +64,30 @@ def get_new_line_indices(string: str) -> List[int]:
new lines in string.
"""
indices = [0]
- indices += [m.start() + 1 for m in re.finditer('\n', string)]
+ indices += [m.start() + 1 for m in re.finditer("\n", string)]
return indices
-def list_to_str(mylist: list, modifier: Optional[Callable] = None) -> str:
- """ Convert a list to a string
+def list_to_str(mylist: Iterable[Any], modifier: Optional[Callable] = None) -> str:
+ """Convert a list or iterable to a lower-case string.
+
+ :param mylist: the input iterable
+ :param modifier: function which is additionally called on the stringified elements of the input iterable
+ :return: string representation in lower-case
"""
if modifier is None:
- modifier = lambda x: x
- return "".join(modifier(str(xyz)) + ' ' for xyz in mylist).strip()
+ return " ".join([str(x).lower() for x in mylist])
+ return " ".join([modifier(str(x).lower()) for x in mylist])
-def string_to_bool(string: str) -> bool:
- """ Convert string representation of true/false to True/False.
+def flatten_list(input_list: list) -> Iterator:
+ """Flatten a list of lists and other elements.
- :param string: String
- :return bool
+ :param input_list: input list
+ :return: an iterator for the flattened list
"""
- if string.lower() == 'true':
- return True
- elif string.lower() == 'false':
- return False
- else:
- raise ValueError()
-
-
-
+ for x in input_list:
+ if isinstance(x, list):
+ yield from flatten_list(x)
+ else:
+ yield x
diff --git a/excitingtools/utils/valid_attributes.py b/excitingtools/utils/valid_attributes.py
new file mode 100644
index 0000000..166c0eb
--- /dev/null
+++ b/excitingtools/utils/valid_attributes.py
@@ -0,0 +1,444 @@
+""" Automatically generated file with the valid attributes from the schema.
+Do not manually change. Instead, run "utils/schema_parsing.py" to regenerate. """
+
+# input information
+input_valid_attributes = ["sharedfs", "xsltpath"]
+input_valid_subtrees = ["title", "structure", "groundstate", "relax", "properties", "phonons", "xs", "gw", "MD", "eph",
+ "keywords"]
+input_mandatory_attributes = ["groundstate", "structure", "title"]
+
+
+# common information
+origin_valid_attributes = ["coord"]
+
+point_valid_attributes = ["breakafter", "coord", "label"]
+point_mandatory_attributes = ["coord"]
+
+plot1d_valid_subtrees = ["path"]
+plot1d_mandatory_attributes = ["path"]
+
+path_valid_attributes = ["outfileprefix", "steps"]
+path_valid_subtrees = ["point"]
+path_mandatory_attributes = ["point", "steps"]
+path_multiple_children = ["point"]
+
+plot2d_valid_subtrees = ["parallelogram"]
+plot2d_mandatory_attributes = ["parallelogram"]
+
+parallelogram_valid_attributes = ["grid", "outfileprefix"]
+parallelogram_valid_subtrees = ["origin", "point"]
+parallelogram_mandatory_attributes = ["grid", "origin", "point"]
+parallelogram_multiple_children = ["point"]
+
+plot3d_valid_attributes = ["usesym"]
+plot3d_valid_subtrees = ["box"]
+plot3d_mandatory_attributes = ["box"]
+
+box_valid_attributes = ["grid", "outfileprefix"]
+box_valid_subtrees = ["origin", "point"]
+box_mandatory_attributes = ["grid", "origin", "point"]
+box_multiple_children = ["point"]
+
+kstlist_valid_subtrees = ["pointstatepair"]
+kstlist_mandatory_attributes = ["pointstatepair"]
+kstlist_multiple_children = ["pointstatepair"]
+
+energywindow_valid_attributes = ["intv", "points"]
+
+qpointset_valid_subtrees = ["qpoint"]
+qpointset_mandatory_attributes = ["qpoint"]
+qpointset_multiple_children = ["qpoint"]
+
+parts_valid_subtrees = ["dopart"]
+parts_multiple_children = ["dopart"]
+
+dopart_valid_attributes = ["id"]
+dopart_mandatory_attributes = ["id"]
+
+qpoints_valid_attributes = ["qf", "qi"]
+
+
+# structure information
+structure_valid_attributes = ["autormt", "autormtscaling", "cartesian", "epslat", "primcell", "speciespath", "tshift"]
+structure_valid_subtrees = ["crystal", "species", "symmetries"]
+structure_mandatory_attributes = ["speciespath"]
+structure_multiple_children = ["species"]
+
+crystal_valid_attributes = ["scale", "stretch"]
+crystal_valid_subtrees = ["basevect"]
+crystal_multiple_children = ["basevect"]
+
+species_valid_attributes = ["atomicNumber", "chemicalSymbol", "fixrmt", "rmt", "speciesfile"]
+species_valid_subtrees = ["atom", "LDAplusU", "dfthalfparam"]
+species_mandatory_attributes = ["speciesfile"]
+species_multiple_children = ["atom"]
+
+atom_valid_attributes = ["bfcmt", "coord", "lockxyz", "mommtfix", "velocity"]
+atom_mandatory_attributes = ["coord"]
+
+LDAplusU_valid_attributes = ["J", "U", "l"]
+
+dfthalfparam_valid_attributes = ["ampl", "cut", "exponent"]
+dfthalfparam_valid_subtrees = ["shell"]
+dfthalfparam_mandatory_attributes = ["shell"]
+dfthalfparam_multiple_children = ["shell"]
+
+shell_valid_attributes = ["ionization", "number"]
+
+
+# groundstate information
+groundstate_valid_attributes = ["APWprecision", "CoreRelativity", "ExplicitKineticEnergy", "PrelimLinSteps",
+ "ValenceRelativity", "autokpt", "beta0", "betadec", "betainc", "cfdamp", "chgexs",
+ "deband", "dipolecorrection", "dipoleposition", "dlinengyfermi", "do", "energyref",
+ "epsband", "epschg", "epsengy", "epsforcescf", "epsocc", "epspot", "fermilinengy",
+ "findlinentype", "fracinr", "frozencore", "gmaxvr", "isgkmax", "ldapu", "lmaxapw",
+ "lmaxinr", "lmaxmat", "lmaxvr", "lradstep", "maxscl", "mixer", "mixerswitch",
+ "modifiedsv", "msecStoredSteps", "nempty", "ngridk", "niterconvcheck", "nktot",
+ "nosource", "nosym", "nprad", "npsden", "nwrite", "outputlevel", "ptnucl",
+ "radialgridtype", "radkpt", "reducek", "rgkmax", "scfconv", "stype", "swidth",
+ "symmorph", "tevecsv", "tfibs", "tforce", "tpartcharges", "useAPWprecision",
+ "useDensityMatrix", "vdWcorrection", "vkloff", "xctype"]
+groundstate_valid_subtrees = ["DFTD2parameters", "TSvdWparameters", "spin", "HartreeFock", "dfthalf", "Hybrid",
+ "sirius", "solver", "OEP", "RDMFT", "output", "libxc", "xsLO", "lorecommendation"]
+
+DFTD2parameters_valid_attributes = ["cutoff", "d", "s6", "sr6"]
+
+TSvdWparameters_valid_attributes = ["cutoff", "d", "nr", "nsph", "s6", "sr6"]
+
+spin_valid_attributes = ["bfieldc", "fixspin", "momfix", "nosv", "realspace", "reducebf", "spinorb", "spinsprl", "svlo",
+ "taufsm", "vqlss"]
+
+dfthalf_valid_attributes = ["printVSfile"]
+
+Hybrid_valid_attributes = ["HSEsingularity", "eccoeff", "epsmb", "exchangetype", "excoeff", "gmb", "lmaxmb", "maxscl",
+ "mblksiz", "omega", "updateRadial"]
+
+sirius_valid_attributes = ["cfun", "density", "densityinit", "eigenstates", "sfacg", "vha", "xc"]
+
+solver_valid_attributes = ["constructHS", "evaltol", "minenergy", "packedmatrixstorage", "type"]
+
+OEP_valid_attributes = ["convoep", "maxitoep", "tauoep"]
+
+RDMFT_valid_attributes = ["maxitc", "maxitn", "rdmalpha", "rdmmaxscl", "rdmtemp", "rdmxctype", "taurdmc", "taurdmn"]
+
+output_valid_attributes = ["state"]
+
+libxc_valid_attributes = ["correlation", "exchange", "xc"]
+
+xsLO_valid_attributes = ["emax", "lmax", "maxnodes"]
+
+lorecommendation_valid_attributes = ["lmaxlo", "nodesmaxlo"]
+
+
+# relax information
+relax_valid_attributes = ["addtohistory", "endbfgs", "epsforce", "history", "historyformat", "maxbfgs", "maxsteps",
+ "method", "outputlevel", "printtorque", "taubfgs", "taunewton"]
+
+
+# phonons information
+phonons_valid_attributes = ["canonical", "delete_eigensystem_response", "deltaph", "do", "drynumprocs", "gamma",
+ "maxprocsperpart", "method", "minprocsperpart", "ngridq", "polar", "reduceq", "sumrule",
+ "write_schedule"]
+phonons_valid_subtrees = ["qpointset", "phonondos", "phonondispplot", "reformatdynmat", "interpolate", "parts"]
+
+phonondos_valid_attributes = ["inttype", "ngrdos", "ngridqint", "nsmdos", "ntemp", "nwdos"]
+
+phonondispplot_valid_subtrees = ["plot1d"]
+phonondispplot_mandatory_attributes = ["plot1d"]
+
+interpolate_valid_attributes = ["ngridq", "vqloff", "writeeigenvectors"]
+interpolate_mandatory_attributes = ["ngridq"]
+
+
+# properties information
+properties_valid_subtrees = ["spintext", "coreoverlap", "bandstructure", "stm", "wfplot", "dos", "LSJ", "masstensor",
+ "chargedensityplot", "TSvdW", "DFTD2", "exccplot", "elfplot", "mvecfield", "xcmvecfield",
+ "electricfield", "gradmvecfield", "fermisurfaceplot", "EFG", "mossbauer", "expiqr",
+ "elnes", "eliashberg", "momentummatrix", "dielmat", "boltzequ", "raman", "moke", "shg",
+ "wannier", "wannierplot", "wanniergap", "ldos", "polarization"]
+
+spintext_valid_attributes = ["bands"]
+spintext_valid_subtrees = ["plot2d"]
+spintext_mandatory_attributes = ["plot2d"]
+
+coreoverlap_valid_attributes = ["coreatom", "corespecies"]
+
+bandstructure_valid_attributes = ["character", "deriv", "scissor", "wannier"]
+bandstructure_valid_subtrees = ["plot1d"]
+bandstructure_mandatory_attributes = ["plot1d"]
+
+stm_valid_attributes = ["bias", "stmmode", "stmtype"]
+stm_valid_subtrees = ["plot2d", "region"]
+
+region_valid_attributes = ["grid2d", "grid3d", "height", "zrange"]
+
+wfplot_valid_attributes = ["version"]
+wfplot_valid_subtrees = ["kstlist", "plot1d", "plot2d", "plot3d"]
+wfplot_mandatory_attributes = ["kstlist"]
+
+dos_valid_attributes = ["inttype", "jdos", "linkpt", "lmirep", "lonly", "ngrdos", "ngridkint", "nsmdos", "nwdos",
+ "scissor", "sqados", "wannier", "winddos"]
+
+LSJ_valid_subtrees = ["kstlist"]
+
+masstensor_valid_attributes = ["deltaem", "ndspem", "vklem"]
+
+chargedensityplot_valid_attributes = ["nocore"]
+chargedensityplot_valid_subtrees = ["plot1d", "plot2d", "plot3d"]
+
+exccplot_valid_subtrees = ["plot1d", "plot2d", "plot3d"]
+
+elfplot_valid_subtrees = ["plot1d", "plot2d", "plot3d"]
+
+mvecfield_valid_subtrees = ["plot2d", "plot3d"]
+
+xcmvecfield_valid_subtrees = ["plot2d", "plot3d"]
+
+electricfield_valid_subtrees = ["plot2d", "plot3d"]
+
+gradmvecfield_valid_subtrees = ["plot1d", "plot2d", "plot3d"]
+
+fermisurfaceplot_valid_attributes = ["nstfsp"]
+fermisurfaceplot_valid_subtrees = ["plot2d", "plot3d"]
+
+expiqr_valid_subtrees = ["kstlist"]
+
+elnes_valid_attributes = ["ngrid", "vecql", "wgrid", "wmax", "wmin"]
+
+eliashberg_valid_attributes = ["mustar"]
+
+momentummatrix_valid_attributes = ["fastpmat"]
+
+dielmat_valid_attributes = ["drude", "intraband", "scissor", "swidth", "tevout", "wgrid", "wmax"]
+dielmat_valid_subtrees = ["epscomp"]
+dielmat_multiple_children = ["epscomp"]
+
+boltzequ_valid_attributes = ["chemicalPotentialRange", "chemicalPotentialSpacing", "dopingConcentration",
+ "energyReference", "evOutputEnergies", "siOutputUnits", "temperatureRange",
+ "temperatureSpacing", "transportDfBroadening", "transportDfRange", "transportDfSpacing",
+ "useDopingConcentration", "useTransportDf"]
+boltzequ_valid_subtrees = ["etCoeffComponents"]
+boltzequ_multiple_children = ["etCoeffComponents"]
+
+raman_valid_attributes = ["broad", "degree", "displ", "doequilibrium", "elaser", "elaserunit", "getphonon", "mode",
+ "molecule", "ninter", "nstate", "nstep", "temp", "useforces", "usesym", "writefunc", "xmax",
+ "xmin"]
+raman_valid_subtrees = ["eigvec", "energywindow"]
+raman_mandatory_attributes = ["energywindow"]
+raman_multiple_children = ["eigvec"]
+
+eigvec_valid_attributes = ["comp"]
+eigvec_mandatory_attributes = ["comp"]
+
+moke_valid_attributes = ["drude", "intraband", "scissor", "swidth", "tevout", "wgrid", "wmax"]
+
+shg_valid_attributes = ["etol", "scissor", "swidth", "tevout", "wgrid", "wmax"]
+shg_valid_subtrees = ["chicomp"]
+shg_mandatory_attributes = ["chicomp"]
+shg_multiple_children = ["chicomp"]
+
+wannier_valid_attributes = ["cutshell", "do", "fermizero", "input", "mindist", "minshell", "nbzshell", "printproj"]
+wannier_valid_subtrees = ["projection", "group"]
+wannier_multiple_children = ["group"]
+
+projection_valid_attributes = ["dordmax", "epsld", "nprojtot", "nunocc"]
+
+group_valid_attributes = ["epsdis", "epsmax", "epsopf", "epsproj", "fst", "innerwindow", "lst", "maxitdis", "maxitmax",
+ "maxitopf", "memlendis", "memlenmax", "memlenopf", "method", "minitdis", "minitmax",
+ "minitopf", "minstepdis", "minstepmax", "minstepopf", "neighcells", "nproj", "nwf", "nwrite",
+ "optim", "outerwindow", "writeconv"]
+group_valid_subtrees = ["projector"]
+group_multiple_children = ["projector"]
+
+projector_valid_attributes = ["nr"]
+projector_mandatory_attributes = ["nr"]
+
+wannierplot_valid_attributes = ["cell", "fst", "lst"]
+wannierplot_valid_subtrees = ["plot1d", "plot2d", "plot3d"]
+
+wanniergap_valid_attributes = ["auto", "ngridkint"]
+wanniergap_valid_subtrees = ["pointband"]
+wanniergap_multiple_children = ["pointband"]
+
+pointband_valid_attributes = ["band", "extremal", "vkl"]
+pointband_mandatory_attributes = ["band", "vkl"]
+
+ldos_valid_attributes = ["delta", "grid", "newint", "ngrdos", "nsmdos", "nwdos", "scissor", "tol", "winddos"]
+
+
+# xs information
+xs_valid_attributes = ["bfieldc", "broad", "dbglev", "dfoffdiag", "dogroundstate", "emattype", "emaxdf", "epsdfde",
+ "fastpmat", "gqmax", "gqmaxtype", "h5fname", "h5gname", "lmaxapwwf", "lmaxemat", "maxscl",
+ "nempty", "ngridk", "ngridq", "nosym", "pwmat", "reducek", "reduceq", "rgkmax", "scissor",
+ "skipgnd", "swidth", "tappinfo", "tevout", "vkloff", "writexsgrids", "xstype"]
+xs_valid_subtrees = ["storeexcitons", "pwelements", "writeexcitons", "writekpathweights", "excitonPlot",
+ "realTimeTDDFT", "tddft", "screening", "phonon_screening", "expand_eps", "BSE", "fastBSE",
+ "transitions", "qpointset", "tetra", "energywindow", "plan"]
+xs_mandatory_attributes = ["xstype"]
+
+storeexcitons_valid_attributes = ["MaxEnergyExcitons", "MaxNumberExcitons", "MinEnergyExcitons", "MinNumberExcitons",
+ "selectenergy", "useev"]
+
+pwelements_valid_attributes = ["band_combinations"]
+pwelements_mandatory_attributes = ["band_combinations"]
+
+writeexcitons_valid_attributes = ["MaxEnergyExcitons", "MaxNumberExcitons", "MinEnergyExcitons", "MinNumberExcitons",
+ "abscutares", "abscutres", "selectenergy", "useev"]
+
+writekpathweights_valid_attributes = ["MaxEnergyExcitons", "MaxNumberExcitons", "MinEnergyExcitons",
+ "MinNumberExcitons", "intorder", "printgridweights", "selectenergy", "useev"]
+
+excitonPlot_valid_attributes = ["epstol"]
+excitonPlot_valid_subtrees = ["exciton", "hole", "electron"]
+excitonPlot_mandatory_attributes = ["electron", "hole"]
+excitonPlot_multiple_children = ["exciton"]
+
+exciton_valid_attributes = ["fix", "lambda"]
+
+hole_valid_subtrees = ["plot1d", "plot2d", "plot3d"]
+
+electron_valid_subtrees = ["plot1d", "plot2d", "plot3d"]
+
+realTimeTDDFT_valid_attributes = ["TaylorOrder", "calculateNExcitedElectrons", "calculateTotalEnergy", "endTime",
+ "normalizeWF", "printAfterIterations", "printTimingDetailed", "printTimingGeneral",
+ "propagator", "subtractJ0", "timeStep", "vectorPotentialSolver"]
+realTimeTDDFT_valid_subtrees = ["predictorCorrector", "screenshots", "laser", "pmat"]
+realTimeTDDFT_mandatory_attributes = ["pmat"]
+
+predictorCorrector_valid_attributes = ["maxIterations", "tol"]
+
+screenshots_valid_attributes = ["niter"]
+screenshots_valid_subtrees = ["eigenvalues", "projectionCoefficients", "occupations"]
+
+eigenvalues_valid_attributes = ["nEigenvalues", "tolerance"]
+
+projectionCoefficients_valid_attributes = ["format", "printAbsoluteValue"]
+
+occupations_valid_attributes = ["format"]
+
+laser_valid_attributes = ["fieldType"]
+laser_valid_subtrees = ["kick", "trapCos", "sinSq"]
+laser_multiple_children = ["kick", "sinSq", "trapCos"]
+
+kick_valid_attributes = ["amplitude", "direction", "t0", "width"]
+
+trapCos_valid_attributes = ["amplitude", "direction", "omega", "phase", "riseTime", "t0", "width"]
+
+sinSq_valid_attributes = ["amplitude", "direction", "omega", "phase", "pulseLength", "t0"]
+
+pmat_valid_attributes = ["forceHermitian", "readFromFile", "writeToFile"]
+
+tddft_valid_attributes = ["acont", "ahc", "alphalrc", "alphalrcdyn", "aresdf", "aresfxc", "betalrcdyn", "do", "drude",
+ "fxcbsesplit", "fxctype", "intraband", "kerndiag", "lindhard", "lmaxalda", "mdfqtype",
+ "nwacont", "torddf", "tordfxc"]
+
+screening_valid_attributes = ["do", "intraband", "nempty", "ngridk", "ngridq_interpolation", "nosym", "nqpt_unique",
+ "qpointsgamma", "quasiparticle_correction", "reducek", "rgkmax", "screentype", "tr",
+ "vkloff"]
+
+phonon_screening_valid_attributes = ["alat_qe", "excitation_energy", "file_type", "phonon_file", "zstar_file"]
+phonon_screening_mandatory_attributes = ["alat_qe", "excitation_energy", "phonon_file", "zstar_file"]
+
+expand_eps_valid_attributes = ["supercell_1", "supercell_2"]
+
+BSE_valid_attributes = ["aresbse", "blocks", "brixshdf5", "bsedirsing", "bsetype", "checkposdef", "chibar0",
+ "chibar0comp", "chibarq", "coupling", "cuttype", "dichroism", "distribute", "econv", "eecs",
+ "efind", "fbzq", "iqmtrange", "lmaxdielt", "measure", "nexc", "ngridksub", "nleblaik", "nosym",
+ "nosymspec", "nstlbse", "nstlxas", "outputlevel", "reducek", "rgkmax", "sciavbd", "sciavqbd",
+ "sciavqhd", "sciavqwg", "sciavtype", "scrherm", "solver", "vkloff", "xas", "xasatom", "xasedge",
+ "xasspecies", "xes"]
+
+fastBSE_valid_attributes = ["clanczos", "cvtsteplim", "cvttol", "ngridr", "nisdf", "nlanczos", "saveQ", "seed"]
+
+transitions_valid_subtrees = ["individual", "ranges", "lists"]
+
+individual_valid_subtrees = ["trans"]
+individual_multiple_children = ["trans"]
+
+trans_valid_attributes = ["action", "final", "initial", "kpointnumber"]
+
+ranges_valid_subtrees = ["range"]
+ranges_multiple_children = ["range"]
+
+range_valid_attributes = ["action", "kpointnumber", "start", "statestype", "stop"]
+range_mandatory_attributes = ["statestype"]
+
+lists_valid_subtrees = ["istate"]
+lists_multiple_children = ["istate"]
+
+istate_valid_attributes = ["action", "kpointnumber", "state", "statestype"]
+istate_mandatory_attributes = ["statestype"]
+
+tetra_valid_attributes = ["cw1k", "kordexc", "qweights", "tetradf", "tetraocc"]
+
+plan_valid_subtrees = ["doonly"]
+plan_multiple_children = ["doonly"]
+
+doonly_valid_attributes = ["task"]
+doonly_mandatory_attributes = ["task"]
+
+
+# gw information
+gw_valid_attributes = ["at1", "at2", "coreflag", "debug", "eph", "ibgw", "ibmax", "ibmax2", "ibmin", "ibmin2", "igmax",
+ "igmin", "iik", "jjk", "mblksiz", "nbgw", "nempty", "ngridq", "printSelfC",
+ "printSpectralFunction", "qdepw", "reduceq", "rmax", "rpath", "rpmat", "skipgnd", "taskname",
+ "vqloff", "wlo", "wto"]
+gw_valid_subtrees = ["plot1d", "freqgrid", "selfenergy", "mixbasis", "barecoul", "scrcoul", "taskGroup"]
+
+freqgrid_valid_attributes = ["eta", "fconv", "fgrid", "freqmax", "freqmin", "nomeg"]
+
+selfenergy_valid_attributes = ["actype", "eqpsolver", "eshift", "method", "nempty", "singularity", "swidth", "tol"]
+selfenergy_valid_subtrees = ["wgrid"]
+
+wgrid_valid_attributes = ["size", "type", "wmax", "wmin"]
+
+mixbasis_valid_attributes = ["epsmb", "gmb", "lmaxmb"]
+
+barecoul_valid_attributes = ["barcevtol", "basis", "cutofftype", "pwm", "stctol"]
+
+scrcoul_valid_attributes = ["averaging", "omegap", "q0eps", "scrtype"]
+
+taskGroup_valid_attributes = ["outputFormat"]
+taskGroup_valid_subtrees = ["Coulomb", "epsilon", "invertEpsilon"]
+
+Coulomb_valid_subtrees = ["qpoints"]
+Coulomb_mandatory_attributes = ["qpoints"]
+Coulomb_multiple_children = ["qpoints"]
+
+epsilon_valid_subtrees = ["qpoints"]
+epsilon_mandatory_attributes = ["qpoints"]
+epsilon_multiple_children = ["qpoints"]
+
+invertEpsilon_valid_subtrees = ["qpoints"]
+invertEpsilon_mandatory_attributes = ["qpoints"]
+invertEpsilon_multiple_children = ["qpoints"]
+
+
+# MD information
+MD_valid_attributes = ["basisDerivative", "coreCorrections", "integrationAlgorithm", "printAllForces", "timeStep",
+ "type", "updateOverlap", "updatePmat", "valenceCorrections"]
+
+
+# eph information
+eph_valid_attributes = ["debugeph", "ibeph", "ibsumeph", "nbeph", "nbsumeph", "nemptyeph", "ngridqeph", "tasknameeph",
+ "vqloffeph"]
+eph_valid_subtrees = ["freqgrideph", "selfenergyeph"]
+
+freqgrideph_valid_attributes = ["freqmaxeph", "nomegeph"]
+
+selfenergyeph_valid_subtrees = ["SpectralFunctionPloteph"]
+
+SpectralFunctionPloteph_valid_attributes = ["axis", "eta", "nwgrid", "wmax", "wmin"]
+
+
+# valid entries for the xs subtree 'plan'
+valid_plan_entries = ["bse", "bsegenspec", "bsesurvey", "df", "df2", "dielectric", "emattest", "exccoulint",
+ "excitonWavefunction", "expand_add_eps", "fastBSE_groundstate_properties",
+ "fastBSE_human_readable_output", "fastBSE_isdf_cvt", "fastBSE_main", "fxc_alda_check", "idf",
+ "kernxc_bse", "kernxc_bse3", "phonon_screening", "planewave_elements", "pmatxs2orig",
+ "portstate(-1)", "portstate(-2)", "portstate(1)", "portstate(2)", "scrcoulint", "screen",
+ "scrgeneigvec", "scrtetcalccw", "scrwritepmat", "testmain", "testxs", "tetcalccw",
+ "write_dielectric_matrix", "write_pmat_hdf5_xs", "write_screen", "write_screened_coulomb",
+ "writebandgapgrid", "writebevec", "writeemat", "writeematasc", "writekpathweights",
+ "writeoverlapxs", "writepmat", "writepmatasc", "writepmatxs", "writepwmat", "x0toasc", "x0tobin",
+ "xsestimate", "xsgeneigvec"]
diff --git a/pyproject.toml b/pyproject.toml
index 4ff5470..c5e9d7a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,26 +1,29 @@
[project]
name = "excitingtools"
description = "Utilities for aiding in the construction of exciting inputs and the postprocessing exciting outputs."
-version = "1.1.0"
+version = "1.7.1"
authors = [
- { name = "Alexander Buccheri", email = "alexander.buccheri@mpsd.mpg.de"},
- { name = "Fabian Peschel", email = "peschelf@physik.hu-berlin.de"}
+ {name = "Alexander Buccheri", email = "alexander.buccheri@mpsd.mpg.de"},
+ {name = "Fabian Peschel", email = "peschelf@physik.hu-berlin.de"}
]
-license = {file = "COPYING.md"}
+license = {text = "GNU GENERAL PUBLIC LICENSE, see 'COPYING.md'"}
readme = "README.md"
-requires-python = ">=3.6"
+requires-python = ">=3.7"
dependencies = [
- 'wheel>=0.35.0',
'numpy>=1.14.5',
- 'matplotlib>=2.2.0',
+ 'matplotlib>=2.2.0'
]
[project.optional-dependencies]
dev = [
"pytest",
- "ase>=3.20.0"
+ "ase>=3.20.0",
+ "ruff",
]
+schemaparsing = ["xmlschema"]
+h5_parsing = ["h5py"]
+
[project.urls]
repository = "https://github.com/exciting/excitingtools"
@@ -28,12 +31,52 @@ repository = "https://github.com/exciting/excitingtools"
[build-system]
requires = [
"setuptools >= 35.0.2",
- "setuptools_scm >= 3.5.0"
+ "setuptools_scm >= 2.0.0, <3"
]
build-backend = "setuptools.build_meta"
-[tool.distutils.bdist_wheel]
-universal = true
-
[tool.setuptools]
packages = ["excitingtools"]
+
+[tool.ruff]
+line-length = 120
+extend-exclude = ["build/*", "valid_attributes.py"]
+
+[tool.ruff.format]
+skip-magic-trailing-comma = true
+
+[tool.ruff.lint]
+ignore = [
+ "E741", # ambigous variable name like 'l'
+ "UP006", # py3.7 doesn't support type hinting with built-in 'list'
+ "UP007", # py3.7 doesn't support type hinting with 'X | Y'
+ "COM812", # problems with linter
+ "ISC001", # problems with linter, see https://github.com/astral-sh/ruff/issues/8272
+ "RET504", # Unnecessary assignment
+ "SIM115", # context handler not used for opening files
+ "PLR2004", # magic values, maybe reintroduce at some point
+]
+extend-select = [
+ "I", # sort imports
+ "UP", # pyupgrade
+ "COM", # trailing commas
+ "ISC", # implicit string concats
+ "PIE", # flake8-pie
+ "T20", # no print statements
+ "Q", # Quotes
+ "RET", # return statements
+ "SIM", # flake8 simplify
+ "ARG", # unused arguments
+ "ERA", # commented-out code
+ "NPY", # find deprecated numpy code
+ "PERF", # perflint
+ "PL", # pylint
+]
+
+[tool.ruff.lint.isort]
+split-on-trailing-comma = false
+
+[tool.ruff.lint.pylint]
+max-args = 7
+max-branches = 14
+max-statements = 60
diff --git a/requirements.txt b/requirements.txt
index 6cdf7cd..09bbcda 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,5 @@
numpy
matplotlib
-pytest
\ No newline at end of file
+pytest
+xmlschema
+h5py
\ No newline at end of file
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..0872e88
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,17 @@
+"""
+File containing pytest fixtures. They can be seen from all other files and subdirectories.
+That's because of the special name of the file.
+Needed for testing environment variables.
+"""
+
+import pytest
+
+
+@pytest.fixture
+def mock_env_jobflow(monkeypatch):
+ monkeypatch.setenv("USE_JOBFLOW", "true")
+
+
+@pytest.fixture
+def mock_env_jobflow_missing(monkeypatch):
+ monkeypatch.delenv("USE_JOBFLOW", raising=False)
diff --git a/tests/dataclasses/test_band_structure.py b/tests/dataclasses/test_band_structure.py
index 5ccc411..239da47 100644
--- a/tests/dataclasses/test_band_structure.py
+++ b/tests/dataclasses/test_band_structure.py
@@ -1,47 +1,59 @@
-from excitingtools.dataclasses.band_structure import BandData
-import pytest
import numpy as np
+import pytest
+
+from excitingtools.dataclasses.band_structure import BandData
@pytest.fixture
def band_data():
- """ Initialise an instance of BandData, and check attributes are set correctly.
+ """Initialise an instance of BandData, and check attributes are set correctly.
Reference data taken from 'bandstructure.xml' and 'bandstructure.dat' files for silver, containing only two bands
and only 6 k-sampling points per band.
"""
- ref_bands = np.array([[-3.37071333, 0.59519479],
- [-3.37071074, 0.59537998],
- [-3.37070519, 0.59575556],
- [-3.3706986, 0.59616303],
- [-3.3706822, 0.59664939],
- [-3.37066123, 0.59639211]])
-
- ref_k_points = np.array([[1.000000, 0.000000, 0.000000],
- [0.988281, 0.011719, 0.000000],
- [0.976562, 0.023438, 0.000000],
- [0.964844, 0.035156, 0.000000],
- [0.953125, 0.046875, 0.000000],
- [0.941406, 0.058594, 0.000000]])
+ ref_bands = np.array(
+ [
+ [-3.37071333, 0.59519479],
+ [-3.37071074, 0.59537998],
+ [-3.37070519, 0.59575556],
+ [-3.3706986, 0.59616303],
+ [-3.3706822, 0.59664939],
+ [-3.37066123, 0.59639211],
+ ]
+ )
+
+ ref_k_points = np.array(
+ [
+ [1.000000, 0.000000, 0.000000],
+ [0.988281, 0.011719, 0.000000],
+ [0.976562, 0.023438, 0.000000],
+ [0.964844, 0.035156, 0.000000],
+ [0.953125, 0.046875, 0.000000],
+ [0.941406, 0.058594, 0.000000],
+ ]
+ )
ref_e_fermi = 0.0
- ref_flattened_k_points = np.array([0., 0.02697635, 0.05395270, 0.08092905, 0.10790540, 0.13488176])
+ ref_flattened_k_points = np.array([0.0, 0.02697635, 0.05395270, 0.08092905, 0.10790540, 0.13488176])
- ref_vertices = [{'distance': 0.0, 'label': 'Gamma', 'coord': [0.0, 0.0, 0.0]},
- {'distance': 0.8632432750, 'label': 'K', 'coord': [0.625, 0.375, 0.0]},
- {'distance': 1.150991033, 'label': 'X', 'coord': [0.5, 0.5, 0.0]},
- {'distance': 1.964864598, 'label': 'G', 'coord': [0.0, 0.0, 0.0]},
- {'distance': 2.669699781, 'label': 'L', 'coord': [0.5, 0.0, 0.0]}]
+ ref_vertices = [
+ {"distance": 0.0, "label": "Gamma", "coord": [0.0, 0.0, 0.0]},
+ {"distance": 0.8632432750, "label": "K", "coord": [0.625, 0.375, 0.0]},
+ {"distance": 1.150991033, "label": "X", "coord": [0.5, 0.5, 0.0]},
+ {"distance": 1.964864598, "label": "G", "coord": [0.0, 0.0, 0.0]},
+ {"distance": 2.669699781, "label": "L", "coord": [0.5, 0.0, 0.0]},
+ ]
band_data = BandData(ref_bands, ref_k_points, ref_e_fermi, ref_flattened_k_points, ref_vertices)
- assert band_data.n_k_points == band_data.bands.shape[0], (
- "First dim of bands array equals the number of k-sampling points in the band structure")
+ assert (
+ band_data.n_k_points == band_data.bands.shape[0]
+ ), "First dim of bands array equals the number of k-sampling points in the band structure"
assert band_data.n_k_points == 6, "sampling points per band"
assert band_data.n_bands == 2, "band_structure_xml contains two bands"
- assert np.allclose(band_data.k_points, ref_k_points, atol=1.e-8)
- assert np.allclose(band_data.bands, ref_bands, atol=1.e-8)
+ assert np.allclose(band_data.k_points, ref_k_points, atol=1.0e-8)
+ assert np.allclose(band_data.bands, ref_bands, atol=1.0e-8)
assert band_data.vertices == ref_vertices
return band_data
@@ -49,7 +61,7 @@ def band_data():
def test_xticks_and_labels(band_data):
flattened_high_sym_points_ref = [0.000000000, 0.86324327, 1.15099103, 1.9648646, 2.66969978]
- unicode_gamma = '\u0393'
+ unicode_gamma = "\u0393"
labels_ref = [unicode_gamma, "K", "X", unicode_gamma, "L"]
flattened_high_sym_points, labels = band_data.band_path()
diff --git a/tests/dataclasses/test_eigenvalues.py b/tests/dataclasses/test_eigenvalues.py
index fac178b..3259dc5 100644
--- a/tests/dataclasses/test_eigenvalues.py
+++ b/tests/dataclasses/test_eigenvalues.py
@@ -1,14 +1,15 @@
+from typing import List
+
import numpy as np
import pytest
-from typing import List
-from excitingtools.dataclasses.data_structs import NumberOfStates, PointIndex, BandIndices
+from excitingtools.dataclasses.data_structs import BandIndices, NumberOfStates, PointIndex
from excitingtools.dataclasses.eigenvalues import EigenValues
@pytest.fixture
def eigenvalues_instance():
- """ Initialise an instance of EigenValues, and check attributes are set correctly.
+ """Initialise an instance of EigenValues, and check attributes are set correctly.
Reference data taken from `evalqp_oxygen` in test_gw_eigenvalues.py
G0W0 band structure
@@ -22,17 +23,25 @@ def eigenvalues_instance():
Direct Bandgap at k(VBM) (eV): 5.5451
Direct Bandgap at k(CBm) (eV): 5.9468
"""
- ref_gw_eigenvalues = np.array([[-0.12905, -0.12891, -0.12896, 0.08958, 0.08957, 0.18396],
- [-0.19801, -0.17047, -0.17035, 0.11429, 0.11430, 0.19514],
- [-0.15828, -0.15818, -0.10809, 0.09569, 0.14613, 0.18404]])
-
- ref_k_points = np.array([[0.000000, 0.000000, 0.000000],
- [0.000000, 0.000000, 0.500000],
- [0.000000, 0.500000, 0.500000]])
+ ref_gw_eigenvalues = np.array(
+ [
+ [-0.12905, -0.12891, -0.12896, 0.08958, 0.08957, 0.18396],
+ [-0.19801, -0.17047, -0.17035, 0.11429, 0.11430, 0.19514],
+ [-0.15828, -0.15818, -0.10809, 0.09569, 0.14613, 0.18404],
+ ]
+ )
+
+ ref_k_points = np.array(
+ [[0.000000, 0.000000, 0.000000], [0.000000, 0.000000, 0.500000], [0.000000, 0.500000, 0.500000]]
+ )
ref_weights = [0.125, 0.5, 0.375000]
- eigen_values = EigenValues(NumberOfStates(19, 24), ref_k_points, [1, 2, 3], ref_gw_eigenvalues, ref_weights)
+ ref_occupations = np.array([[2, 2, 2, 0, 0, 0], [2, 2, 2, 0, 0, 0], [2, 2, 2, 0, 0, 0]])
+
+ eigen_values = EigenValues(
+ NumberOfStates(19, 24), ref_k_points, [1, 2, 3], ref_gw_eigenvalues, ref_weights, ref_occupations
+ )
assert ref_gw_eigenvalues.shape == (len(eigen_values.k_points), eigen_values.state_range.n_states)
assert eigen_values.state_range.first_state == 19
@@ -41,6 +50,7 @@ def eigenvalues_instance():
assert eigen_values.k_indices == [1, 2, 3]
assert np.allclose(eigen_values.all_eigenvalues, ref_gw_eigenvalues), "GW column eigenvalues, for all k-points"
assert eigen_values.weights == ref_weights
+ assert np.allclose(eigen_values.occupations, ref_occupations)
return eigen_values
@@ -75,11 +85,9 @@ def test_class_eigenvalues_get_k_points(eigenvalues_instance):
def test_class_eigenvalues_get_eigenvalues(eigenvalues_instance):
eigenvalues = eigenvalues_instance.get_eigenvalues(k_point=[0.0, 0.5, 0.5])
- assert np.allclose(eigenvalues,
- [-0.15828, -0.15818, -0.10809, 0.09569, 0.14613, 0.18404])
+ assert np.allclose(eigenvalues, [-0.15828, -0.15818, -0.10809, 0.09569, 0.14613, 0.18404])
- assert eigenvalues_instance.get_eigenvalues(k_point=[0.5, 0.5, 0.5]).size == 0, \
- "No k-point, hence eigenvalues"
+ assert eigenvalues_instance.get_eigenvalues(k_point=[0.5, 0.5, 0.5]).size == 0, "No k-point, hence eigenvalues"
def test_class_eigenvalues_band_gap(eigenvalues_instance):
@@ -89,5 +97,22 @@ def test_class_eigenvalues_band_gap(eigenvalues_instance):
indirect_band_gap = eigenvalues_instance.band_gap(BandIndices(21, 22), k_points=[k_valence, k_conduction])
direct_band_gap_at_Gamma = eigenvalues_instance.band_gap(BandIndices(21, 22), k_points=[k_conduction, k_conduction])
- assert np.isclose(indirect_band_gap, 0.19767), 'Indirect band gap in Ha'
- assert np.isclose(direct_band_gap_at_Gamma, 0.218540887), 'Direct band gap at Gamma, in Ha'
+ assert np.isclose(indirect_band_gap, 0.19767), "Indirect band gap in Ha"
+ assert np.isclose(direct_band_gap_at_Gamma, 0.218540887), "Direct band gap at Gamma, in Ha"
+
+
+def test_class_eigenvalues_get_transition_energy(eigenvalues_instance):
+ k_valence = [0.000, 0.500, 0.500]
+ k_conduction = [0.000, 0.000, 0.000]
+
+ indirect_gap = eigenvalues_instance.get_transition_energy(k_valence, k_conduction)
+ direct_gap_at_Gamma = eigenvalues_instance.get_transition_energy(k_conduction, k_conduction)
+
+ assert np.isclose(indirect_gap, 0.19767), "Transition energy for X→Γ, in Ha"
+ assert np.isclose(direct_gap_at_Gamma, 0.21854), "Transition energy for Γ→Γ, in Ha"
+
+ with pytest.raises(ValueError, match="Requested conduction k-point \\[1, 1, 1\\] not present."):
+ (
+ eigenvalues_instance.get_transition_energy(k_valence, [1, 1, 1]),
+ "ValueError is returned if k-point is not matched.",
+ )
diff --git a/tests/dict_parsers/test_RT_TDDFT_parser.py b/tests/dict_parsers/test_RT_TDDFT_parser.py
new file mode 100644
index 0000000..ba9b864
--- /dev/null
+++ b/tests/dict_parsers/test_RT_TDDFT_parser.py
@@ -0,0 +1,51 @@
+"""
+Test for the RT_TDDFT_parser
+"""
+
+import numpy as np
+import pytest
+
+from excitingtools.exciting_dict_parsers.RT_TDDFT_parser import parse_proj_screenshots
+
+proj_file_str_square_matrices = """ ik: 1
+ 1.00000 0.00000
+ 0.00000 1.00000
+ ik: 2
+ 1.00000 0.00000
+ 0.00000 1.00000
+"""
+
+reference_parsed_proj_square_matrices = {
+ "ik": [1, 2],
+ "projection": [np.array([[1.0, 0.0], [0.0, 1.0]]), np.array([[1.0, 0.0], [0.0, 1.0]])],
+}
+
+proj_file_str_rectangular_matrices = """ ik: 1
+ 1.00000 0.00000 0.00000
+ 0.00000 1.00000 0.00000
+ ik: 2
+ 0.60000 0.80000 0.00000
+ 0.00000 0.00000 1.00000
+"""
+
+reference_parsed_proj_rectangular_matrices = {
+ "ik": [1, 2],
+ "projection": [np.array([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]), np.array([[0.6, 0.8, 0.0], [0.0, 0.0, 1.0]])],
+}
+
+
+@pytest.mark.parametrize(
+ ["proj_file_str", "reference_parsed_dict"],
+ [
+ (proj_file_str_square_matrices, reference_parsed_proj_square_matrices),
+ (proj_file_str_rectangular_matrices, reference_parsed_proj_rectangular_matrices),
+ ],
+)
+def test_parse_proj_screenshots(proj_file_str, reference_parsed_dict, tmp_path):
+ proj_file_path = tmp_path / "PROJ_0.OUT"
+ proj_file_path.write_text(proj_file_str)
+ proj_out = parse_proj_screenshots(proj_file_path.as_posix())
+ is_equal = proj_out["ik"] == reference_parsed_dict["ik"]
+ key = "projection"
+ is_equal = is_equal and all([np.allclose(x, y) for (x, y) in zip(proj_out[key], reference_parsed_dict[key])])
+ assert is_equal
diff --git a/tests/dict_parsers/test_bse_parser.py b/tests/dict_parsers/test_bse_parser.py
index fd3e254..ed6c291 100644
--- a/tests/dict_parsers/test_bse_parser.py
+++ b/tests/dict_parsers/test_bse_parser.py
@@ -4,8 +4,16 @@
Execute tests from exciting_tools directory:
pytest --capture=tee-sys
"""
+
+import numpy as np
import pytest
-from excitingtools.exciting_dict_parsers.bse_parser import parse_infoxs_out
+
+from excitingtools.exciting_dict_parsers.bse_parser import (
+ parse_fastBSE_absorption_spectrum_out,
+ parse_fastBSE_exciton_energies_out,
+ parse_infoxs_out,
+)
+from excitingtools.utils.test_utils import MockFile
infoxs_file_str_success = """================================================================================
| EXCITING NITROGEN-14 started for task xsgeneigvec (301) =
@@ -13,18 +21,7 @@
| =
| Date (DD-MM-YYYY) : 10-12-2020 =
================================================================================
-================================================================================
-One-shot GS runs for BSE calculations
-================================================================================
-++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-One-shot GS runs for k+qmt/2 grids
-++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-Info(xsgeneigvec): Generating eigenvectors for Q-point 1
---------------------------------------------------------------------------------
-Info(xsgeneigvec): Generation of eigenvectors finished
-
-Info(xsfinit): task Nr. 301 stopped gracefully
-
+
Timings:
Date (DD-MM-YYYY) : 10-12-2020
Time (hh:mm:ss) : 20:05:23
@@ -44,18 +41,15 @@
| EXCITING NITROGEN-14 started for task writepmatxs (320) =
| Date (DD-MM-YYYY) : 10-12-2020 =
================================================================================
-Info(writepmatxs): Momentum matrix elements finished
-Info(xsfinit): task Nr. 320 stopped gracefully
-
- Timings:
+ Timings:
Date (DD-MM-YYYY) : 10-12-2020
- Time (hh:mm:ss) : 20:05:24
- CPU time : 4.46 sec; 0.00 hrs; ( 0 d, 00 h, 00 m, 04 s )
- wall time : 0.84 sec; 0.00 hrs; ( 0 d, 00 h, 00 m, 01 s )
- CPU load : 533.19 %
- CPU time (cumulative) : 116.04 sec; 0.03 hrs; ( 0 d, 00 h, 01 m, 56 s )
- wall time (cumulative) : 2.96 sec; 0.00 hrs; ( 0 d, 00 h, 00 m, 03 s )
- CPU load (cumulative) : 533.19 %
+ Time (hh:mm:ss) : 20:05:23
+ CPU time : 14.57 sec; 0.00 hrs; ( 0 d, 00 h, 00 m, 15 s )
+ wall time : 2.13 sec; 0.00 hrs; ( 0 d, 00 h, 00 m, 02 s )
+ CPU load : 684.78 %
+ CPU time (cumulative) : 111.58 sec; 0.03 hrs; ( 0 d, 00 h, 01 m, 52 s )
+ wall time (cumulative) : 2.13 sec; 0.00 hrs; ( 0 d, 00 h, 00 m, 02 s )
+ CPU load (cumulative) : 684.78 %
================================================================================
= EXCITING NITROGEN-14 stopped for task 320 =
@@ -66,21 +60,36 @@
| EXCITING NITROGEN-14 started for task scrgeneigvec (401) =
| Date (DD-MM-YYYY) : 10-12-2020 =
================================================================================
-
-Info(xsinit): mapping screening-specific parameters
+
+ Timings:
+ Date (DD-MM-YYYY) : 10-12-2020
+ Time (hh:mm:ss) : 20:05:23
+ CPU time : 14.57 sec; 0.00 hrs; ( 0 d, 00 h, 00 m, 15 s )
+ wall time : 2.13 sec; 0.00 hrs; ( 0 d, 00 h, 00 m, 02 s )
+ CPU load : 684.78 %
+ CPU time (cumulative) : 111.58 sec; 0.03 hrs; ( 0 d, 00 h, 01 m, 52 s )
+ wall time (cumulative) : 2.13 sec; 0.00 hrs; ( 0 d, 00 h, 00 m, 02 s )
+ CPU load (cumulative) : 684.78 %
================================================================================
= EXCITING NITROGEN-14 stopped for task 401 =
================================================================================
-
================================================================================
| EXCITING NITROGEN-14 started for task scrwritepmat (420) =
| Date (DD-MM-YYYY) : 10-12-2020 =
================================================================================
-Info(xsinit): mapping screening-specific parameters
-
+ Timings:
+ Date (DD-MM-YYYY) : 10-12-2020
+ Time (hh:mm:ss) : 20:05:23
+ CPU time : 14.57 sec; 0.00 hrs; ( 0 d, 00 h, 00 m, 15 s )
+ wall time : 2.13 sec; 0.00 hrs; ( 0 d, 00 h, 00 m, 02 s )
+ CPU load : 684.78 %
+ CPU time (cumulative) : 111.58 sec; 0.03 hrs; ( 0 d, 00 h, 01 m, 52 s )
+ wall time (cumulative) : 2.13 sec; 0.00 hrs; ( 0 d, 00 h, 00 m, 02 s )
+ CPU load (cumulative) : 684.78 %
+
================================================================================
= EXCITING NITROGEN-14 stopped for task 420 =
================================================================================
@@ -89,8 +98,16 @@
| EXCITING NITROGEN-14 started for task bse (445) =
| Date (DD-MM-YYYY) : 10-12-2020 =
================================================================================
-
-Info(xsinit): mapping BSE-specific parameters
+
+ Timings:
+ Date (DD-MM-YYYY) : 10-12-2020
+ Time (hh:mm:ss) : 20:05:23
+ CPU time : 14.57 sec; 0.00 hrs; ( 0 d, 00 h, 00 m, 15 s )
+ wall time : 2.13 sec; 0.00 hrs; ( 0 d, 00 h, 00 m, 02 s )
+ CPU load : 684.78 %
+ CPU time (cumulative) : 111.58 sec; 0.03 hrs; ( 0 d, 00 h, 01 m, 52 s )
+ wall time (cumulative) : 2.13 sec; 0.00 hrs; ( 0 d, 00 h, 00 m, 02 s )
+ CPU load (cumulative) : 684.78 %
================================================================================
= EXCITING NITROGEN-14 stopped for task 445 =
@@ -98,24 +115,13 @@
"""
infoxs_file_str_fail = """================================================================================
-| EXCITING NITROGEN-14 started for task xsgeneigvec (301) =
+| EXCITING NITROGEN-14 started for task xsgeneigvec ( 301) =
| version hash id: 1775bff4453c84689fb848894a9224f155377cfc =
| =
| Date (DD-MM-YYYY) : 10-12-2020 =
================================================================================
-================================================================================
-One-shot GS runs for BSE calculations
-================================================================================
-++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-One-shot GS runs for k+qmt/2 grids
-++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-Info(xsgeneigvec): Generating eigenvectors for Q-point 1
---------------------------------------------------------------------------------
-Info(xsgeneigvec): Generation of eigenvectors finished
-Info(xsfinit): task Nr. 301 stopped gracefully
-
- Timings:
+ Timings:
Date (DD-MM-YYYY) : 10-12-2020
Time (hh:mm:ss) : 20:05:23
CPU time : 14.57 sec; 0.00 hrs; ( 0 d, 00 h, 00 m, 15 s )
@@ -130,11 +136,9 @@
================================================================================
================================================================================
-| EXCITING NITROGEN-14 started for task writepmatxs (320) =
+| EXCITING NITROGEN-14 started for task writepmatxs ( 320) =
| Date (DD-MM-YYYY) : 10-12-2020 =
================================================================================
-Info(writepmatxs): Momentum matrix elements finished
-Info(xsfinit): task Nr. 320 stopped gracefully
================================================================================
| EXCITING NITROGEN-14 started for task xsgeneigvec (301) =
@@ -142,19 +146,8 @@
| =
| Date (DD-MM-YYYY) : 10-12-2020 =
================================================================================
-================================================================================
-One-shot GS runs for BSE calculations
-================================================================================
-++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-One-shot GS runs for k+qmt/2 grids
-++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-Info(xsgeneigvec): Generating eigenvectors for Q-point 1
---------------------------------------------------------------------------------
-Info(xsgeneigvec): Generation of eigenvectors finished
-
-Info(xsfinit): task Nr. 301 stopped gracefully
- Timings:
+ Timings:
Date (DD-MM-YYYY) : 10-12-2020
Time (hh:mm:ss) : 20:05:23
CPU time : 14.57 sec; 0.00 hrs; ( 0 d, 00 h, 00 m, 15 s )
@@ -173,18 +166,16 @@
| EXCITING NITROGEN-14 started for task writepmatxs (320) =
| Date (DD-MM-YYYY) : 10-12-2020 =
================================================================================
-Info(writepmatxs): Momentum matrix elements finished
-Info(xsfinit): task Nr. 320 stopped gracefully
- Timings:
+ Timings:
Date (DD-MM-YYYY) : 10-12-2020
- Time (hh:mm:ss) : 20:05:24
- CPU time : 4.46 sec; 0.00 hrs; ( 0 d, 00 h, 00 m, 04 s )
- wall time : 0.84 sec; 0.00 hrs; ( 0 d, 00 h, 00 m, 01 s )
- CPU load : 533.19 %
- CPU time (cumulative) : 116.04 sec; 0.03 hrs; ( 0 d, 00 h, 01 m, 56 s )
- wall time (cumulative) : 2.96 sec; 0.00 hrs; ( 0 d, 00 h, 00 m, 03 s )
- CPU load (cumulative) : 533.19 %
+ Time (hh:mm:ss) : 20:05:23
+ CPU time : 14.57 sec; 0.00 hrs; ( 0 d, 00 h, 00 m, 15 s )
+ wall time : 2.13 sec; 0.00 hrs; ( 0 d, 00 h, 00 m, 02 s )
+ CPU load : 684.78 %
+ CPU time (cumulative) : 111.58 sec; 0.03 hrs; ( 0 d, 00 h, 01 m, 52 s )
+ wall time (cumulative) : 2.13 sec; 0.00 hrs; ( 0 d, 00 h, 00 m, 02 s )
+ CPU load (cumulative) : 684.78 %
================================================================================
= EXCITING NITROGEN-14 stopped for task 320 =
@@ -196,8 +187,16 @@
| Date (DD-MM-YYYY) : 10-12-2020 =
================================================================================
-Info(xsinit): mapping screening-specific parameters
-
+ Timings:
+ Date (DD-MM-YYYY) : 10-12-2020
+ Time (hh:mm:ss) : 20:05:23
+ CPU time : 14.57 sec; 0.00 hrs; ( 0 d, 00 h, 00 m, 15 s )
+ wall time : 2.13 sec; 0.00 hrs; ( 0 d, 00 h, 00 m, 02 s )
+ CPU load : 684.78 %
+ CPU time (cumulative) : 111.58 sec; 0.03 hrs; ( 0 d, 00 h, 01 m, 52 s )
+ wall time (cumulative) : 2.13 sec; 0.00 hrs; ( 0 d, 00 h, 00 m, 02 s )
+ CPU load (cumulative) : 684.78 %
+
================================================================================
= EXCITING NITROGEN-14 stopped for task 401 =
================================================================================
@@ -209,40 +208,240 @@
================================================================================
"""
+
reference_parsed_infoxs_file_success = {
- 'tasks': [{'name': 'xsgeneigvec', 'number': 301,
- 'finished': True}, {'name': 'writepmatxs', 'number': 320,
- 'finished': True},
- {'name': 'scrgeneigvec', 'number': 401,
- 'finished': True}, {'name': 'scrwritepmat', 'number': 420,
- 'finished': True},
- {'name': 'bse', 'number': 445, 'finished': True}],
- 'success': True,
- 'last_finished_task': 'bse'
- }
+ "tasks": [
+ {"name": "xsgeneigvec", "number": 301, "finished": True},
+ {"name": "writepmatxs", "number": 320, "finished": True},
+ {"name": "scrgeneigvec", "number": 401, "finished": True},
+ {"name": "scrwritepmat", "number": 420, "finished": True},
+ {"name": "bse", "number": 445, "finished": True},
+ ],
+ "success": True,
+ "last_finished_task": "bse",
+}
reference_parsed_infoxs_file_fail = {
- 'tasks': [{'name': 'xsgeneigvec', 'number': 301,
- 'finished': True}, {'name': 'writepmatxs', 'number': 320,
- 'finished': False},
- {'name': 'xsgeneigvec', 'number': 301,
- 'finished': True}, {'name': 'writepmatxs', 'number': 320,
- 'finished': True},
- {'name': 'scrgeneigvec', 'number': 401,
- 'finished': True}, {'name': 'scrwritepmat', 'number': 420,
- 'finished': False}],
- 'success': False,
- 'last_finished_task': 'scrgeneigvec'
- }
-
-
-@pytest.mark.parametrize(["infoxs_file_str", "reference_parsed_dict"],
- [(infoxs_file_str_success,
- reference_parsed_infoxs_file_success),
- (infoxs_file_str_fail,
- reference_parsed_infoxs_file_fail)])
+ "tasks": [
+ {"name": "xsgeneigvec", "number": 301, "finished": True},
+ {"name": "writepmatxs", "number": 320, "finished": False},
+ {"name": "xsgeneigvec", "number": 301, "finished": True},
+ {"name": "writepmatxs", "number": 320, "finished": True},
+ {"name": "scrgeneigvec", "number": 401, "finished": True},
+ {"name": "scrwritepmat", "number": 420, "finished": False},
+ ],
+ "success": False,
+ "last_finished_task": "scrgeneigvec",
+}
+
+
+@pytest.mark.parametrize(
+ ["infoxs_file_str", "reference_parsed_dict"],
+ [
+ (infoxs_file_str_success, reference_parsed_infoxs_file_success),
+ (infoxs_file_str_fail, reference_parsed_infoxs_file_fail),
+ ],
+)
def test_parse_info_xs_out(infoxs_file_str, reference_parsed_dict, tmp_path):
infoxs_file_path = tmp_path / "INFOXS.OUT"
infoxs_file_path.write_text(infoxs_file_str)
info_xs_out = parse_infoxs_out(infoxs_file_path.as_posix())
assert info_xs_out == reference_parsed_dict
+
+
+reference_parsed_infoxs_file_times_success = {
+ "tasks": [
+ {
+ "name": "xsgeneigvec",
+ "number": 301,
+ "finished": True,
+ "cpu_time": 14.57,
+ "wall_time": 2.13,
+ "cpu_time_cum": 111.58,
+ "wall_time_cum": 2.13,
+ },
+ {
+ "name": "writepmatxs",
+ "number": 320,
+ "finished": True,
+ "cpu_time": 14.57,
+ "wall_time": 2.13,
+ "cpu_time_cum": 111.58,
+ "wall_time_cum": 2.13,
+ },
+ {
+ "name": "scrgeneigvec",
+ "number": 401,
+ "finished": True,
+ "cpu_time": 14.57,
+ "wall_time": 2.13,
+ "cpu_time_cum": 111.58,
+ "wall_time_cum": 2.13,
+ },
+ {
+ "name": "scrwritepmat",
+ "number": 420,
+ "finished": True,
+ "cpu_time": 14.57,
+ "wall_time": 2.13,
+ "cpu_time_cum": 111.58,
+ "wall_time_cum": 2.13,
+ },
+ {
+ "name": "bse",
+ "number": 445,
+ "finished": True,
+ "cpu_time": 14.57,
+ "wall_time": 2.13,
+ "cpu_time_cum": 111.58,
+ "wall_time_cum": 2.13,
+ },
+ ],
+ "success": True,
+ "last_finished_task": "bse",
+}
+
+reference_parsed_infoxs_file_times_fail = {
+ "tasks": [
+ {
+ "name": "xsgeneigvec",
+ "number": 301,
+ "finished": True,
+ "cpu_time": 14.57,
+ "wall_time": 2.13,
+ "cpu_time_cum": 111.58,
+ "wall_time_cum": 2.13,
+ },
+ {"name": "writepmatxs", "number": 320, "finished": False},
+ {
+ "name": "xsgeneigvec",
+ "number": 301,
+ "finished": True,
+ "cpu_time": 14.57,
+ "wall_time": 2.13,
+ "cpu_time_cum": 111.58,
+ "wall_time_cum": 2.13,
+ },
+ {
+ "name": "writepmatxs",
+ "number": 320,
+ "finished": True,
+ "cpu_time": 14.57,
+ "wall_time": 2.13,
+ "cpu_time_cum": 111.58,
+ "wall_time_cum": 2.13,
+ },
+ {
+ "name": "scrgeneigvec",
+ "number": 401,
+ "finished": True,
+ "cpu_time": 14.57,
+ "wall_time": 2.13,
+ "cpu_time_cum": 111.58,
+ "wall_time_cum": 2.13,
+ },
+ {"name": "scrwritepmat", "number": 420, "finished": False},
+ ],
+ "success": False,
+ "last_finished_task": "scrgeneigvec",
+}
+
+
+@pytest.mark.parametrize(
+ ["infoxs_file_str", "reference_parsed_dict"],
+ [
+ (infoxs_file_str_success, reference_parsed_infoxs_file_times_success),
+ (infoxs_file_str_fail, reference_parsed_infoxs_file_times_fail),
+ ],
+)
+def test_parse_info_xs_out_timing(infoxs_file_str, reference_parsed_dict, tmp_path):
+ infoxs_file_path = tmp_path / "INFOXS.OUT"
+ infoxs_file_path.write_text(infoxs_file_str)
+ info_xs_out = parse_infoxs_out(infoxs_file_path.as_posix(), parse_timing=True)
+ assert info_xs_out == reference_parsed_dict
+
+
+@pytest.fixture
+def fastBSE_absorption_spectrum_out_mock(tmp_path):
+ string_contents = """# fastBSE imaginary macroscopic dielectric function
+#
+# Energy unit: 0.3674932539796232E-01 Hartree
+# Broadening: 0.1360569193000000E+00 energy unit
+#
+# omega oc11 oc22 oc33
++0.0000000000000000E+00 +0.0000000000000000E+00 +0.0000000000000000E+00 +0.0000000000000000E+00
++0.6046974191111111E+01 +0.2462935121365850E+00 +0.2462935121365850E+00 +0.2462935121365850E+00
++0.1209394838222222E+02 +0.2028366299352899E+02 +0.2028366299352899E+02 +0.2028366299352899E+02"""
+
+ file = tmp_path / "fastBSE_absorption_spectrum.out"
+ file.write_text(string_contents)
+ return MockFile(file, string_contents)
+
+
+reference_fastBSE_absorption_spectrum = {
+ "energy_unit": 0.3674932539796232e-01,
+ "broadening": 0.1360569193000000e00,
+ "frequency": np.array([+0.0000000000000000e00, +0.6046974191111111e01, +0.1209394838222222e02]),
+ "imag_epsilon": np.array(
+ [
+ [+0.0000000000000000e00, +0.0000000000000000e00, +0.0000000000000000e00],
+ [+0.2462935121365850e00, +0.2462935121365850e00, +0.2462935121365850e00],
+ [+0.2028366299352899e02, +0.2028366299352899e02, +0.2028366299352899e02],
+ ]
+ ),
+}
+
+
+def test_parse_fastBSE_absorption_spectrum_out_parser(fastBSE_absorption_spectrum_out_mock):
+ fastBSE_absorption_spectrum_out = parse_fastBSE_absorption_spectrum_out(fastBSE_absorption_spectrum_out_mock.file)
+ assert np.allclose(
+ fastBSE_absorption_spectrum_out["energy_unit"], reference_fastBSE_absorption_spectrum["energy_unit"]
+ )
+ assert np.allclose(
+ fastBSE_absorption_spectrum_out["broadening"], reference_fastBSE_absorption_spectrum["broadening"]
+ )
+ assert np.allclose(fastBSE_absorption_spectrum_out["frequency"], reference_fastBSE_absorption_spectrum["frequency"])
+ assert np.allclose(
+ fastBSE_absorption_spectrum_out["imag_epsilon"], reference_fastBSE_absorption_spectrum["imag_epsilon"]
+ )
+
+
+@pytest.fixture
+def fastBSE_exciton_energies_out_mock(tmp_path):
+ string_contents = """# fastBSE exciton eigen energies
+# The three rows correspond to the results of the three Lanczos runs, each for one of the
+# directions of as starting point.
+#
+# Energy unit: 0.3674932539796232E-01 Hartree
+# IP band gap: 0.8205751967708917E+01 energy unit
+#
+# E -> E -> E ->
++0.8166524450038512E+01 +0.8166363823856052E+01 +0.8166439398878977E+01
++0.8168631668065551E+01 +0.8198150340644158E+01 +0.8588070401753557E+01
++0.8587998131087492E+01 +0.8588200092979093E+01 +0.9161347631625411E+01"""
+
+ file = tmp_path / "fastBSE_exciton_energies.out"
+ file.write_text(string_contents)
+ return MockFile(file, string_contents)
+
+
+reference_fastBSE_exciton_energies = {
+ "energy_unit": 0.3674932539796232e-01,
+ "ip_band_gap": 0.8205751967708917e01,
+ "exciton_energies": np.array(
+ [
+ [+0.8166524450038512e01, +0.8166363823856052e01, +0.8166439398878977e01],
+ [+0.8168631668065551e01, +0.8198150340644158e01, +0.8588070401753557e01],
+ [+0.8587998131087492e01, +0.8588200092979093e01, +0.9161347631625411e01],
+ ]
+ ),
+}
+
+
+def test_parse_fastBSE_exciton_energies_out_parser(fastBSE_exciton_energies_out_mock):
+ fastBSE_exciton_energies_out = parse_fastBSE_exciton_energies_out(fastBSE_exciton_energies_out_mock.file)
+ assert np.allclose(fastBSE_exciton_energies_out["energy_unit"], reference_fastBSE_exciton_energies["energy_unit"])
+ assert np.allclose(fastBSE_exciton_energies_out["ip_band_gap"], reference_fastBSE_exciton_energies["ip_band_gap"])
+ assert np.allclose(
+ fastBSE_exciton_energies_out["exciton_energies"], reference_fastBSE_exciton_energies["exciton_energies"]
+ )
diff --git a/tests/dict_parsers/test_groundstate_parser.py b/tests/dict_parsers/test_groundstate_parser.py
index d704cfd..d88ef12 100644
--- a/tests/dict_parsers/test_groundstate_parser.py
+++ b/tests/dict_parsers/test_groundstate_parser.py
@@ -3,7 +3,12 @@
Execute tests from exciting_tools directory:
pytest --capture=tee-sys
"""
-from excitingtools.exciting_dict_parsers.groundstate_parser import parse_info_out
+
+from excitingtools.exciting_dict_parsers.groundstate_parser import (
+ parse_info_out,
+ parse_linengy,
+ parse_lo_recommendation,
+)
def test_parse_info_out(tmp_path):
@@ -18,151 +23,137 @@ def test_parse_info_out(tmp_path):
# Only retained first and last SCF keys
info_ref = {
"initialization": {
- "APW functions": "8",
- "Brillouin zone volume": "0.0734963595",
- "Effective Wigner radius, r_s": "3.55062021",
- "Exchange-correlation type": "100",
+ "APW functions": 8,
+ "Brillouin zone volume": 0.0734963595,
+ "Effective Wigner radius, r_s": 3.55062021,
+ "Exchange-correlation type": 100,
"G-vector grid sizes": "36 36 36",
- "Lattice vectors (cartesian)": [
- "15.0000000000",
- "0.0000000000",
- "0.0000000000",
- "0.0000000000",
- "15.0000000000",
- "0.0000000000",
- "0.0000000000",
- "0.0000000000",
- "15.0000000000"
- ],
- "Maximum Hamiltonian size": "263",
- "Maximum number of plane-waves": "251",
- "Maximum |G| for potential and density": "7.50000000",
- "Number of Bravais lattice symmetries": "48",
- "Number of crystal symmetries": "48",
- "Number of empty states": "5",
- "Polynomial order for pseudochg. density": "9",
+ "Lattice vectors (cartesian)": [15.0, 0.0, 0.0, 0.0, 15.0, 0.0, 0.0, 0.0, 15.0],
+ "Maximum Hamiltonian size": 263,
+ "Maximum number of plane-waves": 251,
+ "Maximum |G+k| for APW functions": 1.66666667,
+ "Maximum |G| for potential and density": 7.5,
+ "Number of Bravais lattice symmetries": 48,
+ "Number of crystal symmetries": 48,
+ "Number of empty states": 5,
+ "Polynomial order for pseudochg. density": 9,
+ "R^MT_min * |G+k|_max (rgkmax)": 10.0,
"Reciprocal lattice vectors (cartesian)": [
- "0.4188790205",
- "0.0000000000",
- "0.0000000000",
- "0.0000000000",
- "0.4188790205",
- "0.0000000000",
- "0.0000000000",
- "0.0000000000",
- "0.4188790205"
+ 0.4188790205,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.4188790205,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.4188790205,
],
"Smearing scheme": "Gaussian",
- "Smearing width": "0.00100000",
+ "Smearing width": 0.001,
"Species 1": {
- "# of radial points in muffin-tin": "1000",
- "Atomic positions": {
- "Atom 1": "0.00000000 0.00000000 0.00000000"
- },
+ "# of radial points in muffin-tin": 1000,
+ "Atomic positions": {"Atom 1": "0.00000000 0.00000000 0.00000000"},
"Species": "1 (Ar)",
"Species symbol": "Ar",
- "atomic mass": "72820.74920000",
- "electronic charge": "18.00000000",
- "muffin-tin radius": "6.00000000",
+ "atomic mass": 72820.7492,
+ "electronic charge": 18.0,
+ "muffin-tin radius": 6.0,
"name": "argon",
- "nuclear charge": "-18.00000000",
- "parameters loaded from": "Ar.xml"
+ "nuclear charge": -18.0,
+ "parameters loaded from": "Ar.xml",
},
"Species with R^MT_min": "1 (Ar)",
- "Maximum |G+k| for APW functions": "1.66666667",
"Spin treatment": "spin-unpolarised",
- "Total core charge": "10.00000000",
- "Total electronic charge": "18.00000000",
- "Total nuclear charge": "-18.00000000",
- "Total number of G-vectors": "23871",
- "Total number of atoms per unit cell": "1",
- "Total number of k-points": "1",
- "Total number of local-orbitals": "12",
- "Total number of valence states": "10",
- "Total valence charge": "8.00000000",
- "Unit cell volume": "3375.0000000000",
- "computing H and O matrix elements": "4",
- "inner part of muffin-tin": "2",
+ "Total core charge": 10.0,
+ "Total electronic charge": 18.0,
+ "Total nuclear charge": -18.0,
+ "Total number of G-vectors": 23871,
+ "Total number of atoms per unit cell": 1,
+ "Total number of k-points": 1,
+ "Total number of local-orbitals": 12,
+ "Total number of valence states": 10,
+ "Total valence charge": 8.0,
+ "Unit cell volume": 3375.0,
+ "computing H and O matrix elements": 4,
+ "inner part of muffin-tin": 2,
"k-point grid": "1 1 1",
- "R^MT_min * |G+k|_max (rgkmax)": "10.00000000",
"libxc; exchange": "Slater exchange; correlation",
"mixing": "Using multisecant Broyden potential mixing",
- "potential and density": "4",
- "units": {
- "positions": "lattice"
- }
+ "potential and density": 4,
+ "units": {"positions": "lattice"},
},
"scl": {
"1": {
- "Core-electron kinetic energy": "0.00000000",
- "Correlation energy": "-1.43085548",
- "Coulomb energy": "-1029.02167746",
- "Coulomb potential energy": "-796.81322609",
- "DOS at Fermi energy (states/Ha/cell)": "0.00000000",
- "Effective potential energy": "-835.64023227",
+ "Core-electron kinetic energy": 0.0,
+ "Correlation energy": -1.43085548,
+ "Coulomb energy": -1029.02167746,
+ "Coulomb potential energy": -796.81322609,
+ "DOS at Fermi energy (states/Ha/cell)": 0.0,
+ "Effective potential energy": -835.64023227,
"Electron charges": "",
- "Electron-nuclear energy": "-1208.12684923",
- "Estimated fundamental gap": "0.36071248",
- "Exchange energy": "-27.93377198",
- "Fermi energy": "-0.20111449",
- "Hartree energy": "205.65681157",
- "Kinetic energy": "530.56137212",
- "Madelung energy": "-630.61506441",
- "Nuclear-nuclear energy": "-26.55163980",
- "Sum of eigenvalues": "-305.07886015",
- "Total energy": "-527.82493279",
- "Wall time (seconds)": "1.05",
- "atom 1 Ar": "17.99816103",
+ "Electron-nuclear energy": -1208.12684923,
+ "Estimated fundamental gap": 0.36071248,
+ "Exchange energy": -27.93377198,
+ "Fermi energy": -0.20111449,
+ "Hartree energy": 205.65681157,
+ "Kinetic energy": 530.56137212,
+ "Madelung energy": -630.61506441,
+ "Nuclear-nuclear energy": -26.5516398,
+ "Sum of eigenvalues": -305.07886015,
+ "Total energy": -527.82493279,
+ "Wall time (seconds)": 1.05,
+ "atom 1 Ar": 17.99816103,
"charge in muffin-tin spheres": "",
- "core": "10.00000000",
- "core leakage": "0.00000000",
- "interstitial": "0.00183897",
- "total charge": "18.00000000",
- "total charge in muffin-tins": "17.99816103",
- "valence": "8.00000000",
- "xc potential energy": "-38.82700618"
+ "core": 10.0,
+ "core leakage": 0.0,
+ "interstitial": 0.00183897,
+ "total charge": 18.0,
+ "total charge in muffin-tins": 17.99816103,
+ "valence": 8.0,
+ "xc potential energy": -38.82700618,
},
- "11": {
- "Core-electron kinetic energy": "0.00000000",
- "Correlation energy": "-1.43084350",
- "Coulomb energy": "-1029.02642037",
- "Coulomb potential energy": "-796.82023455",
- "DOS at Fermi energy (states/Ha/cell)": "0.00000000",
- "Effective potential energy": "-835.64716936",
+ "12": {
+ "Core-electron kinetic energy": 0.0,
+ "Correlation energy": -1.4308435,
+ "Coulomb energy": -1029.02642037,
+ "Coulomb potential energy": -796.82023455,
+ "DOS at Fermi energy (states/Ha/cell)": 0.0,
+ "Effective potential energy": -835.64716936,
"Electron charges": "",
- "Electron-nuclear energy": "-1208.12932661",
- "Estimated fundamental gap": "0.36095838",
- "Exchange energy": "-27.93372809",
- "Fermi energy": "-0.20044598",
- "Hartree energy": "205.65454603",
- "Kinetic energy": "530.57303096",
- "Madelung energy": "-630.61630310",
- "Nuclear-nuclear energy": "-26.55163980",
- "Sum of eigenvalues": "-305.07413840",
- "Total energy": "-527.81796101",
- "Wall time (seconds)": "4.95",
- "atom 1 Ar": "17.99815963",
+ "Electron-nuclear energy": -1208.12932661,
+ "Estimated fundamental gap": 0.36095838,
+ "Exchange energy": -27.93372809,
+ "Fermi energy": -0.20044598,
+ "Hartree energy": 205.65454603,
+ "Kinetic energy": 530.57303096,
+ "Madelung energy": -630.6163031,
+ "Nuclear-nuclear energy": -26.55163980,
+ "Sum of eigenvalues": -305.0741384,
+ "Total energy": -527.81796101,
+ "atom 1 Ar": 17.99815963,
"charge in muffin-tin spheres": "",
- "core": "10.00000000",
- "core leakage": "0.00000000",
- "interstitial": "0.00184037",
- "total charge": "18.00000000",
- "total charge in muffin-tins": "17.99815963",
- "valence": "8.00000000",
- "xc potential energy": "-38.82693481"
- }
- }
+ "core": 10.0,
+ "core leakage": 0.0,
+ "interstitial": 0.00184037,
+ "total charge": 18.0,
+ "total charge in muffin-tins": 17.99815963,
+ "valence": 8.0,
+ "xc potential energy": -38.82693481,
+ },
+ },
}
- file = tmp_path / 'INFO.OUT'
+ file = tmp_path / "INFO.OUT"
file.write_text(LDA_VWN_Ar_INFO_OUT)
assert file.exists(), "INFO.OUT not written to tmp_path"
info_out = parse_info_out(file.as_posix())
- assert info_out['initialization'] == info_ref['initialization'], "Initialization data consistent"
- assert info_out['scl']['1'] == info_ref['scl']['1'], "SCF first iteration data consistent"
- assert info_out['scl']['11'] == info_ref['scl']['11'], "SCF last iteration data consistent"
+ assert info_out["initialization"] == info_ref["initialization"], "Initialization data consistent"
+ assert len(info_out["scl"]) == 12, "expected 12 SCF steps"
+ assert info_out["scl"]["1"] == info_ref["scl"]["1"], "SCF first iteration data consistent"
+ assert info_out["scl"]["12"] == info_ref["scl"]["12"], "SCF last iteration data consistent"
LDA_VWN_Ar_INFO_OUT = """================================================================================
@@ -797,3 +788,560 @@ def test_parse_info_out(tmp_path):
| EXCITING NITROGEN-14 stopped =
================================================================================
"""
+
+
+def test_parse_linengy(tmp_path):
+ """
+ Note, this test will break if:
+ * the parser keys change
+ * the structure of the output changes
+ """
+
+ # Reference of the dictionary parsed with parse_linengy
+ # Generated with print(json.dumps(info_out, sort_keys=True, indent=4))
+
+ linengy_ref = {
+ "0": {
+ "apw": [-2.1, -1.1, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15],
+ "lo": [
+ 1.839014478,
+ 1.839014478,
+ -0.7435250385,
+ -0.7435250385,
+ 1.839014478,
+ 1.839014478,
+ 1.839014478,
+ -1.775228393,
+ -0.7435250385,
+ -0.7435250385,
+ ],
+ },
+ "1": {
+ "apw": [-0.3, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15],
+ "lo": [
+ 0.7475318132,
+ 0.7475318132,
+ 1.385527458,
+ 1.385527458,
+ 1.974765418,
+ 1.974765418,
+ 0.7475318132,
+ 0.7475318132,
+ 0.7475318132,
+ -9.003002855,
+ -9.003002855,
+ -9.003002855,
+ -9.003002855,
+ -9.003002855,
+ 1.385527458,
+ 1.385527458,
+ 1.385527458,
+ -6.768218591,
+ -6.768218591,
+ -6.768218591,
+ -6.768218591,
+ -6.768218591,
+ 1.974765418,
+ 1.974765418,
+ ],
+ },
+ }
+
+ file = tmp_path / "LINENGY.OUT"
+ file.write_text(GGA_PBE_SOL_automatic_trial_energies_NaCl_LINENGY_OUT)
+ assert file.exists(), "LINENGY.OUT not written to tmp_path"
+
+ linengy = parse_linengy(file.as_posix())
+
+ assert linengy["0"]["apw"] == linengy_ref["0"]["apw"], "First species apw data consistent"
+ assert linengy["0"]["lo"] == linengy_ref["0"]["lo"], "First species lo data consistent"
+ assert linengy["1"]["apw"] == linengy_ref["1"]["apw"], "Second species apw data consistent"
+ assert linengy["1"]["lo"] == linengy_ref["1"]["lo"], "Second species lo data consistent"
+
+
+GGA_PBE_SOL_automatic_trial_energies_NaCl_LINENGY_OUT = """Species : 1 (Na), atom : 1
+ APW functions :
+ l = 0, order = 1 : -2.100000000
+ l = 1, order = 1 : -1.100000000
+ l = 2, order = 1 : 0.1500000000
+ l = 2, order = 2 : 0.1500000000
+ l = 3, order = 1 : 0.1500000000
+ l = 3, order = 2 : 0.1500000000
+ l = 4, order = 1 : 0.1500000000
+ l = 4, order = 2 : 0.1500000000
+ l = 5, order = 1 : 0.1500000000
+ l = 5, order = 2 : 0.1500000000
+ l = 6, order = 1 : 0.1500000000
+ l = 6, order = 2 : 0.1500000000
+ l = 7, order = 1 : 0.1500000000
+ l = 7, order = 2 : 0.1500000000
+ l = 8, order = 1 : 0.1500000000
+ l = 8, order = 2 : 0.1500000000
+ local-orbital functions :
+ l.o. = 1, l = 0, order = 1 : 1.839014478
+ l.o. = 1, l = 0, order = 2 : 1.839014478
+ l.o. = 2, l = 1, order = 1 : -0.7435250385
+ l.o. = 2, l = 1, order = 2 : -0.7435250385
+ l.o. = 3, l = 0, order = 1 : 1.839014478
+ l.o. = 3, l = 0, order = 2 : 1.839014478
+ l.o. = 4, l = 0, order = 1 : 1.839014478
+ l.o. = 4, l = 0, order = 2 : -1.775228393
+ l.o. = 5, l = 1, order = 1 : -0.7435250385
+ l.o. = 5, l = 1, order = 2 : -0.7435250385
+
+Species : 2 (Cl), atom : 1
+ APW functions :
+ l = 0, order = 1 : -0.3000000000
+ l = 1, order = 1 : 0.1500000000
+ l = 2, order = 1 : 0.1500000000
+ l = 3, order = 1 : 0.1500000000
+ l = 3, order = 2 : 0.1500000000
+ l = 4, order = 1 : 0.1500000000
+ l = 4, order = 2 : 0.1500000000
+ l = 5, order = 1 : 0.1500000000
+ l = 5, order = 2 : 0.1500000000
+ l = 6, order = 1 : 0.1500000000
+ l = 6, order = 2 : 0.1500000000
+ l = 7, order = 1 : 0.1500000000
+ l = 7, order = 2 : 0.1500000000
+ l = 8, order = 1 : 0.1500000000
+ l = 8, order = 2 : 0.1500000000
+ local-orbital functions :
+ l.o. = 1, l = 0, order = 1 : 0.7475318132
+ l.o. = 1, l = 0, order = 2 : 0.7475318132
+ l.o. = 2, l = 1, order = 1 : 1.385527458
+ l.o. = 2, l = 1, order = 2 : 1.385527458
+ l.o. = 3, l = 2, order = 1 : 1.974765418
+ l.o. = 3, l = 2, order = 2 : 1.974765418
+ l.o. = 4, l = 0, order = 1 : 0.7475318132
+ l.o. = 4, l = 0, order = 2 : 0.7475318132
+ l.o. = 5, l = 0, order = 1 : 0.7475318132
+ l.o. = 5, l = 0, order = 2 : -9.003002855
+ l.o. = 6, l = 0, order = 1 : -9.003002855
+ l.o. = 6, l = 0, order = 2 : -9.003002855
+ l.o. = 7, l = 0, order = 1 : -9.003002855
+ l.o. = 7, l = 0, order = 2 : -9.003002855
+ l.o. = 8, l = 1, order = 1 : 1.385527458
+ l.o. = 8, l = 1, order = 2 : 1.385527458
+ l.o. = 9, l = 1, order = 1 : 1.385527458
+ l.o. = 9, l = 1, order = 2 : -6.768218591
+ l.o. = 10, l = 1, order = 1 : -6.768218591
+ l.o. = 10, l = 1, order = 2 : -6.768218591
+ l.o. = 11, l = 1, order = 1 : -6.768218591
+ l.o. = 11, l = 1, order = 2 : -6.768218591
+ l.o. = 12, l = 2, order = 1 : 1.974765418
+ l.o. = 12, l = 2, order = 2 : 1.974765418 """
+
+
+def test_parse_lo_recommendation(tmp_path):
+ """
+ Note, this test will break if:
+ * the parser keys change
+ * the structure of the output changes
+ """
+
+ # Reference of the dictionary parsed with parse_lo_recommendation
+ # Generated with print(json.dumps(info_out, sort_keys=True, indent=4))
+
+ lo_recommendation_ref = {
+ "n_species": 2,
+ "n_l_channels": 4,
+ "n_nodes": 21,
+ "Na": {
+ 0: [
+ [0.0, 1.0, -37.65990706181],
+ [1.0, 2.0, -1.796282469826],
+ [2.0, 3.0, 1.821425619362],
+ [3.0, 4.0, 7.892575399809],
+ [4.0, 5.0, 16.986165613637],
+ [5.0, 6.0, 28.846080585981],
+ [6.0, 7.0, 43.369227333017],
+ [7.0, 8.0, 60.495491296112],
+ [8.0, 9.0, 80.187724549122],
+ [9.0, 10.0, 102.422176950914],
+ [10.0, 11.0, 127.182990712735],
+ [11.0, 12.0, 154.458866712915],
+ [12.0, 13.0, 184.241158329361],
+ [13.0, 14.0, 216.522955263927],
+ [14.0, 15.0, 251.298756937161],
+ [15.0, 16.0, 288.564346156074],
+ [16.0, 17.0, 328.316602395923],
+ [17.0, 18.0, 370.553203330272],
+ [18.0, 19.0, 415.272297641907],
+ [19.0, 20.0, 462.472263422762],
+ [20.0, 21.0, 512.15160534914],
+ ],
+ 1: [
+ [0.0, 2.0, -0.764380938525],
+ [1.0, 3.0, 2.150459202106],
+ [2.0, 4.0, 7.68064315441],
+ [3.0, 5.0, 15.944823175319],
+ [4.0, 6.0, 26.826688781275],
+ [5.0, 7.0, 40.283086869083],
+ [6.0, 8.0, 56.289616891011],
+ [7.0, 9.0, 74.830023777453],
+ [8.0, 10.0, 95.891983053944],
+ [9.0, 11.0, 119.465424264199],
+ [10.0, 12.0, 145.542112894232],
+ [11.0, 13.0, 174.115704751485],
+ [12.0, 14.0, 205.181692970898],
+ [13.0, 15.0, 238.737011824859],
+ [14.0, 16.0, 274.779422707574],
+ [15.0, 17.0, 313.306969990248],
+ [16.0, 18.0, 354.317721540693],
+ [17.0, 19.0, 397.809787603557],
+ [18.0, 20.0, 443.781471675578],
+ [19.0, 21.0, 492.231385973938],
+ [20.0, 22.0, 543.158450851233],
+ ],
+ 2: [
+ [0.0, 3.0, 2.044659668292],
+ [1.0, 4.0, 6.427853785834],
+ [2.0, 5.0, 13.431600603035],
+ [3.0, 6.0, 23.065707720667],
+ [4.0, 7.0, 35.286723443086],
+ [5.0, 8.0, 50.060639647089],
+ [6.0, 9.0, 67.365856793742],
+ [7.0, 10.0, 87.18903211822],
+ [8.0, 11.0, 109.521849753452],
+ [9.0, 12.0, 134.358761651081],
+ [10.0, 13.0, 161.695405680071],
+ [11.0, 14.0, 191.52770328581],
+ [12.0, 15.0, 223.85164087452],
+ [13.0, 16.0, 258.663517453378],
+ [14.0, 17.0, 295.960290921283],
+ [15.0, 18.0, 335.73972677672],
+ [16.0, 19.0, 378.000263814603],
+ [17.0, 20.0, 422.740724390955],
+ [18.0, 21.0, 469.960046963641],
+ [19.0, 22.0, 519.657164852307],
+ [20.0, 23.0, 571.831020081621],
+ ],
+ 3: [
+ [0.0, 4.0, 3.879401688185],
+ [1.0, 5.0, 9.964477889618],
+ [2.0, 6.0, 18.47378344193],
+ [3.0, 7.0, 29.505302500625],
+ [4.0, 8.0, 43.071695950394],
+ [5.0, 9.0, 59.165686791534],
+ [6.0, 10.0, 77.776580159806],
+ [7.0, 11.0, 98.89485358398],
+ [8.0, 12.0, 122.513132271493],
+ [9.0, 13.0, 148.626189112094],
+ [10.0, 14.0, 177.230592656783],
+ [11.0, 15.0, 208.324116670413],
+ [12.0, 16.0, 241.905061644769],
+ [13.0, 17.0, 277.971722458938],
+ [14.0, 18.0, 316.522185899964],
+ [15.0, 19.0, 357.554436071786],
+ [16.0, 20.0, 401.066608041866],
+ [17.0, 21.0, 447.057187991954],
+ [18.0, 22.0, 495.525055433203],
+ [19.0, 23.0, 546.469383913223],
+ [20.0, 24.0, 599.889489594382],
+ ],
+ },
+ "Cl": {
+ 0: [
+ [0.0, 1.0, -100.974592263771],
+ [1.0, 2.0, -8.943406336517],
+ [2.0, 3.0, 0.803820215121],
+ [3.0, 4.0, 11.265670674793],
+ [4.0, 5.0, 27.995235804499],
+ [5.0, 6.0, 50.139499480779],
+ [6.0, 7.0, 77.425670325936],
+ [7.0, 8.0, 109.722811813338],
+ [8.0, 9.0, 146.943417860813],
+ [9.0, 10.0, 189.030459361669],
+ [10.0, 11.0, 235.945624307269],
+ [11.0, 12.0, 287.660637659017],
+ [12.0, 13.0, 344.155876726629],
+ [13.0, 14.0, 405.415989381911],
+ [14.0, 15.0, 471.42935525429],
+ [15.0, 16.0, 542.186581729314],
+ [16.0, 17.0, 617.680068606306],
+ [17.0, 18.0, 697.90359882698],
+ [18.0, 19.0, 782.852080659573],
+ [19.0, 20.0, 872.521357718829],
+ [20.0, 21.0, 966.908013775037],
+ ],
+ 1: [
+ [0.0, 2.0, -6.707821362791],
+ [1.0, 3.0, 1.442002863349],
+ [2.0, 4.0, 11.086329356142],
+ [3.0, 5.0, 26.446761118127],
+ [4.0, 6.0, 46.908960145565],
+ [5.0, 7.0, 72.31449630133],
+ [6.0, 8.0, 102.593748915541],
+ [7.0, 9.0, 137.708872214526],
+ [8.0, 10.0, 177.628868217528],
+ [9.0, 11.0, 222.335815545055],
+ [10.0, 12.0, 271.812774967963],
+ [11.0, 13.0, 326.047911895729],
+ [12.0, 14.0, 385.030908226461],
+ [13.0, 15.0, 448.753672165588],
+ [14.0, 16.0, 517.209705744261],
+ [15.0, 17.0, 590.393819776316],
+ [16.0, 18.0, 668.301879780751],
+ [17.0, 19.0, 750.930453348811],
+ [18.0, 20.0, 838.276595190814],
+ [19.0, 21.0, 930.337672332757],
+ [20.0, 22.0, 1027.111295649087],
+ ],
+ 2: [
+ [0.0, 3.0, 2.030626399034],
+ [1.0, 4.0, 9.540398069814],
+ [2.0, 5.0, 22.521806870793],
+ [3.0, 6.0, 40.580505794574],
+ [4.0, 7.0, 63.608967480673],
+ [5.0, 8.0, 91.521851721479],
+ [6.0, 9.0, 124.28110055295],
+ [7.0, 10.0, 161.847878698612],
+ [8.0, 11.0, 204.202486065259],
+ [9.0, 12.0, 251.327905737439],
+ [10.0, 13.0, 303.212514505293],
+ [11.0, 14.0, 359.847427715492],
+ [12.0, 15.0, 421.225227525549],
+ [13.0, 16.0, 487.339835071361],
+ [14.0, 17.0, 558.185801503932],
+ [15.0, 18.0, 633.758349847869],
+ [16.0, 19.0, 714.053292909356],
+ [17.0, 20.0, 799.067051302979],
+ [18.0, 21.0, 888.796639877965],
+ [19.0, 22.0, 983.239587157865],
+ [20.0, 23.0, 1082.393826570133],
+ ],
+ 3: [
+ [0.0, 4.0, 5.877668530234],
+ [1.0, 5.0, 16.734445897029],
+ [2.0, 6.0, 32.521343858776],
+ [3.0, 7.0, 53.248636736501],
+ [4.0, 8.0, 78.839760672149],
+ [5.0, 9.0, 109.265751512702],
+ [6.0, 10.0, 144.502223464379],
+ [7.0, 11.0, 184.526132806013],
+ [8.0, 12.0, 229.322756357998],
+ [9.0, 13.0, 278.877755682806],
+ [10.0, 14.0, 333.181582371119],
+ [11.0, 15.0, 392.226089160902],
+ [12.0, 16.0, 456.00524371215],
+ [13.0, 17.0, 524.514261786364],
+ [14.0, 18.0, 597.749258861014],
+ [15.0, 19.0, 675.706956131225],
+ [16.0, 20.0, 758.384407267418],
+ [17.0, 21.0, 845.778892453188],
+ [18.0, 22.0, 937.887883257667],
+ [19.0, 23.0, 1034.709065214155],
+ [20.0, 24.0, 1136.240377687629],
+ ],
+ },
+ }
+
+ file = tmp_path / "LO_RECOMMENDATION.OUT"
+ file.write_text(GGA_PBE_SOL_automatic_trial_energies_NaCl_LO_RECOMMENDATION_OUT)
+ assert file.exists(), "LO_RECOMMENDATION.OUT not written to tmp_path"
+
+ lo_recommendation = parse_lo_recommendation(file.as_posix())
+
+ assert lo_recommendation["Na"] == lo_recommendation_ref["Na"], "First species data consistent"
+ assert lo_recommendation["Cl"] == lo_recommendation_ref["Cl"], "Second species data consistent"
+
+
+GGA_PBE_SOL_automatic_trial_energies_NaCl_LO_RECOMMENDATION_OUT = """# Recommended linearization energies computet with Wigner-Seitz rules.
+ --------------------------------------------------------------------
+ # n_species: 2
+ # n_l-channels: 4
+ # n_nodes: 21
+
+ # species: Na, l : 0
+ # nodes n trial energy
+ 0 1 -37.659907061810
+ 1 2 -1.796282469826
+ 2 3 1.821425619362
+ 3 4 7.892575399809
+ 4 5 16.986165613637
+ 5 6 28.846080585981
+ 6 7 43.369227333017
+ 7 8 60.495491296112
+ 8 9 80.187724549122
+ 9 10 102.422176950914
+ 10 11 127.182990712735
+ 11 12 154.458866712915
+ 12 13 184.241158329361
+ 13 14 216.522955263927
+ 14 15 251.298756937161
+ 15 16 288.564346156074
+ 16 17 328.316602395923
+ 17 18 370.553203330272
+ 18 19 415.272297641907
+ 19 20 462.472263422762
+ 20 21 512.151605349140
+
+ # species: Na, l : 1
+ # nodes n trial energy
+ 0 2 -0.764380938525
+ 1 3 2.150459202106
+ 2 4 7.680643154410
+ 3 5 15.944823175319
+ 4 6 26.826688781275
+ 5 7 40.283086869083
+ 6 8 56.289616891011
+ 7 9 74.830023777453
+ 8 10 95.891983053944
+ 9 11 119.465424264199
+ 10 12 145.542112894232
+ 11 13 174.115704751485
+ 12 14 205.181692970898
+ 13 15 238.737011824859
+ 14 16 274.779422707574
+ 15 17 313.306969990248
+ 16 18 354.317721540693
+ 17 19 397.809787603557
+ 18 20 443.781471675578
+ 19 21 492.231385973938
+ 20 22 543.158450851233
+
+ # species: Na, l : 2
+ # nodes n trial energy
+ 0 3 2.044659668292
+ 1 4 6.427853785834
+ 2 5 13.431600603035
+ 3 6 23.065707720667
+ 4 7 35.286723443086
+ 5 8 50.060639647089
+ 6 9 67.365856793742
+ 7 10 87.189032118220
+ 8 11 109.521849753452
+ 9 12 134.358761651081
+ 10 13 161.695405680071
+ 11 14 191.527703285810
+ 12 15 223.851640874520
+ 13 16 258.663517453378
+ 14 17 295.960290921283
+ 15 18 335.739726776720
+ 16 19 378.000263814603
+ 17 20 422.740724390955
+ 18 21 469.960046963641
+ 19 22 519.657164852307
+ 20 23 571.831020081621
+
+ # species: Na, l : 3
+ # nodes n trial energy
+ 0 4 3.879401688185
+ 1 5 9.964477889618
+ 2 6 18.473783441930
+ 3 7 29.505302500625
+ 4 8 43.071695950394
+ 5 9 59.165686791534
+ 6 10 77.776580159806
+ 7 11 98.894853583980
+ 8 12 122.513132271493
+ 9 13 148.626189112094
+ 10 14 177.230592656783
+ 11 15 208.324116670413
+ 12 16 241.905061644769
+ 13 17 277.971722458938
+ 14 18 316.522185899964
+ 15 19 357.554436071786
+ 16 20 401.066608041866
+ 17 21 447.057187991954
+ 18 22 495.525055433203
+ 19 23 546.469383913223
+ 20 24 599.889489594382
+
+ # species: Cl, l : 0
+ # nodes n trial energy
+ 0 1 -100.974592263771
+ 1 2 -8.943406336517
+ 2 3 0.803820215121
+ 3 4 11.265670674793
+ 4 5 27.995235804499
+ 5 6 50.139499480779
+ 6 7 77.425670325936
+ 7 8 109.722811813338
+ 8 9 146.943417860813
+ 9 10 189.030459361669
+ 10 11 235.945624307269
+ 11 12 287.660637659017
+ 12 13 344.155876726629
+ 13 14 405.415989381911
+ 14 15 471.429355254290
+ 15 16 542.186581729314
+ 16 17 617.680068606306
+ 17 18 697.903598826980
+ 18 19 782.852080659573
+ 19 20 872.521357718829
+ 20 21 966.908013775037
+
+ # species: Cl, l : 1
+ # nodes n trial energy
+ 0 2 -6.707821362791
+ 1 3 1.442002863349
+ 2 4 11.086329356142
+ 3 5 26.446761118127
+ 4 6 46.908960145565
+ 5 7 72.314496301330
+ 6 8 102.593748915541
+ 7 9 137.708872214526
+ 8 10 177.628868217528
+ 9 11 222.335815545055
+ 10 12 271.812774967963
+ 11 13 326.047911895729
+ 12 14 385.030908226461
+ 13 15 448.753672165588
+ 14 16 517.209705744261
+ 15 17 590.393819776316
+ 16 18 668.301879780751
+ 17 19 750.930453348811
+ 18 20 838.276595190814
+ 19 21 930.337672332757
+ 20 22 1027.111295649087
+
+ # species: Cl, l : 2
+ # nodes n trial energy
+ 0 3 2.030626399034
+ 1 4 9.540398069814
+ 2 5 22.521806870793
+ 3 6 40.580505794574
+ 4 7 63.608967480673
+ 5 8 91.521851721479
+ 6 9 124.281100552950
+ 7 10 161.847878698612
+ 8 11 204.202486065259
+ 9 12 251.327905737439
+ 10 13 303.212514505293
+ 11 14 359.847427715492
+ 12 15 421.225227525549
+ 13 16 487.339835071361
+ 14 17 558.185801503932
+ 15 18 633.758349847869
+ 16 19 714.053292909356
+ 17 20 799.067051302979
+ 18 21 888.796639877965
+ 19 22 983.239587157865
+ 20 23 1082.393826570133
+
+ # species: Cl, l : 3
+ # nodes n trial energy
+ 0 4 5.877668530234
+ 1 5 16.734445897029
+ 2 6 32.521343858776
+ 3 7 53.248636736501
+ 4 8 78.839760672149
+ 5 9 109.265751512702
+ 6 10 144.502223464379
+ 7 11 184.526132806013
+ 8 12 229.322756357998
+ 9 13 278.877755682806
+ 10 14 333.181582371119
+ 11 15 392.226089160902
+ 12 16 456.005243712150
+ 13 17 524.514261786364
+ 14 18 597.749258861014
+ 15 19 675.706956131225
+ 16 20 758.384407267418
+ 17 21 845.778892453188
+ 18 22 937.887883257667
+ 19 23 1034.709065214155
+ 20 24 1136.240377687629
+"""
diff --git a/tests/dict_parsers/test_gw/mock_gw_info_out.py b/tests/dict_parsers/test_gw/mock_gw_info_out.py
index c1bb8c4..6c472c5 100644
--- a/tests/dict_parsers/test_gw/mock_gw_info_out.py
+++ b/tests/dict_parsers/test_gw/mock_gw_info_out.py
@@ -1,24 +1,4 @@
-"""Outputs from GW_INFO.OUT
-"""
-import pytest
-from excitingtools.utils.test_utils import MockFile
-
-from excitingtools.exciting_dict_parsers.gw_info_parser import _file_name
-
-
-@pytest.fixture
-def zro2_gw_info_out_mock(tmp_path):
- file = tmp_path / _file_name
- file.write_text(zro2_gw_info_out)
- return MockFile(file, zro2_gw_info_out)
-
-
-@pytest.fixture
-def si_2_gw_info_out_mock(tmp_path):
- file = tmp_path / _file_name
- file.write_text(si_2_gw_info_out)
- return MockFile(file, si_2_gw_info_out)
-
+"""Outputs from GW_INFO.OUT"""
# GW output for ZrO2. This reports an indirect gap, as well as its direct gap
zro2_gw_info_out = """
@@ -418,4 +398,3 @@ def si_2_gw_info_out_mock(tmp_path):
_________________________________________________________
Total : 4.25
"""
-
diff --git a/tests/dict_parsers/test_gw/test_gw_dos_parser.py b/tests/dict_parsers/test_gw/test_gw_dos_parser.py
index 8badf91..6754168 100644
--- a/tests/dict_parsers/test_gw/test_gw_dos_parser.py
+++ b/tests/dict_parsers/test_gw/test_gw_dos_parser.py
@@ -4,15 +4,16 @@
pytest --capture=tee-sys
"""
+import numpy as np
import pytest
-from excitingtools.utils.test_utils import MockFile
+
from excitingtools.exciting_dict_parsers.gw_eigenvalues_parser import parse_gw_dos
-import numpy as np
+from excitingtools.utils.test_utils import MockFile
+
@pytest.fixture
def gw_dos_mock(tmp_path) -> MockFile:
- """ Mock TDOS.OUT data for 15 energy and DOS value pairs
- """
+ """Mock TDOS.OUT data for 15 energy and DOS value pairs"""
dos_str = """-0.5000000000 0.000000000
-0.4949748744 0.000000000
-0.4899497487 0.000000000
@@ -35,17 +36,48 @@ def gw_dos_mock(tmp_path) -> MockFile:
def test_parse_gw_dos(gw_dos_mock):
- """ Test parsing of energy and DOS values from TDOS.OUT
- """
+ """Test parsing of energy and DOS values from TDOS.OUT"""
data = parse_gw_dos(gw_dos_mock.file)
- ref_energies = np.array([-0.5000000000, -0.4949748744, -0.4899497487, -0.4849246231, -0.4798994975, -0.4748743719,
- -0.4698492462, -0.4648241206, -0.4597989950, -0.4547738693, -0.4497487437, -0.4447236181,
- -0.4396984925, -0.4346733668, -0.4296482412])
- ref_dos = np.array([0.000000000, 0.000000000, 0.000000000, 0.000000000, 0.000000000, 0.000000000,
- 0.000000000, 0.000000000, 0.000000000, 0.000000000, 0.1025457101E-01, 0.1050068072, 0.3502961458,
- 0.6899275378, 1.164919267])
+ ref_energies = np.array(
+ [
+ -0.5000000000,
+ -0.4949748744,
+ -0.4899497487,
+ -0.4849246231,
+ -0.4798994975,
+ -0.4748743719,
+ -0.4698492462,
+ -0.4648241206,
+ -0.4597989950,
+ -0.4547738693,
+ -0.4497487437,
+ -0.4447236181,
+ -0.4396984925,
+ -0.4346733668,
+ -0.4296482412,
+ ]
+ )
+ ref_dos = np.array(
+ [
+ 0.000000000,
+ 0.000000000,
+ 0.000000000,
+ 0.000000000,
+ 0.000000000,
+ 0.000000000,
+ 0.000000000,
+ 0.000000000,
+ 0.000000000,
+ 0.000000000,
+ 0.1025457101e-01,
+ 0.1050068072,
+ 0.3502961458,
+ 0.6899275378,
+ 1.164919267,
+ ]
+ )
- assert set(data) == {'energy', 'dos'}
- assert np.allclose(data['energy'], ref_energies)
- assert np.allclose(data['dos'], ref_dos)
+ assert set(data) == {"energy", "dos"}
+ assert np.allclose(data["energy"], ref_energies)
+ assert np.allclose(data["dos"], ref_dos)
diff --git a/tests/dict_parsers/test_gw/test_gw_eignvalues_parser.py b/tests/dict_parsers/test_gw/test_gw_eignvalues_parser.py
index fed338f..09e924c 100644
--- a/tests/dict_parsers/test_gw/test_gw_eignvalues_parser.py
+++ b/tests/dict_parsers/test_gw/test_gw_eignvalues_parser.py
@@ -1,17 +1,19 @@
-import pytest
import numpy as np
-
-from excitingtools.utils.test_utils import MockFile
+import pytest
from excitingtools.dataclasses.data_structs import NumberOfStates
-from excitingtools.exciting_dict_parsers.gw_eigenvalues_parser import k_points_from_evalqp, n_states_from_evalqp, \
- parse_column_labels, parse_evalqp
+from excitingtools.exciting_dict_parsers.gw_eigenvalues_parser import (
+ k_points_from_evalqp,
+ n_states_from_evalqp,
+ parse_column_labels,
+ parse_evalqp,
+)
+from excitingtools.utils.test_utils import MockFile
@pytest.fixture
def evalqp_mock(tmp_path):
- """ Mock EVALQP.OUT data with energies for 3 k-points, and corrections starting from the first state
- """
+ """Mock EVALQP.OUT data with energies for 3 k-points, and corrections starting from the first state"""
evalqp_str = """k-point # 1: 0.000000 0.000000 0.000000 0.125000
state E_KS E_HF E_GW Sx Re(Sc) Im(Sc) Vxc DE_HF DE_GW Znk
1 -14.68627 -16.50308 -16.31014 -4.72516 0.17351 0.00002 -2.90835 -1.81681 -1.62387 0.98817
@@ -62,7 +64,7 @@ def evalqp_mock(tmp_path):
@pytest.fixture
def evalqp_mock_partial_corrections(tmp_path):
- """ Mock EVALQP.OUT data with energies for 3 k-points, BUT corrections starting from NOT the
+ """Mock EVALQP.OUT data with energies for 3 k-points, BUT corrections starting from NOT the
first state.
"""
evalqp_str = """k-point # 1: 0.000000 0.000000 0.000000 0.015625
@@ -92,21 +94,19 @@ def evalqp_mock_partial_corrections(tmp_path):
def test_k_points_from_evalqp(evalqp_mock):
- """ Test parsing of EVALQP.DAT
- """
+ """Test parsing of EVALQP.DAT"""
ref = {
- 1: {'k_point': [0.000000, 0.000000, 0.000000], 'weight': 0.125000},
- 2: {'k_point': [0.000000, 0.000000, 0.500000], 'weight': 0.500000},
- 3: {'k_point': [0.000000, 0.500000, 0.500000], 'weight': 0.375000}
+ 1: {"k_point": [0.000000, 0.000000, 0.000000], "weight": 0.125000},
+ 2: {"k_point": [0.000000, 0.000000, 0.500000], "weight": 0.500000},
+ 3: {"k_point": [0.000000, 0.500000, 0.500000], "weight": 0.375000},
}
k_points_and_weights = k_points_from_evalqp(evalqp_mock.string)
assert k_points_and_weights == ref, "k_points_and_weights should match reference"
-def test_n_states_from_evalqp_one_kpoint(evalqp_mock):
- """ Test parsing of EVALQP.DAT
- """
+def test_n_states_from_evalqp_one_kpoint():
+ """Test parsing of EVALQP.DAT"""
evalqp_str_with_one_kpoint = """k-point # 1: 0.000000 0.000000 0.000000 0.125000
state E_KS E_HF E_GW Sx Re(Sc) Im(Sc) Vxc DE_HF DE_GW Znk
1 -14.68627 -16.50308 -16.31014 -4.72516 0.17351 0.00002 -2.90835 -1.81681 -1.62387 0.98817
@@ -142,92 +142,122 @@ def test_n_states_from_evalqp_first_state_not_one(evalqp_mock_partial_correction
def test_parse_column_labels_for_oxygen(evalqp_mock):
column_labels = parse_column_labels(evalqp_mock.string)
- assert column_labels._member_names_ == ['E_KS', 'E_HF', 'E_GW', 'Sx',
- 'Re(Sc)', 'Im(Sc)', 'Vxc', 'DE_HF', 'DE_GW', 'Znk']
+ assert column_labels._member_names_ == [
+ "E_KS",
+ "E_HF",
+ "E_GW",
+ "Sx",
+ "Re(Sc)",
+ "Im(Sc)",
+ "Vxc",
+ "DE_HF",
+ "DE_GW",
+ "Znk",
+ ]
assert column_labels.E_KS.value == 0, "Expect first label value to start at 0"
def test_parse_column_labels_for_nitrogen(evalqp_mock):
column_labels = parse_column_labels(evalqp_mock.string)
- assert column_labels._member_names_ == ['E_KS', 'E_HF', 'E_GW', 'Sx',
- 'Re(Sc)', 'Im(Sc)', 'Vxc', 'DE_HF', 'DE_GW', 'Znk']
+ assert column_labels._member_names_ == [
+ "E_KS",
+ "E_HF",
+ "E_GW",
+ "Sx",
+ "Re(Sc)",
+ "Im(Sc)",
+ "Vxc",
+ "DE_HF",
+ "DE_GW",
+ "Znk",
+ ]
assert column_labels.E_KS.value == 0, "Expect first label value to start at 0"
def test_parse_evalqp(evalqp_mock):
- """ Test parsing eigenvalues and weights from EVALQP.DAT.
- """
+ """Test parsing eigenvalues and weights from EVALQP.DAT."""
# Energies for all states, per k-point
energies_1 = np.array(
- [[-14.68627, -16.50308, -16.31014, -4.72516, 0.17351, 0.00002, -2.90835, -1.81681, -1.62387, 0.98817],
- [-11.48914, -12.99338, -12.79204, -4.36834, 0.18150, 0.00003, -2.86410, -1.50424, -1.30290, 0.98500],
- [-11.48914, -12.99338, -12.79204, -4.36834, 0.18150, 0.00003, -2.86410, -1.50424, -1.30290, 0.98500],
- [-11.48914, -12.99338, -12.79203, -4.36834, 0.18151, 0.00003, -2.86410, -1.50424, -1.30289, 0.98500],
- [-6.24399, -7.20716, -7.01655, -3.72742, 0.17144, 0.00000, -2.76425, -0.96317, -0.77257, 0.97579],
- [-6.24399, -7.20716, -7.01652, -3.72742, 0.17144, -0.00001, -2.76425, -0.96317, -0.77253, 0.97575],
- [-6.24340, -7.20928, -7.01752, -3.73069, 0.17268, 0.00003, -2.76481, -0.96588, -0.77412, 0.97594],
- [-6.24340, -7.20928, -7.01752, -3.73069, 0.17268, 0.00003, -2.76481, -0.96588, -0.77412, 0.97594],
- [-6.24340, -7.20928, -7.01752, -3.73069, 0.17268, 0.00003, -2.76481, -0.96588, -0.77412, 0.97594],
- [-1.82156, -2.31023, -2.07248, -1.52298, 0.20180, 0.00623, -1.03431, -0.48867, -0.25092, 0.87468],
- [-1.00771, -1.29282, -1.07701, -1.21488, 0.19790, 0.02221, -0.92977, -0.28511, -0.06930, 0.79459]]
- )
+ [
+ [-14.68627, -16.50308, -16.31014, -4.72516, 0.17351, 0.00002, -2.90835, -1.81681, -1.62387, 0.98817],
+ [-11.48914, -12.99338, -12.79204, -4.36834, 0.18150, 0.00003, -2.86410, -1.50424, -1.30290, 0.98500],
+ [-11.48914, -12.99338, -12.79204, -4.36834, 0.18150, 0.00003, -2.86410, -1.50424, -1.30290, 0.98500],
+ [-11.48914, -12.99338, -12.79203, -4.36834, 0.18151, 0.00003, -2.86410, -1.50424, -1.30289, 0.98500],
+ [-6.24399, -7.20716, -7.01655, -3.72742, 0.17144, 0.00000, -2.76425, -0.96317, -0.77257, 0.97579],
+ [-6.24399, -7.20716, -7.01652, -3.72742, 0.17144, -0.00001, -2.76425, -0.96317, -0.77253, 0.97575],
+ [-6.24340, -7.20928, -7.01752, -3.73069, 0.17268, 0.00003, -2.76481, -0.96588, -0.77412, 0.97594],
+ [-6.24340, -7.20928, -7.01752, -3.73069, 0.17268, 0.00003, -2.76481, -0.96588, -0.77412, 0.97594],
+ [-6.24340, -7.20928, -7.01752, -3.73069, 0.17268, 0.00003, -2.76481, -0.96588, -0.77412, 0.97594],
+ [-1.82156, -2.31023, -2.07248, -1.52298, 0.20180, 0.00623, -1.03431, -0.48867, -0.25092, 0.87468],
+ [-1.00771, -1.29282, -1.07701, -1.21488, 0.19790, 0.02221, -0.92977, -0.28511, -0.06930, 0.79459],
+ ]
+ )
energies_2 = np.array(
- [[-14.68627, -16.50308, -16.31014, -4.72516, 0.17351, 0.00002, -2.90835, -1.81681, -1.62387, 0.98817],
- [-11.48915, -12.99338, -12.79204, -4.36834, 0.18150, 0.00003, -2.86410, -1.50424, -1.30289, 0.98500],
- [-11.48915, -12.99338, -12.79204, -4.36834, 0.18151, 0.00003, -2.86410, -1.50424, -1.30289, 0.98500],
- [-11.48914, -12.99338, -12.79204, -4.36834, 0.18150, 0.00003, -2.86410, -1.50424, -1.30290, 0.98500],
- [-6.24401, -7.20719, -7.01653, -3.72740, 0.17145, -0.00001, -2.76423, -0.96318, -0.77252, 0.97574],
- [-6.24401, -7.20719, -7.01650, -3.72740, 0.17145, -0.00001, -2.76423, -0.96318, -0.77249, 0.97570],
- [-6.24345, -7.20933, -7.01756, -3.73065, 0.17268, 0.00003, -2.76477, -0.96588, -0.77411, 0.97593],
- [-6.24344, -7.20932, -7.01754, -3.73066, 0.17268, 0.00003, -2.76478, -0.96588, -0.77410, 0.97593],
- [-6.24344, -7.20932, -7.01755, -3.73066, 0.17268, 0.00003, -2.76478, -0.96588, -0.77411, 0.97593],
- [-1.81969, -2.30723, -2.07258, -1.52476, 0.20051, 0.00867, -1.03723, -0.48754, -0.25289, 0.88104],
- [-1.03473, -1.34882, -1.09998, -1.20344, 0.22865, 0.02686, -0.88936, -0.31408, -0.06525, 0.76380]]
+ [
+ [-14.68627, -16.50308, -16.31014, -4.72516, 0.17351, 0.00002, -2.90835, -1.81681, -1.62387, 0.98817],
+ [-11.48915, -12.99338, -12.79204, -4.36834, 0.18150, 0.00003, -2.86410, -1.50424, -1.30289, 0.98500],
+ [-11.48915, -12.99338, -12.79204, -4.36834, 0.18151, 0.00003, -2.86410, -1.50424, -1.30289, 0.98500],
+ [-11.48914, -12.99338, -12.79204, -4.36834, 0.18150, 0.00003, -2.86410, -1.50424, -1.30290, 0.98500],
+ [-6.24401, -7.20719, -7.01653, -3.72740, 0.17145, -0.00001, -2.76423, -0.96318, -0.77252, 0.97574],
+ [-6.24401, -7.20719, -7.01650, -3.72740, 0.17145, -0.00001, -2.76423, -0.96318, -0.77249, 0.97570],
+ [-6.24345, -7.20933, -7.01756, -3.73065, 0.17268, 0.00003, -2.76477, -0.96588, -0.77411, 0.97593],
+ [-6.24344, -7.20932, -7.01754, -3.73066, 0.17268, 0.00003, -2.76478, -0.96588, -0.77410, 0.97593],
+ [-6.24344, -7.20932, -7.01755, -3.73066, 0.17268, 0.00003, -2.76478, -0.96588, -0.77411, 0.97593],
+ [-1.81969, -2.30723, -2.07258, -1.52476, 0.20051, 0.00867, -1.03723, -0.48754, -0.25289, 0.88104],
+ [-1.03473, -1.34882, -1.09998, -1.20344, 0.22865, 0.02686, -0.88936, -0.31408, -0.06525, 0.76380],
+ ]
)
energies_3 = np.array(
- [[-14.68627, -16.50308, -16.31014, -4.72516, 0.17351, 0.00002, -2.90835, -1.81681, -1.62387, 0.98818],
- [-11.48915, -12.99338, -12.79204, -4.36834, 0.18150, 0.00003, -2.86410, -1.50424, -1.30290, 0.98500],
- [-11.48915, -12.99338, -12.79204, -4.36834, 0.18150, 0.00003, -2.86410, -1.50424, -1.30289, 0.98500],
- [-11.48915, -12.99338, -12.79204, -4.36834, 0.18151, 0.00003, -2.86410, -1.50424, -1.30289, 0.98500],
- [-6.24405, -7.20722, -7.01657, -3.72737, 0.17144, -0.00001, -2.76420, -0.96317, -0.77252, 0.97574],
- [-6.24403, -7.20721, -7.01657, -3.72738, 0.17144, -0.00000, -2.76421, -0.96317, -0.77253, 0.97575],
- [-6.24346, -7.20934, -7.01757, -3.73064, 0.17268, 0.00004, -2.76476, -0.96588, -0.77411, 0.97593],
- [-6.24344, -7.20932, -7.01755, -3.73066, 0.17268, 0.00003, -2.76478, -0.96588, -0.77412, 0.97594],
- [-6.24344, -7.20932, -7.01755, -3.73066, 0.17268, 0.00003, -2.76478, -0.96588, -0.77411, 0.97593],
- [-1.81908, -2.30620, -2.07323, -1.52531, 0.19899, 0.00760, -1.03818, -0.48712, -0.25415, 0.88205],
- [-1.03685, -1.35370, -1.10126, -1.20433, 0.23184, 0.02427, -0.88748, -0.31684, -0.06441, 0.75775]]
+ [
+ [-14.68627, -16.50308, -16.31014, -4.72516, 0.17351, 0.00002, -2.90835, -1.81681, -1.62387, 0.98818],
+ [-11.48915, -12.99338, -12.79204, -4.36834, 0.18150, 0.00003, -2.86410, -1.50424, -1.30290, 0.98500],
+ [-11.48915, -12.99338, -12.79204, -4.36834, 0.18150, 0.00003, -2.86410, -1.50424, -1.30289, 0.98500],
+ [-11.48915, -12.99338, -12.79204, -4.36834, 0.18151, 0.00003, -2.86410, -1.50424, -1.30289, 0.98500],
+ [-6.24405, -7.20722, -7.01657, -3.72737, 0.17144, -0.00001, -2.76420, -0.96317, -0.77252, 0.97574],
+ [-6.24403, -7.20721, -7.01657, -3.72738, 0.17144, -0.00000, -2.76421, -0.96317, -0.77253, 0.97575],
+ [-6.24346, -7.20934, -7.01757, -3.73064, 0.17268, 0.00004, -2.76476, -0.96588, -0.77411, 0.97593],
+ [-6.24344, -7.20932, -7.01755, -3.73066, 0.17268, 0.00003, -2.76478, -0.96588, -0.77412, 0.97594],
+ [-6.24344, -7.20932, -7.01755, -3.73066, 0.17268, 0.00003, -2.76478, -0.96588, -0.77411, 0.97593],
+ [-1.81908, -2.30620, -2.07323, -1.52531, 0.19899, 0.00760, -1.03818, -0.48712, -0.25415, 0.88205],
+ [-1.03685, -1.35370, -1.10126, -1.20433, 0.23184, 0.02427, -0.88748, -0.31684, -0.06441, 0.75775],
+ ]
)
ref = {
- 1: {'k_point': [0.000000, 0.000000, 0.000000], 'weight': 0.125000},
- 2: {'k_point': [0.000000, 0.000000, 0.500000], 'weight': 0.500000},
- 3: {'k_point': [0.000000, 0.500000, 0.500000], 'weight': 0.375000}
- }
+ 1: {"k_point": [0.000000, 0.000000, 0.000000], "weight": 0.125000},
+ 2: {"k_point": [0.000000, 0.000000, 0.500000], "weight": 0.500000},
+ 3: {"k_point": [0.000000, 0.500000, 0.500000], "weight": 0.375000},
+ }
output = parse_evalqp(evalqp_mock.full_path)
- assert set(output.keys()) == {'state_range', 'column_labels', 1, 2, 3}, \
- 'Key include state range, columns and k-indices'
+ assert set(output.keys()) == {
+ "state_range",
+ "column_labels",
+ 1,
+ 2,
+ 3,
+ }, "Key include state range, columns and k-indices"
- del output['state_range']
- del output['column_labels']
+ del output["state_range"]
+ del output["column_labels"]
# k-points
assert len(output) == 3, "Expect 3 k-points"
- assert min([ik for ik in output.keys()]) == 1, 'k-point indexing starts at 1'
- assert output[1]['k_point'] == ref[1]['k_point'], "Compare k-point 1 to reference"
- assert output[2]['k_point'] == ref[2]['k_point'], "Compare k-point 2 to reference"
- assert output[3]['k_point'] == ref[3]['k_point'], "Compare k-point 3 to reference"
+ assert min([ik for ik in output]) == 1, "k-point indexing starts at 1"
+ assert output[1]["k_point"] == ref[1]["k_point"], "Compare k-point 1 to reference"
+ assert output[2]["k_point"] == ref[2]["k_point"], "Compare k-point 2 to reference"
+ assert output[3]["k_point"] == ref[3]["k_point"], "Compare k-point 3 to reference"
# Weights
- assert output[1]['weight'] == ref[1]['weight'], "Compare weight 1 to reference"
- assert output[2]['weight'] == ref[2]['weight'], "Compare weight 2 to reference"
- assert output[3]['weight'] == ref[3]['weight'], "Compare weight 3 to reference"
+ assert output[1]["weight"] == ref[1]["weight"], "Compare weight 1 to reference"
+ assert output[2]["weight"] == ref[2]["weight"], "Compare weight 2 to reference"
+ assert output[3]["weight"] == ref[3]["weight"], "Compare weight 3 to reference"
# Energies
- assert output[1]['energies'].shape == (11, 10), 'rows = 11 states and cols = 10 energies'
- assert np.allclose(output[1]['energies'], energies_1), "Compare energies 1 to reference"
- assert np.allclose(output[2]['energies'], energies_2), "Compare energies 2 to reference"
- assert np.allclose(output[3]['energies'], energies_3), "Compare energies 3 to reference"
+ assert output[1]["energies"].shape == (11, 10), "rows = 11 states and cols = 10 energies"
+ assert np.allclose(output[1]["energies"], energies_1), "Compare energies 1 to reference"
+ assert np.allclose(output[2]["energies"], energies_2), "Compare energies 2 to reference"
+ assert np.allclose(output[3]["energies"], energies_3), "Compare energies 3 to reference"
diff --git a/tests/dict_parsers/test_gw/test_gw_eps00_parser.py b/tests/dict_parsers/test_gw/test_gw_eps00_parser.py
index c255785..b99f4e4 100644
--- a/tests/dict_parsers/test_gw/test_gw_eps00_parser.py
+++ b/tests/dict_parsers/test_gw/test_gw_eps00_parser.py
@@ -1,14 +1,12 @@
-""" Test all GW output file parsers, except GW_INFO.OUT
-"""
-import pytest
+"""Test all GW output file parsers, except GW_INFO.OUT"""
+
import numpy as np
+import pytest
+from excitingtools.exciting_dict_parsers.gw_eps00_parser import _file_name, parse_eps00_frequencies, parse_eps00_gw
from excitingtools.utils.test_utils import MockFile
from excitingtools.utils.utils import get_new_line_indices
-from excitingtools.exciting_dict_parsers.gw_eps00_parser import parse_eps00_frequencies, parse_eps00_gw, \
- _file_name
-
@pytest.fixture
def eps00_mock(tmp_path):
@@ -41,62 +39,60 @@ def eps00_mock(tmp_path):
def test_parse_eps00_frequencies(eps00_mock):
- """ Test parsing frequencies from EPS00_GW.OUT
- """
+ """Test parsing frequencies from EPS00_GW.OUT"""
line = get_new_line_indices(eps00_mock.string)
ref = {1: 0.00529953, 2: 0.02771249, 3: 0.06718440}
- assert eps00_mock.string[line[0]:line[1]].isspace(), "First line of eps_string must be a whiteline"
+ assert eps00_mock.string[line[0] : line[1]].isspace(), "First line of eps_string must be a whiteline"
assert parse_eps00_frequencies(eps00_mock.string) == ref, "Frequency grid for eps00"
def test_parse_eps00_gw(eps00_mock):
- """ Test parsing EPS00_GW.OUT
- """
+ """Test parsing EPS00_GW.OUT"""
line = get_new_line_indices(eps00_mock.string)
- assert eps00_mock.string[line[0]:line[1]].isspace(), "First line of eps_string must be a whiteline"
+ assert eps00_mock.string[line[0] : line[1]].isspace(), "First line of eps_string must be a whiteline"
ref = {
1: {
- 'frequency': 0.00529953,
- 'eps00': {
- 're': np.array([[8.31881773, 0., 0.], [0., 8.31881773, 0.], [0., 0., 8.31881773]]),
- 'img': np.array([[0., 0., 0.], [0., 0., 0.], [0., 0., 0.]])
- }
+ "frequency": 0.00529953,
+ "eps00": {
+ "re": np.array([[8.31881773, 0.0, 0.0], [0.0, 8.31881773, 0.0], [0.0, 0.0, 8.31881773]]),
+ "img": np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]),
},
+ },
2: {
- 'frequency': 0.02771249,
- 'eps00': {
- 're': np.array([[8.22189228, 0., 0.], [0., 8.22189228, 0.], [0., 0., 8.22189228]]),
- 'img': np.array([[-0., 0., 0.], [0., -0., 0.], [0., 0., -0.]])
- }
+ "frequency": 0.02771249,
+ "eps00": {
+ "re": np.array([[8.22189228, 0.0, 0.0], [0.0, 8.22189228, 0.0], [0.0, 0.0, 8.22189228]]),
+ "img": np.array([[-0.0, 0.0, 0.0], [0.0, -0.0, 0.0], [0.0, 0.0, -0.0]]),
},
+ },
3: {
- 'frequency': 0.06718440,
- 'eps00': {
- 're': np.array([[7.78004308, 0.0, 0.0], [0., 7.78004308, 0.], [0., 0., 7.78004308]]),
- 'img': np.array([[0., 0., 0.], [0., 0., 0.], [0., 0., 0.]])
- }
- }
- }
+ "frequency": 0.06718440,
+ "eps00": {
+ "re": np.array([[7.78004308, 0.0, 0.0], [0.0, 7.78004308, 0.0], [0.0, 0.0, 7.78004308]]),
+ "img": np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]),
+ },
+ },
+ }
output = parse_eps00_gw(eps00_mock.file)
assert len(output) == 3, "3 frequency points"
- assert [k for k in output[1].keys()] == ['frequency', 'eps00'], "Frequency point 1 keys "
- assert [k for k in output[2].keys()] == ['frequency', 'eps00'], "Frequency point 2 keys "
- assert [k for k in output[3].keys()] == ['frequency', 'eps00'], "Frequency point 3 keys "
+ assert [k for k in output[1]] == ["frequency", "eps00"], "Frequency point 1 keys "
+ assert [k for k in output[2]] == ["frequency", "eps00"], "Frequency point 2 keys "
+ assert [k for k in output[3]] == ["frequency", "eps00"], "Frequency point 3 keys "
- assert output[1]['frequency'] == 0.00529953, "Frequency point 1 value"
- assert output[2]['frequency'] == 0.02771249, "Frequency point 2 value"
- assert output[3]['frequency'] == 0.06718440, "Frequency point 3 value"
+ assert output[1]["frequency"] == 0.00529953, "Frequency point 1 value"
+ assert output[2]["frequency"] == 0.02771249, "Frequency point 2 value"
+ assert output[3]["frequency"] == 0.06718440, "Frequency point 3 value"
- assert np.allclose(output[1]['eps00']['re'], ref[1]['eps00']['re']),"Re{eps00} at frequency point 1"
- assert np.allclose(output[1]['eps00']['img'], ref[1]['eps00']['img']),"Im{eps00} at frequency point 1"
+ assert np.allclose(output[1]["eps00"]["re"], ref[1]["eps00"]["re"]), "Re{eps00} at frequency point 1"
+ assert np.allclose(output[1]["eps00"]["img"], ref[1]["eps00"]["img"]), "Im{eps00} at frequency point 1"
- assert np.allclose(output[2]['eps00']['re'], ref[2]['eps00']['re']),"Re{eps00} at frequency point 2"
- assert np.allclose(output[2]['eps00']['img'], ref[2]['eps00']['img']),"Im{eps00} at frequency point 2"
+ assert np.allclose(output[2]["eps00"]["re"], ref[2]["eps00"]["re"]), "Re{eps00} at frequency point 2"
+ assert np.allclose(output[2]["eps00"]["img"], ref[2]["eps00"]["img"]), "Im{eps00} at frequency point 2"
- assert np.allclose(output[3]['eps00']['re'], ref[3]['eps00']['re']),"Re{eps00} at frequency point 3"
- assert np.allclose(output[3]['eps00']['img'], ref[3]['eps00']['img']),"Im{eps00} at frequency point 3"
+ assert np.allclose(output[3]["eps00"]["re"], ref[3]["eps00"]["re"]), "Re{eps00} at frequency point 3"
+ assert np.allclose(output[3]["eps00"]["img"], ref[3]["eps00"]["img"]), "Im{eps00} at frequency point 3"
diff --git a/tests/dict_parsers/test_gw/test_gw_info_parser.py b/tests/dict_parsers/test_gw/test_gw_info_parser.py
index 75c39c1..6926d5d 100644
--- a/tests/dict_parsers/test_gw/test_gw_info_parser.py
+++ b/tests/dict_parsers/test_gw/test_gw_info_parser.py
@@ -6,39 +6,63 @@
"""
import numpy as np
-
-from excitingtools.exciting_dict_parsers.gw_info_parser import parse_gw_info, parse_frequency_grid, \
- parse_correlation_self_energy_params, extract_kpoints, parse_ks_eigenstates, \
- parse_n_q_point_cycles, parse_band_structure_info, parse_mixed_product_params, \
- parse_bare_coulomb_potential_params, parse_gw_timings
+import pytest
+
+from excitingtools.exciting_dict_parsers.gw_info_parser import (
+ _file_name,
+ extract_kpoints,
+ parse_band_structure_info,
+ parse_bare_coulomb_potential_params,
+ parse_correlation_self_energy_params,
+ parse_frequency_grid,
+ parse_gw_info,
+ parse_gw_timings,
+ parse_ks_eigenstates,
+ parse_mixed_product_params,
+ parse_n_q_point_cycles,
+)
+from excitingtools.utils.test_utils import MockFile
+
+from .mock_gw_info_out import si_2_gw_info_out, zro2_gw_info_out
# Text files are large, it's easier to store externally.
-# Note, these fixtures are used, even if greyed out by the IDE
-from . mock_gw_info_out import zro2_gw_info_out_mock, si_2_gw_info_out_mock
+
+
+@pytest.fixture
+def zro2_gw_info_out_mock(tmp_path):
+ file = tmp_path / _file_name
+ file.write_text(zro2_gw_info_out)
+ return MockFile(file, zro2_gw_info_out)
+
+
+@pytest.fixture
+def si_2_gw_info_out_mock(tmp_path):
+ file = tmp_path / _file_name
+ file.write_text(si_2_gw_info_out)
+ return MockFile(file, si_2_gw_info_out)
def test_parse_correlation_self_energy_params(zro2_gw_info_out_mock):
- """ Test `Correlation self-energy parameters` block of GW_INFO.OUT.
- """
- reference = {'Solution of the QP equation': 0,
- 'Energy alignment': 0,
- 'Analytic continuation method': "PADE - Thiele's reciprocal difference method",
- 'Analytic continuation method citation': "H. J. Vidberg and J. W. Serence, J. Low Temp. Phys. 29, 179 (1977)",
- 'Scheme to treat singularities': 'Auxiliary function method "mpb"',
- 'Scheme to treat singularities citation': 'S. Massidda, M. Posternak, and A. Baldereschi, PRB 48, 5058 (1993)'
- }
+ """Test `Correlation self-energy parameters` block of GW_INFO.OUT."""
+ reference = {
+ "Solution of the QP equation": 0,
+ "Energy alignment": 0,
+ "Analytic continuation method": "PADE - Thiele's reciprocal difference method",
+ "Analytic continuation method citation": "H. J. Vidberg and J. W. Serence, J. Low Temp. Phys. 29, 179 (1977)",
+ "Scheme to treat singularities": 'Auxiliary function method "mpb"',
+ "Scheme to treat singularities citation": "S. Massidda, M. Posternak, and A. Baldereschi, PRB 48, 5058 (1993)",
+ }
output = parse_correlation_self_energy_params(zro2_gw_info_out_mock.string)
assert reference == output, "parse_correlation_self_energy_params dictionary not consistent with reference"
def test_parse_mixed_product_params(zro2_gw_info_out_mock):
- """ Test `Mixed product basis parameters` block of GW_INFO.OUT.
- """
+ """Test `Mixed product basis parameters` block of GW_INFO.OUT."""
ref = {
- 'MT Angular momentum cutoff': 4,
- 'MT Linear dependence tolerance factor': 0.001,
- 'Plane wave cutoff (in units of Gkmax)': 1.0
+ "MT Angular momentum cutoff": 4,
+ "MT Linear dependence tolerance factor": 0.001,
+ "Plane wave cutoff (in units of Gkmax)": 1.0,
}
output = parse_mixed_product_params(zro2_gw_info_out_mock.string)
@@ -47,12 +71,11 @@ def test_parse_mixed_product_params(zro2_gw_info_out_mock):
def test_parse_bare_coulomb_potential_params(zro2_gw_info_out_mock):
- """ Test `Bare Coulomb potential parameters` block of GW_INFO.OUT.
- """
+ """Test `Bare Coulomb potential parameters` block of GW_INFO.OUT."""
ref = {
- 'Plane wave cutoff (in units of Gkmax*gmb)': 2.0,
- 'Error tolerance for structure constants': 1e-16,
- 'MB tolerance factor': 0.1
+ "Plane wave cutoff (in units of Gkmax*gmb)": 2.0,
+ "Error tolerance for structure constants": 1e-16,
+ "MB tolerance factor": 0.1,
}
output = parse_bare_coulomb_potential_params(zro2_gw_info_out_mock.string)
@@ -61,49 +84,106 @@ def test_parse_bare_coulomb_potential_params(zro2_gw_info_out_mock):
def test_parse_frequency_grid(zro2_gw_info_out_mock):
- """ Test parsing `frequency grid` block of GW_INFO.OUT.
- """
+ """Test parsing `frequency grid` block of GW_INFO.OUT."""
n_points = 32
f_grid = parse_frequency_grid(zro2_gw_info_out_mock.string, n_points)
- ref_frequencies = np.array([5.2995325042E-03, 2.7712488463E-02, 6.7184398806E-02, 0.1222977958, 0.1910618778,
- 0.2709916112, 0.3591982246, 0.4524937451, 0.5475062549, 0.6408017754,
- 0.7290083888, 0.8089381222, 0.8777022042, 0.9328156012, 0.9722875115,
- 0.9947004675, 1.005327767, 1.028502360, 1.072023237, 1.139338599,
- 1.236188495, 1.371726328, 1.560544990, 1.826463152, 2.209975300,
- 2.783978125, 3.690151129, 5.233906478, 8.176762249, 14.88440795,
- 36.08481430, 188.6958895])
-
- ref_weights = np.array([1.3576229706E-02, 3.1126761969E-02, 4.7579255841E-02, 6.2314485628E-02, 7.4797994408E-02,
- 8.4578259698E-02, 9.1301707522E-02, 9.4725305228E-02, 9.4725305228E-02, 9.1301707522E-02,
- 8.4578259698E-02, 7.4797994408E-02, 6.2314485628E-02, 4.7579255841E-02, 3.1126761969E-02,
- 1.3576229706E-02, 1.3721277051E-02, 3.2926421206E-02, 5.4679689940E-02, 8.0889962951E-02,
- 0.1143034524, 0.1591452545, 0.2223471090, 0.3160005534, 0.4626375217,
- 0.7076370069, 1.151720377, 2.048999581, 4.166311667, 10.54097479,
- 40.53058703, 483.3971183])
+ ref_frequencies = np.array(
+ [
+ 5.2995325042e-03,
+ 2.7712488463e-02,
+ 6.7184398806e-02,
+ 0.1222977958,
+ 0.1910618778,
+ 0.2709916112,
+ 0.3591982246,
+ 0.4524937451,
+ 0.5475062549,
+ 0.6408017754,
+ 0.7290083888,
+ 0.8089381222,
+ 0.8777022042,
+ 0.9328156012,
+ 0.9722875115,
+ 0.9947004675,
+ 1.005327767,
+ 1.028502360,
+ 1.072023237,
+ 1.139338599,
+ 1.236188495,
+ 1.371726328,
+ 1.560544990,
+ 1.826463152,
+ 2.209975300,
+ 2.783978125,
+ 3.690151129,
+ 5.233906478,
+ 8.176762249,
+ 14.88440795,
+ 36.08481430,
+ 188.6958895,
+ ]
+ )
+
+ ref_weights = np.array(
+ [
+ 1.3576229706e-02,
+ 3.1126761969e-02,
+ 4.7579255841e-02,
+ 6.2314485628e-02,
+ 7.4797994408e-02,
+ 8.4578259698e-02,
+ 9.1301707522e-02,
+ 9.4725305228e-02,
+ 9.4725305228e-02,
+ 9.1301707522e-02,
+ 8.4578259698e-02,
+ 7.4797994408e-02,
+ 6.2314485628e-02,
+ 4.7579255841e-02,
+ 3.1126761969e-02,
+ 1.3576229706e-02,
+ 1.3721277051e-02,
+ 3.2926421206e-02,
+ 5.4679689940e-02,
+ 8.0889962951e-02,
+ 0.1143034524,
+ 0.1591452545,
+ 0.2223471090,
+ 0.3160005534,
+ 0.4626375217,
+ 0.7076370069,
+ 1.151720377,
+ 2.048999581,
+ 4.166311667,
+ 10.54097479,
+ 40.53058703,
+ 483.3971183,
+ ]
+ )
assert len(ref_frequencies) == 32, "Require 32 reference frequency points"
assert len(ref_weights) == 32, "Require 32 reference weights"
- assert np.allclose(f_grid[0, :],
- ref_frequencies), "Frequency points parsed from gw_info_out disagree with reference"
+ assert np.allclose(
+ f_grid[0, :], ref_frequencies
+ ), "Frequency points parsed from gw_info_out disagree with reference"
assert np.allclose(f_grid[1, :], ref_weights), "Weights parsed from gw_info_out disagree with reference"
def test_parse_ks_eigenstates(zro2_gw_info_out_mock):
- """ Test parsing ` Kohn-Sham eigenstates summary` block from GW_INFO.OUT.
- """
+ """Test parsing ` Kohn-Sham eigenstates summary` block from GW_INFO.OUT."""
ref = {
- 'Maximum number of LAPW states': 847,
- 'Minimal number of LAPW states': 838,
- 'Number of states used in GW - total KS': 838,
- 'Number of states used in GW - occupied': 21,
- 'Number of states used in GW - unoccupied': 2000,
- 'Number of states used in GW - dielectric function': 838,
- 'Number of states used in GW - self energy': 838,
- 'Energy of the highest unoccupied state': 1030.791933,
- 'Number of valence electrons': 42,
- 'Number of valence electrons treated in GW': 42
+ "Maximum number of LAPW states": 847,
+ "Minimal number of LAPW states": 838,
+ "Number of states used in GW - total KS": 838,
+ "Number of states used in GW - occupied": 21,
+ "Number of states used in GW - unoccupied": 2000,
+ "Number of states used in GW - dielectric function": 838,
+ "Number of states used in GW - self energy": 838,
+ "Energy of the highest unoccupied state": 1030.791933,
+ "Number of valence electrons": 42,
+ "Number of valence electrons treated in GW": 42,
}
output = parse_ks_eigenstates(zro2_gw_info_out_mock.string)
@@ -117,9 +197,7 @@ def test_parse_n_q_point_cycles(zro2_gw_info_out_mock):
def test_extract_kpoint(zro2_gw_info_out_mock):
- ref = {'VBM': {'k_point': [0.0, 0.5, 0.5], 'ik': 3},
- 'CBm': {'k_point': [0.0, 0.0, 0.0], 'ik': 1}
- }
+ ref = {"VBM": {"k_point": [0.0, 0.5, 0.5], "ik": 3}, "CBm": {"k_point": [0.0, 0.0, 0.0], "ik": 1}}
output = extract_kpoints(zro2_gw_info_out_mock.string)
@@ -127,166 +205,172 @@ def test_extract_kpoint(zro2_gw_info_out_mock):
def test_parse_band_structure_info(zro2_gw_info_out_mock):
- """ Test parsing the `Kohn-Sham band structure` block of GW_INFO.OUT
- """
+ """Test parsing the `Kohn-Sham band structure` block of GW_INFO.OUT"""
ks_ref = {
- 'Fermi energy': 0.0,
- 'Energy range': [-14.6863, 1030.7919],
- 'Band index of VBM': 21,
- 'Band index of CBm': 22,
- 'Indirect BandGap (eV)': 3.3206,
- 'Direct Bandgap at k(VBM) (eV)': 3.7482,
- 'Direct Bandgap at k(CBm) (eV)': 3.8653,
- 'VBM': {'k_point': [0.0, 0.5, 0.5], 'ik': 3},
- 'CBm': {'k_point': [0.0, 0.0, 0.0], 'ik': 1}
+ "Fermi energy": 0.0,
+ "Energy range": [-14.6863, 1030.7919],
+ "Band index of VBM": 21,
+ "Band index of CBm": 22,
+ "Indirect BandGap (eV)": 3.3206,
+ "Direct Bandgap at k(VBM) (eV)": 3.7482,
+ "Direct Bandgap at k(CBm) (eV)": 3.8653,
+ "VBM": {"k_point": [0.0, 0.5, 0.5], "ik": 3},
+ "CBm": {"k_point": [0.0, 0.0, 0.0], "ik": 1},
}
- ks_output = parse_band_structure_info(zro2_gw_info_out_mock.string, 'ks')
+ ks_output = parse_band_structure_info(zro2_gw_info_out_mock.string, "ks")
assert ks_output == ks_ref, "Expect parsed KS band structure info to match the reference"
def test_parse_g0w0_band_structure_info(si_2_gw_info_out_mock, zro2_gw_info_out_mock):
- """ Test parsing the `G0W0 band structure` block of GW_INFO.OUT
- """
+ """Test parsing the `G0W0 band structure` block of GW_INFO.OUT"""
# Direct gap
g0w0_ref = {
- 'Fermi energy': 0.0176,
- 'Energy range': [-0.4799, 0.5045],
- 'Band index of VBM': 4,
- 'Band index of CBm': 5,
- 'Direct BandGap (eV)': 3.2457,
- 'VBM': {'k_point': [0.0, 0.0, 0.0], 'ik': 1},
- 'CBm': {'k_point': [0.0, 0.0, 0.0], 'ik': 1}
+ "Fermi energy": 0.0176,
+ "Energy range": [-0.4799, 0.5045],
+ "Band index of VBM": 4,
+ "Band index of CBm": 5,
+ "Direct BandGap (eV)": 3.2457,
+ "VBM": {"k_point": [0.0, 0.0, 0.0], "ik": 1},
+ "CBm": {"k_point": [0.0, 0.0, 0.0], "ik": 1},
}
- gw_output = parse_band_structure_info(si_2_gw_info_out_mock.string, 'gw')
- assert gw_output == g0w0_ref, \
- "Expect parsed G0W0 band structure info to match the reference for direct gap "
-
+ gw_output = parse_band_structure_info(si_2_gw_info_out_mock.string, "gw")
+ assert gw_output == g0w0_ref, "Expect parsed G0W0 band structure info to match the reference for direct gap "
# Indirect gap
g0w0_ref = {
- 'Fermi energy': -0.0054,
- 'Energy range': [-16.2632, 1031.409],
- 'Band index of VBM': 21,
- 'Band index of CBm': 22,
- 'Indirect BandGap (eV)': 5.392,
- 'Direct Bandgap at k(VBM) (eV)': 5.5472,
- 'Direct Bandgap at k(CBm) (eV)': 5.9646,
- 'VBM': {'k_point': [0.0, 0.5, 0.5], 'ik': 3},
- 'CBm': {'k_point': [0.0, 0.0, 0.0], 'ik': 1}
+ "Fermi energy": -0.0054,
+ "Energy range": [-16.2632, 1031.409],
+ "Band index of VBM": 21,
+ "Band index of CBm": 22,
+ "Indirect BandGap (eV)": 5.392,
+ "Direct Bandgap at k(VBM) (eV)": 5.5472,
+ "Direct Bandgap at k(CBm) (eV)": 5.9646,
+ "VBM": {"k_point": [0.0, 0.5, 0.5], "ik": 3},
+ "CBm": {"k_point": [0.0, 0.0, 0.0], "ik": 1},
}
- gw_output = parse_band_structure_info(zro2_gw_info_out_mock.string, 'gw')
- assert gw_output == g0w0_ref, \
- "Expect parsed G0W0 band structure info to match the reference for indirect gap"
+ gw_output = parse_band_structure_info(zro2_gw_info_out_mock.string, "gw")
+ assert gw_output == g0w0_ref, "Expect parsed G0W0 band structure info to match the reference for indirect gap"
def test_parse_gw_info(zro2_gw_info_out_mock):
- """ Test parsing of the whole GW_INFO.OUT
- """
+ """Test parsing of the whole GW_INFO.OUT"""
# Reference, without frequencies_weights
- ref = {'correlation_self_energy_parameters': {'Solution of the QP equation': 0,
- 'Energy alignment': 0,
- 'Analytic continuation method': "PADE - Thiele's reciprocal difference method",
- 'Analytic continuation method citation':
- 'H. J. Vidberg and J. W. Serence, J. Low Temp. Phys. 29, 179 (1977)',
- 'Scheme to treat singularities': 'Auxiliary function method "mpb"',
- 'Scheme to treat singularities citation':
- 'S. Massidda, M. Posternak, and A. Baldereschi, PRB 48, 5058 (1993)'},
- 'mixed_product_basis_parameters': {'MT Angular momentum cutoff': 4,
- 'MT Linear dependence tolerance factor': 0.001,
- 'Plane wave cutoff (in units of Gkmax)': 1.0},
- 'bare_coulomb_potential_parameters': {'Plane wave cutoff (in units of Gkmax*gmb)': 2.0,
- 'Error tolerance for structure constants': 1e-16,
- 'MB tolerance factor': 0.1},
- 'screened_coulomb_potential': 'Full-frequency Random-Phase Approximation',
- 'core_electrons_treatment': 'all - Core states are included in all calculations',
- 'qp_interval': [1, 2000],
- 'n_empty': 2000,
- 'q_grid': [2, 2, 2],
- 'mixed_product_wf_info': {'Maximal number of MT wavefunctions per atom': 1069,
- 'Total number of MT wavefunctions': 2733,
- 'Maximal number of PW wavefunctions': 468,
- 'Total number of mixed-product wavefunctions': 3201},
- 'frequency_grid': {'Type: < fgrid >': 'gauleg2',
- 'Frequency axis: < fconv >': 'imfreq',
- 'Number of frequencies: < nomeg >': 32,
- 'Cutoff frequency: < freqmax >': 1.0},
- 'ks_eigenstates_summary': {'Maximum number of LAPW states': 847,
- 'Minimal number of LAPW states': 838,
- 'Number of states used in GW - total KS': 838,
- 'Number of states used in GW - occupied': 21,
- 'Number of states used in GW - unoccupied': 2000,
- 'Number of states used in GW - dielectric function': 838,
- 'Number of states used in GW - self energy': 838,
- 'Energy of the highest unoccupied state': 1030.791933,
- 'Number of valence electrons': 42,
- 'Number of valence electrons treated in GW': 42},
- 'ks_band_structure_summary': {'Fermi energy': 0.0,
- 'Energy range': [-14.6863, 1030.7919],
- 'Band index of VBM': 21, 'Band index of CBm': 22,
- 'Indirect BandGap (eV)': 3.3206,
- 'Direct Bandgap at k(VBM) (eV)': 3.7482,
- 'Direct Bandgap at k(CBm) (eV)': 3.8653,
- 'VBM': {'k_point': [0.0, 0.5, 0.5], 'ik': 3},
- 'CBm': {'k_point': [0.0, 0.0, 0.0], 'ik': 1}},
- 'n_q_cycles': 2,
- 'g0w0_band_structure_summary': {'Fermi energy': -0.0054,
- 'Energy range': [-16.2632, 1031.409],
- 'Band index of VBM': 21,
- 'Band index of CBm': 22,
- 'Indirect BandGap (eV)': 5.392,
- 'Direct Bandgap at k(VBM) (eV)': 5.5472,
- 'Direct Bandgap at k(CBm) (eV)': 5.9646,
- 'VBM': {'k_point': [0.0, 0.5, 0.5], 'ik': 3},
- 'CBm': {'k_point': [0.0, 0.0, 0.0], 'ik': 1}}
- }
+ ref = {
+ "correlation_self_energy_parameters": {
+ "Solution of the QP equation": 0,
+ "Energy alignment": 0,
+ "Analytic continuation method": "PADE - Thiele's reciprocal difference method",
+ "Analytic continuation method citation": "H. J. Vidberg and J. W. Serence, J. Low Temp. Phys. 29, 179 (1977)",
+ "Scheme to treat singularities": 'Auxiliary function method "mpb"',
+ "Scheme to treat singularities citation": "S. Massidda, M. Posternak, and A. Baldereschi, PRB 48, 5058 (1993)",
+ },
+ "mixed_product_basis_parameters": {
+ "MT Angular momentum cutoff": 4,
+ "MT Linear dependence tolerance factor": 0.001,
+ "Plane wave cutoff (in units of Gkmax)": 1.0,
+ },
+ "bare_coulomb_potential_parameters": {
+ "Plane wave cutoff (in units of Gkmax*gmb)": 2.0,
+ "Error tolerance for structure constants": 1e-16,
+ "MB tolerance factor": 0.1,
+ },
+ "screened_coulomb_potential": "Full-frequency Random-Phase Approximation",
+ "core_electrons_treatment": "all - Core states are included in all calculations",
+ "qp_interval": [1, 2000],
+ "n_empty": 2000,
+ "q_grid": [2, 2, 2],
+ "mixed_product_wf_info": {
+ "Maximal number of MT wavefunctions per atom": 1069,
+ "Total number of MT wavefunctions": 2733,
+ "Maximal number of PW wavefunctions": 468,
+ "Total number of mixed-product wavefunctions": 3201,
+ },
+ "frequency_grid": {
+ "Type: < fgrid >": "gauleg2",
+ "Frequency axis: < fconv >": "imfreq",
+ "Number of frequencies: < nomeg >": 32,
+ "Cutoff frequency: < freqmax >": 1.0,
+ },
+ "ks_eigenstates_summary": {
+ "Maximum number of LAPW states": 847,
+ "Minimal number of LAPW states": 838,
+ "Number of states used in GW - total KS": 838,
+ "Number of states used in GW - occupied": 21,
+ "Number of states used in GW - unoccupied": 2000,
+ "Number of states used in GW - dielectric function": 838,
+ "Number of states used in GW - self energy": 838,
+ "Energy of the highest unoccupied state": 1030.791933,
+ "Number of valence electrons": 42,
+ "Number of valence electrons treated in GW": 42,
+ },
+ "ks_band_structure_summary": {
+ "Fermi energy": 0.0,
+ "Energy range": [-14.6863, 1030.7919],
+ "Band index of VBM": 21,
+ "Band index of CBm": 22,
+ "Indirect BandGap (eV)": 3.3206,
+ "Direct Bandgap at k(VBM) (eV)": 3.7482,
+ "Direct Bandgap at k(CBm) (eV)": 3.8653,
+ "VBM": {"k_point": [0.0, 0.5, 0.5], "ik": 3},
+ "CBm": {"k_point": [0.0, 0.0, 0.0], "ik": 1},
+ },
+ "n_q_cycles": 2,
+ "g0w0_band_structure_summary": {
+ "Fermi energy": -0.0054,
+ "Energy range": [-16.2632, 1031.409],
+ "Band index of VBM": 21,
+ "Band index of CBm": 22,
+ "Indirect BandGap (eV)": 5.392,
+ "Direct Bandgap at k(VBM) (eV)": 5.5472,
+ "Direct Bandgap at k(CBm) (eV)": 5.9646,
+ "VBM": {"k_point": [0.0, 0.5, 0.5], "ik": 3},
+ "CBm": {"k_point": [0.0, 0.0, 0.0], "ik": 1},
+ },
+ }
output = parse_gw_info(zro2_gw_info_out_mock.file)
# frequencies and weights tested in separate unit test
- f_w = output['frequency_grid'].pop('frequencies_weights')
+ output["frequency_grid"].pop("frequencies_weights")
assert output == ref, "Output from parse_gw_info does not agree with reference dictionary"
def test_parse_gw_timings(zro2_gw_info_out_mock):
- """ Test parsing `GW timing info` block in GW_INFO.OUT
- """
+ """Test parsing `GW timing info` block in GW_INFO.OUT"""
ref = {
- 'Initialization': {
- 'Initialization': 15.46,
- 'init_scf': 8.38,
- 'init_kpt': 0.04,
- 'init_eval': 0.02,
- 'init_freq': 0.0,
- 'init_mb': 6.76
- },
- 'Subroutines': {
- 'Subroutines': None,
- 'calcpmat': 5.12,
- 'calcbarcmb': 5.65,
- 'BZ integration weights': 18.35
+ "Initialization": {
+ "Initialization": 15.46,
+ "init_scf": 8.38,
+ "init_kpt": 0.04,
+ "init_eval": 0.02,
+ "init_freq": 0.0,
+ "init_mb": 6.76,
},
- 'Dielectric function': {
- 'Dielectric function': 422.09,
- 'head': 0.3,
- 'wings': 70.14,
- 'body (not timed)': 0.0,
- 'inversion': 1.72
+ "Subroutines": {"Subroutines": None, "calcpmat": 5.12, "calcbarcmb": 5.65, "BZ integration weights": 18.35},
+ "Dielectric function": {
+ "Dielectric function": 422.09,
+ "head": 0.3,
+ "wings": 70.14,
+ "body (not timed)": 0.0,
+ "inversion": 1.72,
},
- 'WF products expansion': {
- 'WF products expansion': 2525.59,
- 'diagsgi': 0.24,
- 'calcmpwipw': 0.04,
- 'calcmicm': 2.63,
- 'calcminc': 2.1,
- 'calcminm': 2520.58
+ "WF products expansion": {
+ "WF products expansion": 2525.59,
+ "diagsgi": 0.24,
+ "calcmpwipw": 0.04,
+ "calcmicm": 2.63,
+ "calcminc": 2.1,
+ "calcminm": 2520.58,
},
- 'Self-energy': {'Self-energy': 7069.46, 'calcselfx': 43.33, 'calcselfc': 7026.14},
- 'calcvxcnn': {'calcvxcnn': 27.52},
- 'input/output': {'input/output': 0.0},
- 'Total': {'Total': 7555.78}
+ "Self-energy": {"Self-energy": 7069.46, "calcselfx": 43.33, "calcselfc": 7026.14},
+ "calcvxcnn": {"calcvxcnn": 27.52},
+ "input/output": {"input/output": 0.0},
+ "Total": {"Total": 7555.78},
}
- assert parse_gw_timings(zro2_gw_info_out_mock.string) == ref, "Parsed timings do not agree with reference dictionary"
+ assert (
+ parse_gw_timings(zro2_gw_info_out_mock.string) == ref
+ ), "Parsed timings do not agree with reference dictionary"
diff --git a/tests/dict_parsers/test_gw/test_gw_taskgroup_parser.py b/tests/dict_parsers/test_gw/test_gw_taskgroup_parser.py
new file mode 100644
index 0000000..23ec129
--- /dev/null
+++ b/tests/dict_parsers/test_gw/test_gw_taskgroup_parser.py
@@ -0,0 +1,145 @@
+"""
+Tests for the gw_taskgroup_parser
+"""
+
+import numpy as np
+import pytest
+
+from excitingtools.exciting_dict_parsers.gw_taskgroup_parser import (
+ parse_barc,
+ parse_epsilon,
+ parse_inverse_epsilon,
+ parse_sgi,
+)
+
+rectangular_matrix = """ 2
+1 1 2 3
+(1.01E-4,-5.5E-8)
+(0.25,0.77)
+(0.25,0.77)
+(0.000000000000000E+000,0.000000000000000E+000)
+(5.4E+005,-1.1)
+(-5.4E+005,1.1)
+"""
+
+reference_rectangular_matrix = {
+ "matrix": np.array(
+ [
+ [complex(1.01e-4, -5.5e-8), complex(0.25, 0.77), complex(5.4e5, -1.1)],
+ [complex(0.25, 0.77), complex(0.0, 0.0), complex(-5.4e5, 1.1)],
+ ]
+ )
+}
+
+square_matrix = """ 2
+1 1 2 2
+(1.01E-4,-5.5E-8) (0.000000000000000E+000,0.000000000000000E+000)
+(5.4E+005,-1.1)
+(-5.4E+005,1.1)
+"""
+
+reference_square_matrix = {
+ "matrix": np.array([[complex(1.01e-4, -5.5e-8), complex(5.4e5, -1.1)], [complex(0.0, 0.0), complex(-5.4e5, 1.1)]])
+}
+
+
+@pytest.mark.parametrize(
+ ["barc_file_str", "reference_barc"],
+ [(rectangular_matrix, reference_rectangular_matrix), (square_matrix, reference_square_matrix)],
+)
+def test_parse_barc(barc_file_str, reference_barc, tmp_path):
+ barc_file_path = tmp_path / "BARC_1.OUT"
+ barc_file_path.write_text(barc_file_str)
+ barc = parse_barc(barc_file_path.as_posix())
+ A = reference_barc["matrix"]
+ ref = {"CoulombMatrix": np.matmul(A.T.conj(), A)}
+ np.testing.assert_allclose(barc["CoulombMatrix"], ref["CoulombMatrix"])
+
+
+@pytest.mark.parametrize(
+ ["sgi_file_str", "reference_sgi"],
+ [(rectangular_matrix, reference_rectangular_matrix), (square_matrix, reference_square_matrix)],
+)
+def test_parse_sgi(sgi_file_str, reference_sgi, tmp_path):
+ sgi_file_path = tmp_path / "SGI_1.OUT"
+ sgi_file_path.write_text(sgi_file_str)
+ sgi = parse_sgi(sgi_file_path.as_posix())
+ A = reference_sgi["matrix"]
+ ref = {"OverlapMatrix": np.matmul(A.T.conj(), A)}
+ np.testing.assert_allclose(sgi["OverlapMatrix"], ref["OverlapMatrix"])
+
+
+array_of_rank_3_example_1 = """ 3
+1 1 1 2 2 1
+(1.01E-4,-5.5E-8) (0.25,0.77)
+(0.35,0.88) (0.000000000000000E+000,0.000000000000000E+000)
+"""
+
+reference_array_of_rank_3_example_1 = {
+ "array": np.array(
+ [[[complex(1.01e-4, -5.5e-8)], [complex(0.35, 0.88)]], [[complex(0.25, 0.77)], [complex(0.0, 0.0)]]]
+ )
+}
+
+array_of_rank_3_example_2 = """ 3
+1 1 1 3 4 2
+(1.01E-4,-5.5E-8) (0.25,0.77)
+(0.35,0.88) (0.000000000000000E+000,0.000000000000000E+000)
+(-1.01E+004,-5.4E-8) (0.19,0.21) (1.5E-3,-9E6)
+(0.07,0.00) (0.08,0.09) (1.1,2.2) (-4.5,-2.1) (-6.0E+000,8.0E-006)
+(0.1,0.2) (1.5,2.5) (3.5,7.8) (7.0,8.7) (5.0,6.5) (8.0,9.1)
+(0.01,0.00) (0.00,0.01) (0.02,0.00) (0.00,0.02) (0.3,0.0) (0.0,0.3)
+"""
+
+reference_array_of_rank_3_example_2 = {
+ "array": np.array(
+ [
+ [
+ [complex(1.01e-4, -5.5e-8), complex(0.1, 0.2)],
+ [complex(0.0, 0.0), complex(7.0, 8.7)],
+ [complex(1.5e-3, -9e6), complex(0.01, 0.00)],
+ [complex(1.1, 2.2), complex(0.00, 0.02)],
+ ],
+ [
+ [complex(0.25, 0.77), complex(1.5, 2.5)],
+ [complex(-1.01e4, -5.4e-8), complex(5.0, 6.5)],
+ [complex(0.07, 0.00), complex(0.00, 0.01)],
+ [complex(-4.5, -2.1), complex(0.3, 0.0)],
+ ],
+ [
+ [complex(0.35, 0.88), complex(3.5, 7.8)],
+ [complex(0.19, 0.21), complex(8.0, 9.1)],
+ [complex(0.08, 0.09), complex(0.02, 0.00)],
+ [complex(-6, 8e-6), complex(0.0, 0.3)],
+ ],
+ ]
+ )
+}
+
+
+@pytest.mark.parametrize(
+ ["file_epsilon_str", "reference_epsilon"],
+ [
+ (array_of_rank_3_example_1, reference_array_of_rank_3_example_1),
+ (array_of_rank_3_example_2, reference_array_of_rank_3_example_2),
+ ],
+)
+def test_parse_epsilon(file_epsilon_str, reference_epsilon, tmp_path):
+ epsilon_file_path = tmp_path / "EPSILON-GW_Q1.OUT"
+ epsilon_file_path.write_text(file_epsilon_str)
+ epsilon = parse_epsilon(epsilon_file_path.as_posix())
+ np.testing.assert_allclose(epsilon["epsilon_tensor"], reference_epsilon["array"])
+
+
+@pytest.mark.parametrize(
+ ["file_inverse_epsilon_str", "reference_inverse_epsilon"],
+ [
+ (array_of_rank_3_example_1, reference_array_of_rank_3_example_1),
+ (array_of_rank_3_example_2, reference_array_of_rank_3_example_2),
+ ],
+)
+def test_parse_inverse_epsilon(file_inverse_epsilon_str, reference_inverse_epsilon, tmp_path):
+ inverse_epsilon_file_path = tmp_path / "INVERSE-EPSILON_Q1.OUT"
+ inverse_epsilon_file_path.write_text(file_inverse_epsilon_str)
+ inverse_epsilon = parse_inverse_epsilon(inverse_epsilon_file_path.as_posix())
+ np.testing.assert_allclose(inverse_epsilon["inverse_epsilon_tensor"], reference_inverse_epsilon["array"])
diff --git a/tests/dict_parsers/test_gw/test_gw_vxc_parser.py b/tests/dict_parsers/test_gw/test_gw_vxc_parser.py
index 7882cff..44b60b6 100644
--- a/tests/dict_parsers/test_gw/test_gw_vxc_parser.py
+++ b/tests/dict_parsers/test_gw/test_gw_vxc_parser.py
@@ -1,14 +1,13 @@
-import pytest
import numpy as np
+import pytest
-from excitingtools.utils.test_utils import MockFile
from excitingtools.exciting_dict_parsers.gw_vxc_parser import parse_vxcnn, vkl_from_vxc
+from excitingtools.utils.test_utils import MockFile
@pytest.fixture
def vxc_mock(tmp_path):
- """ Mock VXCNN.DAT data with energies for 3 k-points
- """
+ """Mock VXCNN.DAT data with energies for 3 k-points"""
vxc_string = """ik= 1 vkl= 0.0000 0.0000 0.0000
1 -2.908349 -0.000000
2 -2.864103 0.000000
@@ -55,46 +54,76 @@ def vxc_mock(tmp_path):
def test_vkl_from_vxc(vxc_mock):
# k-points in units of the lattice vectors
- vkl_ref = {
- 1: [0.0000, 0.0000, 0.0000], 2: [0.0000, 0.0000, 0.5000], 3: [0.0000, 0.5000, 0.5000]
- }
+ vkl_ref = {1: [0.0000, 0.0000, 0.0000], 2: [0.0000, 0.0000, 0.5000], 3: [0.0000, 0.5000, 0.5000]}
output = vkl_from_vxc(vxc_mock.string)
assert len(output) == 3, "Expect 3 k-points"
assert output == vkl_ref, "vkl values equal to vkl_ref"
def test_parse_vxcnn(vxc_mock):
-
# Reference V_xc extracted from vxc_string, defined above
- v_xc_1 = np.array([[-2.908349, -0.000000], [-2.864103, 0.000000], [-2.864103, -0.000000],
- [-2.864103, -0.000000], [-2.764246, -0.000000], [-2.764246, 0.000000],
- [-2.764809, -0.000000], [-2.764809, -0.000000], [-2.764809, -0.000000],
- [-1.034312, -0.000000], [-0.929773, -0.000000]])
-
- v_xc_2 = np.array([[-2.908349, 0.000000], [-2.864100, -0.000000], [-2.864100, 0.000000],
- [-2.864101, -0.000000], [-2.764227, -0.000000], [-2.764227, 0.000000],
- [-2.764770, 0.000000], [-2.764777, 0.000000], [-2.764777, 0.000000],
- [-1.037228, -0.000000], [-0.889360, 0.000000]])
-
- v_xc_3 = np.array([[-2.908349, -0.000000], [-2.864099, -0.000000], [-2.864099, -0.000000],
- [-2.864099, 0.000000], [-2.764195, 0.000000], [-2.764208, 0.000000],
- [-2.764760, -0.000000], [-2.764780, -0.000000], [-2.764780, -0.000000],
- [-1.038185, 0.000000], [-0.887485, -0.000000]])
+ v_xc_1 = np.array(
+ [
+ [-2.908349, -0.000000],
+ [-2.864103, 0.000000],
+ [-2.864103, -0.000000],
+ [-2.864103, -0.000000],
+ [-2.764246, -0.000000],
+ [-2.764246, 0.000000],
+ [-2.764809, -0.000000],
+ [-2.764809, -0.000000],
+ [-2.764809, -0.000000],
+ [-1.034312, -0.000000],
+ [-0.929773, -0.000000],
+ ]
+ )
+
+ v_xc_2 = np.array(
+ [
+ [-2.908349, 0.000000],
+ [-2.864100, -0.000000],
+ [-2.864100, 0.000000],
+ [-2.864101, -0.000000],
+ [-2.764227, -0.000000],
+ [-2.764227, 0.000000],
+ [-2.764770, 0.000000],
+ [-2.764777, 0.000000],
+ [-2.764777, 0.000000],
+ [-1.037228, -0.000000],
+ [-0.889360, 0.000000],
+ ]
+ )
+
+ v_xc_3 = np.array(
+ [
+ [-2.908349, -0.000000],
+ [-2.864099, -0.000000],
+ [-2.864099, -0.000000],
+ [-2.864099, 0.000000],
+ [-2.764195, 0.000000],
+ [-2.764208, 0.000000],
+ [-2.764760, -0.000000],
+ [-2.764780, -0.000000],
+ [-2.764780, -0.000000],
+ [-1.038185, 0.000000],
+ [-0.887485, -0.000000],
+ ]
+ )
output = parse_vxcnn(vxc_mock.file)
- assert [key for key in output[1].keys()] == ['vkl', 'v_xc_nn'], "Key consistency for ik=1 of parsed vxcnn"
- assert [key for key in output[2].keys()] == ['vkl', 'v_xc_nn'], "Key consistency for ik=2 of parsed vxcnn"
- assert [key for key in output[3].keys()] == ['vkl', 'v_xc_nn'], "Key consistency for ik=3 of parsed vxcnn"
+ assert [key for key in output[1]] == ["vkl", "v_xc_nn"], "Key consistency for ik=1 of parsed vxcnn"
+ assert [key for key in output[2]] == ["vkl", "v_xc_nn"], "Key consistency for ik=2 of parsed vxcnn"
+ assert [key for key in output[3]] == ["vkl", "v_xc_nn"], "Key consistency for ik=3 of parsed vxcnn"
- assert output[1]['vkl'] == [0.0000, 0.0000, 0.0000], "vkl (ik=1)"
- assert output[2]['vkl'] == [0.0000, 0.0000, 0.5000], "vkl (ik=2)"
- assert output[3]['vkl'] == [0.0000, 0.5000, 0.5000], "vkl (ik=3)"
+ assert output[1]["vkl"] == [0.0000, 0.0000, 0.0000], "vkl (ik=1)"
+ assert output[2]["vkl"] == [0.0000, 0.0000, 0.5000], "vkl (ik=2)"
+ assert output[3]["vkl"] == [0.0000, 0.5000, 0.5000], "vkl (ik=3)"
- assert output[1]['v_xc_nn'].shape == (11, 2), "Expect V_xc to have 2 cols for 11 states"
- assert output[2]['v_xc_nn'].shape == (11, 2), "Expect V_xc to have 2 cols for 11 states"
- assert output[3]['v_xc_nn'].shape == (11, 2), "Expect V_xc to have 2 cols for 11 states"
+ assert output[1]["v_xc_nn"].shape == (11, 2), "Expect V_xc to have 2 cols for 11 states"
+ assert output[2]["v_xc_nn"].shape == (11, 2), "Expect V_xc to have 2 cols for 11 states"
+ assert output[3]["v_xc_nn"].shape == (11, 2), "Expect V_xc to have 2 cols for 11 states"
- assert np.allclose(output[1]['v_xc_nn'], v_xc_1), "v_xc_nn for ik=1"
- assert np.allclose(output[2]['v_xc_nn'], v_xc_2), "v_xc_nn for ik=2"
- assert np.allclose(output[3]['v_xc_nn'], v_xc_3), "v_xc_nn for ik=3"
+ assert np.allclose(output[1]["v_xc_nn"], v_xc_1), "v_xc_nn for ik=1"
+ assert np.allclose(output[2]["v_xc_nn"], v_xc_2), "v_xc_nn for ik=2"
+ assert np.allclose(output[3]["v_xc_nn"], v_xc_3), "v_xc_nn for ik=3"
diff --git a/tests/dict_parsers/test_input_parser.py b/tests/dict_parsers/test_input_parser.py
index f870aaa..501db94 100644
--- a/tests/dict_parsers/test_input_parser.py
+++ b/tests/dict_parsers/test_input_parser.py
@@ -1,32 +1,63 @@
"""
Test for the input.xml file parser
"""
-import pytest
-from excitingtools.exciting_dict_parsers.input_parser import parse_groundstate, parse_structure, parse_xs, \
- parse_input_xml
+import pytest
+from excitingtools.exciting_dict_parsers.input_parser import parse_element_xml, parse_input_xml, parse_structure
reference_input_str = """
-
+
Lithium Fluoride BSE
-
+
3.80402 3.80402 0.00000
3.80402 0.00000 3.80402
0.00000 3.80402 3.80402
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ keyword1 keyword2
+
"""
+def test_parse_title():
+ assert parse_element_xml(reference_input_str, tag="title") == "Lithium Fluoride BSE"
+
+
+def test_parse_keywords():
+ assert parse_element_xml(reference_input_str, tag="keywords") == "keyword1 keyword2"
+
+
def test_parse_groundstate():
- ground_state = parse_groundstate(reference_input_str)
+ ground_state = parse_element_xml(reference_input_str, tag="groundstate")
assert ground_state == {
- 'xctype': 'GGA_PBE', 'ngridk': '4 4 4',
- 'epsengy': '1d-7', 'outputlevel': 'high'
- }
+ "xctype": "GGA_PBE",
+ "ngridk": [4, 4, 4],
+ "epsengy": 1e-7,
+ "outputlevel": "high",
+ "spin": {"bfieldc": [0, 0, 0], "fixspin": "total FSM"},
+ "OEP": {"maxitoep": 100},
+ }
+
+
+def test_parse_groundstate_from_gs_root():
+ ground_state = parse_element_xml(
+ '', tag="groundstate"
+ )
+ assert ground_state == {"xctype": "GGA_PBE", "ngridk": [4, 4, 4], "epsengy": 1e-7, "outputlevel": "high"}
def test_parse_structure():
structure = parse_structure(reference_input_str)
structure_ref = {
- 'atoms': [{'species': 'Li', 'position': [0.0, 0.0, 0.0],
- 'bfcmt': '0.0 0.0 0.0'},
- {'species': 'F', 'position': [0.5, 0.5, 0.5],
- 'lockxyz': 'false true false'}],
- 'lattice': [[3.80402, 3.80402, 0.0],
- [3.80402, 0.0, 3.80402],
- [0.0, 3.80402, 3.80402]],
- 'species_path': '.',
- 'structure_properties': {'autormt': 'false', 'epslat': '1.0d-6'},
- 'crystal_properties': {'scale': '1.0', 'stretch': '1.0'},
- 'species_properties': {'Li': {'rmt': '1.5'}, 'F': {}}
- }
+ "atoms": [
+ {"species": "Li", "position": [0.0, 0.0, 0.0], "bfcmt": [0.0, 0.0, 0.0]},
+ {"species": "F", "position": [0.5, 0.5, 0.5], "lockxyz": [False, True, False]},
+ ],
+ "lattice": [[3.80402, 3.80402, 0.0], [3.80402, 0.0, 3.80402], [0.0, 3.80402, 3.80402]],
+ "species_path": ".",
+ "crystal_properties": {"scale": 1.0, "stretch": [1.0, 1.0, 1.0]},
+ "species_properties": {
+ "F": {"LDAplusU": {"J": 2.3, "U": 0.5, "l": 3}},
+ "Li": {
+ "dfthalfparam": {"ampl": 1, "cut": 3.9, "exponent": 8, "shell": [{"ionization": 0.25, "number": 0}]},
+ "rmt": 1.5,
+ },
+ },
+ "autormt": False,
+ "epslat": 1.0e-6,
+ }
assert structure_ref == structure
def test_parse_xs():
- xs = parse_xs(reference_input_str)
+ xs = parse_element_xml(reference_input_str, tag="xs")
xs_ref = {
- 'xstype': 'BSE',
- 'xs_properties': {
- 'ngridq': '3 3 3',
- 'vkloff': '0.05 0.15 0.25',
- 'nempty': '1',
- 'broad': '0.0073499',
- 'nosym': 'true'
- },
- 'energywindow': {'intv': '0.0 1.0', 'points': '50'},
- 'screening': {'screentype': 'full', 'nempty': '115'},
- 'BSE': {'bsetype': 'singlet', 'nstlbse': '1 5 1 2', 'aresbse': 'false'},
- 'qpointset': [[0.0, 0.0, 0.0]],
- 'plan': ['screen', 'bse']
- }
+ "xstype": "BSE",
+ "ngridq": [3, 3, 3],
+ "vkloff": [0.05, 0.15, 0.25],
+ "nempty": 1,
+ "broad": 0.0073499,
+ "nosym": True,
+ "energywindow": {"intv": [0.0, 1.0], "points": 50},
+ "screening": {"screentype": "full", "nempty": 115},
+ "BSE": {"bsetype": "singlet", "nstlbse": [1, 5, 1, 2], "aresbse": False},
+ "qpointset": [[0.0, 0.0, 0.0]],
+ "plan": ["screen", "bse"],
+ }
assert xs_ref == xs
+ assert isinstance(xs["ngridq"][0], int)
-def test_parse_input_xml():
- parsed_data = parse_input_xml(reference_input_str)
- assert set(parsed_data.keys()) == {'groundstate', 'structure', 'xs'}
- parsed_objects = parse_input_xml(reference_input_str)
- assert set(parsed_objects.keys()) == {'groundstate', 'structure', 'xs'}
-
+input_ref_parsed_keys = {"title", "groundstate", "structure", "xs", "sharedfs", "properties", "keywords"}
-reference_input_str_without_xs = """
-
-
- Lithium Fluoride BSE
-
-
-
- 3.80402 3.80402 0.00000
- 3.80402 0.00000 3.80402
- 0.00000 3.80402 3.80402
-
-
-
-
-
-
-
-
-
-
-
-
-"""
-
-
-def test_parse_xs_no_xs():
- xs = parse_xs(reference_input_str_without_xs)
- assert xs == {}
-
-
-reference_input_str_warning = """
-
-
-
-
-
-
-
-"""
+def test_parse_input_xml():
+ parsed_data = parse_element_xml(reference_input_str)
+ assert set(parsed_data.keys()) == input_ref_parsed_keys
+ assert parsed_data["sharedfs"]
-def test_parse_xs_warning():
- with pytest.warns(UserWarning, match='Subelement transitions not yet supported. Its ignored...'):
- xs = parse_xs(reference_input_str_warning)
- assert xs == {'xstype': 'BSE',
- 'xs_properties':
- {'ngridq': '3 3 3', 'vkloff': '0.05 0.15 0.25',
- 'nempty': '1', 'broad': '0.0073499', 'nosym': 'true'}}
+def test_parse_input_xml_directly():
+ parsed_data = parse_input_xml(reference_input_str)
+ assert set(parsed_data.keys()) == input_ref_parsed_keys
+
+
+def test_parse_missing_tag():
+ with pytest.raises(ValueError, match="Your specified input has no tag missing_tag"):
+ parse_element_xml(reference_input_str, "missing_tag")
+
+
+def test_parse_input_xml_with_tag():
+ parsed_data = parse_element_xml(reference_input_str, tag="input")
+ assert set(parsed_data.keys()) == input_ref_parsed_keys
+
+
+def test_parse_properties():
+ properties = parse_element_xml(reference_input_str, tag="properties")
+ properties_ref = {
+ "dos": {"nsmdos": 2, "ngrdos": 300, "nwdos": 1000, "winddos": [-0.3, 0.3]},
+ "bandstructure": {
+ "plot1d": {
+ "path": {
+ "steps": 100,
+ "point": [
+ {"coord": [1, 0, 0], "label": "Gamma"},
+ {"coord": [0.625, 0.375, 0], "label": "K"},
+ {"coord": [0.5, 0.5, 0], "label": "X", "breakafter": True},
+ {"coord": [0, 0, 0], "label": "Gamma"},
+ {"coord": [0.5, 0, 0], "label": "L"},
+ ],
+ }
+ }
+ },
+ }
+ assert properties_ref == properties
diff --git a/tests/dict_parsers/test_ks_band_structure.py b/tests/dict_parsers/test_ks_band_structure.py
index 3fd495d..785d2f6 100644
--- a/tests/dict_parsers/test_ks_band_structure.py
+++ b/tests/dict_parsers/test_ks_band_structure.py
@@ -1,12 +1,13 @@
import numpy as np
import pytest
+
+from excitingtools.exciting_dict_parsers.properties_parser import parse_band_structure_dat, parse_band_structure_xml
from excitingtools.utils.test_utils import MockFile
-from excitingtools.exciting_dict_parsers.properties_parser import parse_band_structure_xml, parse_band_structure_dat
@pytest.fixture
def band_structure_dat_mock(tmp_path) -> MockFile:
- """ Mock 'bandstructure.dat' data, containing only two bands and
+ """Mock 'bandstructure.dat' data, containing only two bands and
only 6 k-sampling points per band.
"""
bs_dat_str = """# 1 2 6
@@ -32,69 +33,86 @@ def band_structure_dat_mock(tmp_path) -> MockFile:
def test_parse_band_structure_xml():
band_data = parse_band_structure_xml(band_structure_xml)
- assert band_data['n_kpts'] == band_data['band_energies'].shape[0], (
- "First dim of bands array equals the number of k-sampling points in the band structure")
- assert band_data['n_kpts'] == 6, "sampling points per band"
- assert band_data['n_bands'] == 2, "band_structure_xml contains two bands"
+ assert (
+ band_data["n_kpts"] == band_data["band_energies"].shape[0]
+ ), "First dim of bands array equals the number of k-sampling points in the band structure"
+ assert band_data["n_kpts"] == 6, "sampling points per band"
+ assert band_data["n_bands"] == 2, "band_structure_xml contains two bands"
- ref_k_points = [0., 0.04082159, 0.08164318, 0.12246477, 0.16328636, 0.20410795]
+ ref_k_points = [0.0, 0.04082159, 0.08164318, 0.12246477, 0.16328636, 0.20410795]
- ref_bands = np.array([[-0.45003454, -0.00937631],
- [-0.44931675, -0.01419609],
- [-0.44716535, -0.02681183],
- [-0.44358550, -0.04401707],
- [-0.43858583, -0.06361773],
- [-0.43217908, -0.0844593]])
+ ref_bands = np.array(
+ [
+ [-0.45003454, -0.00937631],
+ [-0.44931675, -0.01419609],
+ [-0.44716535, -0.02681183],
+ [-0.44358550, -0.04401707],
+ [-0.43858583, -0.06361773],
+ [-0.43217908, -0.0844593],
+ ]
+ )
- assert np.allclose(band_data['k_points_along_band'], ref_k_points, atol=1.e-8)
- assert np.allclose(band_data['band_energies'], ref_bands, atol=1.e-8)
+ assert np.allclose(band_data["k_points_along_band"], ref_k_points, atol=1.0e-8)
+ assert np.allclose(band_data["band_energies"], ref_bands, atol=1.0e-8)
def test_parse_band_structure_xml_vertices():
- vertices_ref = [{'distance': 0.0, 'label': 'G', 'coord': [0.0, 0.0, 0.0]},
- {'distance': 0.6123238446, 'label': 'X', 'coord': [0.5, 0.0, 0.5]},
- {'distance': 0.918485767, 'label': 'W', 'coord': [0.5, 0.25, 0.75]},
- {'distance': 1.134974938, 'label': 'K', 'coord': [0.375, 0.375, 0.75]},
- {'distance': 1.784442453, 'label': 'G', 'coord': [0.0, 0.0, 0.0]},
- {'distance': 2.314730457, 'label': 'L', 'coord': [0.5, 0.5, 0.5]},
- {'distance': 2.689700702, 'label': 'U', 'coord': [0.625, 0.25, 0.625]},
- {'distance': 2.906189873, 'label': 'W', 'coord': [0.5, 0.25, 0.75]},
- {'distance': 3.339168216, 'label': 'L', 'coord': [0.5, 0.5, 0.5]},
- {'distance': 3.71413846, 'label': 'K', 'coord': [0.375, 0.375, 0.75]},
- {'distance': 3.71413846, 'label': 'U', 'coord': [0.625, 0.25, 0.625]},
- {'distance': 3.930627631, 'label': 'X', 'coord': [0.5, 0.0, 0.5]}]
+ vertices_ref = [
+ {"distance": 0.0, "label": "G", "coord": [0.0, 0.0, 0.0]},
+ {"distance": 0.6123238446, "label": "X", "coord": [0.5, 0.0, 0.5]},
+ {"distance": 0.918485767, "label": "W", "coord": [0.5, 0.25, 0.75]},
+ {"distance": 1.134974938, "label": "K", "coord": [0.375, 0.375, 0.75]},
+ {"distance": 1.784442453, "label": "G", "coord": [0.0, 0.0, 0.0]},
+ {"distance": 2.314730457, "label": "L", "coord": [0.5, 0.5, 0.5]},
+ {"distance": 2.689700702, "label": "U", "coord": [0.625, 0.25, 0.625]},
+ {"distance": 2.906189873, "label": "W", "coord": [0.5, 0.25, 0.75]},
+ {"distance": 3.339168216, "label": "L", "coord": [0.5, 0.5, 0.5]},
+ {"distance": 3.71413846, "label": "K", "coord": [0.375, 0.375, 0.75]},
+ {"distance": 3.71413846, "label": "U", "coord": [0.625, 0.25, 0.625]},
+ {"distance": 3.930627631, "label": "X", "coord": [0.5, 0.0, 0.5]},
+ ]
band_data = parse_band_structure_xml(band_structure_xml)
- assert band_data['vertices'] == vertices_ref
+ assert band_data["vertices"] == vertices_ref
def test_parse_band_structure_dat(band_structure_dat_mock):
band_data = parse_band_structure_dat(band_structure_dat_mock.file)
- assert band_data['n_kpts'] == band_data['band_energies'].shape[0], (
- "First dim of bands array equals the number of k-sampling points in the band structure")
- assert band_data['n_kpts'] == 6, "sampling points per band"
- assert band_data['n_bands'] == 2, "band_structure_xml contains two bands"
-
- ref_k_points = np.array([[1.000000, 0.000000, 0.000000],
- [0.988281, 0.011719, 0.000000],
- [0.976562, 0.023438, 0.000000],
- [0.964844, 0.035156, 0.000000],
- [0.953125, 0.046875, 0.000000],
- [0.941406, 0.058594, 0.000000]])
-
- ref_bands = np.array([[-3.370713328, -2.024168147],
- [-3.370710744, -2.024186985],
- [-3.370705193, -2.024297489],
- [-3.370698602, -2.024460642],
- [-3.370682200, -2.024597185],
- [-3.370661229, -2.024765908]])
+ assert (
+ band_data["n_kpts"] == band_data["band_energies"].shape[0]
+ ), "First dim of bands array equals the number of k-sampling points in the band structure"
+ assert band_data["n_kpts"] == 6, "sampling points per band"
+ assert band_data["n_bands"] == 2, "band_structure_xml contains two bands"
+
+ ref_k_points = np.array(
+ [
+ [1.000000, 0.000000, 0.000000],
+ [0.988281, 0.011719, 0.000000],
+ [0.976562, 0.023438, 0.000000],
+ [0.964844, 0.035156, 0.000000],
+ [0.953125, 0.046875, 0.000000],
+ [0.941406, 0.058594, 0.000000],
+ ]
+ )
+
+ ref_bands = np.array(
+ [
+ [-3.370713328, -2.024168147],
+ [-3.370710744, -2.024186985],
+ [-3.370705193, -2.024297489],
+ [-3.370698602, -2.024460642],
+ [-3.370682200, -2.024597185],
+ [-3.370661229, -2.024765908],
+ ]
+ )
+
+ ref_flattened_k_points = np.array([0.0, 0.02697635, 0.05395270, 0.08092905, 0.10790540, 0.13488176])
+
+ assert np.allclose(band_data["k_points"], ref_k_points, atol=1.0e-8)
+ assert np.allclose(band_data["flattened_k_points"], ref_flattened_k_points, atol=1.0e-8)
+ assert np.allclose(band_data["band_energies"], ref_bands, atol=1.0e-8)
- ref_flattened_k_points = np.array([0., 0.02697635, 0.05395270, 0.08092905, 0.10790540, 0.13488176])
-
- assert np.allclose(band_data['k_points'], ref_k_points, atol=1.e-8)
- assert np.allclose(band_data['flattened_k_points'], ref_flattened_k_points, atol=1.e-8)
- assert np.allclose(band_data['band_energies'], ref_bands, atol=1.e-8)
# Band structure of silicon, containing two lowest bands and only 6 k-sampling points per band
@@ -132,4 +150,3 @@ def test_parse_band_structure_dat(band_structure_dat_mock):
"""
-
diff --git a/tests/dict_parsers/test_parser_factory.py b/tests/dict_parsers/test_parser_factory.py
new file mode 100644
index 0000000..2615620
--- /dev/null
+++ b/tests/dict_parsers/test_parser_factory.py
@@ -0,0 +1,40 @@
+"""Test parser factory."""
+
+from pathlib import Path
+
+from numpy.testing import assert_allclose
+
+from excitingtools.exciting_dict_parsers.parser_factory import parse
+
+
+def test_parse(tmp_path: Path) -> None:
+ file = tmp_path / "EPSILON_11.OUT"
+ file.write_text("a\n0 1 0\n2 0 0")
+ parsed_data = parse(file.as_posix())
+ assert set(parsed_data) == {"energy", "im", "re"}
+ assert_allclose(parsed_data["energy"], [0.0, 2.0])
+ assert_allclose(parsed_data["im"], [0.0, 0.0])
+ assert_allclose(parsed_data["re"], [1.0, 0.0])
+
+ file = tmp_path / "EPSILON_BSE-NAR_TDA-BAR_OC11.OUT"
+ file.write_text("a\n" * 14 + "0 0 0 1\n0 0 0 2")
+ parsed_data = parse(file.as_posix())
+ assert set(parsed_data) == {
+ "frequency",
+ "imag_oscillator_strength",
+ "real_oscillator_strength",
+ "real_oscillator_strength_kkt",
+ }
+ assert_allclose(parsed_data["frequency"], [0.0, 0.0])
+ assert_allclose(parsed_data["imag_oscillator_strength"], [0.0, 0.0])
+ assert_allclose(parsed_data["real_oscillator_strength"], [0.0, 0.0])
+ assert_allclose(parsed_data["real_oscillator_strength_kkt"], [1.0, 2.0])
+
+ file = tmp_path / "Z_11.OUT"
+ file.write_text("0 0 1 2\n0 0 3 4")
+ parsed_data = parse(file.as_posix())
+ assert set(parsed_data) == {"im", "mu", "re", "temperature"}
+ assert_allclose(parsed_data["im"], [2.0, 4.0])
+ assert_allclose(parsed_data["mu"], [0.0, 0.0])
+ assert_allclose(parsed_data["re"], [1.0, 3.0])
+ assert_allclose(parsed_data["temperature"], [0.0, 0.0])
diff --git a/tests/dict_parsers/test_properties_parser.py b/tests/dict_parsers/test_properties_parser.py
index ab60c5b..4796191 100644
--- a/tests/dict_parsers/test_properties_parser.py
+++ b/tests/dict_parsers/test_properties_parser.py
@@ -2,7 +2,6 @@
from excitingtools.exciting_dict_parsers.properties_parser import parse_charge_density
-
# Most of the values have been removed to shorten the test
RHO1_xml = """
@@ -24,12 +23,17 @@
"""
+
def test_parse_charge_density():
rho1 = parse_charge_density(RHO1_xml)
- ref = np.array([[0.00000000e+00, 1.98518837e+03],
- [4.48811667e-02, 5.08882988e+02],
- [8.97623334e-02, 1.51827164e+02],
- [1.34643500e-01, 5.22636138e+01],
- [1.79524667e-01, 2.43321622e+01],
- [2.24405834e-01, 1.59499145e+01]])
- assert np.allclose(rho1, ref, atol=1.e-8)
+ ref = np.array(
+ [
+ [0.00000000e00, 1.98518837e03],
+ [4.48811667e-02, 5.08882988e02],
+ [8.97623334e-02, 1.51827164e02],
+ [1.34643500e-01, 5.22636138e01],
+ [1.79524667e-01, 2.43321622e01],
+ [2.24405834e-01, 1.59499145e01],
+ ]
+ )
+ assert np.allclose(rho1, ref, atol=1.0e-8)
diff --git a/tests/dict_parsers/test_species_parser.py b/tests/dict_parsers/test_species_parser.py
index b7406ac..9980537 100644
--- a/tests/dict_parsers/test_species_parser.py
+++ b/tests/dict_parsers/test_species_parser.py
@@ -2,104 +2,188 @@
def test_parse_species_xml():
- """ Test parsing of species.xml files.
- """
- assert isinstance(species_str, str), (
- "Expect species parser to handle strings of XML data, "
- "due to use of decorator"
- )
+ """Test parsing of species.xml files."""
+ assert isinstance(species_str, str), "Expect species parser to handle strings of XML data, due to use of decorator"
species_dict = parse_species_xml(species_str)
- assert set(species_dict) == {'species', 'muffin_tin', 'atomic_states', 'basis'}, 'Top level species file keys'
- assert species_dict['species'] == {
- 'chemicalSymbol': 'Zn', 'name': 'zinc', 'z': -30.0, 'mass': 119198.678
- }
- assert species_dict['muffin_tin'] == {
- 'rmin': 1e-06, 'radius': 2.0, 'rinf': 21.8982, 'radialmeshPoints': 600.0
- }
-
- assert isinstance(species_dict['atomic_states'], list), 'Atomic states stored as a list'
- atomic_states = [{'n': 1, 'l': 0, 'kappa': 1, 'occ': 2.00000,
- 'core': True}, {'n': 2, 'l': 0, 'kappa': 1, 'occ': 2.00000, 'core': True},
- {'n': 2, 'l': 1, 'kappa': 1, 'occ': 2.00000,
- 'core': True}, {'n': 2, 'l': 1, 'kappa': 2, 'occ': 4.00000, 'core': True},
- {'n': 3, 'l': 0, 'kappa': 1, 'occ': 2.00000,
- 'core': False}, {'n': 3, 'l': 1, 'kappa': 1, 'occ': 2.00000, 'core': False},
- {'n': 3, 'l': 1, 'kappa': 2, 'occ': 4.00000,
- 'core': False}, {'n': 3, 'l': 2, 'kappa': 2, 'occ': 4.00000, 'core': False},
- {'n': 3, 'l': 2, 'kappa': 3, 'occ': 6.00000,
- 'core': False}, {'n': 4, 'l': 0, 'kappa': 1, 'occ': 2.00000, 'core': False}]
- assert species_dict['atomic_states'] == atomic_states
-
- basis = species_dict['basis']
- assert set(basis) == {'default', 'custom', 'lo'}, 'Keys for basis'
-
- assert basis['default'] == [{'type': 'lapw', 'trialEnergy': 0.1500, 'searchE': True}]
+ assert set(species_dict) == {"species", "muffin_tin", "atomic_states", "basis"}, "Top level species file keys"
+ assert species_dict["species"] == {"chemicalSymbol": "Zn", "name": "zinc", "z": -30.0, "mass": 119198.678}
+ assert species_dict["muffin_tin"] == {"rmin": 1e-06, "radius": 2.0, "rinf": 21.8982, "radialmeshPoints": 600.0}
+
+ assert isinstance(species_dict["atomic_states"], list), "Atomic states stored as a list"
+ atomic_states = [
+ {"n": 1, "l": 0, "kappa": 1, "occ": 2.00000, "core": True},
+ {"n": 2, "l": 0, "kappa": 1, "occ": 2.00000, "core": True},
+ {"n": 2, "l": 1, "kappa": 1, "occ": 2.00000, "core": True},
+ {"n": 2, "l": 1, "kappa": 2, "occ": 4.00000, "core": True},
+ {"n": 3, "l": 0, "kappa": 1, "occ": 2.00000, "core": False},
+ {"n": 3, "l": 1, "kappa": 1, "occ": 2.00000, "core": False},
+ {"n": 3, "l": 1, "kappa": 2, "occ": 4.00000, "core": False},
+ {"n": 3, "l": 2, "kappa": 2, "occ": 4.00000, "core": False},
+ {"n": 3, "l": 2, "kappa": 3, "occ": 6.00000, "core": False},
+ {"n": 4, "l": 0, "kappa": 1, "occ": 2.00000, "core": False},
+ ]
+ assert species_dict["atomic_states"] == atomic_states
+
+ basis = species_dict["basis"]
+ assert set(basis) == {"default", "custom", "lo"}, "Keys for basis"
+
+ assert basis["default"] == [{"type": "lapw", "trialEnergy": 0.1500, "searchE": True}]
# Custom apw, lapw or apw+lo
- assert basis['custom'] == [
- {'l': 0, 'type': 'lapw', 'trialEnergy': 1.35670550183736, 'searchE': False},
- {'l': 1, 'type': 'lapw', 'trialEnergy': -2.69952312512447,
- 'searchE': False}, {'l': 2, 'type': 'lapw', 'trialEnergy': 0.00, 'searchE': False},
- {'l': 3, 'type': 'lapw', 'trialEnergy': 1.000,
- 'searchE': False}, {'l': 4, 'type': 'lapw', 'trialEnergy': 1.000, 'searchE': False},
- {'l': 5, 'type': 'lapw', 'trialEnergy': 1.000, 'searchE': False}
- ]
+ assert basis["custom"] == [
+ {"l": 0, "type": "lapw", "trialEnergy": 1.35670550183736, "searchE": False},
+ {"l": 1, "type": "lapw", "trialEnergy": -2.69952312512447, "searchE": False},
+ {"l": 2, "type": "lapw", "trialEnergy": 0.00, "searchE": False},
+ {"l": 3, "type": "lapw", "trialEnergy": 1.000, "searchE": False},
+ {"l": 4, "type": "lapw", "trialEnergy": 1.000, "searchE": False},
+ {"l": 5, "type": "lapw", "trialEnergy": 1.000, "searchE": False},
+ ]
# All explicitly specified LOs
- los = [{'l': 0, 'matchingOrder': [0, 1], 'trialEnergy': [-4.37848525995355, -4.37848525995355],
- 'searchE': [False, False]},
- {'l': 0, 'matchingOrder': [0, 1], 'trialEnergy': [1.35670550183736, 1.35670550183736],
- 'searchE': [False, False]},
- {'l': 0, 'matchingOrder': [0, 0], 'trialEnergy': [1.35670550183736, -4.37848525995355],
- 'searchE': [False, False]},
- {'l': 0, 'matchingOrder': [1, 2], 'trialEnergy': [1.35670550183736, 1.35670550183736],
- 'searchE': [False, False]},
- {'l': 1, 'matchingOrder': [0, 1], 'trialEnergy': [-2.69952312512447, -2.69952312512447],
- 'searchE': [False, False]},
- {'l': 1, 'matchingOrder': [1, 2], 'trialEnergy': [-2.69952312512447, -2.69952312512447],
- 'searchE': [False, False]},
- {'l': 2, 'matchingOrder': [0, 1], 'trialEnergy': [0.0, 0.0], 'searchE': [False, False]},
- {'l': 2, 'matchingOrder': [1, 2], 'trialEnergy': [0.0, 0.0], 'searchE': [False, False]},
- {'l': 3, 'matchingOrder': [0, 1], 'trialEnergy': [1.0, 1.0], 'searchE': [False, False]},
- {'l': 3, 'matchingOrder': [1, 2], 'trialEnergy': [1.0, 1.0], 'searchE': [False, False]},
- {'l': 4, 'matchingOrder': [0, 1], 'trialEnergy': [1.0, 1.0], 'searchE': [False, False]},
- {'l': 4, 'matchingOrder': [1, 2], 'trialEnergy': [1.0, 1.0], 'searchE': [False, False]},
- {'l': 5, 'matchingOrder': [0, 1], 'trialEnergy': [1.0, 1.0], 'searchE': [False, False]},
- {'l': 5, 'matchingOrder': [1, 2], 'trialEnergy': [1.0, 1.0], 'searchE': [False, False]}]
-
- assert len(basis['lo']) == 14, "Number of explicitly-defined local orbitals"
- assert set(basis['lo'][0]) == {'l', 'matchingOrder', 'trialEnergy', 'searchE'}, \
- "Attributes defining a local orbital"
- assert basis['lo'] == los
+ los = [
+ {
+ "l": 0,
+ "wf": [
+ {"matchingOrder": 0, "searchE": False, "trialEnergy": -4.37848525995355},
+ {"matchingOrder": 1, "searchE": False, "trialEnergy": -4.37848525995355},
+ ],
+ },
+ {
+ "l": 0,
+ "wf": [
+ {"matchingOrder": 0, "searchE": False, "trialEnergy": 1.35670550183736},
+ {"matchingOrder": 1, "searchE": False, "trialEnergy": 1.35670550183736},
+ ],
+ },
+ {
+ "l": 0,
+ "wf": [
+ {"matchingOrder": 0, "searchE": False, "trialEnergy": 1.35670550183736},
+ {"matchingOrder": 0, "searchE": False, "trialEnergy": -4.37848525995355},
+ ],
+ },
+ {
+ "l": 0,
+ "wf": [
+ {"matchingOrder": 1, "searchE": False, "trialEnergy": 1.35670550183736},
+ {"matchingOrder": 2, "searchE": False, "trialEnergy": 1.35670550183736},
+ ],
+ },
+ {
+ "l": 1,
+ "wf": [
+ {"matchingOrder": 0, "searchE": False, "trialEnergy": -2.69952312512447},
+ {"matchingOrder": 1, "searchE": False, "trialEnergy": -2.69952312512447},
+ ],
+ },
+ {
+ "l": 1,
+ "wf": [
+ {"matchingOrder": 1, "searchE": False, "trialEnergy": -2.69952312512447},
+ {"matchingOrder": 2, "searchE": False, "trialEnergy": -2.69952312512447},
+ ],
+ },
+ {
+ "l": 2,
+ "wf": [
+ {"matchingOrder": 0, "searchE": False, "trialEnergy": 0.0},
+ {"matchingOrder": 1, "searchE": False, "trialEnergy": 0.0},
+ ],
+ },
+ {
+ "l": 2,
+ "wf": [
+ {"matchingOrder": 1, "searchE": False, "trialEnergy": 0.0},
+ {"matchingOrder": 2, "searchE": False, "trialEnergy": 0.0},
+ ],
+ },
+ {
+ "l": 3,
+ "wf": [
+ {"matchingOrder": 0, "searchE": False, "trialEnergy": 1.0},
+ {"matchingOrder": 1, "searchE": False, "trialEnergy": 1.0},
+ ],
+ },
+ {
+ "l": 3,
+ "wf": [
+ {"matchingOrder": 1, "searchE": False, "trialEnergy": 1.0},
+ {"matchingOrder": 2, "searchE": False, "trialEnergy": 1.0},
+ ],
+ },
+ {
+ "l": 4,
+ "wf": [
+ {"matchingOrder": 0, "searchE": False, "trialEnergy": 1.0},
+ {"matchingOrder": 1, "searchE": False, "trialEnergy": 1.0},
+ ],
+ },
+ {
+ "l": 4,
+ "wf": [
+ {"matchingOrder": 1, "searchE": False, "trialEnergy": 1.0},
+ {"matchingOrder": 2, "searchE": False, "trialEnergy": 1.0},
+ ],
+ },
+ {
+ "l": 5,
+ "wf": [
+ {"matchingOrder": 0, "searchE": False, "trialEnergy": 1.0},
+ {"matchingOrder": 1, "searchE": False, "n": 5},
+ ],
+ },
+ {
+ "l": 5,
+ "wf": [{"matchingOrder": 1, "n": 5, "searchE": False}, {"matchingOrder": 2, "n": 6, "searchE": False}],
+ },
+ ]
+
+ assert len(basis["lo"]) == 14, "Number of explicitly-defined local orbitals"
+ assert set(basis["lo"][0]) == {"l", "wf"}, "Attributes defining a local orbital"
+ assert basis["lo"] == los
def test_parse_species_xml_different_ordering():
species_dict = parse_species_xml(species_str_diff_order)
- assert set(species_dict) == {'species', 'muffin_tin', 'atomic_states', 'basis'}, 'Top level species file keys'
+ assert set(species_dict) == {"species", "muffin_tin", "atomic_states", "basis"}, "Top level species file keys"
# References
- ref_species = {'chemicalSymbol': 'C', 'name': 'carbon', 'z': -6.0, 'mass': 21894.16673}
- ref_muffin_tin = {'rmin': 1e-05, 'radius': 1.45, 'rinf': 21.0932, 'radialmeshPoints': 250.0}
- ref_atomic_states = [{'n': 1, 'l': 0, 'kappa': 1, 'occ': 2.0, 'core': False},
- {'n': 2, 'l': 0, 'kappa': 1, 'occ': 2.0, 'core': False},
- {'n': 2, 'l': 1, 'kappa': 1, 'occ': 1.0, 'core': False},
- {'n': 2, 'l': 1, 'kappa': 2, 'occ': 1.0, 'core': False}]
- ref_basis = {'default': [{'type': 'lapw', 'trialEnergy': 0.15, 'searchE': False}],
- 'custom': [{'l': 0, 'type': 'apw+lo', 'trialEnergy': 0.15, 'searchE': True},
- {'l': 1, 'type': 'apw+lo', 'trialEnergy': 0.15, 'searchE': True}],
- 'lo': [{'l': 0, 'wfproj': False, 'matchingOrder': [0, 1, 0], 'trialEnergy': [0.15, 0.15, -9.8],
- 'searchE': [True, True, False]}]
- }
-
- assert species_dict['species'] == ref_species, "species data disagrees"
- assert species_dict['muffin_tin'] == ref_muffin_tin, "muffin tin data disagrees"
- assert species_dict['atomic_states'] == ref_atomic_states, "atomic state data disagrees"
- assert species_dict['basis']['default'] == ref_basis['default'], "basis default data disagrees"
- assert species_dict['basis']['custom'] == ref_basis['custom'], "basis custom data disagrees"
- assert species_dict['basis']['lo'] == ref_basis['lo'], "basis lo data disagrees"
+ ref_species = {"chemicalSymbol": "C", "name": "carbon", "z": -6.0, "mass": 21894.16673}
+ ref_muffin_tin = {"rmin": 1e-05, "radius": 1.45, "rinf": 21.0932, "radialmeshPoints": 250.0}
+ ref_atomic_states = [
+ {"n": 1, "l": 0, "kappa": 1, "occ": 2.0, "core": False},
+ {"n": 2, "l": 0, "kappa": 1, "occ": 2.0, "core": False},
+ {"n": 2, "l": 1, "kappa": 1, "occ": 1.0, "core": False},
+ {"n": 2, "l": 1, "kappa": 2, "occ": 1.0, "core": False},
+ ]
+ ref_basis = {
+ "default": [{"type": "lapw", "trialEnergy": 0.15, "searchE": False}],
+ "custom": [
+ {"l": 0, "type": "apw+lo", "trialEnergy": 0.15, "searchE": True},
+ {"l": 1, "type": "apw+lo", "trialEnergy": 0.15, "searchE": True},
+ ],
+ "lo": [
+ {
+ "l": 0,
+ "wfproj": False,
+ "wf": [
+ {"matchingOrder": 0, "trialEnergy": 0.15, "searchE": True},
+ {"matchingOrder": 1, "trialEnergy": 0.15, "searchE": True},
+ {"matchingOrder": 0, "trialEnergy": -9.8, "searchE": False},
+ ],
+ }
+ ],
+ }
+
+ assert species_dict["species"] == ref_species, "species data disagrees"
+ assert species_dict["muffin_tin"] == ref_muffin_tin, "muffin tin data disagrees"
+ assert species_dict["atomic_states"] == ref_atomic_states, "atomic state data disagrees"
+ assert species_dict["basis"]["default"] == ref_basis["default"], "basis default data disagrees"
+ assert species_dict["basis"]["custom"] == ref_basis["custom"], "basis custom data disagrees"
+ assert species_dict["basis"]["lo"] == ref_basis["lo"], "basis lo data disagrees"
species_str = """
@@ -180,11 +264,11 @@ def test_parse_species_xml_different_ordering():
-
+
-
-
+
+
@@ -192,7 +276,6 @@ def test_parse_species_xml_different_ordering():
"""
-
species_str_diff_order = """
diff --git a/tests/dict_parsers/test_state_parser.py b/tests/dict_parsers/test_state_parser.py
new file mode 100644
index 0000000..4c9e97f
--- /dev/null
+++ b/tests/dict_parsers/test_state_parser.py
@@ -0,0 +1,157 @@
+import sys
+from pathlib import Path
+
+import numpy as np
+
+from excitingtools.exciting_dict_parsers.state_parser import parse_state_out
+
+
+def write_int(file, i: int, byteorder):
+ """Writes an integer as bytes to the file.
+
+ :param file: file object to write the int
+ :param i: integer, which bytes should be written
+ :param byteorder: endianness of the byteorder ("little" or "big")
+ """
+ file.write(int.to_bytes(4, 4, byteorder))
+ file.write(i.to_bytes(4, byteorder))
+ file.write(int.to_bytes(4, 4, byteorder))
+
+
+def write_array(file, a: np.ndarray, byteorder):
+ """Writes a numpy array to the file as bytes.
+
+ :param file: file object to write the int
+ :param a: numpy array, which bytes should be written
+ :param byteorder: endianness of the byteorder ("little" or "big")
+ """
+ num_bytes = a.size * 8
+ file.write(num_bytes.to_bytes(4, byteorder))
+ file.write(a.tobytes(order="F"))
+ file.write(num_bytes.to_bytes(4, byteorder))
+
+
+def write_state(path, state: dict):
+ """Creates a file at the specified path and writes the dictionary in the same structure as STATE.OUT
+
+ :param path: path to the file to which should be written to
+ :param state: state dictionary, which should be converted to a binary STATE file
+ """
+ byteorder = sys.byteorder
+ with open(path, "wb+") as file:
+ # write version information
+ file.write(int.to_bytes(52, 4, byteorder))
+ for i in state["version"]:
+ i: int
+ file.write(i.to_bytes(4, byteorder))
+ file.write(b"YomNFS0t4s8uhGxs4FCNGsAOp5DapSm7HAQ58aVd")
+ file.write(int.to_bytes(52, 4, byteorder))
+ # write other general information
+ for key in ("spinpol", "nspecies", "lmmaxvr", "nrmtmax"):
+ write_int(file, state[key], byteorder)
+ # next write the species specific information
+ for i in range(state["nspecies"]):
+ write_int(file, state["number of atoms"][i], byteorder)
+ write_int(file, len(state["muffintin radial meshes"][i]), byteorder)
+ write_array(file, state["muffintin radial meshes"][i], byteorder)
+ # next write g vector grid
+ file.write(int.to_bytes(12, 4, byteorder))
+ for i in range(3):
+ file.write(state["g vector grid"][i].to_bytes(4, byteorder))
+ file.write(int.to_bytes(12, 4, byteorder))
+ # next are again a few integers
+ for key in ("ngvec", "ndmag", "nspinor", "ldapu", "lmmaxlu"):
+ write_int(file, state[key], byteorder)
+ # next write the array pairs
+ arrays = (
+ ("muffintin density", "interstitial density"),
+ ("muffintin coulomb potential", "interstitial coulomb potential"),
+ ("muffintin exchange-correlation potential", "interstitial exchange-correlation potential"),
+ )
+ for muffintin_array, interstitial_array in arrays:
+ muffintin_array_state: np.ndarray = state[muffintin_array]
+ interstitial_array_state: np.ndarray = state[interstitial_array]
+ num_bytes = (muffintin_array_state.size + interstitial_array_state.size) * 8
+ file.write(num_bytes.to_bytes(4, byteorder))
+ file.write(muffintin_array_state.tobytes(order="F"))
+ file.write(interstitial_array_state.tobytes(order="F"))
+ file.write(num_bytes.to_bytes(4, byteorder))
+ # Lastly write the potential arrays
+ num_bytes = 8 * (
+ state["muffintin effective potential"].size
+ + state["interstitial effective potential"].size
+ + 2 * state["reciprocal interstitial effective potential"].size
+ )
+ file.write(num_bytes.to_bytes(4, byteorder))
+ file.write(state["muffintin effective potential"].tobytes(order="F"))
+ file.write(state["interstitial effective potential"].tobytes(order="F"))
+ file.write(state["reciprocal interstitial effective potential"].tobytes(order="F"))
+ file.write(num_bytes.to_bytes(4, byteorder))
+
+
+def test_parse_state_out(tmp_path: Path):
+ directory = tmp_path
+
+ # setting seed to avoid randomness in tests
+ rng = np.random.default_rng(0)
+
+ state_ref = {
+ "version": (1, 2, 3),
+ "versionhash": "YomNFS0t4s8uhGxs4FCNGsAOp5DapSm7HAQ58aVd",
+ "spinpol": False,
+ "nspecies": 2,
+ "lmmaxvr": 3,
+ "nrmtmax": 3,
+ "number of atoms": [1, 1],
+ "muffintin radial meshes": [np.array([1.0, 2.0]), np.array([0.5, 0.75, 1.5])],
+ "g vector grid": (2, 2, 2),
+ "ngvec": 5,
+ "ndmag": 0,
+ "nspinor": 1,
+ "ldapu": 0,
+ "lmmaxlu": 16,
+ "muffintin density": rng.random((3, 3, 2)),
+ "interstitial density": rng.random(8),
+ "muffintin coulomb potential": rng.random((3, 3, 2)),
+ "interstitial coulomb potential": rng.random(8),
+ "muffintin exchange-correlation potential": rng.random((3, 3, 2)),
+ "interstitial exchange-correlation potential": rng.random(8),
+ "muffintin effective potential": rng.random((3, 3, 2)),
+ "interstitial effective potential": rng.random(8),
+ "reciprocal interstitial effective potential": rng.random(5) + 1j * rng.random(5),
+ }
+ write_state(directory / "STATE.OUT", state_ref)
+ state_out = parse_state_out(directory / "STATE.OUT")
+
+ assert state_out["version"] == state_ref["version"]
+ assert state_out["versionhash"] == state_ref["versionhash"]
+ assert state_out["spinpol"] == state_ref["spinpol"]
+ assert state_out["nspecies"] == state_ref["nspecies"]
+ assert state_out["lmmaxvr"] == state_ref["lmmaxvr"]
+ assert state_out["nrmtmax"] == state_ref["nrmtmax"]
+ assert state_out["number of atoms"] == state_ref["number of atoms"]
+ assert np.allclose(state_out["muffintin radial meshes"][0], state_ref["muffintin radial meshes"][0])
+ assert np.allclose(state_out["muffintin radial meshes"][1], state_ref["muffintin radial meshes"][1])
+ assert state_out["g vector grid"] == state_ref["g vector grid"]
+ assert state_out["ngvec"] == state_ref["ngvec"]
+ assert state_out["ndmag"] == state_ref["ndmag"]
+ assert state_out["nspinor"] == state_ref["nspinor"]
+ assert state_out["ldapu"] == state_ref["ldapu"]
+ assert state_out["lmmaxlu"] == state_ref["lmmaxlu"]
+ assert np.allclose(state_out["muffintin density"], state_ref["muffintin density"])
+ assert np.allclose(state_out["interstitial density"], state_ref["interstitial density"])
+ assert np.allclose(state_out["muffintin coulomb potential"], state_ref["muffintin coulomb potential"])
+ assert np.allclose(state_out["interstitial coulomb potential"], state_ref["interstitial coulomb potential"])
+ assert np.allclose(
+ state_out["muffintin exchange-correlation potential"], state_ref["muffintin exchange-correlation potential"]
+ )
+ assert np.allclose(
+ state_out["interstitial exchange-correlation potential"],
+ state_ref["interstitial exchange-correlation potential"],
+ )
+ assert np.allclose(state_out["muffintin effective potential"], state_ref["muffintin effective potential"])
+ assert np.allclose(state_out["interstitial effective potential"], state_ref["interstitial effective potential"])
+ assert np.allclose(
+ state_out["reciprocal interstitial effective potential"],
+ state_ref["reciprocal interstitial effective potential"],
+ )
diff --git a/tests/eigenstates/test_eigenstates.py b/tests/eigenstates/test_eigenstates.py
index 1c6094f..af10f0c 100644
--- a/tests/eigenstates/test_eigenstates.py
+++ b/tests/eigenstates/test_eigenstates.py
@@ -1,18 +1,23 @@
-""" Test for get_index function in eigenstates.py
-"""
+"""Test for get_index function in eigenstates.py"""
-from excitingtools.eigenstates.eigenstates import get_k_point_index
-import pytest
-import numpy as np
import re
+import numpy as np
+import pytest
+
+from excitingtools.eigenstates.eigenstates import get_k_point_index
+
def test_get_k_point_index():
- k_points = np.array([[1.000000, 0.000000, 0.000000],
- [0.988281, 0.011719, 0.000000],
- [0.988281, 0.011719, 0.000000],
- [0.976562, 0.023438, 0.000000],
- [0.953125, 0.046875, 0.000000]])
+ k_points = np.array(
+ [
+ [1.000000, 0.000000, 0.000000],
+ [0.988281, 0.011719, 0.000000],
+ [0.988281, 0.011719, 0.000000],
+ [0.976562, 0.023438, 0.000000],
+ [0.953125, 0.046875, 0.000000],
+ ]
+ )
assert get_k_point_index([1.000000, 0.000000, 0.000000], k_points) == 0
assert get_k_point_index([0.953125, 0.046875, 0.000000], k_points) == 4
diff --git a/tests/input/test_base_class.py b/tests/input/test_base_class.py
index 33e98b0..54a19ed 100644
--- a/tests/input/test_base_class.py
+++ b/tests/input/test_base_class.py
@@ -1,11 +1,17 @@
-"""Test exi class and its methods."""
+"""Test exiting input base class and its methods.
-import pathlib
+NOTE:
+All attribute tests should assert on the XML tree content's as the attribute
+order is not preserved by the ElementTree.tostring method. Elements appear to
+be fine.
+"""
+
+from pathlib import Path
from excitingtools.input.base_class import query_exciting_version
-def test_query_exciting_version(tmp_path):
+def test_query_exciting_version(tmp_path: Path) -> None:
"""
Test querying the exciting version.
"""
@@ -14,21 +20,22 @@ def test_query_exciting_version(tmp_path):
#define COMPILERVERSION "GNU Fortran (MacPorts gcc9 9.3.0_4) 9.3.0"
#define VERSIONFROMDATE /21,12,01/
"""
-
# Mock the version.inc file, and prepended path
- exciting_root = pathlib.Path(tmp_path)
- src = exciting_root / "src"
+ src = tmp_path / "src"
src.mkdir()
- assert exciting_root.is_dir(), ("exciting_root tmp_path directory does not exist")
- version_inc = exciting_root / "src" / "version.inc"
+ version_inc = tmp_path / "src" / "version.inc"
version_inc.write_text(version_inc_contents)
- assert version_inc.exists(), "version.inc does not exist"
- versioning: dict = query_exciting_version(exciting_root)
- assert set(versioning.keys()) == {'compiler', 'git_hash'}, (
+ mod_misc = src / "mod_misc.F90"
+ mod_misc.write_text(" !> Code version\n character(40) :: versionname = 'NEON'\n")
+
+ versioning: dict = query_exciting_version(tmp_path)
+ assert set(versioning.keys()) == {"compiler", "git_hash", "major"}, (
"Expect `query_exciting_version` to return compiler used "
- "for last build, and exciting git hash")
+ "for last build, exciting git hash and major version name."
+ )
- assert versioning['compiler'] == 'GNU Fortran (MacPorts gcc9 9.3.0_4) 9.3.0'
- assert versioning['git_hash'] == '1a2087b0775a87059d535d01a5475a10f00d0ad7'
+ assert versioning["compiler"] == "GNU Fortran (MacPorts gcc9 9.3.0_4) 9.3.0"
+ assert versioning["git_hash"] == "1a2087b0775a87059d535d01a5475a10f00d0ad7"
+ assert versioning["major"] == "NEON"
diff --git a/tests/input/test_dynamic_class.py b/tests/input/test_dynamic_class.py
new file mode 100644
index 0000000..6c31527
--- /dev/null
+++ b/tests/input/test_dynamic_class.py
@@ -0,0 +1,60 @@
+"""Test for dynamic class functions."""
+
+from excitingtools.input.dynamic_class import (
+ class_constructor_string,
+ class_name_uppercase,
+ get_all_valid_subtrees,
+ give_class_dictionary,
+)
+
+
+def test_get_all_valid_subtrees():
+ ref_subtrees = {
+ "crystal",
+ "shell",
+ "dfthalfparam",
+ "structure",
+ "symmetries",
+ "basevect",
+ "species",
+ "atom",
+ "LDAplusU",
+ }
+ assert set(get_all_valid_subtrees(["structure"])) == ref_subtrees
+
+
+def test_class_dir():
+ ref_dir = {
+ "attributes": {
+ "__doc__": "Class for exciting structure input.",
+ "__module__": "excitingtools.input.input_classes",
+ "name": "structure",
+ },
+ "bases": "(ExcitingXMLInput, )",
+ }
+ assert give_class_dictionary("structure") == ref_dir
+
+
+def test_class_constructor_string():
+ test_input = {
+ "Spin": {
+ "bases": "(ExcitingXMLInput, )",
+ "attributes": {"__doc__": "Class for exciting spin input.", "name": "spin"},
+ }
+ }
+ ref_string = (
+ "from excitingtools.input.base_class import ExcitingXMLInput \n"
+ "ExcitingSpinInput = type('ExcitingSpinInput', (ExcitingXMLInput, ), "
+ "{'__doc__': 'Class for exciting spin input.', 'name': 'spin'}) \n"
+ )
+ assert class_constructor_string(test_input) == ref_string
+
+
+def test_class_name_uppercase():
+ assert class_name_uppercase("groundstate") == "GroundState"
+ assert class_name_uppercase("xs") == "XS"
+ assert class_name_uppercase("structure") == "Structure"
+ assert class_name_uppercase("LDAplusU") == "LDAplusU"
+ assert class_name_uppercase("HartreeFock") == "HartreeFock"
+ assert class_name_uppercase("EFG") == "EFG"
+ assert class_name_uppercase("etCoeffComponents") == "EtCoeffComponents"
diff --git a/tests/input/test_ground_state.py b/tests/input/test_ground_state.py
index 6685f29..0b51e29 100644
--- a/tests/input/test_ground_state.py
+++ b/tests/input/test_ground_state.py
@@ -17,73 +17,87 @@
' '
"""
+
import pytest
-from excitingtools.input.ground_state import ExcitingGroundStateInput
+from excitingtools.input.input_classes import ExcitingGroundStateInput
@pytest.mark.parametrize(
"test_input,expected",
- [({"rgkmax": 8.6}, [('rgkmax', '8.6')]), ({"ngridk": [8, 8, 8]}, [('ngridk', '8 8 8')]),
- ({'vkloff': [0.1, 0.2, 0.3]}, [('vkloff', '0.1 0.2 0.3')]),
- ({'CoreRelativity': 'dirac'}, [('CoreRelativity', 'dirac')]),
- ({'ExplicitKineticEnergy': True}, [('ExplicitKineticEnergy', 'true')]),
- ({'PrelimLinSteps': 2}, [('PrelimLinSteps', '2')]),
- ({'ValenceRelativity': 'zora'}, [('ValenceRelativity', 'zora')]),
- ({'autokpt': False}, [('autokpt', 'false')]), ({'beta0': 0.4}, [('beta0', '0.4')]),
- ({'betadec': 0.6}, [('betadec', '0.6')]), ({'betainc': 1.1}, [('betainc', '1.1')]),
- ({'cfdamp': 0.0}, [('cfdamp', '0.0')]), ({'chgexs': 0.0}, [('chgexs', '0.0')]),
- ({'deband': 2.5e-3}, [('deband', '0.0025')]), ({'dipolecorrection': True}, [
- ('dipolecorrection', 'true')
- ]), ({'dipoleposition': 1.0}, [('dipoleposition', '1.0')]), ({'dlinengyfermi': -0.1}, [
- ('dlinengyfermi', '-0.1')
- ]), ({'do': "fromscratch"}, [('do', "fromscratch")]),
- ({'energyref': 0.0}, [('energyref', '0.0')]), ({'epsband': 1.0e-6}, [('epsband', '1e-06')]),
- ({'epschg': 1.0e-5}, [('epschg', '1e-05')]), ({'epsengy': 1e-6}, [('epsengy', '1e-06')]),
- ({'epsforcescf': 5.0e-5}, [('epsforcescf', '5e-05')]),
- ({'epsocc': 1e-8}, [('epsocc', '1e-08')]), ({'epspot': 1e-6}, [('epspot', '1e-06')]),
- ({'fermilinengy': False}, [('fermilinengy', 'false')]), ({'findlinentype': "Wigner_Seitz"}, [
- ('findlinentype', "Wigner_Seitz")
- ]), ({'fracinr': 0.02}, [('fracinr', '0.02')]), ({'frozencore': False}, [
- ('frozencore', 'false')
- ]), ({'gmaxvr': 12}, [('gmaxvr', '12')]), ({'isgkmax': -1}, [('isgkmax', '-1')]),
- ({'ldapu': "none"}, [('ldapu', "none")]), ({'lmaxapw': 8}, [('lmaxapw', '8')]),
- ({'lmaxinr': 2}, [('lmaxinr', '2')]), ({'lmaxmat': 8}, [('lmaxmat', '8')]), ({'lmaxvr': 8}, [
- ('lmaxvr', '8')
- ]), ({'lorecommendation': False}, [('lorecommendation', 'false')]), ({'lradstep': 1}, [
- ('lradstep', '1')
- ]), ({'maxscl': 200}, [('maxscl', '200')]), ({'mixer': 'msec'}, [('mixer', 'msec')]),
- ({'mixerswitch': 1}, [('mixerswitch', '1')]), ({'modifiedsv': False}, [
- ('modifiedsv', 'false')
- ]), ({'msecStoredSteps': 8}, [('msecStoredSteps', '8')]), ({'nempty': 5}, [
- ('nempty', '5')
- ]), ({'niterconvcheck': 2}, [('niterconvcheck', '2')]), ({'nktot': 0}, [
- ('nktot', '0')
- ]), ({'nosource': False}, [('nosource', 'false')]), ({'nosym': False
- }, [('nosym', 'false')]),
- ({'nprad': 4}, [('nprad', '4')]), ({'npsden': 9}, [('npsden', '9')]), ({'nwrite': 0}, [
- ('nwrite', '0')
- ]), ({'outputlevel': 'normal'}, [('outputlevel', 'normal')]), ({'ptnucl': True}, [
- ('ptnucl', 'true')
- ]), ({'radialgridtype': 'cubic'}, [('radialgridtype', 'cubic')]), ({'radkpt': 40.0}, [
- ('radkpt', '40.0')
- ]), ({'reducek': True}, [('reducek', 'true')]), ({'scfconv': 'multiple'}, [
- ('scfconv', 'multiple')
- ]), ({'stype': 'Gaussian'}, [('stype', 'Gaussian')]), ({'swidth': 0.001}, [
- ('swidth', '0.001')
- ]), ({'symmorph': False}, [('symmorph', 'false')]), ({'tevecsv': False}, [
- ('tevecsv', 'false')
- ]), ({'tfibs': True}, [('tfibs', 'true')]), ({'tforce': False}, [
- ('tforce', 'false')
- ]), ({'tpartcharges': False}, [('tpartcharges', 'false')]),
- ({'useDensityMatrix': True}, [('useDensityMatrix', 'true')]), ({'vdWcorrection': 'none'}, [
- ('vdWcorrection', 'none')
- ]), ({'xctype': 'GGA_PBE'}, [('xctype', 'GGA_PBE')])]
- )
+ [
+ ({"rgkmax": 8.6}, [("rgkmax", "8.6")]),
+ ({"ngridk": [8, 8, 8]}, [("ngridk", "8 8 8")]),
+ ({"vkloff": [0.1, 0.2, 0.3]}, [("vkloff", "0.1 0.2 0.3")]),
+ ({"CoreRelativity": "dirac"}, [("CoreRelativity", "dirac")]),
+ ({"ExplicitKineticEnergy": True}, [("ExplicitKineticEnergy", "true")]),
+ ({"PrelimLinSteps": 2}, [("PrelimLinSteps", "2")]),
+ ({"ValenceRelativity": "zora"}, [("ValenceRelativity", "zora")]),
+ ({"autokpt": False}, [("autokpt", "false")]),
+ ({"beta0": 0.4}, [("beta0", "0.4")]),
+ ({"betadec": 0.6}, [("betadec", "0.6")]),
+ ({"betainc": 1.1}, [("betainc", "1.1")]),
+ ({"cfdamp": 0.0}, [("cfdamp", "0.0")]),
+ ({"chgexs": 0.0}, [("chgexs", "0.0")]),
+ ({"deband": 2.5e-3}, [("deband", "0.0025")]),
+ ({"dipolecorrection": True}, [("dipolecorrection", "true")]),
+ ({"dipoleposition": 1.0}, [("dipoleposition", "1.0")]),
+ ({"dlinengyfermi": -0.1}, [("dlinengyfermi", "-0.1")]),
+ ({"do": "fromscratch"}, [("do", "fromscratch")]),
+ ({"energyref": 0.0}, [("energyref", "0.0")]),
+ ({"epsband": 1.0e-6}, [("epsband", "1e-06")]),
+ ({"epschg": 1.0e-5}, [("epschg", "1e-05")]),
+ ({"epsengy": 1e-6}, [("epsengy", "1e-06")]),
+ ({"epsforcescf": 5.0e-5}, [("epsforcescf", "5e-05")]),
+ ({"epsocc": 1e-8}, [("epsocc", "1e-08")]),
+ ({"epspot": 1e-6}, [("epspot", "1e-06")]),
+ ({"fermilinengy": False}, [("fermilinengy", "false")]),
+ ({"findlinentype": "Wigner_Seitz"}, [("findlinentype", "Wigner_Seitz")]),
+ ({"fracinr": 0.02}, [("fracinr", "0.02")]),
+ ({"frozencore": False}, [("frozencore", "false")]),
+ ({"gmaxvr": 12}, [("gmaxvr", "12")]),
+ ({"isgkmax": -1}, [("isgkmax", "-1")]),
+ ({"ldapu": "none"}, [("ldapu", "none")]),
+ ({"lmaxapw": 8}, [("lmaxapw", "8")]),
+ ({"lmaxinr": 2}, [("lmaxinr", "2")]),
+ ({"lmaxmat": 8}, [("lmaxmat", "8")]),
+ ({"lmaxvr": 8}, [("lmaxvr", "8")]),
+ ({"lradstep": 1}, [("lradstep", "1")]),
+ ({"maxscl": 200}, [("maxscl", "200")]),
+ ({"mixer": "msec"}, [("mixer", "msec")]),
+ ({"mixerswitch": 1}, [("mixerswitch", "1")]),
+ ({"modifiedsv": False}, [("modifiedsv", "false")]),
+ ({"msecStoredSteps": 8}, [("msecStoredSteps", "8")]),
+ ({"nempty": 5}, [("nempty", "5")]),
+ ({"niterconvcheck": 2}, [("niterconvcheck", "2")]),
+ ({"nktot": 0}, [("nktot", "0")]),
+ ({"nosource": False}, [("nosource", "false")]),
+ ({"nosym": False}, [("nosym", "false")]),
+ ({"nprad": 4}, [("nprad", "4")]),
+ ({"npsden": 9}, [("npsden", "9")]),
+ ({"nwrite": 0}, [("nwrite", "0")]),
+ ({"outputlevel": "normal"}, [("outputlevel", "normal")]),
+ ({"ptnucl": True}, [("ptnucl", "true")]),
+ ({"radialgridtype": "cubic"}, [("radialgridtype", "cubic")]),
+ ({"radkpt": 40.0}, [("radkpt", "40.0")]),
+ ({"reducek": True}, [("reducek", "true")]),
+ ({"scfconv": "multiple"}, [("scfconv", "multiple")]),
+ ({"stype": "Gaussian"}, [("stype", "Gaussian")]),
+ ({"swidth": 0.001}, [("swidth", "0.001")]),
+ ({"symmorph": False}, [("symmorph", "false")]),
+ ({"tevecsv": False}, [("tevecsv", "false")]),
+ ({"tfibs": True}, [("tfibs", "true")]),
+ ({"tforce": False}, [("tforce", "false")]),
+ ({"tpartcharges": False}, [("tpartcharges", "false")]),
+ ({"useDensityMatrix": True}, [("useDensityMatrix", "true")]),
+ ({"vdWcorrection": "none"}, [("vdWcorrection", "none")]),
+ ({"xctype": "GGA_PBE"}, [("xctype", "GGA_PBE")]),
+ ],
+)
def test_class_exciting_ground_state_input_parametrized(test_input, expected):
gs_input = ExcitingGroundStateInput(**test_input)
gs_xml = gs_input.to_xml()
- assert gs_xml.tag == 'groundstate'
+ assert gs_xml.tag == "groundstate"
assert gs_xml.items() == expected
@@ -92,8 +106,111 @@ def test_invalid_input():
Test error is raised when giving bogus attributes to class constructor.
"""
# Use an erroneous ground state attribute
- with pytest.raises(ValueError) as error:
+ with pytest.raises(ValueError, match="groundstate keys are not valid: {'erroneous_attribute'}"):
ExcitingGroundStateInput(erroneous_attribute=True)
- assert error.value.args[
- 0] == "groundstate keys are not valid: {'erroneous_attribute'}"
+
+@pytest.mark.usefixtures("mock_env_jobflow_missing")
+def test_as_dict():
+ ref_rgkmax = 8.5
+ gs_input = ExcitingGroundStateInput(rgkmax=ref_rgkmax)
+ ref_dict = {"xml_string": f' '}
+ assert gs_input.as_dict() == ref_dict, "expected different dict representation"
+
+
+@pytest.mark.usefixtures("mock_env_jobflow")
+def test_as_dict_jobflow():
+ ref_rgkmax = 8.5
+ gs_input = ExcitingGroundStateInput(rgkmax=ref_rgkmax)
+ ref_dict = {
+ "@class": "ExcitingGroundStateInput",
+ "@module": "excitingtools.input.input_classes",
+ "xml_string": f' ',
+ }
+ assert gs_input.as_dict() == ref_dict, "expected different dict representation"
+
+
+def test_from_dict():
+ ref_rgkmax = 8.5
+ ref_dict = {"xml_string": f' '}
+ gs_input = ExcitingGroundStateInput.from_dict(ref_dict)
+
+ assert gs_input.name == "groundstate"
+ # added comment for pylint to disable warning, because of dynamic attributes
+ assert gs_input.rgkmax == ref_rgkmax, f"Expect rgkmax to be equal {ref_rgkmax}" # pylint: disable=no-member
+
+
+def test_spin_input():
+ spin_attributes = {"bfieldc": [0, 0, 0], "fixspin": "total FSM"}
+ spin_keys = list(spin_attributes)
+ gs_input = ExcitingGroundStateInput(rgkmax=7.0, spin=spin_attributes)
+
+ gs_xml = gs_input.to_xml()
+ assert gs_xml.tag == "groundstate"
+ assert set(gs_xml.keys()) == {"rgkmax"}
+
+ elements = list(gs_xml)
+ assert len(elements) == 1
+
+ spin_xml = elements[0]
+ assert spin_xml.tag == "spin"
+ assert spin_xml.keys() == spin_keys, "Should contain all spin attributes"
+ assert spin_xml.get("bfieldc") == "0 0 0"
+ assert spin_xml.get("fixspin") == "total FSM"
+
+
+def test_solver_input():
+ solver_attributes = {"packedmatrixstorage": True, "type": "Lapack"}
+ solver_keys = list(solver_attributes)
+ gs_input = ExcitingGroundStateInput(solver=solver_attributes)
+
+ gs_xml = gs_input.to_xml()
+ assert gs_xml.tag == "groundstate"
+ assert set(gs_xml.keys()) == set()
+
+ elements = list(gs_xml)
+ assert len(elements) == 1
+
+ solver_xml = elements[0]
+ assert solver_xml.tag == "solver"
+ assert solver_xml.keys() == solver_keys, "Should contain all spin attributes"
+ assert solver_xml.get("packedmatrixstorage") == "true"
+ assert solver_xml.get("type") == "Lapack"
+
+
+def test_dfthalf_input():
+ dfthalf_attributes = {"printVSfile": True}
+ dfthalf_keys = list(dfthalf_attributes)
+ gs_input = ExcitingGroundStateInput(dfthalf=dfthalf_attributes)
+
+ gs_xml = gs_input.to_xml()
+ assert gs_xml.tag == "groundstate"
+ assert set(gs_xml.keys()) == set()
+
+ elements = list(gs_xml)
+ assert len(elements) == 1
+
+ dfthalf_xml = elements[0]
+ assert dfthalf_xml.tag == "dfthalf"
+ assert dfthalf_xml.keys() == dfthalf_keys, "Should contain all dfthalf attributes"
+ assert dfthalf_xml.get("printVSfile") == "true"
+
+
+def test_OEP_input():
+ oep_attributes = {"convoep": 1e-5, "maxitoep": 200, "tauoep": [1, 2, 3]}
+ oep_keys = list(oep_attributes)
+ gs_input = ExcitingGroundStateInput(OEP=oep_attributes)
+
+ gs_xml = gs_input.to_xml()
+ assert gs_xml.tag == "groundstate"
+ assert set(gs_xml.keys()) == set()
+
+ elements = list(gs_xml)
+ assert len(elements) == 1
+
+ oep_xml = elements[0]
+ assert oep_xml.tag == "OEP"
+ assert oep_xml.keys() == oep_keys, "Should contain all dfthalf attributes"
+ assert oep_xml.get("convoep") == "1e-05"
+ assert oep_xml.get("maxitoep") == "200"
+ assert oep_xml.get("tauoep") == "1 2 3"
diff --git a/tests/input/test_input_xml.py b/tests/input/test_input_xml.py
index 4e70830..f099cbb 100644
--- a/tests/input/test_input_xml.py
+++ b/tests/input/test_input_xml.py
@@ -3,25 +3,37 @@
TODO(Fab/Alex/Dan) Issue 117. Would be nice to assert that the output is valid
XML * https://lxml.de/validation.html
Also see: https://xmlschema.readthedocs.io/en/latest/usage.html#xsd-declarations
+
+NOTE:
+All attribute tests should assert on the XML tree content's as the attribute
+order is not preserved by the ElementTree.tostring method. Elements appear to
+be fine.
"""
-from excitingtools.input.input_xml import exciting_input_xml
+
+import numpy as np
+import pytest
+
+from excitingtools.input.input_classes import ExcitingGroundStateInput, ExcitingKeywordsInput, ExcitingXSInput
+from excitingtools.input.input_xml import ExcitingInputXML
from excitingtools.input.structure import ExcitingStructure
-from excitingtools.input.ground_state import ExcitingGroundStateInput
-from excitingtools.input.xs import ExcitingXSInput
-def test_exciting_input_xml_structure_and_gs_and_xs():
- """Test the XML created for a ground state input is valid.
- Test SubTree composition using only mandatory attributes for each XML subtree.
- """
- # Structure
+@pytest.fixture
+def exciting_structure() -> ExcitingStructure:
+ """Initialise an exciting structure."""
cubic_lattice = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
- arbitrary_atoms = [{'species': 'Li', 'position': [0.0, 0.0, 0.0]},
- {'species': 'Li', 'position': [1.0, 0.0, 0.0]},
- {'species': 'F', 'position': [2.0, 0.0, 0.0]}]
+ arbitrary_atoms = [
+ {"species": "Li", "position": [0.0, 0.0, 0.0]},
+ {"species": "Li", "position": [1.0, 0.0, 0.0]},
+ {"species": "F", "position": [2.0, 0.0, 0.0]},
+ ]
+
+ return ExcitingStructure(arbitrary_atoms, cubic_lattice, ".")
- structure = ExcitingStructure(arbitrary_atoms, cubic_lattice, '.')
+@pytest.fixture
+def exciting_input_xml(exciting_structure: ExcitingStructure) -> ExcitingInputXML:
+ """Initialises a complete input file."""
ground_state = ExcitingGroundStateInput(
rgkmax=8.0,
do="fromscratch",
@@ -29,103 +41,238 @@ def test_exciting_input_xml_structure_and_gs_and_xs():
xctype="GGA_PBE_SOL",
vkloff=[0, 0, 0],
tforce=True,
- nosource=False
- )
-
- xs_attributes = {'broad': 0.32, 'ngridk': [8, 8, 8]}
- bse_attributes = {'bsetype': 'singlet', 'xas': True}
- energywindow_attributes = {'intv': [5.8, 8.3], 'points': 5000}
- screening_attributes = {'screentype': 'full', 'nempty': 15}
- plan_input = ['screen', 'bse']
+ nosource=False,
+ )
+
+ bse_attributes = {"bsetype": "singlet", "xas": True}
+ energywindow_attributes = {"intv": [5.8, 8.3], "points": 5000}
+ screening_attributes = {"screentype": "full", "nempty": 15}
+ plan_input = ["screen", "bse"]
qpointset_input = [[0, 0, 0], [0.5, 0.5, 0.5]]
- xs = ExcitingXSInput("BSE", xs=xs_attributes,
- BSE=bse_attributes,
- energywindow=energywindow_attributes,
- screening=screening_attributes,
- qpointset=qpointset_input,
- plan=plan_input)
+ xs = ExcitingXSInput(
+ xstype="BSE",
+ broad=0.32,
+ ngridk=[8, 8, 8],
+ BSE=bse_attributes,
+ energywindow=energywindow_attributes,
+ screening=screening_attributes,
+ qpointset=qpointset_input,
+ plan=plan_input,
+ )
+ keywords = ExcitingKeywordsInput("keyword1 keyword2 keyword3")
+
+ return ExcitingInputXML(
+ sharedfs=True,
+ structure=exciting_structure,
+ title="Test Case",
+ groundstate=ground_state,
+ xs=xs,
+ keywords=keywords,
+ )
+
- input_xml_tree = exciting_input_xml(
- structure, title='Test Case', groundstate=ground_state, xs=xs)
+def test_exciting_input_xml_structure_and_gs_and_xs(exciting_input_xml: ExcitingInputXML): # noqa: PLR0915
+ """Test the XML created for a ground state input is valid.
+ Test SubTree composition using only mandatory attributes for each XML subtree.
+ """
+ input_xml_tree = exciting_input_xml.to_xml()
- assert input_xml_tree.tag == 'input'
- assert input_xml_tree.keys() == []
+ assert input_xml_tree.tag == "input"
+ assert input_xml_tree.keys() == ["sharedfs"]
subelements = list(input_xml_tree)
- assert len(subelements) == 4
+ assert len(subelements) == 5
title_xml = subelements[0]
- assert title_xml.tag == 'title'
+ assert title_xml.tag == "title"
assert title_xml.keys() == []
- assert title_xml.text == 'Test Case'
+ assert title_xml.text == "Test Case"
structure_xml = subelements[1]
- assert structure_xml.tag == 'structure'
- assert structure_xml.keys() == ['speciespath']
+ assert structure_xml.tag == "structure"
+ assert structure_xml.keys() == ["speciespath"]
assert len(list(structure_xml)) == 3
groundstate_xml = subelements[2]
- assert groundstate_xml.tag == 'groundstate'
- assert groundstate_xml.text == ' '
- assert groundstate_xml.keys() == \
- ['rgkmax', 'do', 'ngridk', 'xctype', 'vkloff', 'tforce', 'nosource']
- assert groundstate_xml.get('rgkmax') == "8.0"
- assert groundstate_xml.get('do') == "fromscratch"
- assert groundstate_xml.get('ngridk') == "6 6 6"
- assert groundstate_xml.get('xctype') == "GGA_PBE_SOL"
- assert groundstate_xml.get('vkloff') == "0 0 0"
- assert groundstate_xml.get('tforce') == "true"
- assert groundstate_xml.get('nosource') == "false"
+ assert groundstate_xml.tag == "groundstate"
+ assert groundstate_xml.text == " "
+ assert groundstate_xml.keys() == ["rgkmax", "do", "ngridk", "xctype", "vkloff", "tforce", "nosource"]
+ assert groundstate_xml.get("rgkmax") == "8.0"
+ assert groundstate_xml.get("do") == "fromscratch"
+ assert groundstate_xml.get("ngridk") == "6 6 6"
+ assert groundstate_xml.get("xctype") == "GGA_PBE_SOL"
+ assert groundstate_xml.get("vkloff") == "0 0 0"
+ assert groundstate_xml.get("tforce") == "true"
+ assert groundstate_xml.get("nosource") == "false"
xs_xml = subelements[3]
- assert xs_xml.tag == 'xs'
- try:
- assert xs_xml.keys() == ['broad', 'ngridk', 'xstype']
- except AssertionError:
- assert xs_xml.keys() == ['xstype', 'broad', 'ngridk']
- assert xs_xml.get('broad') == '0.32'
- assert xs_xml.get('ngridk') == '8 8 8'
- assert xs_xml.get('xstype') == 'BSE'
+ assert xs_xml.tag == "xs"
+ assert set(xs_xml.keys()) == {"broad", "ngridk", "xstype"}
+ assert xs_xml.get("broad") == "0.32"
+ assert xs_xml.get("ngridk") == "8 8 8"
+ assert xs_xml.get("xstype") == "BSE"
xs_subelements = list(xs_xml)
assert len(xs_subelements) == 5
+ valid_tags = {"screening", "BSE", "energywindow", "qpointset", "plan"}
+ assert valid_tags == set(xs_subelement.tag for xs_subelement in xs_subelements)
- screening_xml = xs_subelements[0]
+ screening_xml = xs_xml.find("screening")
assert screening_xml.tag == "screening"
- assert screening_xml.keys() == ['screentype', 'nempty']
- assert screening_xml.get('screentype') == 'full'
- assert screening_xml.get('nempty') == '15'
+ assert screening_xml.keys() == ["screentype", "nempty"]
+ assert screening_xml.get("screentype") == "full"
+ assert screening_xml.get("nempty") == "15"
- bse_xml = xs_subelements[1]
- assert bse_xml.tag == 'BSE'
- assert bse_xml.keys() == ['bsetype', 'xas']
- assert bse_xml.get('bsetype') == 'singlet'
- assert bse_xml.get('xas') == 'true'
+ bse_xml = xs_xml.find("BSE")
+ assert bse_xml.tag == "BSE"
+ assert bse_xml.keys() == ["bsetype", "xas"]
+ assert bse_xml.get("bsetype") == "singlet"
+ assert bse_xml.get("xas") == "true"
- energywindow_xml = xs_subelements[2]
+ energywindow_xml = xs_xml.find("energywindow")
assert energywindow_xml.tag == "energywindow"
- assert energywindow_xml.keys() == ['intv', 'points']
- assert energywindow_xml.get('intv') == '5.8 8.3'
- assert energywindow_xml.get('points') == '5000'
+ assert energywindow_xml.keys() == ["intv", "points"]
+ assert energywindow_xml.get("intv") == "5.8 8.3"
+ assert energywindow_xml.get("points") == "5000"
- qpointset_xml = xs_subelements[3]
+ qpointset_xml = xs_xml.find("qpointset")
assert qpointset_xml.tag == "qpointset"
assert qpointset_xml.items() == []
qpoints = list(qpointset_xml)
assert len(qpoints) == 2
- assert qpoints[0].tag == 'qpoint'
+ assert qpoints[0].tag == "qpoint"
assert qpoints[0].items() == []
- valid_qpoints = {'0 0 0', '0.5 0.5 0.5'}
+ valid_qpoints = {"0 0 0", "0.5 0.5 0.5"}
assert qpoints[0].text in valid_qpoints
valid_qpoints.discard(qpoints[0].text)
assert qpoints[1].text in valid_qpoints
- plan_xml = xs_subelements[4]
+ plan_xml = xs_xml.find("plan")
assert plan_xml.tag == "plan"
assert plan_xml.items() == []
doonlys = list(plan_xml)
assert len(doonlys) == 2
- assert doonlys[0].tag == 'doonly'
- assert doonlys[0].items() == [('task', 'screen')]
- assert doonlys[1].tag == 'doonly'
- assert doonlys[1].items() == [('task', 'bse')]
+ assert doonlys[0].tag == "doonly"
+ assert doonlys[0].items() == [("task", "screen")]
+ assert doonlys[1].tag == "doonly"
+ assert doonlys[1].items() == [("task", "bse")]
+
+ title_xml = subelements[4]
+ assert title_xml.tag == "keywords"
+ assert title_xml.keys() == []
+ assert title_xml.text == "keyword1 keyword2 keyword3"
+
+
+def test_attribute_modification(exciting_input_xml: ExcitingInputXML):
+ """Test the XML created for a ground state input is valid.
+ Test SubTree composition using only mandatory attributes for each XML subtree.
+ """
+ exciting_input_xml.set_title("New Test Case")
+ exciting_input_xml.structure.crystal_properties.scale = 2.3
+ exciting_input_xml.groundstate.rgkmax = 9.0
+ exciting_input_xml.xs.energywindow.points = 4000
+ input_xml_tree = exciting_input_xml.to_xml()
+
+ subelements = list(input_xml_tree)
+ assert len(subelements) == 5
+
+ title_xml = subelements[0]
+ assert title_xml.tag == "title"
+ assert title_xml.text == "New Test Case"
+
+ structure_xml = subelements[1]
+ assert structure_xml[0].get("scale") == "2.3"
+
+ groundstate_xml = subelements[2]
+ assert groundstate_xml.get("rgkmax") == "9.0"
+
+ xs_xml = subelements[3]
+ xs_subelements = list(xs_xml)
+ assert len(xs_subelements) == 5
+
+ energywindow_xml = xs_xml.find("energywindow")
+ assert energywindow_xml.get("points") == "4000"
+
+
+@pytest.mark.usefixtures("mock_env_jobflow_missing")
+def test_as_dict(exciting_input_xml: ExcitingInputXML):
+ dict_representation = exciting_input_xml.as_dict()
+ assert set(dict_representation.keys()) == {"xml_string"}
+ # check only that the xml string starts with the correct first lines:
+ assert dict_representation["xml_string"].startswith(
+ '\n\n\tTest Case\n\t\n\n\tTest Case\n\t
+
+ BN (B3)
+
+
+ 0. 0.5 0.5
+ 0.5 0. 0.5
+ 0.5 0.5 0.
+
+
+
+
+
+
+
+
+
+
+ """
+ input_xml = ExcitingInputXML.from_xml(input_str)
+ assert input_xml.title.title == "BN (B3)"
+
+ assert input_xml.structure.speciespath == "speciespath"
+ assert input_xml.structure.autormt is True
+ assert input_xml.structure.species == ["B", "N"]
+ np.testing.assert_allclose(input_xml.structure.positions, [[0.0] * 3, [0.25] * 3])
+ np.testing.assert_allclose(input_xml.structure.lattice, np.full((3, 3), 0.5) - 0.5 * np.eye(3))
+ assert input_xml.structure.crystal_properties.scale == pytest.approx(6.816242132875)
+
+ assert input_xml.groundstate.outputlevel == "high"
+ assert input_xml.groundstate.ngridk == [10, 10, 10]
+ assert input_xml.groundstate.rgkmax == pytest.approx(7.0)
+ assert input_xml.groundstate.maxscl == 200
+ assert input_xml.groundstate.do == "fromscratch"
+ assert input_xml.groundstate.xctype == "GGA_PBE"
diff --git a/tests/input/test_properties.py b/tests/input/test_properties.py
new file mode 100644
index 0000000..1893bc2
--- /dev/null
+++ b/tests/input/test_properties.py
@@ -0,0 +1,192 @@
+"""Test properties.
+
+NOTE:
+All attribute tests should assert on the XML tree content's as the attribute
+order is not preserved by the ElementTree.tostring method. Elements appear to
+be fine.
+"""
+
+import re
+
+import pytest
+
+from excitingtools.exciting_dict_parsers.input_parser import parse_element_xml
+from excitingtools.input.bandstructure import (
+ band_structure_input_from_ase_atoms_obj,
+ band_structure_input_from_cell_or_bandpath,
+ get_bandstructure_input_from_exciting_structure,
+)
+from excitingtools.input.input_classes import ExcitingGroundStateInput, ExcitingPropertiesInput
+from excitingtools.input.input_xml import ExcitingInputXML
+from excitingtools.input.structure import ExcitingStructure
+
+
+@pytest.fixture
+def ase_ag():
+ """If we cannot import ase we skip tests with this fixture.
+
+ :returns: bulk Silver crystal
+ """
+ ase_build = pytest.importorskip("ase.build")
+ return ase_build.bulk("Ag")
+
+
+def test_bandstructure_properties_input():
+ """Test giving the bandstructure input as a nested dict."""
+ properties = {
+ "bandstructure": {
+ "plot1d": {
+ "path": {
+ "steps": 100,
+ "point": [
+ {"coord": [1, 0, 0], "label": "Gamma"},
+ {"coord": [0.625, 0.375, 0], "label": "K"},
+ {"coord": [0.5, 0.5, 0], "label": "X", "breakafter": True},
+ {"coord": [0, 0, 0], "label": "Gamma"},
+ {"coord": [0.5, 0, 0], "label": "L"},
+ ],
+ }
+ }
+ }
+ }
+
+ properties_input = ExcitingPropertiesInput(**properties)
+ xml_string = properties_input.to_xml_str()
+
+ assert properties == parse_element_xml(xml_string)
+
+
+def test_bs_from_ase(ase_ag):
+ """Test getting XML bandstructure input for elemental silver.
+
+ We test first that the bandstructure path defined by the high symmetry
+ points is correct. We also check the high symmetry point names are
+ correct.
+ """
+ ase_ag.set_cell(ase_ag.cell * 3)
+ bs = band_structure_input_from_ase_atoms_obj(ase_ag)
+ properties_input = ExcitingPropertiesInput(bandstructure=bs)
+ bs_xml_string = properties_input.to_xml_str()
+ # Get the high symmetry path.
+ all_labels = re.findall(r"label=\"([A-Z])", bs_xml_string)
+ assert all_labels == ["G", "X", "W", "K", "G", "L", "U", "W", "L", "K", "U", "X"]
+ # Ensure there is only one breakafter statement.
+ assert len(re.findall(r"breakafter", bs_xml_string)) == 1
+
+ bs_xml = properties_input.to_xml()
+ points = bs_xml.find("bandstructure").find("plot1d").find("path").findall("point")
+ assert len(points) == 12
+ assert points[0].get("coord") == "0.0 0.0 0.0"
+ assert points[1].get("coord") == "0.5 0.0 0.5"
+ assert points[2].get("coord") == "0.5 0.25 0.75"
+ assert points[3].get("coord") == "0.375 0.375 0.75"
+ assert points[4].get("coord") == "0.0 0.0 0.0"
+ assert points[5].get("coord") == "0.5 0.5 0.5"
+ assert points[6].get("coord") == "0.625 0.25 0.625"
+ assert points[7].get("coord") == "0.5 0.25 0.75"
+ assert points[8].get("coord") == "0.5 0.5 0.5"
+ assert points[9].get("coord") == "0.375 0.375 0.75"
+ assert points[9].get("breakafter") == "true"
+ assert points[10].get("coord") == "0.625 0.25 0.625"
+ assert points[11].get("coord") == "0.5 0.0 0.5"
+
+
+def test_bs_input(ase_ag):
+ """Test writing exciting full input xml file with bandstructure property."""
+ gs = ExcitingGroundStateInput(rgkmax=5.0)
+ struct = ExcitingStructure(ase_ag)
+ bs = band_structure_input_from_ase_atoms_obj(ase_ag)
+ properties_input = ExcitingPropertiesInput(bandstructure=bs)
+ input_xml = ExcitingInputXML(
+ structure=struct, groundstate=gs, title="BS exciting", properties=properties_input
+ ).to_xml()
+
+ assert input_xml.tag == "input"
+ assert input_xml.keys() == []
+
+ subelements = list(input_xml)
+ assert len(subelements) == 4
+ title_xml = subelements[0]
+ assert title_xml.tag == "title"
+ assert title_xml.keys() == []
+ assert title_xml.text == "BS exciting"
+
+ properties_xml = subelements[3]
+ assert properties_xml.tag == "properties"
+ assert properties_xml.keys() == []
+ assert properties_xml.text == " "
+ assert properties_xml[0].tag == "bandstructure"
+ assert properties_xml[0][0].tag == "plot1d"
+ assert properties_xml[0][0][0].tag == "path"
+
+ first_coordinates = properties_xml[0][0][0][0].get("coord")
+ first_label = properties_xml[0][0][0][0].get("label")
+ assert first_coordinates == "0.0 0.0 0.0"
+ assert first_label == "G"
+
+
+def test_get_bandstructure_input_from_exciting_structure(ase_ag):
+ structure = ExcitingStructure(ase_ag)
+ bs_xml = get_bandstructure_input_from_exciting_structure(structure).to_xml()
+ points = bs_xml.find("plot1d").find("path").findall("point")
+ assert len(points) == 12
+ assert points[0].get("coord") == "0.0 0.0 0.0"
+ assert points[1].get("coord") == "0.5 0.0 0.5"
+
+
+def test_get_bandstructure_input_from_exciting_structure_stretch(ase_ag):
+ structure = ExcitingStructure(ase_ag)
+ structure.crystal_properties.stretch = [2, 1, 1]
+ bs_xml = get_bandstructure_input_from_exciting_structure(structure).to_xml()
+ points = bs_xml.find("plot1d").find("path").findall("point")
+ assert len(points) == 13
+ assert points[0].get("coord") == "0.0 0.0 0.0"
+ assert points[1].get("coord") == "0.5 0.0 0.5"
+ assert points[2].get("coord") == "0.125 -0.375 0.3125"
+
+
+def test_get_bandstructure_input_from_ase_bandpath(ase_ag):
+ """Test creating required bandstructure input from an ASE bandpath.
+
+ Someone could create a bandpath that doesn't fall on special points
+ e.g., 'Kpt0Kpt1,GMX,XZ'.
+ """
+ ase = pytest.importorskip("ase")
+ bandpath = ase.dft.kpoints.bandpath(cell=ase_ag.cell, path=[(0, 0, 0), (0.1, 0.2, 0.1), (0, 0, 0)])
+ bandstructure = band_structure_input_from_cell_or_bandpath(bandpath)
+ bs_xml = bandstructure.to_xml()
+ assert bs_xml.tag == "bandstructure", 'Root tag should be "bandstructure"'
+ plot1d_xml = bs_xml.find("plot1d")
+ assert plot1d_xml is not None, 'Missing "plot1d" subtree'
+
+ path_xml = plot1d_xml.find("path")
+ assert path_xml is not None, 'Missing "path" subtree'
+ assert path_xml.get("steps") == "100", 'Invalid value for "steps" attribute'
+
+ assert len(list(path_xml)) == 3
+ point1 = path_xml[0]
+ assert point1.get("coord") == "0.0 0.0 0.0", 'Invalid value for "coord" attribute of point 1'
+ assert point1.get("label") == "G", 'Invalid value for "label" attribute of point 1'
+
+ point2 = path_xml[1]
+ assert point2.get("coord") == "0.1 0.2 0.1", 'Invalid value for "coord" attribute of point 2'
+ assert point2.get("label") == "Kpt0", 'Invalid value for "label" attribute of point 2'
+
+ point3 = path_xml[2]
+ assert point3.get("coord") == "0.0 0.0 0.0", 'Invalid value for "coord" attribute of point 3'
+ assert point3.get("label") == "G", 'Invalid value for "label" attribute of point 3'
+
+
+def test_class_ExcitingKstlistInput():
+ properties = {"wfplot": {"kstlist": [[1, 4], [2, 5]]}}
+ properties_input = ExcitingPropertiesInput(**properties)
+ properties_tree = properties_input.to_xml()
+ wfplot = properties_tree.find("wfplot")
+ kstlist = wfplot.find("kstlist")
+
+ pointstatepair = list(kstlist)
+ assert len(pointstatepair) == 2
+ assert pointstatepair[0].tag == "pointstatepair"
+ assert pointstatepair[0].items() == []
+ assert pointstatepair[0].text == "1 4"
+ assert pointstatepair[1].text == "2 5"
diff --git a/tests/input/test_structure.py b/tests/input/test_structure.py
index a97fc1b..1155b76 100644
--- a/tests/input/test_structure.py
+++ b/tests/input/test_structure.py
@@ -1,7 +1,7 @@
"""Test ExcitingStructure, python API that generates exciting's structure XML.
NOTE:
-All attribute tests should assert on the XML tree content,s as the attribute
+All attribute tests should assert on the XML tree content, as the attribute
order is not preserved by the ElementTree.tostring method. Elements appear to
be fine.
@@ -17,15 +17,11 @@
' '
"""
-import sys
-import pytest
-import numpy as np
-try:
- import ase
-except ImportError:
- pass
+import numpy as np
+import pytest
+from excitingtools.input.bandstructure import get_bandstructure_input_from_exciting_structure
from excitingtools.input.structure import ExcitingStructure
@@ -35,10 +31,12 @@ def xml_structure_H2He():
structure object initialised with a mock crystal, using mandatory arguments only.
"""
cubic_lattice = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
- arbitrary_atoms = [{'species': 'H', 'position': [0, 0, 0]},
- {'species': 'H', 'position': [1, 0, 0]},
- {'species': 'He', 'position': [2, 0, 0]}]
- structure = ExcitingStructure(arbitrary_atoms, cubic_lattice, './')
+ arbitrary_atoms = [
+ {"species": "H", "position": [0, 0, 0]},
+ {"species": "H", "position": [1, 0, 0]},
+ {"species": "He", "position": [2, 0, 0]},
+ ]
+ structure = ExcitingStructure(arbitrary_atoms, cubic_lattice, "./")
return structure.to_xml()
@@ -46,9 +44,9 @@ def test_class_exciting_structure_xml(xml_structure_H2He):
"""
Test input XML attributes from an instance of ExcitingStructure.
"""
- assert xml_structure_H2He.tag == 'structure', 'XML root should be structure'
- assert xml_structure_H2He.keys() == ['speciespath'], 'structure defined to have only speciespath '
- assert xml_structure_H2He.get('speciespath') == './', 'species path set to ./'
+ assert xml_structure_H2He.tag == "structure", "XML root should be structure"
+ assert xml_structure_H2He.keys() == ["speciespath"], "structure defined to have only speciespath "
+ assert xml_structure_H2He.get("speciespath") == "./", "species path set to ./"
def test_class_exciting_structure_crystal_xml(xml_structure_H2He):
@@ -56,15 +54,15 @@ def test_class_exciting_structure_crystal_xml(xml_structure_H2He):
crystal subtree of structure.
"""
elements = list(xml_structure_H2He)
- assert len(elements) == 3, 'Expect structure tree to have 3 sub-elements'
+ assert len(elements) == 3, "Expect structure tree to have 3 sub-elements"
crystal_xml = elements[0]
- assert crystal_xml.tag == "crystal", 'First subtree is crystal'
+ assert crystal_xml.tag == "crystal", "First subtree is crystal"
assert crystal_xml.items() == [], "No required attributes in crystal."
lattice_vectors = list(crystal_xml)
assert len(lattice_vectors) == 3, "Always expect three lattice vectors"
- assert lattice_vectors[0].items() == [], 'Lattice vectors have no items'
+ assert lattice_vectors[0].items() == [], "Lattice vectors have no items"
assert lattice_vectors[0].text == "1.0 0.0 0.0", "Lattice vector `a` differs from input"
assert lattice_vectors[1].text == "0.0 1.0 0.0", "Lattice vector `b` differs from input"
assert lattice_vectors[2].text == "0.0 0.0 1.0", "Lattice vector `c` differs from input"
@@ -75,25 +73,25 @@ def test_class_exciting_structure_species_xml(xml_structure_H2He):
species subtrees of structure.
"""
elements = list(xml_structure_H2He)
- assert len(elements) == 3, 'Expect structure tree to have 3 sub-elements'
+ assert len(elements) == 3, "Expect structure tree to have 3 sub-elements"
species_h_xml = elements[1]
- assert species_h_xml.tag == "species", 'Second subtree is species'
+ assert species_h_xml.tag == "species", "Second subtree is species"
species_he_xml = elements[2]
- assert species_he_xml.tag == "species", 'Third subtree is species'
+ assert species_he_xml.tag == "species", "Third subtree is species"
- assert species_h_xml.items() == [('speciesfile', 'H.xml')], 'species is inconsistent'
- assert species_he_xml.items() == [('speciesfile', 'He.xml')], 'species is inconsistent'
+ assert species_h_xml.items() == [("speciesfile", "H.xml")], "species is inconsistent"
+ assert species_he_xml.items() == [("speciesfile", "He.xml")], "species is inconsistent"
atoms_h = list(species_h_xml)
- assert len(atoms_h) == 2, 'Number of H atoms is wrong'
- assert atoms_h[0].items() == [('coord', '0 0 0')], "Coordinate of first H differs to input"
- assert atoms_h[1].items() == [('coord', '1 0 0')], "Coordinate of second H differs to input"
+ assert len(atoms_h) == 2, "Number of H atoms is wrong"
+ assert atoms_h[0].items() == [("coord", "0 0 0")], "Coordinate of first H differs to input"
+ assert atoms_h[1].items() == [("coord", "1 0 0")], "Coordinate of second H differs to input"
atoms_he = list(species_he_xml)
- assert len(atoms_he) == 1, 'Number of He atoms is wrong'
- assert atoms_he[0].items() == [('coord', '2 0 0')], "Coordinate of only He differs to input"
+ assert len(atoms_he) == 1, "Number of He atoms is wrong"
+ assert atoms_he[0].items() == [("coord", "2 0 0")], "Coordinate of only He differs to input"
@pytest.fixture
@@ -103,14 +101,17 @@ def xml_structure_CdS():
Optional atom attributes require the generic container, List[dict].
"""
cubic_lattice = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
- arbitrary_atoms = [{
- 'species': 'Cd',
- 'position': [0.0, 0.0, 0.0],
- 'bfcmt': [1.0, 1.0, 1.0],
- 'lockxyz': [False, False, False],
- 'mommtfix': [2.0, 2.0, 2.0]
- }, {'species': 'S', 'position': [1.0, 0.0, 0.0]}]
- structure = ExcitingStructure(arbitrary_atoms, cubic_lattice, './')
+ arbitrary_atoms = [
+ {
+ "species": "Cd",
+ "position": [0.0, 0.0, 0.0],
+ "bfcmt": [1.0, 1.0, 1.0],
+ "lockxyz": [False, False, False],
+ "mommtfix": [2.0, 2.0, 2.0],
+ },
+ {"species": "S", "position": [1.0, 0.0, 0.0]},
+ ]
+ structure = ExcitingStructure(arbitrary_atoms, cubic_lattice, "./")
return structure.to_xml()
@@ -118,43 +119,46 @@ def test_optional_atom_attributes_xml(xml_structure_CdS):
"""
Test optional atom attributes are correctly set in XML tree.
"""
- assert xml_structure_CdS.tag == 'structure'
- assert xml_structure_CdS.keys() == ['speciespath'], 'structure defined to have only speciespath '
- assert xml_structure_CdS.get('speciespath') == './', 'speciespath set to be ./'
+ assert xml_structure_CdS.tag == "structure"
+ assert xml_structure_CdS.keys() == ["speciespath"], "structure defined to have only speciespath "
+ assert xml_structure_CdS.get("speciespath") == "./", "speciespath set to be ./"
elements = list(xml_structure_CdS)
- assert len(elements) == 3, 'Expect structure tree to have 3 sub-elements'
+ assert len(elements) == 3, "Expect structure tree to have 3 sub-elements"
# Crystal
crystal_xml = elements[0]
- assert crystal_xml.tag == "crystal", 'First subtree is crystal'
+ assert crystal_xml.tag == "crystal", "First subtree is crystal"
assert crystal_xml.items() == [], "No required attributes in crystal."
# Species
species_cd_xml = elements[1]
- assert species_cd_xml.tag == "species", 'Second subtree is species'
- assert species_cd_xml.items() == [('speciesfile', 'Cd.xml')]
+ assert species_cd_xml.tag == "species", "Second subtree is species"
+ assert species_cd_xml.items() == [("speciesfile", "Cd.xml")]
species_s_xml = elements[2]
- assert species_s_xml.tag == "species", 'Third subtree is species'
- assert species_s_xml.items() == [('speciesfile', 'S.xml')]
+ assert species_s_xml.tag == "species", "Third subtree is species"
+ assert species_s_xml.items() == [("speciesfile", "S.xml")]
# Cd
atoms_cd = list(species_cd_xml)
- assert len(atoms_cd) == 1, 'Wrong number of Cd atoms'
- assert set(atoms_cd[0].keys()) == {'coord', 'bfcmt', 'mommtfix', 'lockxyz'}, \
- 'Cd contains all mandatory and optional atom properties'
- assert ('coord', '0.0 0.0 0.0') in atoms_cd[0].items()
- assert ('bfcmt', '1.0 1.0 1.0') in atoms_cd[0].items()
- assert ('mommtfix', '2.0 2.0 2.0') in atoms_cd[0].items()
- assert ('lockxyz', 'false false false') in atoms_cd[0].items()
+ assert len(atoms_cd) == 1, "Wrong number of Cd atoms"
+ assert set(atoms_cd[0].keys()) == {
+ "coord",
+ "bfcmt",
+ "mommtfix",
+ "lockxyz",
+ }, "Cd contains all mandatory and optional atom properties"
+ assert ("coord", "0.0 0.0 0.0") in atoms_cd[0].items()
+ assert ("bfcmt", "1.0 1.0 1.0") in atoms_cd[0].items()
+ assert ("mommtfix", "2.0 2.0 2.0") in atoms_cd[0].items()
+ assert ("lockxyz", "false false false") in atoms_cd[0].items()
# S
atoms_s = list(species_s_xml)
- assert len(atoms_s) == 1, 'Wrong number of S atoms'
- assert atoms_s[0].keys() == ['coord'], \
- 'S only contains mandatory atom properties'
- assert atoms_s[0].items() == [('coord', '1.0 0.0 0.0')]
+ assert len(atoms_s) == 1, "Wrong number of S atoms"
+ assert atoms_s[0].keys() == ["coord"], "S only contains mandatory atom properties"
+ assert atoms_s[0].items() == [("coord", "1.0 0.0 0.0")]
@pytest.fixture
@@ -163,8 +167,7 @@ def lattice_and_atoms_CdS():
structure object initialised with a mock crystal
"""
cubic_lattice = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
- arbitrary_atoms = [{'species': 'Cd', 'position': [0.0, 0.0, 0.0]},
- {'species': 'S', 'position': [1.0, 0.0, 0.0]}]
+ arbitrary_atoms = [{"species": "Cd", "position": [0.0, 0.0, 0.0]}, {"species": "S", "position": [1.0, 0.0, 0.0]}]
return cubic_lattice, arbitrary_atoms
@@ -173,26 +176,23 @@ def test_optional_structure_attributes_xml(lattice_and_atoms_CdS):
Test optional structure attributes.
"""
cubic_lattice, arbitrary_atoms = lattice_and_atoms_CdS
- structure_attributes = {
- 'autormt': True, 'cartesian': False, 'epslat': 1.e-6, 'primcell': False, 'tshift': True
- }
- structure = ExcitingStructure(
- arbitrary_atoms, cubic_lattice, './', structure_properties=structure_attributes
- )
+ structure_attributes = {"autormt": True, "cartesian": False, "epslat": 1.0e-6, "primcell": False, "tshift": True}
+ structure = ExcitingStructure(arbitrary_atoms, cubic_lattice, "./", **structure_attributes)
xml_structure = structure.to_xml()
- mandatory = ['speciespath']
- optional = list(structure_attributes)
+ mandatory = {"speciespath"}
+ optional = set(structure_attributes)
- assert xml_structure.tag == 'structure'
- assert xml_structure.keys() == mandatory + optional, \
- 'Should contain mandatory speciespath plus all optional attributes'
- assert xml_structure.get('speciespath') == './', 'species path should be ./'
- assert xml_structure.get('autormt') == 'true'
- assert xml_structure.get('cartesian') == 'false'
- assert xml_structure.get('epslat') == '1e-06'
- assert xml_structure.get('primcell') == 'false'
- assert xml_structure.get('tshift') == 'true'
+ assert xml_structure.tag == "structure"
+ assert (
+ set(xml_structure.keys()) == mandatory | optional
+ ), "Should contain mandatory speciespath plus all optional attributes"
+ assert xml_structure.get("speciespath") == "./", "species path should be ./"
+ assert xml_structure.get("autormt") == "true"
+ assert xml_structure.get("cartesian") == "false"
+ assert xml_structure.get("epslat") == "1e-06"
+ assert xml_structure.get("primcell") == "false"
+ assert xml_structure.get("tshift") == "true"
def test_optional_crystal_attributes_xml(lattice_and_atoms_CdS):
@@ -202,21 +202,18 @@ def test_optional_crystal_attributes_xml(lattice_and_atoms_CdS):
cubic_lattice, arbitrary_atoms = lattice_and_atoms_CdS
structure = ExcitingStructure(
- arbitrary_atoms,
- cubic_lattice,
- './',
- crystal_properties={'scale': 1.00, 'stretch': 1.00}
- )
+ arbitrary_atoms, cubic_lattice, "./", crystal_properties={"scale": 1.00, "stretch": [1.00, 1.00, 1.00]}
+ )
xml_structure = structure.to_xml()
elements = list(xml_structure)
- assert len(elements) == 3, 'Number of sub-elements in structure tree'
+ assert len(elements) == 3, "Number of sub-elements in structure tree"
crystal_xml = elements[0]
- assert crystal_xml.tag == "crystal", 'First subtree is crystal'
- assert crystal_xml.keys() == ['scale', 'stretch'], 'Optional crystal properties'
- assert crystal_xml.get('scale') == '1.0', 'scale value inconsistent with input'
- assert crystal_xml.get('stretch') == '1.0', 'stretch value inconsistent with input'
+ assert crystal_xml.tag == "crystal", "First subtree is crystal"
+ assert crystal_xml.keys() == ["scale", "stretch"], "Optional crystal properties"
+ assert crystal_xml.get("scale") == "1.0", "scale value inconsistent with input"
+ assert crystal_xml.get("stretch") == "1.0 1.0 1.0", "stretch value inconsistent with input"
def test_optional_species_attributes_xml(lattice_and_atoms_CdS):
@@ -224,29 +221,138 @@ def test_optional_species_attributes_xml(lattice_and_atoms_CdS):
Test optional species attributes.
"""
cubic_lattice, arbitrary_atoms = lattice_and_atoms_CdS
- species_attributes = {'Cd': {'rmt': 3.0}, 'S': {'rmt': 4.0}}
-
- structure = ExcitingStructure(
- arbitrary_atoms, cubic_lattice, './', species_properties=species_attributes
- )
+ species_attributes = {
+ "Cd": {"rmt": 3.0, "LDAplusU": {"J": 1.5, "U": 2.4, "l": 2}},
+ "S": {
+ "rmt": 4.0,
+ "dfthalfparam": {"ampl": 1.2, "cut": 1.9, "exponent": 5, "shell": [{"ionization": 0.8, "number": 1}]},
+ },
+ }
+
+ structure = ExcitingStructure(arbitrary_atoms, cubic_lattice, "./", species_properties=species_attributes)
xml_structure = structure.to_xml()
elements = list(xml_structure)
- assert len(elements) == 3, 'Number of sub-elements in structure tree'
+ assert len(elements) == 3, "Number of sub-elements in structure tree"
species_cd_xml = elements[1]
- assert species_cd_xml.tag == "species", 'Second subtree is species'
+ assert species_cd_xml.tag == "species", "Second subtree is species"
species_s_xml = elements[2]
- assert species_s_xml.tag == "species", 'Third subtree is species'
+ assert species_s_xml.tag == "species", "Third subtree is species"
+
+ assert set(species_cd_xml.keys()) == {"speciesfile", "rmt"}, "species attributes differ from expected"
+ assert species_cd_xml.get("speciesfile") == "Cd.xml", "speciesfile differs from expected"
+ assert species_cd_xml.get("rmt") == "3.0", "Cd muffin tin radius differs from input"
+
+ species_cd_elements = list(species_cd_xml)
+ assert len(species_cd_elements) == 2
+ ldaplusu_xml = species_cd_elements[0]
+ assert ldaplusu_xml.tag == "LDAplusU"
+
+ assert set(ldaplusu_xml.keys()) == {"J", "U", "l"}
+ assert ldaplusu_xml.get("J") == "1.5"
+ assert ldaplusu_xml.get("U") == "2.4"
+ assert ldaplusu_xml.get("l") == "2"
+
+ assert set(species_s_xml.keys()) == {"speciesfile", "rmt"}, "species attributes differ from expected"
+ assert species_s_xml.get("speciesfile") == "S.xml", "speciesfile differs from expected"
+ assert species_s_xml.get("rmt") == "4.0", "S muffin tin radius differs from input"
+
+ species_s_elements = list(species_s_xml)
+ assert len(species_s_elements) == 2
+ dfthalfparam_xml = species_s_elements[0]
+ assert dfthalfparam_xml.tag == "dfthalfparam"
+
+ assert set(dfthalfparam_xml.keys()) == {"ampl", "cut", "exponent"}
+ assert dfthalfparam_xml.get("ampl") == "1.2"
+ assert dfthalfparam_xml.get("cut") == "1.9"
+ assert dfthalfparam_xml.get("exponent") == "5"
+
+ dfthalfparam_elements = list(dfthalfparam_xml)
+ assert len(dfthalfparam_elements) == 1
+ shell_xml = dfthalfparam_elements[0]
+ assert shell_xml.tag == "shell"
+
+ assert set(shell_xml.keys()) == {"ionization", "number"}
+ assert shell_xml.get("ionization") == "0.8"
+ assert shell_xml.get("number") == "1"
+
- assert species_cd_xml.keys() == ['speciesfile', 'rmt'], "species attributes differ from expected"
- assert species_cd_xml.get('speciesfile') == 'Cd.xml', 'speciesfile differs from expected'
- assert species_cd_xml.get('rmt') == '3.0', 'Cd muffin tin radius differs from input'
+ref_dict = {
+ "xml_string": ' 1.0 0.0 0.0'
+ "0.0 1.0 0.00.0 0.0 1.0"
+ ' '
+ ' '
+}
- assert species_s_xml.keys() == ['speciesfile', 'rmt'], "species attributes differ from expected"
- assert species_s_xml.get('speciesfile') == 'S.xml', 'speciesfile differs from expected'
- assert species_s_xml.get('rmt') == '4.0', 'S muffin tin radius differs from input'
+
+@pytest.mark.usefixtures("mock_env_jobflow_missing")
+def test_as_dict(lattice_and_atoms_CdS):
+ cubic_lattice, arbitrary_atoms = lattice_and_atoms_CdS
+
+ structure = ExcitingStructure(arbitrary_atoms, cubic_lattice, "./")
+
+ assert structure.as_dict() == ref_dict, "expected different dict representation"
+
+
+@pytest.mark.usefixtures("mock_env_jobflow")
+def test_as_dict_jobflow(lattice_and_atoms_CdS):
+ cubic_lattice, arbitrary_atoms = lattice_and_atoms_CdS
+
+ structure = ExcitingStructure(arbitrary_atoms, cubic_lattice, "./")
+
+ assert structure.as_dict() == {
+ **ref_dict,
+ "@class": "ExcitingStructure",
+ "@module": "excitingtools.input.structure",
+ }, "expected different dict representation"
+
+
+def test_from_dict(lattice_and_atoms_CdS):
+ cubic_lattice, arbitrary_atoms = lattice_and_atoms_CdS
+ structure = ExcitingStructure.from_dict(ref_dict)
+
+ assert np.allclose(structure.lattice, np.array(cubic_lattice))
+ assert structure.species == [d["species"] for d in arbitrary_atoms]
+ assert structure.positions == [d["position"] for d in arbitrary_atoms]
+ assert structure.speciespath == "./" # pylint: disable=no-member
+
+
+def test_add_and_remove_atoms(lattice_and_atoms_CdS):
+ cubic_lattice, arbitrary_atoms = lattice_and_atoms_CdS
+ structure = ExcitingStructure(arbitrary_atoms, cubic_lattice, "./")
+
+ assert len(structure.species) == 2, "initially there are 2 atoms in the structure"
+ # just confirm that the xml tree can be built, not that it is fully correct
+ structure.to_xml()
+
+ structure.add_atom("Cd", [0.25, 0.25, 0.25], {"bfcmt": [1.0, 1.0, 1.0]})
+ xml_tree = structure.to_xml()
+ assert xml_tree.findall("species")[0].findall("atom")[1].attrib == {
+ "coord": "0.25 0.25 0.25",
+ "bfcmt": "1.0 1.0 1.0",
+ }
+
+ structure.add_atom("Mg", [0.75, 0.25, 0.0], species_properties={"rmt": 3})
+ xml_tree = structure.to_xml()
+ mg_tree = xml_tree.findall("species")[1]
+ assert mg_tree.attrib == {"rmt": "3", "speciesfile": "Mg.xml"}
+ mg_atom = mg_tree.findall("atom")[0]
+ assert mg_atom.attrib == {"coord": "0.75 0.25 0.0"}
+
+ structure.remove_atom(0)
+ xml_tree = structure.to_xml()
+ atoms = xml_tree.findall("species")[0].findall("atom")
+ assert len(atoms) == 1
+ assert atoms[0].attrib == {"coord": "0.25 0.25 0.25", "bfcmt": "1.0 1.0 1.0"}
+
+ structure.remove_atom(-1)
+ xml_tree = structure.to_xml()
+ species_trees = xml_tree.findall("species")
+ assert len(species_trees) == 2
+ assert species_trees[0].get("speciesfile") == "Cd.xml"
+ assert species_trees[1].get("speciesfile") == "S.xml"
@pytest.fixture
@@ -255,9 +361,11 @@ def lattice_and_atoms_H20():
H20 molecule in a big box (angstrom)
"""
cubic_lattice = [[10.0, 0.0, 0.0], [0.0, 10.0, 0.0], [0.0, 0.0, 10.0]]
- atoms = [{'species': 'H', 'position': [0.00000, 0.75545, -0.47116]},
- {'species': 'O', 'position': [0.00000, 0.00000, 0.11779]},
- {'species': 'H', 'position': [0.00000, 0.75545, -0.47116]}]
+ atoms = [
+ {"species": "H", "position": [0.00000, 0.75545, -0.47116]},
+ {"species": "O", "position": [0.00000, 0.00000, 0.11779]},
+ {"species": "H", "position": [0.00000, 0.75545, -0.47116]},
+ ]
return cubic_lattice, atoms
@@ -266,16 +374,16 @@ def test_group_atoms_by_species(lattice_and_atoms_H20):
Test group_atoms_by_species method.
"""
cubic_lattice, atoms = lattice_and_atoms_H20
- structure = ExcitingStructure(atoms, cubic_lattice, './')
- assert structure.species == ['H', 'O', 'H'], 'Species list differs from lattice_and_atoms_H20'
+ structure = ExcitingStructure(atoms, cubic_lattice, "./")
+ assert structure.species == ["H", "O", "H"], "Species list differs from lattice_and_atoms_H20"
- indices = structure._group_atoms_by_species()
- assert set(indices.keys()) == {'H', 'O'}, 'List unique species in structure'
- assert indices['H'] == [0, 2], "Expect atoms 0 and 2 to be H"
- assert indices['O'] == [1], "Expect atom 1 to be O"
+ indices = dict(structure._group_atoms_by_species())
+ assert set(indices.keys()) == {"H", "O"}, "List unique species in structure"
+ assert indices["H"] == [0, 2], "Expect atoms 0 and 2 to be H"
+ assert indices["O"] == [1], "Expect atom 1 to be O"
- hydrogen = [structure.species[i] for i in indices['H']]
- oxygen = [structure.species[i] for i in indices['O']]
+ hydrogen = [structure.species[i] for i in indices["H"]]
+ oxygen = [structure.species[i] for i in indices["O"]]
assert hydrogen == ["H", "H"], "Expect list to only contain H symbols"
assert oxygen == ["O"], "Expect list to contain only one O symbol"
@@ -286,35 +394,120 @@ def ase_atoms_H20(lattice_and_atoms_H20):
H20 molecule in a big box (angstrom), in ASE Atoms()
Converts a List[dict] to ase.atoms.Atoms.
"""
+ ase = pytest.importorskip("ase")
lattice, atoms = lattice_and_atoms_H20
- symbols = [atom['species'] for atom in atoms]
+ symbols = [atom["species"] for atom in atoms]
cubic_cell = np.asarray(lattice)
- positions = [atom['position'] for atom in atoms]
- if "ase" in sys.modules:
- return ase.atoms.Atoms(symbols=symbols, positions=positions, cell=cubic_cell)
- # TODO(Alex) Issue 117. Not sure of the best way to handle if ase is not present
- return []
+ positions = [atom["position"] for atom in atoms]
+ return ase.Atoms(symbols=symbols, positions=positions, cell=cubic_cell, pbc=True)
def test_class_exciting_structure_ase(ase_atoms_H20):
"""
Test the ASE Atoms object gets used correctly by the ExcitingStructure constructor.
"""
- if "ase" not in sys.modules:
- # ASE not available, so do not run
- return
-
atoms = ase_atoms_H20
- structure = ExcitingStructure(atoms, species_path='./')
+ structure = ExcitingStructure(atoms, species_path="./")
assert structure.species == ["H", "O", "H"]
- assert np.allclose(structure.lattice,
- [[10.0, 0.0, 0.0], [0.0, 10.0, 0.0], [0.0, 0.0, 10.0]]), \
- 'Expect lattice vectors to match input values'
+ assert np.allclose(
+ structure.lattice,
+ [[18.897261246257703, 0.0, 0.0], [0.0, 18.897261246257703, 0.0], [0.0, 0.0, 18.897261246257703]],
+ ), "Expect lattice vectors to match input values"
- assert np.allclose(structure.positions, atoms.positions), 'Expect positions to match input values.'
+ assert np.allclose(structure.positions, atoms.get_scaled_positions()), "Expect positions to match input values."
# TODO(Alex) Issue 117. Compare xml_structure built with and without ASE - should be consistent
# This just confirms the XML tree is built, not that it is correct.
xml_structure = structure.to_xml()
- assert list(xml_structure.keys()) == ['speciespath'], 'Only expect speciespath in structure xml keys'
+ assert list(xml_structure.keys()) == ["speciespath"], "Only expect speciespath in structure xml keys"
+
+
+def test_using_non_standard_species_symbol():
+ cubic_lattice = [[10.0, 0.0, 0.0], [0.0, 10.0, 0.0], [0.0, 0.0, 10.0]]
+ atoms = [{"species": "C_molecule", "position": [0.00000, 0.75545, -0.47116]}]
+ structure = ExcitingStructure(atoms, cubic_lattice)
+
+ structure_xml = structure.to_xml()
+ assert structure_xml.tag == "structure", "XML root should be structure"
+ assert structure_xml.keys() == ["speciespath"], "structure defined to have only speciespath "
+ assert structure_xml.get("speciespath") == "./", "species path set to ./"
+
+ elements = list(structure_xml)
+ assert len(elements) == 2, "Expect structure tree to have 2 sub-elements"
+
+ species_c_xml = elements[1]
+ assert species_c_xml.tag == "species", "Second subtree is species"
+
+ assert species_c_xml.items() == [("speciesfile", "C_molecule.xml")], "species is inconsistent"
+
+ atoms_h = list(species_c_xml)
+ assert len(atoms_h) == 1
+ assert atoms_h[0].items() == [("coord", "0.0 0.75545 -0.47116")]
+
+
+def test_get_full_lattice(lattice_and_atoms_CdS):
+ cubic_lattice, arbitrary_atoms = lattice_and_atoms_CdS
+ structure = ExcitingStructure(
+ arbitrary_atoms, cubic_lattice, "./", crystal_properties={"scale": 1.50, "stretch": [2.00, 1.00, 3.00]}
+ )
+ ref_lattice = np.array([[3, 0, 0], [0, 1.5, 0], [0, 0, 4.5]])
+ assert np.allclose(structure.get_lattice(), ref_lattice)
+
+
+def test_structure_input_with_integers():
+ atoms = [{"species": "C", "position": [0, 0, 0]}]
+ structure = ExcitingStructure(atoms, [[1, 0, 0], [0, 1, 0], [0, 0, 1]], "./")
+ ref_lattice = np.array([[0.52917721, 0.0, 0.0], [0.0, 0.52917721, 0.0], [0.0, 0.0, 0.52917721]])
+ assert np.allclose(structure.get_lattice(convert_to_angstrom=True), ref_lattice)
+
+
+def test_get_bandstructure_input_from_exciting_structure(lattice_and_atoms_H20):
+ pytest.importorskip("ase")
+ cubic_lattice, atoms = lattice_and_atoms_H20
+ structure = ExcitingStructure(atoms, cubic_lattice, "./")
+ bandstructure = get_bandstructure_input_from_exciting_structure(structure)
+ bs_xml = bandstructure.to_xml()
+
+ assert bs_xml.tag == "bandstructure", 'Root tag should be "bandstructure"'
+
+ plot1d_xml = bs_xml.find("plot1d")
+ assert plot1d_xml is not None, 'Missing "plot1d" subtree'
+
+ path_xml = plot1d_xml.find("path")
+ assert path_xml is not None, 'Missing "path" subtree'
+ assert path_xml.get("steps") == "100", 'Invalid value for "steps" attribute'
+
+ assert len(list(path_xml)) == 8
+ point1 = path_xml[0]
+ assert point1.get("coord") == "0.0 0.0 0.0", 'Invalid value for "coord" attribute of point 1'
+ assert point1.get("label") == "G", 'Invalid value for "label" attribute of point 1'
+
+ point2 = path_xml[1]
+ assert point2.get("coord") == "0.0 0.5 0.0", 'Invalid value for "coord" attribute of point 2'
+ assert point2.get("label") == "X", 'Invalid value for "label" attribute of point 2'
+
+ point3 = path_xml[2]
+ assert point3.get("coord") == "0.5 0.5 0.0", 'Invalid value for "coord" attribute of point 3'
+ assert point3.get("label") == "M", 'Invalid value for "label" attribute of point 3'
+
+ point4 = path_xml[3]
+ assert point4.get("coord") == "0.0 0.0 0.0", 'Invalid value for "coord" attribute of point 4'
+ assert point4.get("label") == "G", 'Invalid value for "label" attribute of point 4'
+
+ point5 = path_xml[4]
+ assert point5.get("coord") == "0.5 0.5 0.5", 'Invalid value for "coord" attribute of point 5'
+ assert point5.get("label") == "R", 'Invalid value for "label" attribute of point 5'
+
+ point6 = path_xml[5]
+ assert point6.get("coord") == "0.0 0.5 0.0", 'Invalid value for "coord" attribute of point 6'
+ assert point6.get("label") == "X", 'Invalid value for "label" attribute of point 6'
+ assert point6.get("breakafter") == "true", 'Invalid value for "breakafter" attribute of point 6'
+
+ point7 = path_xml[6]
+ assert point7.get("coord") == "0.5 0.5 0.0", 'Invalid value for "coord" attribute of point 7'
+ assert point7.get("label") == "M", 'Invalid value for "label" attribute of point 7'
+
+ point8 = path_xml[7]
+ assert point8.get("coord") == "0.5 0.5 0.5", 'Invalid value for "coord" attribute of point 8'
+ assert point8.get("label") == "R", 'Invalid value for "label" attribute of point 8'
diff --git a/tests/input/test_xml_utils.py b/tests/input/test_xml_utils.py
index c262684..bc13974 100644
--- a/tests/input/test_xml_utils.py
+++ b/tests/input/test_xml_utils.py
@@ -1,9 +1,6 @@
-"""Test XML utilities.
-"""
-import pytest
+"""Test XML utilities."""
-from excitingtools.input.xml_utils import line_reformatter, \
- prettify_tag_attributes
+from excitingtools.input.xml_utils import line_reformatter
def test_line_reformatter_long_ending():
@@ -11,10 +8,10 @@ def test_line_reformatter_long_ending():
input_str = (
' '
- ''
+ "