Source code for aiida.orm.implementation.django.calculation.job

# -*- coding: utf-8 -*-
###########################################################################
# Copyright (c), The AiiDA team. All rights reserved.                     #
# This file is part of the AiiDA code.                                    #
#                                                                         #
# The code is hosted on GitHub at https://github.com/aiidateam/aiida_core #
# For further information on the license, see the LICENSE.txt file        #
# For further information please visit http://www.aiida.net               #
###########################################################################

import datetime
from django.db import transaction, IntegrityError
from django.db.models import Q
from aiida.common.utils import str_timedelta
from aiida.common.datastructures import sort_states, calc_states
from aiida.common.exceptions import ModificationNotAllowed, DbContentError
from aiida.backends.djsite.utils import get_automatic_user
from aiida.orm.group import Group
from aiida.orm.implementation.django.calculation import Calculation
from aiida.orm.implementation.general.calculation.job import (
    AbstractJobCalculation)
from aiida.common.old_pluginloader import from_type_to_pluginclassname
from aiida.utils import timezone



[docs]class JobCalculation(AbstractJobCalculation, Calculation):
[docs] def _set_state(self, state): """ Set the state of the calculation. Set it in the DbCalcState to have also the uniqueness check. Moreover (except for the IMPORTED state) also store in the 'state' attribute, useful to know it also after importing, and for faster querying. .. todo:: Add further checks to enforce that the states are set in order? :param state: a string with the state. This must be a valid string, from ``aiida.common.datastructures.calc_states``. :raise: ModificationNotAllowed if the given state was already set. """ from aiida.common.datastructures import sort_states from aiida.backends.djsite.db.models import DbCalcState if not self.is_stored: raise ModificationNotAllowed("Cannot set the calculation state " "before storing") if state not in calc_states: raise ValueError( "'{}' is not a valid calculation status".format(state)) old_state = self.get_state() if old_state: state_sequence = [state, old_state] # sort from new to old: if they are equal, then it is a valid # advance in state (otherwise, we are going backwards...) if sort_states(state_sequence) != state_sequence: raise ModificationNotAllowed("Cannot change the state from {} " "to {}".format(old_state, state)) try: with transaction.atomic(): new_state = DbCalcState(dbnode=self.dbnode, state=state).save() except IntegrityError: raise ModificationNotAllowed( "Calculation pk= {} already transited through " "the state {}".format(self.pk, state)) # For non-imported states, also set in the attribute (so that, if we # export, we can still see the original state the calculation had. if state != calc_states.IMPORTED: self._set_attr('state', state)
[docs] def get_state(self, from_attribute=False): """ Get the state of the calculation. .. note:: this method returns the NOTFOUND state if no state is found in the DB. .. note:: the 'most recent' state is obtained using the logic in the ``aiida.common.datastructures.sort_states`` function. .. todo:: Understand if the state returned when no state entry is found in the DB is the best choice. :param from_attribute: if set to True, read it from the attributes (the attribute is also set with set_state, unless the state is set to IMPORTED; in this way we can also see the state before storing). :return: a string. If from_attribute is True and no attribute is found, return None. If from_attribute is False and no entry is found in the DB, return the "NOTFOUND" state. """ from aiida.backends.djsite.db.models import DbCalcState if from_attribute: return self.get_attr('state', None) else: if not self.is_stored: return calc_states.NEW else: this_calc_states = DbCalcState.objects.filter( dbnode=self).values_list('state', flat=True) if not this_calc_states: return None else: try: most_recent_state = sort_states(this_calc_states)[0] except ValueError as e: raise DbContentError("Error in the content of the " "DbCalcState table ({})".format( e.message)) return most_recent_state
[docs] @classmethod def _list_calculations_old(cls, states=None, past_days=None, group=None, group_pk=None, all_users=False, pks=[], relative_ctime=True): """ Return a string with a description of the AiiDA calculations. .. todo:: does not support the query for the IMPORTED state (since it checks the state in the Attributes, not in the DbCalcState table). Decide which is the correct logic and implement the correct query. :param states: a list of string with states. If set, print only the calculations in the states "states", otherwise shows all. Default = None. :param past_days: If specified, show only calculations that were created in the given number of past days. :param group: If specified, show only calculations belonging to a user-defined group with the given name. Can use colons to separate the group name from the type, as specified in :py:meth:`aiida.orm.group.Group.get_from_string` method. :param group_pk: If specified, show only calculations belonging to a user-defined group with the given PK. :param pks: if specified, must be a list of integers, and only calculations within that list are shown. Otherwise, all calculations are shown. If specified, sets state to None and ignores the value of the ``past_days`` option.") :param relative_ctime: if true, prints the creation time relative from now. (like 2days ago). Default = True :param all_users: if True, list calculation belonging to all users. Default = False :return: a string with description of calculations. """ # I assume that calc_states are strings. If this changes in the future, # update the filter below from dbattributes__tval to the correct field. from aiida.backends.djsite.db.models import DbAuthInfo, DbAttribute from aiida.daemon.timestamps import get_last_daemon_timestamp if states: for state in states: if state not in calc_states: return "Invalid state provided: {}.".format(state) warnings_list = [] now = timezone.now() if pks: q_object = Q(pk__in=pks) else: q_object = Q() if group is not None: g_pk = Group.get_from_string(group).pk q_object.add(Q(dbgroups__pk=g_pk), Q.AND) if group_pk is not None: q_object.add(Q(dbgroups__pk=group_pk), Q.AND) if not all_users: q_object.add(Q(user=get_automatic_user()), Q.AND) if states is not None: q_object.add(Q(dbattributes__key='state', dbattributes__tval__in=states, ), Q.AND) if past_days is not None: now = timezone.now() n_days_ago = now - datetime.timedelta(days=past_days) q_object.add(Q(ctime__gte=n_days_ago), Q.AND) calc_list_pk = list( cls.query(q_object).distinct().values_list('pk', flat=True)) calc_list = cls.query(pk__in=calc_list_pk).order_by('ctime') scheduler_states = dict( DbAttribute.objects.filter(dbnode__pk__in=calc_list_pk, key='scheduler_state').values_list( 'dbnode__pk', 'tval')) # I do the query now, so that the list of pks gets cached calc_list_data = list( calc_list.filter( # dbcomputer__dbauthinfo__aiidauser=F('user') ).distinct().order_by('ctime').values( 'pk', 'dbcomputer__name', 'ctime', 'type', 'dbcomputer__enabled', 'dbcomputer__pk', 'user__pk')) list_comp_pk = [i['dbcomputer__pk'] for i in calc_list_data] list_aiduser_pk = [i['user__pk'] for i in calc_list_data] enabled_data = DbAuthInfo.objects.filter( dbcomputer__pk__in=list_comp_pk, aiidauser__pk__in=list_aiduser_pk ).values_list('dbcomputer__pk', 'aiidauser__pk', 'enabled') enabled_auth_dict = {(i[0], i[1]): i[2] for i in enabled_data} states = {c.pk: c._get_state_string() for c in calc_list} scheduler_lastcheck = dict(DbAttribute.objects.filter( dbnode__in=calc_list, key='scheduler_lastchecktime').values_list('dbnode__pk', 'dval')) ## Get the last daemon check try: last_daemon_check = get_last_daemon_timestamp('updater', when='stop') except ValueError: last_check_string = ("# Last daemon state_updater check: " "(Error while retrieving the information)") else: if last_daemon_check is None: last_check_string = "# Last daemon state_updater check: (Never)" else: last_check_string = ("# Last daemon state_updater check: " "{} ({})".format( str_timedelta(now - last_daemon_check, negative_to_zero=True), timezone.localtime(last_daemon_check).strftime( "at %H:%M:%S on %Y-%m-%d"))) disabled_ignorant_states = [ None, calc_states.FINISHED, calc_states.SUBMISSIONFAILED, calc_states.RETRIEVALFAILED, calc_states.PARSINGFAILED, calc_states.FAILED ] if not calc_list: return last_check_string else: # first save a matrix of results to be printed res_str_list = [last_check_string] str_matrix = [] title = ['# Pk', 'State', 'Creation', 'Sched. state', 'Computer', 'Type'] str_matrix.append(title) len_title = [len(i) for i in title] for calcdata in calc_list_data: remote_state = "None" calc_state = states[calcdata['pk']] remote_computer = calcdata['dbcomputer__name'] try: sched_state = scheduler_states.get(calcdata['pk'], None) if sched_state is None: remote_state = "(unknown)" else: remote_state = '{}'.format(sched_state) if calc_state == calc_states.WITHSCHEDULER: last_check = scheduler_lastcheck.get(calcdata['pk'], None) if last_check is not None: when_string = " {}".format( str_timedelta(now - last_check, short=True, negative_to_zero=True)) verb_string = "was " else: when_string = "" verb_string = "" remote_state = "{}{}{}".format(verb_string, sched_state, when_string) except ValueError: raise calc_module = \ from_type_to_pluginclassname(calcdata['type']).rsplit(".", 1)[0] prefix = 'calculation.job.' prefix_len = len(prefix) if calc_module.startswith(prefix): calc_module = calc_module[prefix_len:].strip() if relative_ctime: calc_ctime = str_timedelta(now - calcdata['ctime'], negative_to_zero=True, max_num_fields=1) else: calc_ctime = " ".join([timezone.localtime( calcdata['ctime']).isoformat().split('T')[0], timezone.localtime(calcdata[ 'ctime']).isoformat().split( 'T')[1].split('.')[ 0].rsplit(":", 1)[0]]) the_state = states[calcdata['pk']] # decide if it is needed to print enabled/disabled information # By default, if the computer is not configured for the # given user, assume it is user_enabled user_enabled = enabled_auth_dict.get( (calcdata['dbcomputer__pk'], calcdata['user__pk']), True) global_enabled = calcdata["dbcomputer__enabled"] enabled = "" if (user_enabled and global_enabled or the_state in disabled_ignorant_states) else " [Disabled]" str_matrix.append([calcdata['pk'], the_state, calc_ctime, remote_state, remote_computer + "{}".format(enabled), calc_module ]) # prepare a formatted text of minimal row length (to fit in terminals!) rows = [] for j in range(len(str_matrix[0])): rows.append([len(str(i[j])) for i in str_matrix]) line_lengths = [str(max(max(rows[i]), len_title[i])) for i in range(len(rows))] fmt_string = "{:<" + "}|{:<".join(line_lengths) + "}" for row in str_matrix: res_str_list.append(fmt_string.format(*[str(i) for i in row])) res_str_list += ["# {}".format(_) for _ in warnings_list] return "\n".join(res_str_list)