def get_from_string(cls, code_string): """ Get a Computer object with given identifier string, that can either be the numeric ID (pk), or the label (if unique); the label can either be simply the label, or in the format label@machinename. See the note below for details on the string detection algorithm. .. note:: If a string that can be converted to an integer is given, the numeric ID is verified first (therefore, is a code A with a label equal to the ID of another code B is present, code A cannot be referenced by label). Similarly, the (leftmost) '@' symbol is always used to split code and computername. Therefore do not use '@' in the code name if you want to use this function ('@' in the computer name are instead valid). :param code_string: the code string identifying the code to load :raise NotExistent: if no code identified by the given string is found :raise MultipleObjectsError: if the string cannot identify uniquely a code """ from aiida.common.exceptions import NotExistent, MultipleObjectsError from aiida.orm.utils import load_node try: code_int = int(code_string) try: return load_node(code_int, parent_class=cls) except NotExistent: raise ValueError() # Jump to the following section # to check if a code with the given # label exists. except MultipleObjectsError: raise MultipleObjectsError("More than one code in the DB " "with pk='{}'!".format(code_string)) except ValueError: # Before dying, try to see if the user passed a (unique) label. # split with the leftmost '@' symbol (i.e. code names cannot # contain '@' symbols, computer names can) codename, sep, computername = code_string.partition('@') if sep: codes = cls.query(label=codename, dbcomputer__name=computername) else: codes = cls.query(label=codename) if len(codes) == 0: raise NotExistent("'{}' is not a valid code " "ID or label.".format(code_string)) elif len(codes) > 1: retstr = ( "There are multiple codes with label '{}', having IDs: " "".format(code_string)) retstr += ", ".join(sorted([str(c.pk) for c in codes])) + ".\n" retstr += ("Relabel them (using their ID), or refer to them " "with their ID.") raise MultipleObjectsError(retstr) else: return codes[0]
def _get_calculation(node): """ Gets the parent (immediate) calculation, attached as the input of the node. :param node: an instance of subclass of :py:class:`aiida.orm.node.Node` :return: an instance of subclass of :py:class:`aiida.orm.calculation.Calculation` :raises MultipleObjectsError: if the node has more than one calculation attached. """ from aiida.common.exceptions import MultipleObjectsError from aiida.orm.calculation import Calculation from aiida.common.links import LinkType if len(node.get_inputs(node_type=Calculation, link_type=LinkType.CREATE)) == 1: return node.get_inputs(node_type=Calculation, link_type=LinkType.CREATE)[0] elif len(node.get_inputs(node_type=Calculation, link_type=LinkType.CREATE)) == 0: return None else: raise MultipleObjectsError("Node {} seems to have more than one " "parent (immediate) calculation -- " "exporter does not know which one of " "them produced the node".format(node))
def _show_vmd(exec_name, structure_list): """ Plugin for vmd """ import tempfile import subprocess if len(structure_list) > 1: raise MultipleObjectsError("Visualization of multiple objects " "is not implemented") structure = structure_list[0] # pylint: disable=protected-access with tempfile.NamedTemporaryFile(suffix='.xsf') as tmpf: tmpf.write(structure._exportstring('xsf')[0]) tmpf.flush() try: subprocess.check_output([exec_name, tmpf.name]) except subprocess.CalledProcessError: # The program died: just print a message echo.echo_info( "the call to {} ended with an error.".format(exec_name)) except OSError as err: if err.errno == 2: echo.echo_critical( "No executable '{}' found. Add to the path, " "or try with an absolute path.".format(exec_name)) else: raise
def load_entity(cls, identifier, identifier_type=None, sub_classes=None, query_with_dashes=True): """ Load an entity that uniquely corresponds to the provided identifier of the identifier type. :param identifier: the identifier :param identifier_type: the type of the identifier :param sub_classes: an optional tuple of orm classes, that should each be strict sub classes of the base orm class of the loader, that will narrow the queryset :returns: the loaded entity :raises aiida.common.MultipleObjectsError: if the identifier maps onto multiple entities :raises aiida.common.NotExistent: if the identifier maps onto not a single entity """ builder, query_parameters = cls.get_query_builder( identifier, identifier_type, sub_classes, query_with_dashes) builder.limit(2) classes = ' or '.join( [sub_class.__name__ for sub_class in query_parameters['classes']]) identifier = query_parameters['identifier'] identifier_type = query_parameters['identifier_type'].value try: entity = builder.one()[0] except MultipleObjectsError: error = f'multiple {classes} entries found with {identifier_type}<{identifier}>' raise MultipleObjectsError(error) except NotExistent as exception: error = f'no {classes} found with {identifier_type}<{identifier}>: {exception}' raise NotExistent(error) return entity
def load_entity(cls, identifier, identifier_type=None, sub_classes=None, query_with_dashes=True): from aiida.orm import Workflow if identifier_type is None: identifier, identifier_type = cls.infer_identifier_type(identifier) if identifier_type == IdentifierType.ID: result = Workflow.query(pk=identifier) elif identifier_type == IdentifierType.UUID: result = Workflow.query(uuid=identifier) elif identifier_type == IdentifierType.LABEL: result = Workflow.query(label=identifier) result = [workflow for workflow in result] if len(result) > 1: error = 'multiple legacy workflows found with {} <{}>'.format( identifier_type, identifier) raise MultipleObjectsError(error) elif not result: error = 'no legacy workflow found with {} <{}>'.format( identifier_type, identifier) raise NotExistent(error) return result[0]
def get_pseudos_from_structure(structure, family_name): """ Given a family name (a PsfFamily group in the DB) and a AiiDA structure, return a dictionary associating each kind name with its PsfData object. :raise MultipleObjectsError: if more than one PSF for the same element is found in the group. :raise NotExistent: if no PSF for an element in the group is found in the group. """ from aiida.common.exceptions import NotExistent, MultipleObjectsError family_pseudos = {} family = PsfData.get_psf_group(family_name) for node in family.nodes: if isinstance(node, PsfData): if node.element in family_pseudos: raise MultipleObjectsError( "More than one PSF for element {} found in " "family {}".format(node.element, family_name)) family_pseudos[node.element] = node pseudo_list = {} for kind in structure.kinds: symbol = kind.symbol try: pseudo_list[kind.name] = family_pseudos[symbol] except KeyError: raise NotExistent( "No PSF for element {} found in family {}".format( symbol, family_name)) return pseudo_list
def get_pseudos_from_structure(structure, family_name): """Given a family name (a Siesta pseudo group in the DB, possibly with mixed psf and psml pseudopotentials) and an AiiDA structure object, return a dictionary associating each 'kind' name in the structure with its object (PsfData or PsmlData). :raise MultipleObjectsError: if more than one pseudo for the same element is found in the group. :raise NotExistent: if no pseudo for an element in the group is found in the group. """ from aiida.common.exceptions import NotExistent, MultipleObjectsError family_pseudos = {} family = Group.get(label=family_name) for node in family.nodes: if isinstance(node, (PsfData, PsmlData)): if node.element in family_pseudos: raise MultipleObjectsError( "More than one pseudo for element {} found in " "family {}".format(node.element, family_name) ) family_pseudos[node.element] = node pseudo_list = {} for kind in structure.kinds: symbol = kind.symbol try: pseudo_list[kind.name] = family_pseudos[symbol] except KeyError: raise NotExistent("No pseudo for element {} found in family {}".format(symbol, family_name)) return pseudo_list
def _show_xcrysden(exec_name, object_list, **kwargs): """ Plugin for xcrysden """ import tempfile import subprocess if len(object_list) > 1: raise MultipleObjectsError( 'Visualization of multiple trajectories is not implemented') obj = object_list[0] # pylint: disable=protected-access with tempfile.NamedTemporaryFile(mode='w+b', suffix='.xsf') as tmpf: tmpf.write(obj._exportcontent('xsf', **kwargs)[0]) tmpf.flush() try: subprocess.check_output([exec_name, '--xsf', tmpf.name]) except subprocess.CalledProcessError: # The program died: just print a message echo.echo_info( 'the call to {} ended with an error.'.format(exec_name)) except OSError as err: if err.errno == 2: echo.echo_critical( "No executable '{}' found. Add to the path, " 'or try with an absolute path.'.format(exec_name)) else: raise
def get_code_helper(cls, label, machinename=None): """ :param label: the code label identifying the code to load :param machinename: the machine name where code is setup :raise aiida.common.NotExistent: if no code identified by the given string is found :raise aiida.common.MultipleObjectsError: if the string cannot identify uniquely a code """ from aiida.common.exceptions import NotExistent, MultipleObjectsError from aiida.orm.querybuilder import QueryBuilder from aiida.orm.computers import Computer qb = QueryBuilder() qb.append(cls, filters={'label': {'==': label}}, project=['*'], tag='code') if machinename: qb.append(Computer, filters={'name': {'==': machinename}}, with_node='code') if qb.count() == 0: raise NotExistent("'{}' is not a valid code name.".format(label)) elif qb.count() > 1: codes = qb.all(flat=True) retstr = ("There are multiple codes with label '{}', having IDs: ".format(label)) retstr += ', '.join(sorted([str(c.pk) for c in codes])) + '.\n' retstr += ('Relabel them (using their ID), or refer to them with their ID.') raise MultipleObjectsError(retstr) else: return qb.first()[0]
def get_from_string(cls, code_string): """ Get a Computer object with given identifier string in the format label@machinename. See the note below for details on the string detection algorithm. .. note:: the (leftmost) '@' symbol is always used to split code and computername. Therefore do not use '@' in the code name if you want to use this function ('@' in the computer name are instead valid). :param code_string: the code string identifying the code to load :raise aiida.common.NotExistent: if no code identified by the given string is found :raise aiida.common.MultipleObjectsError: if the string cannot identify uniquely a code :raise aiida.common.InputValidationError: if code_string is not of string type """ from aiida.common.exceptions import NotExistent, MultipleObjectsError, InputValidationError try: label, sep, machinename = code_string.partition('@') except AttributeError as exception: raise InputValidationError('the provided code_string is not of valid string type') try: return cls.get_code_helper(label, machinename) except NotExistent: raise NotExistent('{} could not be resolved to a valid code label'.format(code_string)) except MultipleObjectsError: raise MultipleObjectsError('{} could not be uniquely resolved'.format(code_string))
def get(cls, pk=None, label=None, machinename=None): """ Get a Computer object with given identifier string, that can either be the numeric ID (pk), or the label (and computername) (if unique). :param pk: the numeric ID (pk) for code :param label: the code label identifying the code to load :param machinename: the machine name where code is setup :raise NotExistent: if no code identified by the given string is found :raise MultipleObjectsError: if the string cannot identify uniquely a code """ from aiida.common.exceptions import (NotExistent, MultipleObjectsError, InputValidationError) # first check if code pk is provided if (pk): code_int = int(pk) try: return cls.get_subclass_from_pk(code_int) except NotExistent: raise ValueError("{} is not valid code pk".format(pk)) except MultipleObjectsError: raise MultipleObjectsError("More than one code in the DB " "with pk='{}'!".format(pk)) # check if label (and machinename) is provided elif (label != None): return cls.get_code_helper(label, machinename) else: raise InputValidationError( "Pass either pk or code label (and machinename)")
def get_basis_group_map(cls, group_name): """Get a mapping of elements to basissets in a basis set family. Parameters ---------- group_name : str the group name of the basis set Returns ------- dict a mapping of element to basis set Raises ------ aiida.common.exceptions.MultipleObjectsError if there is more than one element s """ family_bases = {} family = cls.get_basis_group(group_name) for node in family.nodes: if isinstance(node, cls): if node.element in family_bases: raise MultipleObjectsError( "More than one BasisSetData for element {} found in " "family {}".format(node.element, group_name) ) family_bases[node.element] = node return family_bases
def get_pseudos_from_structure(structure, family_name): """Return a dictionary mapping each kind name of the structure to corresponding `UpfData` from given family. :param structure: a `StructureData` :param family_name: the name of a UPF family group :return: dictionary mapping each structure kind name onto `UpfData` of corresponding element :raise aiida.common.MultipleObjectsError: if more than one UPF for the same element is found in the group. :raise aiida.common.NotExistent: if no UPF for an element in the group is found in the group. """ from aiida.common.exceptions import NotExistent, MultipleObjectsError pseudo_list = {} family_pseudos = {} family = UpfData.get_upf_group(family_name) for node in family.nodes: if isinstance(node, UpfData): if node.element in family_pseudos: raise MultipleObjectsError( 'More than one UPF for element {} found in family {}'. format(node.element, family_name)) family_pseudos[node.element] = node for kind in structure.kinds: try: pseudo_list[kind.name] = family_pseudos[kind.symbol] except KeyError: raise NotExistent( 'No UPF for element {} found in family {}'.format( kind.symbol, family_name)) return pseudo_list
def __init__(self, **kwargs): # If no arguments are passed, then create a new DbComment if not kwargs: self.dbcomment = DbComment() # If a DbComment is passed as argument. Just use it and # wrap it with a Comment object elif 'dbcomment' in kwargs: # When a dbcomment is passed as argument, then no other arguments # should be passed. if len(kwargs) > 1: raise ValueError("When a DbComment is passed as argument, no" "further arguments are accepted.") dbcomment = kwargs.pop('dbcomment') if not isinstance(dbcomment, DbComment): raise ValueError("Expected a DbComment. Object of a different" "class was given as argument.") self.dbcomment = dbcomment else: id = kwargs.pop('id', None) if id is None: id = kwargs.pop('pk', None) user = kwargs.pop('user', None) dbnode = kwargs.pop('dbnode', None) # Constructing the default query import operator from django.db.models import Q query_list = [] # If an id is specified then we add it to the query if id is not None: query_list.append(Q(pk=id)) # If a user is specified then we add it to the query if user is not None: query_list.append(Q(user=user)) # If a dbnode is specified then we add it to the query if dbnode is not None: query_list.append(Q(dbnode=dbnode)) res = DbComment.objects.filter(reduce(operator.and_, query_list)) ccount = len(res) if ccount > 1: raise MultipleObjectsError( "The arguments that you specified were too vague. More " "than one comments with this data were found in the " "database") elif ccount == 0: raise NotExistent("No comments were found with the given " "arguments") self.dbcomment = res[0]
def get(cls, *args, **kwargs): queryresults = cls.query(*args, **kwargs) if len(queryresults) == 1: return queryresults[0] elif len(queryresults) == 0: raise NotExistent("No Group matching the query found") else: raise MultipleObjectsError("More than one Group found -- " "I found {}".format(len(queryresults)))
def get(cls, element, name=None, version="latest", match_aliases=True, group_label=None, n_el=None): from aiida.orm.querybuilder import QueryBuilder query = QueryBuilder() params = {} if group_label: query.append(Group, filters={"label": group_label}, tag="group") params["with_group"] = "group" query.append(BasisSet, **params) filters = {"attributes.element": {"==": element}} if version != "latest": filters["attributes.version"] = {"==": version} if name: if match_aliases: filters["attributes.aliases"] = {"contains": [name]} else: filters["attributes.name"] = {"==": name} if n_el: filters["attributes.n_el"] = {"==": n_el} query.add_filter(BasisSet, filters) # SQLA ORM only solution: # query.order_by({BasisSet: [{"attributes.version": {"cast": "i", "order": "desc"}}]}) # items = query.first() items = sorted(query.iterall(), key=lambda b: b[0].version, reverse=True) if not items: raise NotExistent( f"No Gaussian Basis Set found for element={element}, name={name}, version={version}" ) # if we get different names there is no well ordering, sorting by version only works if they have the same name if len(set(b[0].name for b in items)) > 1: raise MultipleObjectsError( f"Multiple Gaussian Basis Set found for element={element}, name={name}, version={version}" ) return items[0][0]
def load_node(node_id=None, pk=None, uuid=None, parent_class=None, query_with_dashes=True): """ Returns an AiiDA node given its PK or UUID. :param node_id: PK (integer) or UUID (string) or a node :param pk: PK of a node :param uuid: UUID of a node, or the beginning of the uuid :param parent_class: if specified, checks whether the node loaded is a subclass of parent_class :param bool query_with_dashes: Specific if uuid is passed, allows to put the uuid in the correct form. Default=True :param bool return_node: lets the function return the AiiDA node referred by the input. Default=False :return: the required AiiDA node if existing, unique, and (sub)instance of parent_class :raise InputValidationError: if none or more than one of parameters is supplied :raise TypeError: I the wrong types are provided :raise NotExistent: if no matching Node is found. :raise MultipleObjectsError: If more than one Node was found """ from aiida.common.exceptions import MultipleObjectsError, NotExistent from aiida.orm.implementation import Node # I can use this functions to load only nodes, i.e. not users, groups etc ... # If nothing is specified I assume the big granpa: Node! class_ = parent_class or Node if not issubclass(class_, Node): raise TypeError("{} is not a subclass of {}".format(class_, Node)) # The logic is to use the function is_node_existing with return_node=True kwargs = dict(node_id=node_id, pk=pk, uuid=uuid, parent_class=parent_class, query_with_dashes=query_with_dashes) qb = create_node_id_qb(**kwargs) qb.add_projection('node', '*') # For efficiency I do not go further than 2 results. qb.limit(2) # FInally, I check the existence and unicity of the node try: return qb.one()[0] except MultipleObjectsError: raise MultipleObjectsError("More than one node found. Provide longer " "starting pattern for uuid.") except NotExistent: raise NotExistent("No node was found")
def __init__(self, **kwargs): # If no arguments are passed, then create a new DbComment if not kwargs: self.dbcomment = DbComment() # If a DbComment is passed as argument. Just use it and # wrap it with a Comment object elif 'dbcomment' in kwargs: # When a dbcomment is passed as argument, then no other arguments # should be passed. if len(kwargs) > 1: raise ValueError("When a DbComment is passed as argument, no" "further arguments are accepted.") dbcomment = kwargs.pop('dbcomment') if not isinstance(dbcomment, DbComment): raise ValueError("Expected a DbComment. Object of a different" "class was given as argument.") self.dbcomment = dbcomment else: id = kwargs.pop('id', None) if id is None: id = kwargs.pop('pk', None) user = kwargs.pop('user', None) dbnode = kwargs.pop('dbnode', None) # Constructing the default query dbcomment_query = DbComment.query # If an id is specified then we add it to the query if id is not None: dbcomment_query = dbcomment_query.filter_by(id=id) # If a user is specified then we add it to the query if user is not None: dbcomment_query = dbcomment_query.filter_by(user=user) # If a dbnode is specified then we add it to the query if dbnode is not None: dbcomment_query = dbcomment_query.filter_by(dbnode=dbnode) ccount = dbcomment_query.count() if ccount > 1: raise MultipleObjectsError( "The arguments that you specified were too vague. More " "than one comments with this data were found in the " "database") elif ccount == 0: raise NotExistent("No comments were found with the given " "arguments") self.dbcomment = dbcomment_query.first()
def get_basis_group_map(cls, group_name): """ Return an {element: basis} map for the BasisFamily group with the given name. """ from aiida.common.exceptions import MultipleObjectsError family_bases = {} family = cls.get_basis_group(group_name) for node in family.nodes: if isinstance(node, cls): if node.element in family_bases: raise MultipleObjectsError( "More than one BasisSetData for element {} found in " "family {}".format(node.element, group_name)) family_bases[node.element] = node return family_bases
def get_potcars_dict(cls, elements, family_name, mapping=None): """ Get a dictionary {element: POTCAR} for all given symbols. :param elements: The list of symbols to find POTCARs for :param family_name: The POTCAR family to be used :param mapping: A mapping[element] -> full_name """ if not mapping: mapping = {element: element for element in elements} group_filters = { 'name': { '==': family_name }, 'type': { '==': cls.potcar_family_type_string } } element_filters = { 'attributes.full_name': { 'in': [mapping[element] for element in elements] } } query = QueryBuilder() query.append(Group, tag='family', filters=group_filters) query.append(cls, tag='potcar', member_of='family', filters=element_filters) result_potcars = {} for element in elements: full_name = mapping[element] potcars_of_kind = [ potcar[0] for potcar in query.all() if potcar[0].full_name == full_name ] if not potcars_of_kind: raise NotExistent( 'No POTCAR found for full name {} in family {}'.format( full_name, family_name)) elif len(potcars_of_kind) > 1: raise MultipleObjectsError( 'More than one POTCAR for full name {} found in family {}'. format(full_name, family_name)) result_potcars[element] = potcars_of_kind[0] return result_potcars
def get_or_create(cls, *args, **kwargs): """ Try to retrieve a group from the DB with the given arguments; create (and store) a new group if such a group was not present yet. :return: (group, created) where group is the group (new or existing, in any case already stored) and created is a boolean saying """ res = cls.query(name=kwargs.get("name"), type_string=kwargs.get("type_string")) if res is None or len(res) == 0: return cls.create(*args, **kwargs), True elif len(res) > 1: raise MultipleObjectsError("More than one groups found in the " "database") else: return res[0], False
def get_pseudos_from_structure(structure, family_name): """ Given a family name (a PsmlFamily group in the DB) and a AiiDA structure, return a dictionary associating each kind name with its PsmlData object. :raise MultipleObjectsError: if more than one PSML for the same element is found in the group. :raise NotExistent: if no PSML for an element in the group is found in the group. """ from aiida.common.exceptions import NotExistent, MultipleObjectsError message = ( #pylint: disable=invalid-name 'This function has been deprecated and will be removed in `v2.0.0`. ' + '`get_pseudos_from_structure` is substitued by `fam.get_pseudos(structure=structure)` ' + 'where `fam` is an instance of the families classes in `aiida_pseudo.groups.family`.' ) warnings.warn(message, AiidaSiestaDeprecationWarning) family_pseudos = {} family = PsmlData.get_psml_group(family_name) for node in family.nodes: if isinstance(node, PsmlData): if node.element in family_pseudos: raise MultipleObjectsError( "More than one PSML for element {} found in " "family {}".format(node.element, family_name)) family_pseudos[node.element] = node pseudo_list = {} for kind in structure.kinds: symbol = kind.symbol try: pseudo_list[kind.name] = family_pseudos[symbol] except KeyError: raise NotExistent( "No PSML for element {} found in family {}".format( symbol, family_name)) return pseudo_list
def get_pseudos_from_structure_and_path(structure, path='./'): """ Given a structure and a path where UPF files are supposed to be located, load upf files and return those contained in the structure :raise aiida.common.MultipleObjectsError: if more than one UPF for the same element is found in the group. :raise aiida.common.NotExistent: if no UPF for an element is found in the group. """ from aiida.common.exceptions import NotExistent, MultipleObjectsError # load all pseudos pseudo_files = (set(glob.glob(os.path.join(path, '*UPF'))) | set(glob.glob(os.path.join(path, '*upf'))) | set(glob.glob(os.path.join(path, '*Upf')))) pseudos = {} for pp_file in pseudo_files: upf = UpfData(file=os.path.abspath(pp_file)) if upf.element in pseudos: raise MultipleObjectsError( "More than one UPF for element {} found in " "{}".format(upf.element, path)) pseudos[upf.element] = upf pseudo_list = {} for kind in structure.kinds: symbol = kind.symbol try: pseudo_list[kind.name] = pseudos[kind.name] except KeyError: raise NotExistent("No UPF for element {} found in path {}".format( symbol, path)) return pseudo_list
def load_group(group_id=None, pk=None, uuid=None, query_with_dashes=True): """ Load a group by its pk or uuid :param group_id: pk (integer) or uuid (string) of a group :param pk: pk of a group :param uuid: uuid of a group, or the beginning of the uuid :param bool query_with_dashes: allow to query for a uuid with dashes (default=True) :returns: the requested group if existing and unique :raise InputValidationError: if none or more than one of the arguments are supplied :raise TypeError: if the wrong types are provided :raise NotExistent: if no matching Node is found. :raise MultipleObjectsError: if more than one Node was found """ from aiida.orm import Group kwargs = { 'node_id': group_id, 'pk': pk, 'uuid': uuid, 'parent_class': Group, 'query_with_dashes': query_with_dashes } qb = create_node_id_qb(**kwargs) qb.add_projection('node', '*') qb.limit(2) try: return qb.one()[0] except MultipleObjectsError: raise MultipleObjectsError( 'More than one group found. Provide longer starting pattern for uuid.' ) except NotExistent: raise NotExistent('No group was found')
def get(cls, element, name=None, version="latest", match_aliases=True, group_label=None, n_el=None): """ Get the first matching Pseudopotential for the given parameters. :param element: The atomic symbol :param name: The name of the pseudo :param version: A specific version (if more than one in the database and not the highest/latest) :param match_aliases: Whether to look in the list of of aliases for a matching name """ from aiida.orm.querybuilder import QueryBuilder query = QueryBuilder() params = {} if group_label: query.append(Group, filters={"label": group_label}, tag="group") params["with_group"] = "group" query.append(Pseudopotential, **params) filters = {"attributes.element": {"==": element}} if version != "latest": filters["attributes.version"] = {"==": version} if name: if match_aliases: filters["attributes.aliases"] = {"contains": [name]} else: filters["attributes.name"] = {"==": name} query.add_filter(Pseudopotential, filters) # SQLA ORM only solution: # query.order_by({Pseudopotential: [{"attributes.version": {"cast": "i", "order": "desc"}}]}) # items = query.first() all_iter = query.iterall() if n_el: all_iter = filter(lambda p: sum(p[0].n_el) == n_el, all_iter) items = sorted(all_iter, key=lambda p: p[0].version, reverse=True) if not items: raise NotExistent( f"No Gaussian Pseudopotential found for element={element}, name={name}, version={version}" ) # if we get different names there is no well ordering, sorting by version only works if they have the same name if len(set(p[0].name for p in items)) > 1: raise MultipleObjectsError( f"Multiple Gaussian Pseudopotentials found for element={element}, name={name}, version={version}" ) return items[0][0]
def load_node(node_id=None, pk=None, uuid=None, parent_class=None, query_with_dashes=True): """ Return an AiiDA node given PK or UUID. :param node_id: PK (integer) or UUID (string) or a node :param pk: PK of a node :param uuid: UUID of a node, or the beginning of the uuid :param parent_class: if specified, checks whether the node loaded is a subclass of parent_class :param bool query_with_dashes: Specific if uuid is passed, allows to put the uuid in the correct form. Default=True :return: an AiiDA node :raise InputValidationError: if none or more than one of parameters is supplied :raise TypeError: I the wrong types are provided :raise NotExistent: if no matching Node is found. :raise MultipleObjectsError: If more than one Node was fouuund """ from aiida.common.exceptions import NotExistent, MultipleObjectsError, NotExistent, InputValidationError # This must be done inside here, because at import time the profile # must have been already loaded. If you put it at the module level, # the implementation is frozen to the default one at import time. from aiida.orm.implementation import Node from aiida.orm.querybuilder import QueryBuilder # First checking if the inputs are valid: inputs_provided = [val is not None for val in (node_id, pk, uuid)].count(True) if inputs_provided == 0: raise InputValidationError( "one of the parameters 'node_id', 'pk' and 'uuid' " "has to be supplied") elif inputs_provided > 1: raise InputValidationError( "only one of parameters 'node_id', 'pk' and 'uuid' " "has to be supplied") class_ = parent_class or Node if not issubclass(class_, Node): raise TypeError("{} is not a subclass of {}".format(class_, Node)) # The logic is as follows: If pk is specified I will look for the pk # if UUID is specified for the uuid. # node_id can either be string -> uuid or an integer -> pk # Checking first if node_id specified if node_id is not None: if isinstance(node_id, (str, unicode)): uuid = node_id elif isinstance(node_id, int): pk = node_id else: raise TypeError("'node_id' has to be either string, unicode or " "integer, {} given".format(type(node_id))) #I am checking whether uuid, if supplied, is a string if uuid is not None: if not isinstance(uuid, (str, unicode)): raise TypeError("'uuid' has to be string or unicode") # or whether the pk, if provided, is an integer elif pk is not None: if not isinstance(pk, int): raise TypeError("'pk' has to be an integer") else: # I really shouldn't get here assert True, "Neither pk nor uuid was provided" qb = QueryBuilder() qb.append(class_, tag='node', project='*') if pk: qb.add_filter('node', {'id': pk}) elif uuid: # Put back dashes in the right place start_uuid = uuid.replace('-', '') if query_with_dashes: # Essential that this is ordered from largest to smallest! for dash_pos in [20, 16, 12, 8]: if len(start_uuid) > dash_pos: start_uuid = "{}-{}".format(start_uuid[:dash_pos], start_uuid[dash_pos:]) qb.add_filter('node', {'uuid': {'like': "{}%".format(start_uuid)}}) try: return qb.one()[0] except MultipleObjectsError: raise MultipleObjectsError("More than one node found") except NotExistent: raise NotExistent("No node was found")
def _collect_tags( node, calc, parameters=None, dump_aiida_database=default_options['dump_aiida_database'], exclude_external_contents=default_options['exclude_external_contents'], gzip=default_options['gzip'], gzip_threshold=default_options['gzip_threshold']): """ Retrieve metadata from attached calculation and pseudopotentials and prepare it to be saved in TCOD CIF. """ from aiida.common.links import LinkType import os, json import aiida tags = { '_audit_creation_method': "AiiDA version {}".format(aiida.__version__) } # Recording the dictionaries (if any) if len(conforming_dictionaries): for postfix in ['name', 'version', 'location']: key = '_audit_conform_dict_{}'.format(postfix) if key not in tags: tags[key] = [] for dictionary in conforming_dictionaries: tags['_audit_conform_dict_name'].append(dictionary['name']) tags['_audit_conform_dict_version'].append(dictionary['version']) tags['_audit_conform_dict_location'].append(dictionary['url']) # Collecting metadata from input files: calc_data = [] if calc is not None: calc_data = _collect_calculation_data(calc) for tag in tcod_loops['_tcod_computation'] + tcod_loops['_tcod_file']: tags[tag] = [] export_files = [] sn = 1 for step in calc_data: tags['_tcod_computation_step'].append(sn) tags['_tcod_computation_command'].append('cd {}; ./{}'.format( sn, aiida_executable_name)) tags['_tcod_computation_reference_uuid'].append(step['uuid']) if 'env' in step: tags['_tcod_computation_environment'].append("\n".join( ["%s=%s" % (key, step['env'][key]) for key in step['env']])) else: tags['_tcod_computation_environment'].append('') if 'stdout' in step and step['stdout'] is not None: tags['_tcod_computation_stdout'].append(step['stdout']) else: tags['_tcod_computation_stdout'].append('') if 'stderr' in step and step['stderr'] is not None: tags['_tcod_computation_stderr'].append(step['stderr']) else: tags['_tcod_computation_stderr'].append('') export_files.append({ 'name': "{}{}".format(sn, os.sep), 'type': 'folder' }) for f in step['files']: f['name'] = os.path.join(str(sn), f['name']) export_files.extend(step['files']) sn = sn + 1 # Creating importable AiiDA database dump in CIF tags if dump_aiida_database and node.is_stored: import json from aiida.common.exceptions import LicensingException from aiida.common.folders import SandboxFolder from aiida.orm.importexport import export_tree with SandboxFolder() as folder: try: export_tree([node.dbnode], folder=folder, silent=True, allowed_licenses=['CC0']) except LicensingException as e: raise LicensingException(e.message + \ ". Only CC0 license is accepted.") files = _collect_files(folder.abspath) with open(folder.get_abs_path('data.json')) as f: data = json.loads(f.read()) md5_to_url = {} if exclude_external_contents: for pk in data['node_attributes']: n = data['node_attributes'][pk] if 'md5' in n.keys() and 'source' in n.keys() and \ 'uri' in n['source'].keys(): md5_to_url[n['md5']] = n['source']['uri'] for f in files: f['name'] = os.path.join('aiida', f['name']) if f['type'] == 'file' and f['md5'] in md5_to_url.keys(): f['uri'] = md5_to_url[f['md5']] export_files.extend(files) # Describing seen files in _tcod_file_* loop encodings = list() fn = 0 for f in export_files: # ID and name tags['_tcod_file_id'].append(fn) tags['_tcod_file_name'].append(f['name']) # Checksums md5sum = None sha1sum = None if f['type'] == 'file': md5sum = f['md5'] sha1sum = f['sha1'] else: md5sum = '.' sha1sum = '.' tags['_tcod_file_md5sum'].append(md5sum) tags['_tcod_file_sha1sum'].append(sha1sum) # Content, encoding and URI contents = '?' encoding = None if 'uri' in f.keys(): contents = '.' tags['_tcod_file_URI'].append(f['uri']) else: tags['_tcod_file_URI'].append('?') if f['type'] == 'file': contents,encoding = \ cif_encode_contents(f['contents'], gzip=gzip, gzip_threshold=gzip_threshold) else: contents = '.' if encoding is None: encoding = '.' elif encoding not in encodings: encodings.append(encoding) tags['_tcod_file_contents'].append(contents) tags['_tcod_file_content_encoding'].append(encoding) # Role role = '?' if 'role' in f.keys(): role = f['role'] tags['_tcod_file_role'].append(role) fn = fn + 1 # Describing the encodings if encodings: for tag in tcod_loops['_tcod_content_encoding']: tags[tag] = [] for encoding in encodings: layers = encoding.split('+') for i in range(0, len(layers)): tags['_tcod_content_encoding_id'].append(encoding) tags['_tcod_content_encoding_layer_id'].append(i + 1) tags['_tcod_content_encoding_layer_type'].append(layers[i]) # Describing Brillouin zone (if used) if calc is not None: from aiida.orm.data.array.kpoints import KpointsData kpoints_list = calc.get_inputs(KpointsData, link_type=LinkType.INPUT) # TODO: stop if more than one KpointsData is used? if len(kpoints_list) == 1: kpoints = kpoints_list[0] density, shift = kpoints.get_kpoints_mesh() tags['_dft_BZ_integration_grid_X'] = density[0] tags['_dft_BZ_integration_grid_Y'] = density[1] tags['_dft_BZ_integration_grid_Z'] = density[2] tags['_dft_BZ_integration_grid_shift_X'] = shift[0] tags['_dft_BZ_integration_grid_shift_Y'] = shift[1] tags['_dft_BZ_integration_grid_shift_Z'] = shift[2] from aiida.common.exceptions import MultipleObjectsError from aiida.common.pluginloader import all_plugins, get_plugin category = 'tools.dbexporters.tcod_plugins' plugins = list() if calc is not None: for entry_point in all_plugins(category): plugin = get_plugin(category, entry_point) if calc._plugin_type_string.endswith(plugin._plugin_type_string + '.'): plugins.append(plugin) if len(plugins) > 1: raise MultipleObjectsError('more than one plugin found for {}'.format( calc._plugin_type_string)) if len(plugins) == 1: plugin = plugins[0] translated_tags = translate_calculation_specific_values(calc, plugin) tags.update(translated_tags) return tags
def export_cifnode(what, parameters=None, trajectory_index=None, store=False, reduce_symmetry=default_options['reduce_symmetry'], **kwargs): """ The main exporter function. Exports given coordinate-containing \*Data node to :py:class:`aiida.orm.data.cif.CifData` node, ready to be exported to TCOD. All \*Data types, having method ``_get_cif()``, are supported in addition to :py:class:`aiida.orm.data.cif.CifData`. :param what: data node to be exported. :param parameters: a :py:class:`aiida.orm.data.parameter.ParameterData` instance, produced by the same calculation as the original exported node. :param trajectory_index: a step to be converted and exported in case a :py:class:`aiida.orm.data.array.trajectory.TrajectoryData` is exported. :param store: boolean indicating whether to store intermediate nodes or not. Default False. :param dump_aiida_database: boolean indicating whether to include the dump of AiiDA database (containing only transitive closure of the exported node). Default True. :param exclude_external_contents: boolean indicating whether to exclude nodes from AiiDA database dump, that are taken from external repositores and have a URL link allowing to refetch their contents. Default False. :param gzip: boolean indicating whether to Gzip large CIF text fields. Default False. :param gzip_threshold: integer indicating the maximum size (in bytes) of uncompressed CIF text fields when the **gzip** option is in action. Default 1024. :return: a :py:class:`aiida.orm.data.cif.CifData` node. """ from aiida.common.links import LinkType from aiida.common.exceptions import MultipleObjectsError from aiida.orm.calculation.inline import make_inline CifData = DataFactory('cif') StructureData = DataFactory('structure') TrajectoryData = DataFactory('array.trajectory') ParameterData = DataFactory('parameter') calc = _get_calculation(what) if parameters is not None: if not isinstance(parameters, ParameterData): raise ValueError("Supplied parameters are not an " "instance of ParameterData") elif calc is not None: params = calc.get_outputs(type=ParameterData, link_type=LinkType.CREATE) if len(params) == 1: parameters = params[0] elif len(params) > 0: raise MultipleObjectsError("Calculation {} has more than " "one ParameterData output, please " "specify which one to use with " "an option parameters='' when " "calling export_cif()".format(calc)) if parameters is not None: _assert_same_parents(what, parameters) node = what # Convert node to CifData (if required) if not isinstance(node, CifData) and getattr(node, '_get_cif'): function_args = {'store': store} if trajectory_index is not None: function_args['index'] = trajectory_index node = node._get_cif(**function_args) if not isinstance(node, CifData): raise NotImplementedError("Exporter does not know how to " "export {}".format(type(node))) # Reduction of the symmetry if reduce_symmetry: from aiida.orm.data.cif import refine_inline ret_dict = refine_inline(node=node, store=store) node = ret_dict['cif'] # Addition of the metadata args = ParameterData(dict=kwargs) function_args = {'what': what, 'args': args, 'store': store} if node != what: function_args['node'] = node if parameters is not None: function_args['parameters'] = parameters ret_dict = add_metadata_inline(**function_args) return ret_dict['cif']