def get_aiida_structure_data( optimade_structure: OptimadeStructure) -> StructureData: """Get AiiDA `StructureData` from OPTIMADE structure. Parameters: optimade_structure: OPTIMADE structure. Returns: AiiDA `StructureData` Node. """ if "optimade.adapters" in repr(globals().get("StructureData")): warn(AIIDA_NOT_FOUND) return None attributes = optimade_structure.attributes # Convert null/None values to float("nan") lattice_vectors, adjust_cell = pad_cell(attributes.lattice_vectors) structure = StructureData(cell=lattice_vectors) # Add Kinds for kind in attributes.species: symbols = [] concentration = [] for index, chemical_symbol in enumerate(kind.chemical_symbols): # NOTE: The non-chemical element identifier "X" is identical to how AiiDA handles this, # so it will be treated the same as any other true chemical identifier. if chemical_symbol == "vacancy": # Skip. This is how AiiDA handles vacancies; # to not include them, while keeping the concentration in a site less than 1. continue else: symbols.append(chemical_symbol) concentration.append(kind.concentration[index]) # AiiDA needs a definition for the mass, and for it to be > 0 # mass is OPTIONAL for OPTIMADE structures mass = kind.mass if kind.mass else 1 structure.append_kind( Kind(symbols=symbols, weights=concentration, mass=mass, name=kind.name)) # Add Sites for index in range(attributes.nsites): # range() to ensure 1-to-1 between kind and site structure.append_site( Site( kind_name=attributes.species_at_sites[index], position=attributes.cartesian_site_positions[index], )) if adjust_cell: structure._adjust_default_cell( pbc=[bool(dim.value) for dim in attributes.dimension_types]) return structure
def get_structuredata(self): """Return a StructureData object based on the data in the input file. All of the names corresponding of the ``Kind`` objects composing the ``StructureData`` object will match those found in the ``ATOMIC_SPECIES`` block, so the pseudo potentials can be linked to the calculation using the kind name for each specific type of atom (in the event that you wish to use different pseudo's for two or more of the same atom). :return: structure data node of the structure defined in the input file. :rtype: :class:`~aiida.orm.nodes.data.structure.StructureData` """ from aiida.orm.nodes.data.structure import StructureData, Kind, Site valid_elements_regex = re.compile( """ (?P<ele> H | He | Li | Be | B | C | N | O | F | Ne | Na | Mg | Al | Si | P | S | Cl | Ar | K | Ca | Sc | Ti | V | Cr | Mn | Fe | Co | Ni | Cu | Zn | Ga | Ge | As | Se | Br | Kr | Rb | Sr | Y | Zr | Nb | Mo | Tc | Ru | Rh | Pd | Ag | Cd | In | Sn | Sb | Te | I | Xe | Cs | Ba | Hf | Ta | W | Re | Os | Ir | Pt | Au | Hg | Tl | Pb | Bi | Po | At | Rn | Fr | Ra | Rf | Db | Sg | Bh | Hs | Mt | La | Ce | Pr | Nd | Pm | Sm | Eu | Gd | Tb | Dy | Ho | Er | Tm | Yb | Lu | # Lanthanides Ac | Th | Pa | U | Np | Pu | Am | Cm | Bk | Cf | Es | Fm | Md | No | Lr | # Actinides ) [^a-z] # Any specification of an element is followed by some number # or capital letter or special character. """, re.X | re.I) data = self.get_structure_from_qeinput() species = self.atomic_species structure = StructureData() structure.set_attribute('cell', data['cell']) for mass, name, pseudo in zip(species['masses'], species['names'], species['pseudo_file_names']): try: symbols = valid_elements_regex.search(pseudo).group( 'ele').capitalize() except Exception: raise InputValidationError( 'could not determine element name from pseudo name: {}'. format(pseudo)) structure.append_kind(Kind(name=name, symbols=symbols, mass=mass)) for symbol, position in zip(data['atom_names'], data['positions']): structure.append_site(Site(kind_name=symbol, position=position)) return structure
def reset_kind_names(structure, kind_names): """reset the kind names (per site) of a StructureData node Parameters ---------- structure : aiida.StructureData kind_names : list[str] a name for each site of the structure Returns ------- aiida.StructureData a cloned node Raises ------ AssertionError if the kind_names are not compatible with the current sites """ from aiida.orm.nodes.data.structure import Kind, Site if len(structure.sites) != len(kind_names): raise AssertionError("lengths of sites & names not equal") sites = structure.sites kinds = {k.name: k for k in structure.kinds} structure = structure.clone() structure.clear_sites() structure.clear_kinds() new_kinds = {} for site, name in zip(sites, kind_names): if name not in new_kinds: kind_dict = kinds[site.kind_name].get_raw() kind_dict["name"] = name new_kind = Kind(raw=kind_dict) structure.append_kind(new_kind) new_kinds[name] = new_kind old_symbols = kinds[site.kind_name].symbols new_symbols = new_kinds[name].symbols if old_symbols != new_symbols: raise AssertionError("inconsistent symbols: {} != {}".format( old_symbols, new_symbols)) new_site = Site(kind_name=name, position=site.position) structure.append_site(new_site) return structure
def spglib_tuple_to_structure(structure_tuple, kind_info=None, kinds=None): # pylint: disable=too-many-locals """ Convert a tuple of the format (cell, scaled_positions, element_numbers) to an AiiDA structure. Unless the element_numbers are identical to the Z number of the atoms, you should pass both kind_info and kinds, with the same format as returned by get_tuple_from_aiida_structure. :param structure_tuple: the structure in format (structure_tuple, kind_info) :param kind_info: a dictionary mapping the kind_names to the numbers used in element_numbers. If not provided, assumes {element_name: element_Z} :param kinds: a list of the kinds of the structure. """ if kind_info is None and kinds is not None: raise ValueError('If you pass kind_info, you should also pass kinds') if kinds is None and kind_info is not None: raise ValueError('If you pass kinds, you should also pass kind_info') #Z = {v['symbol']: k for k, v in elements.items()} cell, rel_pos, numbers = structure_tuple if kind_info: _kind_info = copy.copy(kind_info) _kinds = copy.copy(kinds) else: try: # For each site symbols = [elements[num]['symbol'] for num in numbers] except KeyError as exc: raise ValueError( 'You did not pass kind_info, but at least one number ' 'is not a valid Z number: {}'.format(exc.args[0])) _kind_info = {elements[num]['symbol']: num for num in set(numbers)} # Get the default kinds _kinds = [Kind(symbols=sym) for sym in set(symbols)] _kinds_dict = {k.name: k for k in _kinds} # Now I will use in any case _kinds and _kind_info if len(_kind_info) != len(set(_kind_info.values())): raise ValueError( 'There is at least a number repeated twice in kind_info!') # Invert the mapping mapping_num_kindname = {v: k for k, v in _kind_info.items()} # Create the actual mapping try: mapping_to_kinds = { num: _kinds_dict[kindname] for num, kindname in mapping_num_kindname.items() } except KeyError as exc: raise ValueError("Unable to find '{}' in the kinds list".format( exc.args[0])) try: site_kinds = [mapping_to_kinds[num] for num in numbers] except KeyError as exc: raise ValueError( 'Unable to find kind in kind_info for number {}'.format( exc.args[0])) structure = StructureData(cell=cell) for k in _kinds: structure.append_kind(k) abs_pos = np.dot(rel_pos, cell) if len(abs_pos) != len(site_kinds): raise ValueError( 'The length of the positions array is different from the length of the element numbers' ) for kind, pos in zip(site_kinds, abs_pos): structure.append_site(Site(kind_name=kind.name, position=pos)) return structure
def convert_structure(structure, out_type): """convert an AiiDA, ASE or dict object to another type Parameters ---------- structure: aiida.StructureData or dict or ase.Atoms out_type: str one of: 'dict', 'ase' or 'aiida """ from aiida.orm.nodes.data.structure import Kind, Site from aiida.plugins import DataFactory structure_data_cls = DataFactory("structure") if isinstance(structure, dict): if "symbols" in structure and "atomic_numbers" not in structure: structure["atomic_numbers"] = symbols2numbers(structure["symbols"]) if ("fcoords" in structure and "lattice" in structure and "ccoords" not in structure): structure["ccoords"] = frac_to_cartesian(structure["lattice"], structure["fcoords"]) required_keys = ["pbc", "lattice", "ccoords", "atomic_numbers"] if not set(structure.keys()).issuperset(required_keys): raise AssertionError( "dict keys are not a superset of: {}".format(required_keys)) if out_type == "dict": if isinstance(structure, dict): return structure if isinstance(structure, structure_data_cls): return structure_to_dict(structure) if isinstance(structure, Atoms): return { "pbc": structure.pbc.tolist(), "atomic_numbers": structure.get_atomic_numbers().tolist(), "ccoords": structure.positions.tolist(), "lattice": structure.cell.tolist(), "equivalent": structure.get_tags().tolist(), } raise TypeError("structure: {}".format(structure)) elif out_type == "ase": if isinstance(structure, Atoms): return structure if isinstance(structure, structure_data_cls): return structure.get_ase() if isinstance(structure, dict): return Atoms( numbers=structure["atomic_numbers"], cell=structure["lattice"], positions=structure["ccoords"], pbc=structure["pbc"], tags=structure.get("equivalent", None), ) raise TypeError("structure: {}".format(structure)) elif out_type == "aiida": if isinstance(structure, structure_data_cls): return structure if isinstance(structure, Atoms): return structure_data_cls(ase=structure) if isinstance(structure, dict): if structure.get("kinds") is not None: struct = structure_data_cls(cell=structure["lattice"]) struct.set_pbc(structure["pbc"]) for kind, ccoord in zip(structure["kinds"], structure["ccoords"]): if not isinstance(kind, Kind): kind = Kind(raw=kind) if kind.name not in struct.get_site_kindnames(): struct.append_kind(kind) struct.append_site( Site(position=ccoord, kind_name=kind.name)) return struct else: atoms = Atoms( numbers=structure["atomic_numbers"], cell=structure["lattice"], positions=structure["ccoords"], pbc=structure["pbc"], tags=structure.get("equivalent", None), ) return structure_data_cls(ase=atoms) raise ValueError("input type: {}".format(structure)) raise ValueError("output type: {}".format(out_type))
def test_multiple_kinds_auto(cp2k_code, cp2k_basissets, cp2k_pseudos, clear_database): # pylint: disable=unused-argument """Testing CP2K with multiple KIND sections for the same symbol, auto-assigned""" # structure atoms = ase.build.molecule("H2O") atoms.center(vacuum=2.0) structure = StructureData(ase=atoms) structure = StructureData(cell=atoms.cell, pbc=atoms.pbc) structure.append_kind(Kind(name="O", symbols="O")) structure.append_kind(Kind(name="H1", symbols="H")) structure.append_kind(Kind(name="H2", symbols="H")) # ASE stores it as OHH assert all(atoms.numbers == [8, 1, 1] ), "ASE changed positions of atoms in generated structure" structure.append_site(Site(kind_name="O", position=atoms.positions[0])) structure.append_site(Site(kind_name="H1", position=atoms.positions[1])) structure.append_site(Site(kind_name="H2", position=atoms.positions[2])) # parameters parameters = Dict( dict={ "FORCE_EVAL": { "METHOD": "Quickstep", "DFT": { "QS": { "EPS_DEFAULT": 1.0e-12, "WF_INTERPOLATION": "ps", "EXTRAPOLATION_ORDER": 3, }, "MGRID": { "NGRIDS": 4, "CUTOFF": 280, "REL_CUTOFF": 30 }, "XC": { "XC_FUNCTIONAL": { "_": "LDA" } }, "POISSON": { "PERIODIC": "none", "PSOLVER": "MT" }, }, "SUBSYS": { "KIND": [ { "_": "O", "POTENTIAL": "GTH " + cp2k_pseudos["O"].name, "BASIS_SET": cp2k_basissets["O"].name, }, { "_": "H1", "ELEMENT": "H", }, { "_": "H2", # ELEMENT keyword is not even necessary since CP2K guesses from KIND section parameter # these KIND sections are occasionally encountered when specifying MAGNETIZATION # for different sites }, ] }, } }) options = { "resources": { "num_machines": 1, "num_mpiprocs_per_machine": 1 }, "max_wallclock_seconds": 1 * 3 * 60, } inputs = { "structure": structure, "parameters": parameters, "code": cp2k_code, "metadata": { "options": options }, "basissets": cp2k_basissets, "pseudos": cp2k_pseudos, } _, calc_node = run_get_node(CalculationFactory("cp2k"), **inputs) with calc_node.open("aiida.inp") as fhandle: lines = fhandle.readlines() for line in lines: print(line) assert calc_node.exit_status == 0 assert any("&KIND H1" in line for line in lines), "&KIND H1 not found in generated input" assert any("&KIND H2" in line for line in lines), "&KIND H2 not found in generated input" assert len( [True for line in lines if '&KIND H' in line] ) < 3, "More than the expected 2 &KIND H sections found in generated input"