1# coding: utf-8
2"""
3This module defines the events signaled by abinit during the execution. It also
4provides a parser to extract these events form the main output file and the log file.
5"""
6import sys
7import os.path
8import datetime
9import collections
10import ruamel.yaml as yaml
11import abc
12import logging
13import numpy as np
14
15from monty.string import indent, is_string
16from monty.fnmatch import WildCard
17from monty.termcolor import colored
18from monty.inspect import all_subclasses
19from monty.json import MontyDecoder
20from pymatgen.core.structure import Structure
21from monty.json import MSONable
22from pymatgen.util.serialization import pmg_serialize
23from pymatgen.io.abinit.abiinspect import YamlTokenizer
24
25logger = logging.getLogger(__name__)
26
27__all__ = [
28    "EventsParser",
29    "get_event_handler_classes",
30    "ScfConvergenceWarning",
31    "NscfConvergenceWarning",
32    "RelaxConvergenceWarning",
33    "Correction",
34    "DilatmxError",
35    "DilatmxErrorHandler",
36]
37
38
39def straceback():
40    """Returns a string with the traceback."""
41    import traceback
42    return traceback.format_exc()
43
44
45class AbinitEvent(yaml.YAMLObject):
46    """
47    Example (YAML syntax)::
48
49        Normal warning without any handler:
50
51        --- !Warning
52        message: |
53            This is a normal warning that won't
54            trigger any handler in the python code!
55        src_file: routine_name
56        src_line:  112
57        ...
58
59        Critical warning that will trigger some action in the python code.
60
61        --- !ScfConvergeWarning
62        message: |
63            The human-readable message goes here!
64        src_file: foo.F90
65        src_line: 112
66        tolname: tolwfr
67        actual_tol: 1.0e-8
68        required_tol: 1.0e-10
69        nstep: 50
70        ...
71
72    The algorithm to extract the YAML sections is very simple.
73
74    1) We use YamlTokenizer to extract the documents from the output file
75    2) If we have a tag that ends with "Warning", "Error", "Bug", "Comment
76       we know we have encountered a new ABINIT event
77    3) We parse the document with yaml.safe_load(doc.text) and we get the object
78
79    Note that:
80        # --- and ... become reserved words (whey they are placed at
81          the begining of a line) since they are used to mark the beginning and
82          the end of YAML documents.
83
84        # All the possible events should subclass `AbinitEvent` and define
85          the class attribute yaml_tag so that yaml.safe_load will know how to
86          build the instance.
87    """
88    color = None
89
90    def __init__(self, src_file, src_line, message):
91        """
92        Basic constructor for :class:`AbinitEvent`.
93
94        Args:
95            message: String with human-readable message providing info on the event.
96            src_file: String with the name of the Fortran file where the event is raised.
97            src_line Integer giving the line number in src_file.
98        """
99        #print("src_file", src_file, "src_line", src_line)
100        self.message = message
101        self.src_file = src_file
102        self.src_line = src_line
103
104    @pmg_serialize
105    def as_dict(self):
106        # This is needed because the events printed in the main output file do not define scr_file and src_line
107        src_file = getattr(self, "src_file", "Unknown")
108        src_line = getattr(self, "src_line", 0)
109        return dict(message=self.message, src_file=src_file, src_line=src_line, yaml_tag=self.yaml_tag)
110
111    @classmethod
112    def from_dict(cls, d):
113        cls = as_event_class(d.get("yaml_tag"))
114        return cls(**{k: v for k, v in d.items() if k != "yaml_tag" and not k.startswith("@")})
115
116    @property
117    def header(self):
118        try:
119            return "<%s at %s:%s>" % (self.name, self.src_file, self.src_line)
120        except AttributeError:
121            # This is needed because the events printed in the main output file do not define scr_file and src_line
122            return "<%s at %s:%s>" % (self.name, "Unknown", 0)
123
124    def __repr__(self):
125        return self.header
126
127    def __str__(self):
128        return "\n".join((self.header, self.message))
129
130    def __eq__(self, other):
131        if other is None: return False
132        return self.message == other.message
133
134    def __ne__(self, other):
135        return not self.__eq__(other)
136
137    @property
138    def name(self):
139        """Name of the event (class name)"""
140        return self.__class__.__name__
141
142    @property
143    def baseclass(self):
144        """The baseclass of self."""
145        for cls in _BASE_CLASSES:
146            if isinstance(self, cls):
147                return cls
148
149        raise ValueError("Cannot determine the base class of %s" % self.__class__.__name__)
150
151    def correct(self, task):
152        """
153        This method is called when an error is detected in a :class:`Task`
154        It should perform any corrective measures relating to the detected error.
155        The idea is similar to the one used in custodian but the handler receives
156        a :class:`Task` object so that we have access to its methods.
157
158        Returns:
159        (dict) JSON serializable dict that describes the errors and actions taken. E.g.
160        {"errors": list_of_errors, "actions": list_of_actions_taken}.
161        If this is an unfixable error, actions should be set to None.
162        """
163        return 0
164
165
166class AbinitComment(AbinitEvent):
167    """Base class for Comment events"""
168    yaml_tag = '!COMMENT'
169    color = "blue"
170
171
172class AbinitError(AbinitEvent):
173    """Base class for Error events"""
174    yaml_tag = '!ERROR'
175    color = "red"
176
177
178class AbinitYamlError(AbinitError):
179    """
180    Raised if the YAML parser cannot parse the document and the doc tag is an Error.
181    It's an AbinitError because the msg produced by the code is not valid YAML!
182    """
183
184
185class AbinitBug(AbinitEvent):
186    """Base class for Bug events"""
187    yaml_tag = '!BUG'
188    color = "red"
189
190
191class AbinitWarning(AbinitEvent):
192    """
193    Base class for Warning events (the most important class).
194    Developers should subclass this class to define the different exceptions
195    raised by the code and the possible actions that can be performed.
196    """
197    yaml_tag = '!WARNING'
198    color = "magenta"
199
200
201class AbinitCriticalWarning(AbinitWarning):
202    color = "red"
203
204
205class AbinitYamlWarning(AbinitCriticalWarning):
206    """
207    Raised if the YAML parser cannot parse the document and the doc tas is a Warning.
208    """
209
210###############################
211# Warnings triggering restart #
212###############################
213
214
215class ScfConvergenceWarning(AbinitCriticalWarning):
216    """Warning raised when the GS SCF cycle did not converge."""
217    yaml_tag = '!ScfConvergenceWarning'
218
219
220class NscfConvergenceWarning(AbinitCriticalWarning):
221    """Warning raised when the GS NSCF cycle did not converge."""
222    yaml_tag = '!NscfConvergenceWarning'
223
224
225class RelaxConvergenceWarning(AbinitCriticalWarning):
226    """Warning raised when the structural relaxation did not converge."""
227    yaml_tag = '!RelaxConvergenceWarning'
228
229
230# TODO: for the time being we don't discern between GS and PhononCalculations.
231#class PhononConvergenceWarning(AbinitCriticalWarning):
232#    """Warning raised when the phonon calculation did not converge."""
233#    yaml_tag = u'!PhononConvergenceWarning'
234
235
236class QPSConvergenceWarning(AbinitCriticalWarning):
237    """Warning raised when the QPS iteration (GW) did not converge."""
238    yaml_tag = '!QPSConvergenceWarning'
239
240
241class HaydockConvergenceWarning(AbinitCriticalWarning):
242    """Warning raised when the Haydock method (BSE) did not converge."""
243    yaml_tag = '!HaydockConvergenceWarning'
244
245
246# Error classes providing a correct method.
247
248# Register the concrete base classes.
249_BASE_CLASSES = [
250    AbinitComment,
251    AbinitError,
252    AbinitBug,
253    AbinitWarning,
254]
255
256
257class EventReport(collections.abc.Iterable, MSONable):
258    """
259    Iterable storing the events raised by an ABINIT calculation.
260
261    Attributes::
262
263        stat: information about a file as returned by os.stat
264    """
265    def __init__(self, filename, events=None):
266        """
267        List of ABINIT events.
268
269        Args:
270            filename: Name of the file
271            events: List of Event objects
272        """
273        self.filename = os.path.abspath(filename)
274        self.stat = os.stat(self.filename)
275        self.start_datetime, self.end_datetime = None, None
276
277        self._events = []
278        self._events_by_baseclass = collections.defaultdict(list)
279
280        if events is not None:
281            for ev in events:
282                self.append(ev)
283
284    def __len__(self):
285        return len(self._events)
286
287    def __iter__(self):
288        return self._events.__iter__()
289
290    def __getitem__(self, slice):
291        return self._events[slice]
292
293    def __str__(self):
294        #has_colours = stream_has_colours(stream)
295        has_colours = True
296
297        lines = []
298        app = lines.append
299
300        app("Events found in %s\n" % self.filename)
301        for i, event in enumerate(self):
302            if has_colours:
303                app("[%d] %s" % (i+1, colored(event.header, color=event.color)))
304                app(indent(event.message, 4))
305            else:
306                app("[%d] %s" % (i+1, str(event)))
307
308        app("num_errors: %s, num_warnings: %s, num_comments: %s, completed: %s\n" % (
309            self.num_errors, self.num_warnings, self.num_comments, self.run_completed))
310
311        return "\n".join(lines)
312
313    def append(self, event):
314        """Add an event to the list."""
315        self._events.append(event)
316        self._events_by_baseclass[event.baseclass].append(event)
317
318    def set_run_completed(self, boolean, start_datetime, end_datetime):
319        """Set the value of _run_completed."""
320        self._run_completed = boolean
321
322        if (start_datetime, end_datetime) != (None, None):
323            # start_datetime: Sat Feb 28 23:54:27 2015
324            # end_datetime: Sat Feb 28 23:54:30 2015
325            try:
326                fmt = "%a %b %d %H:%M:%S %Y"
327                self.start_datetime = datetime.datetime.strptime(start_datetime, fmt)
328                self.end_datetime = datetime.datetime.strptime(end_datetime, fmt)
329            except Exception as exc:
330                # Maybe LOCALE != en_US
331                logger.warning(str(exc))
332
333    @property
334    def run_etime(self):
335        """Wall-time of the run as `timedelta` object."""
336        if self.start_datetime is None or self.end_datetime is None:
337            return None
338
339        return self.end_datetime - self.start_datetime
340
341    @property
342    def run_completed(self):
343        """True if the calculation terminated."""
344        try:
345            return self._run_completed
346        except AttributeError:
347            return False
348
349    @property
350    def comments(self):
351        """List of comments found."""
352        return self.select(AbinitComment)
353
354    @property
355    def errors(self):
356        """List of errors + bugs found."""
357        return self.select(AbinitError) + self.select(AbinitBug)
358
359    @property
360    def warnings(self):
361        """List of warnings found."""
362        return self.select(AbinitWarning)
363
364    @property
365    def num_warnings(self):
366        """Number of warnings reported."""
367        return len(self.warnings)
368
369    @property
370    def num_errors(self):
371        """Number of errors reported."""
372        return len(self.errors)
373
374    @property
375    def num_comments(self):
376        """Number of comments reported."""
377        return len(self.comments)
378
379    def select(self, base_class):
380        """
381        Return the list of events that inherits from class base_class
382        """
383        return self._events_by_baseclass[base_class]
384
385    def filter_types(self, event_types):
386        events = []
387        for ev in self:
388            if type(ev) in event_types: events.append(ev)
389        return self.__class__(filename=self.filename, events=events)
390
391    def get_events_of_type(self, event_class):
392        """Return a list of events of the given class."""
393        return [ev for ev in self if type(ev) == event_class]
394
395    @pmg_serialize
396    def as_dict(self):
397        return dict(filename=self.filename, events=[e.as_dict() for e in self._events])
398
399    @classmethod
400    def from_dict(cls, d):
401        return cls(filename=d["filename"], events=[AbinitEvent.from_dict(e) for e in d["events"]])
402
403
404class EventsParserError(Exception):
405    """Base class for the exceptions raised by :class:`EventsParser`."""
406
407
408class EventsParser(object):
409    """
410    Parses the output or the log file produced by ABINIT and extract the list of events.
411    """
412    Error = EventsParserError
413
414    def parse(self, filename, verbose=0):
415        """
416        Parse the given file. Return :class:`EventReport`.
417        """
418        run_completed, start_datetime, end_datetime = False, None, None
419        filename = os.path.abspath(filename)
420        report = EventReport(filename)
421
422        w = WildCard("*Error|*Warning|*Comment|*Bug|*ERROR|*WARNING|*COMMENT|*BUG")
423        import warnings
424        warnings.simplefilter('ignore', yaml.error.UnsafeLoaderWarning)
425        with YamlTokenizer(filename) as tokens:
426            for doc in tokens:
427                if w.match(doc.tag):
428                    #print("got doc.tag", doc.tag,"--")
429                    try:
430                        #print(doc.text)
431                        event = yaml.load(doc.text)   # Can't use ruamel safe_load!
432                        #yaml.load(doc.text, Loader=ruamel.yaml.Loader)
433                        #print(event.yaml_tag, type(event))
434                    except Exception:
435                        #raise
436                        # Wrong YAML doc. Check tha doc tag and instantiate the proper event.
437                        message = "Malformatted YAML document at line: %d\n" % doc.lineno
438                        message += doc.text
439
440                        # This call is very expensive when we have many exceptions due to malformatted YAML docs.
441                        if verbose:
442                            message += "Traceback:\n %s" % straceback()
443
444                        if "error" in doc.tag.lower():
445                            print("It seems an error. doc.tag:", doc.tag)
446                            event = AbinitYamlError(message=message, src_file=__file__, src_line=0)
447                        else:
448                            event = AbinitYamlWarning(message=message, src_file=__file__, src_line=0)
449
450                    event.lineno = doc.lineno
451                    report.append(event)
452
453                # Check whether the calculation completed.
454                if doc.tag == "!FinalSummary":
455                    #print(doc)
456                    run_completed = True
457                    d = doc.as_dict()
458                    #print(d)
459                    start_datetime, end_datetime = d["start_datetime"], d["end_datetime"]
460
461        report.set_run_completed(run_completed, start_datetime, end_datetime)
462        return report
463
464    def report_exception(self, filename, exc):
465        """
466        This method is used when self.parser raises an Exception so that
467        we can report a customized :class:`EventReport` object with info the exception.
468        """
469        # Build fake event.
470        event = AbinitError(src_file="Unknown", src_line=0, message=str(exc))
471        return EventReport(filename, events=[event])
472
473
474class EventHandler(MSONable, metaclass=abc.ABCMeta):
475    """
476    Abstract base class defining the interface for an EventHandler.
477
478    The__init__ should always provide default values for its arguments so that we can
479    easily instantiate the handlers with:
480
481        handlers = [cls() for cls in get_event_handler_classes()]
482
483    The defaul values should be chosen so to cover the most typical cases.
484
485    Each EventHandler should define the class attribute `can_change_physics`
486    that is true if the handler changes `important` parameters of the
487    run that are tightly connected to the physics of the system.
488
489    For example, an `EventHandler` that changes the value of `dilatmx` and
490    prepare the restart is not changing the physics. Similarly a handler
491    that changes the mixing algorithm. On the contrary, a handler that
492    changes the value of the smearing is modifying an important physical
493    parameter, and the user should be made aware of this so that
494    there's an explicit agreement between the user and the code.
495
496    The default handlers are those that do not change the physics,
497    other handlers can be installed by the user when constructing with the flow with
498
499        TODO
500
501    .. warning::
502
503        The EventHandler should perform any action at the level of the input files
504        needed to solve the problem and then prepare the task for a new submission
505        The handler should never try to resubmit the task. The submission must be
506        delegated to the scheduler or Fireworks.
507    """
508
509    event_class = AbinitEvent
510    """AbinitEvent subclass associated to this handler."""
511
512    #can_change_physics
513
514    FIXED = 1
515    NOT_FIXED = 0
516
517    def __init__(self):
518        """Simple init for compatibility with introspection in as_dict/from_dict"""
519        return super().__init__()
520
521    @classmethod
522    def cls2str(cls):
523        lines = []
524        app = lines.append
525
526        ecls = cls.event_class
527        app("event name = %s" % ecls.yaml_tag)
528        app("event documentation: ")
529        lines.extend(ecls.__doc__.split("\n"))
530        app("handler documentation: ")
531        lines.extend(cls.__doc__.split("\n"))
532
533        return "\n".join(lines)
534
535    def __str__(self):
536        return "<%s>" % self.__class__.__name__
537
538    def can_handle(self, event):
539        """True if this handler is associated to the given :class:`AbinitEvent`"""
540        return self.event_class == event.__class__
541
542    # TODO: defined CorrectionRecord object and provide helper functions to build it
543
544    def count(self, task):
545        """
546        Return the number of times the event associated to this handler
547        has been already fixed in the :class:`Task`.
548        """
549        return len([c for c in task.corrections if c["event"]["@class"] == self.event_class])
550
551    @abc.abstractmethod
552    def handle_task_event(self, task, event):
553        """
554        Method to handle Abinit events.
555
556        Args:
557            task: :class:`Task` object.
558            event: :class:`AbinitEvent` found in the log file.
559
560        Return:
561            0 if no action has been applied, 1 if the problem has been fixed.
562        """
563
564    @pmg_serialize
565    def as_dict(self):
566        """
567        Basic implementation of as_dict if __init__ has no arguments. Subclasses may need to overwrite.
568        """
569
570        d = {}
571        return d
572
573    @classmethod
574    def from_dict(cls, d):
575        """
576        Basic implementation of from_dict if __init__ has no arguments. Subclasses may need to overwrite.
577        """
578
579        return cls()
580
581    @classmethod
582    def compare_inputs(cls, new_input, old_input):
583
584        def vars_dict(d):
585            """
586            make a simple dictionary and convert numpy arrays to lists
587            """
588            new_d = {}
589            for key, value in d.items():
590                if isinstance(value, np.ndarray): value = value.tolist()
591                new_d[key] = value
592
593            return new_d
594
595        new_vars = vars_dict(new_input)
596        old_vars = vars_dict(old_input)
597
598        new_keys = set(new_vars.keys())
599        old_keys = set(old_vars.keys())
600        intersect = new_keys.intersection(old_keys)
601
602        added_keys = new_keys - intersect
603        removed_keys = old_keys - intersect
604        changed_keys = set(v for v in intersect if new_vars[v] != old_vars[v])
605
606        log_diff = {}
607        if added_keys:
608            log_diff['_set'] = {k: new_vars[k] for k in added_keys}
609
610        if changed_keys:
611            log_diff['_update'] = ({k: {'new': new_vars[k], 'old': old_vars[k]} for k in changed_keys})
612
613        if new_input.structure != old_input.structure:
614            log_diff['_change_structure'] = new_input.structure.as_dict()
615
616        if removed_keys:
617            log_diff['_pop'] = {k: old_vars[k] for k in removed_keys}
618
619        return log_diff
620
621
622class Correction(MSONable):
623
624    def __init__(self, handler, actions, event, reset=False):
625        self.handler = handler
626        self.actions = actions
627        self.event = event
628        self.reset = reset
629
630    @pmg_serialize
631    def as_dict(self):
632        return dict(handler=self.handler.as_dict(), actions=self.actions, event=self.event.as_dict(), reset=self.reset)
633
634    @classmethod
635    def from_dict(cls, d):
636        dec = MontyDecoder()
637        return cls(handler=dec.process_decoded(d['handler']), actions=d['actions'],
638                   event=dec.process_decoded(d['event']), reset=d['reset'])
639
640
641#class WarningHandler(EventHandler):
642#    """Base class for handlers associated to ABINIT warnings."""
643#    event_class = AbinitWarning
644#
645#class BugHandler(EventHandler):
646#    """Base class for handlers associated to ABINIT bugs."""
647#    event_class = AbinitBug
648
649
650class ErrorHandler(EventHandler):
651    """Base class for handlers associated to ABINIT errors."""
652    event_class = AbinitError
653
654
655_ABC_EVHANDLER_CLASSES = set([ErrorHandler,])
656
657
658# Public API
659def autodoc_event_handlers(stream=sys.stdout):
660    """
661    Print to the given string, the documentation for the events
662    and the associated handlers.
663    """
664    lines = []
665    for cls in all_subclasses(EventHandler):
666        if cls in _ABC_EVHANDLER_CLASSES: continue
667        event_class = cls.event_class
668        lines.extend(cls.cls2str().split("\n"))
669
670        # Here we enforce the abstract protocol of the class
671        # The unit test in tests_events will detect the problem.
672        if not hasattr(cls, "can_change_physics"):
673            raise RuntimeError("%s: can_change_physics must be defined" % cls)
674
675    stream.write("\n".join(lines) + "\n")
676
677
678def get_event_handler_classes(categories=None):
679    """Return the list of handler classes."""
680    classes = [c for c in all_subclasses(EventHandler) if c not in _ABC_EVHANDLER_CLASSES]
681    return classes
682
683
684def as_event_class(obj):
685    """
686    Convert obj into a subclass of AbinitEvent.
687    obj can be either a class or a string with the class name or the YAML tag
688    """
689    if is_string(obj):
690        for c in all_subclasses(AbinitEvent):
691            if c.__name__ == obj or c.yaml_tag == obj: return c
692        raise ValueError("Cannot find event class associated to %s" % obj)
693
694    # Assume class.
695    assert obj in all_subclasses(AbinitEvent)
696    return obj
697
698
699############################################
700########## Concrete classes ################
701############################################
702
703class DilatmxError(AbinitError):
704    """
705    This Error occurs in variable cell calculations when the increase in the
706    unit cell volume is too large.
707    """
708    yaml_tag = '!DilatmxError'
709
710
711class DilatmxErrorHandler(ErrorHandler):
712    """
713    Handle DilatmxError. Abinit produces a netcdf file with the last structure before aborting
714    The handler changes the structure in the input with the last configuration and modify the value of dilatmx.
715    """
716    event_class = DilatmxError
717
718    can_change_physics = False
719
720    def __init__(self, max_dilatmx=1.3):
721        self.max_dilatmx = max_dilatmx
722
723    @pmg_serialize
724    def as_dict(self):
725        return {'max_dilatmx': self.max_dilatmx}
726
727    @classmethod
728    def from_dict(cls, d):
729        return cls(max_dilatmx=d['max_dilatmx'])
730
731    def handle_task_event(self, task, event):
732        # Read the last structure dumped by ABINIT before aborting.
733        filepath = task.outdir.has_abiext("DILATMX_STRUCT.nc")
734        last_structure = Structure.from_file(filepath)
735
736        task._change_structure(last_structure)
737
738        #read the suggested dilatmx
739        # new_dilatmx = 1.05
740        # if new_dilatmx > self.max_dilatmx:
741        #     msg = "Suggested dilatmx ({}) exceeds maximux configured value ({}).".format(new_dilatmx, self.max_dilatmx)
742        #     return self.NOT_FIXED
743        # task.strategy.abinit_input.set_vars(dilatmx=new_dilatmx)
744        msg = "Take last structure from DILATMX_STRUCT.nc, will try to restart with dilatmx %s" % task.get_inpvar("dilatmx")
745        task.log_correction(event, msg)
746        # Note that we change the structure but we don't try restart from the previous WFK|DEN file
747        # because Abinit called mpi_abort and therefore no final WFK|DEN file has been produced.
748
749        return self.FIXED
750
751    def handle_input_event(self, abiinput, outdir, event):
752        try:
753            old_abiinput = abiinput.deepcopy()
754            # Read the last structure dumped by ABINIT before aborting.
755            filepath = outdir.has_abiext("DILATMX_STRUCT.nc")
756            last_structure = Structure.from_file(filepath)
757            abiinput.set_structure(last_structure)
758            #FIXME restart from DEN files not always working with interpolation
759            return Correction(self, self.compare_inputs(abiinput, old_abiinput), event, reset=True)
760            # return Correction(self, self.compare_inputs(abiinput, old_abiinput), event, event=False)
761        except Exception as exc:
762            logger.warning('Error while trying to apply the handler {}.'.format(str(self)), exc)
763            return None
764
765
766class TolSymError(AbinitError):
767    """
768    Class of errors raised by Abinit when it cannot detect the symmetries of the system.
769    The handler assumes the structure makes sense and the error is just due to numerical inaccuracies.
770    We increase the value of tolsym in the input file (default 1-8) so that Abinit can find the space group
771    and re-symmetrize the input structure.
772    """
773    yaml_tag = '!TolSymError'
774
775
776class TolSymErrorHandler(ErrorHandler):
777    """
778    Increase the value of tolsym in the input file.
779    """
780    event_class = TolSymError
781
782    can_change_physics = False
783
784    def __init__(self, max_nfixes=3):
785        self.max_nfixes = max_nfixes
786
787    @pmg_serialize
788    def as_dict(self):
789        return {'max_nfixes': self.max_nfixes}
790
791    @classmethod
792    def from_dict(cls, d):
793        return cls(max_nfixes=d['max_nfixes'])
794
795    def handle_task_event(self, task, event):
796        # TODO: Add limit on the number of fixes one can do for the same error
797        # For example in this case, the scheduler will stop after 20 submissions
798        if self.count(task) > self.max_nfixes:
799            return self.NOT_FIXED
800
801        old_tolsym = task.get_inpvar("tolsym")
802        new_tolsym = 1e-6 if old_tolsym is None else old_tolsym * 10
803        task.set_vars(tolsym=new_tolsym)
804
805        task.log_correction(event, "Increasing tolsym from %s to %s" % (old_tolsym, new_tolsym))
806        return self.FIXED
807
808    def handle_input_event(self, abiinput, outdir, event):
809        try:
810            old_abiinput = abiinput.deepcopy()
811            old_tolsym = abiinput["tolsym"]
812            new_tolsym = 1e-6 if old_tolsym is None else old_tolsym * 10
813            abiinput.set_vars(tolsym=new_tolsym)
814            return Correction(self, self.compare_inputs(abiinput, old_abiinput), event, reset=False)
815        except Exception as exc:
816            logger.warning('Error while trying to apply the handler {}.'.format(str(self)), exc)
817            return None
818
819
820class MemanaError(AbinitError):
821    """
822    Class of errors raised by the memory analyzer.
823    (the section that estimates the memory requirements from the input parameters).
824    """
825    yaml_tag = '!MemanaError'
826
827
828class MemanaErrorHandler(ErrorHandler):
829    """
830    Set mem_test to 0 to bypass the memory check.
831    """
832    event_class = MemanaError
833
834    can_change_physics = False
835
836    def handle_task_event(self, task, event):
837        task.set_vars(mem_test=0)
838        task.log_correction(event, "Find MemanaError. Setting mem_test to 0 in input file.")
839        return self.FIXED
840
841    def handle_input_event(self, abiinput, outdir, event):
842        try:
843            old_abiinput = abiinput.deepcopy()
844            abiinput.set_vars(mem_test=0)
845            return Correction(self, self.compare_inputs(abiinput, old_abiinput), event, reset=False)
846        except Exception as exc:
847            logger.warning('Error while trying to apply the handler {}.'.format(str(self)), exc)
848            return None
849
850
851class MemoryError(AbinitError):
852    """
853    This error occurs when a checked allocation fails in Abinit
854    The only way to go is to increase memory
855    """
856    yaml_tag = '!MemoryError'
857
858
859class MemoryErrorHandler(ErrorHandler):
860    """
861    Handle MemoryError. Increase the resources requirements
862    """
863    event_class = MemoryError
864
865    can_change_physics = False
866
867    def handle_task_event(self, task, event):
868        task.manager.increase_resources()
869        return self.FIXED
870
871    def handle_input_event(self, abiinput, outdir, event):
872        """
873        Shouldn't do anything on the input
874        """
875        return None
876