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