def test_empty_log(db_test_app, plugin_name): """Check if the lammps log is empty.""" retrieved = FolderData() for filename in [ 'log.lammps', 'trajectory.lammpstrj', '_scheduler-stdout.txt', '_scheduler-stderr.txt', ]: retrieved.put_object_from_filelike(io.StringIO(''), filename) calc_node = db_test_app.generate_calcjob_node(plugin_name, retrieved) parser = ParserFactory(plugin_name) with db_test_app.sandbox_folder() as temp_path: with temp_path.open('x-trajectory.lammpstrj', 'w'): pass results, calcfunction = parser.parse_from_node( # pylint: disable=unused-variable calc_node, retrieved_temporary_folder=temp_path.abspath, ) assert calcfunction.is_finished, calcfunction.exception assert calcfunction.is_failed, calcfunction.exit_status assert (calcfunction.exit_status == calc_node.process_class.exit_codes.ERROR_LOG_PARSING.status)
def test_stream_history(request, calc_with_retrieved): """Test that the stream parser keeps history.""" file_path = str( request.fspath.join('..') + '../../../test_data/stdout/out') # turn of everything, except misc settings_dict = { 'parser_settings': { 'add_trajectory': False, 'add_bands': False, 'add_chgcar': False, 'add_dos': False, 'add_kpoints': False, 'add_energies': False, 'add_misc': ['notifications'], 'add_structure': False, 'add_projectors': False, 'add_born_charges': False, 'add_dielectrics': False, 'add_hessian': False, 'add_dynmat': False, 'add_wavecar': False, 'add_site_magnetization': False, 'stream_config': { 'random_error': { 'kind': 'ERROR', 'regex': 'I AM A WELL DEFINED ERROR', 'message': 'Okey, this error you do not want.', 'suggestion': '', 'location': 'STDOUT', 'recover': False } }, 'stream_history': True } } node = calc_with_retrieved(file_path, settings_dict) parser_cls = ParserFactory('vasp.vasp') result, _ = parser_cls.parse_from_node( node, store_provenance=False, retrieved_temporary_folder=file_path) misc = result['misc'] misc_dict = misc.get_dict() assert len(misc_dict['notifications']) == 3 assert misc_dict['notifications'][0]['name'] == 'ibzkpt' assert misc_dict['notifications'][0]['kind'] == 'ERROR' assert misc_dict['notifications'][0][ 'regex'] == 'internal error in subroutine IBZKPT' assert misc_dict['notifications'][1]['name'] == 'random_error' assert misc_dict['notifications'][1]['kind'] == 'ERROR' assert misc_dict['notifications'][1][ 'regex'] == 'I AM A WELL DEFINED ERROR' assert misc_dict['notifications'][2]['name'] == 'random_error' assert misc_dict['notifications'][2]['kind'] == 'ERROR' assert misc_dict['notifications'][2][ 'regex'] == 'I AM A WELL DEFINED ERROR' for item in misc_dict['notifications']: assert item['kind'] != 'WARNING'
def test_parser_nodes(request, calc_with_retrieved): """Test a few basic node items of the parser.""" from aiida.plugins import ParserFactory settings_dict = { 'parser_settings': { 'add_bands': True, 'add_kpoints': True, 'add_misc': ['fermi_level'] } } file_path = str(request.fspath.join('..') + '../../../test_data/basic') node = calc_with_retrieved(file_path, settings_dict) parser_cls = ParserFactory('vasp.vasp') result, _ = parser_cls.parse_from_node( node, store_provenance=False, retrieved_temporary_folder=file_path) misc = result['misc'] bands = result['bands'] kpoints = result['kpoints'] assert isinstance(misc, get_data_class('dict')) assert isinstance(bands, get_data_class('array.bands')) assert isinstance(kpoints, get_data_class('array.kpoints')) assert misc.get_dict()['fermi_level'] == 5.96764939
def test_misc(request, calc_with_retrieved): """Test that it is possible to extract misc from both vasprun and OUTCAR.""" # turn of everything, except misc settings_dict = { 'parser_settings': { 'add_trajectory': False, 'add_bands': False, 'add_chgcar': False, 'add_dos': False, 'add_kpoints': False, 'add_energies': False, 'add_misc': [ 'fermi_level', 'maximum_stress', 'maximum_force', 'total_energies', 'symmetries' ], 'add_structure': False, 'add_projectors': False, 'add_born_charges': False, 'add_dielectrics': False, 'add_hessian': False, 'add_dynmat': False, 'add_wavecar': False, 'add_site_magnetization': False, } } file_path = str( request.fspath.join('..') + '../../../test_data/disp_details') node = calc_with_retrieved(file_path, settings_dict) parser_cls = ParserFactory('vasp.vasp') result, _ = parser_cls.parse_from_node( node, store_provenance=False, retrieved_temporary_folder=file_path) misc = result['misc'] assert isinstance(misc, get_data_class('dict')) data = misc.get_dict() # We already have a test to check if the quantities from the OUTCAR is correct, so # only perform rudimentary checks, and the content comming from the xml file. assert data['fermi_level'] == 6.17267267 assert data['maximum_stress'] == 42.96872956444064 assert data['maximum_force'] == 0.21326679 assert data['total_energies']['energy_extrapolated'] == -10.823296
def _generate_parser(entry_point_name): """Fixture to load a parser class for testing parsers. :param entry_point_name: entry point name of the parser class :return: the `Parser` sub class """ from aiida.plugins import ParserFactory return ParserFactory(entry_point_name)
def test_missing_log(db_test_app, plugin_name): """Check if the log file is produced during calculation.""" retrieved = FolderData() calc_node = db_test_app.generate_calcjob_node(plugin_name, retrieved) parser = ParserFactory(plugin_name) with db_test_app.sandbox_folder() as temp_path: results, calcfunction = parser.parse_from_node( # pylint: disable=unused-variable calc_node, retrieved_temporary_folder=temp_path.abspath, ) assert calcfunction.is_finished, calcfunction.exception assert calcfunction.is_failed, calcfunction.exit_status assert (calcfunction.exit_status == calc_node.process_class.exit_codes.ERROR_LOG_FILE_MISSING.status)
def _pop_parser_options(calc_job_instance, settings_dict, ignore_errors=True): """This deletes any parser options from the settings dictionary. The parser options key is found via the get_parser_settings_key() method of the parser class specified as a metadata input.""" from aiida.plugins import ParserFactory from aiida.common import EntryPointError try: parser_name = calc_job_instance.inputs['metadata']['options']['parser_name'] ParserClass = ParserFactory(parser_name) parser_opts_key = ParserClass.get_parser_settings_key().upper() return settings_dict.pop(parser_opts_key, None) except (KeyError, EntryPointError, AttributeError) as exc: # KeyError: input 'metadata.options.parser_name' is not defined; # EntryPointError: there was an error loading the parser class form its entry point # (this will probably cause errors elsewhere too); # AttributeError: the parser class doesn't have a method get_parser_settings_key(). if ignore_errors: pass else: raise exc
def parse_from_node(entry_point_name, node, retrieved_temp=None): """Parse the outputs directly from the `CalcJobNode`. Parameters ---------- entry_point_name : str entry point name of the parser class """ return ParserFactory(entry_point_name).parse_from_node( node, retrieved_temporary_folder=retrieved_temp)
def vasp_parser_with_test(calc_with_retrieved): """Fixture providing a VaspParser instance coupled to a VaspCalculation.""" parser, file_path, node = _get_vasp_parser(calc_with_retrieved) parser.add_parser_definition('_scheduler-stderr.txt', { 'parser_class': ExampleFileParser, 'is_critical': False }) success = parser.parse(retrieved_temporary_folder=file_path) try: yield parser finally: parser = ParserFactory('vasp.vasp')(node)
def validate_parser(parser_name, ctx): """Validate the parser. :raises InputValidationError: if the parser name does not correspond to a loadable `Parser` class. """ from aiida.plugins import ParserFactory if parser_name is not plumpy.UNSPECIFIED: try: ParserFactory(parser_name) except exceptions.EntryPointError as exception: raise exceptions.InputValidationError('invalid parser specified: {}'.format(exception))
def validate_parser(parser_name, _): """Validate the parser. :return: string with error message in case the inputs are invalid """ from aiida.plugins import ParserFactory if parser_name is not plumpy.UNSPECIFIED: try: ParserFactory(parser_name) except exceptions.EntryPointError as exception: return 'invalid parser specified: {}'.format(exception)
def test_missing_traj(db_test_app, plugin_name): """Check if the trajectory file is produced during calculation.""" retrieved = FolderData() retrieved.put_object_from_filelike(io.StringIO(get_log()), 'log.lammps') retrieved.put_object_from_filelike(io.StringIO(''), '_scheduler-stdout.txt') retrieved.put_object_from_filelike(io.StringIO(''), '_scheduler-stderr.txt') calc_node = db_test_app.generate_calcjob_node(plugin_name, retrieved) parser = ParserFactory(plugin_name) with db_test_app.sandbox_folder() as temp_path: results, calcfunction = parser.parse_from_node( # pylint: disable=unused-variable calc_node, retrieved_temporary_folder=temp_path.abspath, ) assert calcfunction.is_finished, calcfunction.exception assert calcfunction.is_failed, calcfunction.exit_status assert (calcfunction.exit_status == calc_node.process_class.exit_codes.ERROR_TRAJ_FILE_MISSING.status)
def validate_parser(parser_name: Any, _: Any) -> Optional[str]: """Validate the parser. :return: string with error message in case the inputs are invalid """ from aiida.plugins import ParserFactory try: ParserFactory(parser_name) except exceptions.EntryPointError as exception: return f'invalid parser specified: {exception}' return None
def parse_from_node(entry_point_name, node, retrieved_temp=None): """Parse the outputs directly from the `CalcJobNode`. Parameters ---------- entry_point_name : str entry point name of the parser class """ from aiida.plugins import ParserFactory return parse_from_node( ParserFactory(entry_point_name), node, retrieved_temp=retrieved_temp )
def get_parser_class(self): """Return the output parser object for this calculation or None if no parser is set. :return: a `Parser` class. :raises `aiida.common.exceptions.EntryPointError`: if the parser entry point can not be resolved. """ from aiida.plugins import ParserFactory parser_name = self.get_option('parser_name') if parser_name is not None: return ParserFactory(parser_name) return None
def test_run_error(db_test_app, plugin_name): """Check if the parser runs without producing errors.""" retrieved = FolderData() retrieved.put_object_from_filelike( io.StringIO(get_log()), 'log.lammps', ) retrieved.put_object_from_filelike( io.StringIO(get_traj_force()), 'x-trajectory.lammpstrj', ) retrieved.put_object_from_filelike( io.StringIO('ERROR description'), '_scheduler-stdout.txt', ) retrieved.put_object_from_filelike( io.StringIO(''), '_scheduler-stderr.txt', ) calc_node = db_test_app.generate_calcjob_node(plugin_name, retrieved) parser = ParserFactory(plugin_name) with db_test_app.sandbox_folder() as temp_path: with temp_path.open('x-trajectory.lammpstrj', 'w') as handle: handle.write(get_traj_force()) results, calcfunction = parser.parse_from_node( # pylint: disable=unused-variable calc_node, retrieved_temporary_folder=temp_path.abspath, ) print(get_calcjob_report(calc_node)) assert calcfunction.is_finished, calcfunction.exception assert calcfunction.is_failed, calcfunction.exit_status assert (calcfunction.exit_status == calc_node.process_class.exit_codes.ERROR_LAMMPS_RUN.status)
def get_parser_cls(entry_point_name): """load a parser class Parameters ---------- entry_point_name : str entry point name of the parser class Returns ------- aiida.parsers.parser.Parser """ return ParserFactory(entry_point_name)
def vasp_parser_with_test(calc_with_retrieved): """Fixture providing a VaspParser instance coupled to a VaspCalculation.""" from aiida.plugins import ParserFactory from aiida.plugins import CalculationFactory settings_dict = { # 'ADDITIONAL_RETRIEVE_LIST': CalculationFactory('vasp.vasp')._ALWAYS_RETRIEVE_LIST, 'parser_settings': { 'add_custom': { 'link_name': 'custom_node', 'type': 'dict', 'quantities': ['quantity2', 'quantity_with_alternatives'] } } } file_path = str( os.path.abspath(os.path.dirname(__file__)) + '/../../test_data/test_relax_wc/out') node = calc_with_retrieved(file_path, settings_dict) parser = ParserFactory('vasp.vasp')(node) parser.add_file_parser('_scheduler-stderr.txt', { 'parser_class': ExampleFileParser, 'is_critical': False }) parser.add_parsable_quantity( 'quantity_with_alternatives', { 'inputs': [], 'prerequisites': [], }, ) success = parser.parse(retrieved_temporary_folder=file_path) try: yield parser finally: parser = ParserFactory('vasp.vasp')(node)
def _get_vasp_parser(calc_with_retrieved): """Return vasp parser before parsing""" settings_dict = { # 'ADDITIONAL_RETRIEVE_LIST': CalculationFactory('vasp.vasp')._ALWAYS_RETRIEVE_LIST, 'parser_settings': { 'add_custom': { 'link_name': 'custom_node', 'type': 'dict', 'quantities': ['quantity2', 'quantity_with_alternatives'] } } } file_path = str( os.path.abspath(os.path.dirname(__file__)) + '/../../test_data/basic_run') node = calc_with_retrieved(file_path, settings_dict) parser = ParserFactory('vasp.vasp')(node) return parser, file_path, node
def _generate_parser(entry_point_name): return ParserFactory(entry_point_name)
def test_structure(request, calc_with_retrieved): """Test that the structure from vasprun and POSCAR is the same.""" from aiida.plugins import ParserFactory # turn of everything, except structure settings_dict = { 'parser_settings': { 'add_trajectory': False, 'add_bands': False, 'add_chgcar': False, 'add_dos': False, 'add_kpoints': False, 'add_energies': False, 'add_misc': False, 'add_structure': True, 'add_projectors': False, 'add_born_charges': False, 'add_dielectrics': False, 'add_hessian': False, 'add_dynmat': False, 'add_wavecar': False, 'add_site_magnetization': False, 'file_parser_set': 'default' } } file_path = str(request.fspath.join('..') + '../../../test_data/basic') node = calc_with_retrieved(file_path, settings_dict) parser_cls = ParserFactory('vasp.vasp') result, _ = parser_cls.parse_from_node( node, store_provenance=False, retrieved_temporary_folder=file_path) # First fetch structure from vasprun structure_vasprun = result['structure'] assert isinstance(structure_vasprun, get_data_class('structure')) # Then from POSCAR/CONTCAR file_path = str( request.fspath.join('..') + '../../../test_data/basic_poscar') node = calc_with_retrieved(file_path, settings_dict) parser_cls = ParserFactory('vasp.vasp') result, _ = parser_cls.parse_from_node( node, store_provenance=False, retrieved_temporary_folder=file_path) structure_poscar = result['structure'] assert isinstance(structure_poscar, get_data_class('structure')) assert np.array_equal(np.round(structure_vasprun.cell, 7), np.round(structure_poscar.cell, 7)) positions_vasprun = [] positions_poscar = [] for site in structure_vasprun.sites: pos = np.round(np.asarray(site.position), 7) positions_vasprun.append(pos) for site in structure_poscar.sites: pos = np.round(np.asarray(site.position), 7) positions_poscar.append(pos) positions_vasprun = np.asarray(positions_vasprun) positions_poscar = np.asarray(positions_poscar) assert np.array_equal(positions_vasprun, positions_poscar)
def test_stream(misc_input, config, request, calc_with_retrieved): """Test that the stream parser works and gets stored on a node.""" file_path = str( request.fspath.join('..') + '../../../test_data/stdout/out') # turn of everything, except misc settings_dict = { 'parser_settings': { 'add_trajectory': False, 'add_bands': False, 'add_chgcar': False, 'add_dos': False, 'add_kpoints': False, 'add_energies': False, 'add_misc': misc_input, 'add_structure': False, 'add_projectors': False, 'add_born_charges': False, 'add_dielectrics': False, 'add_hessian': False, 'add_dynmat': False, 'add_wavecar': False, 'add_site_magnetization': False, 'stream_config': config } } node = calc_with_retrieved(file_path, settings_dict) parser_cls = ParserFactory('vasp.vasp') result, _ = parser_cls.parse_from_node( node, store_provenance=False, retrieved_temporary_folder=file_path) if misc_input == []: # Test empty misc specification, yields no misc output node with pytest.raises(KeyError) as error: misc = result['misc'] else: misc = result['misc'] misc_dict = misc.get_dict() if config is not None: if 'random_error' in config: assert len(misc_dict['notifications']) == 2 assert misc_dict['notifications'][0]['name'] == 'ibzkpt' assert misc_dict['notifications'][0]['kind'] == 'ERROR' assert misc_dict['notifications'][0][ 'regex'] == 'internal error in subroutine IBZKPT' assert misc_dict['notifications'][1]['name'] == 'random_error' assert misc_dict['notifications'][1]['kind'] == 'ERROR' assert misc_dict['notifications'][1][ 'regex'] == 'I AM A WELL DEFINED ERROR' if 'random_warning' in config: assert len(misc_dict['notifications']) == 2 assert misc_dict['notifications'][0]['name'] == 'ibzkpt' assert misc_dict['notifications'][0]['kind'] == 'ERROR' assert misc_dict['notifications'][0][ 'regex'] == 'internal error in subroutine IBZKPT' assert misc_dict['notifications'][1][ 'name'] == 'random_warning' assert misc_dict['notifications'][1]['kind'] == 'WARNING' assert misc_dict['notifications'][1][ 'regex'] == 'I AM A WELL DEFINED WARNING' else: assert len(misc_dict['notifications']) == 1 assert misc_dict['notifications'][0]['name'] == 'ibzkpt' assert misc_dict['notifications'][0]['kind'] == 'ERROR' assert misc_dict['notifications'][0][ 'regex'] == 'internal error in subroutine IBZKPT'
def test_kkrimp_parser_entry_point(self): from aiida.plugins import ParserFactory from aiida_kkr.parsers.kkrimp import KkrimpParser parser = ParserFactory('kkr.kkrimpparser') assert parser == KkrimpParser
"""Test for the `Parser` base class.""" from __future__ import division from __future__ import print_function from __future__ import absolute_import import io from aiida import orm from aiida.backends.testbase import AiidaTestCase from aiida.common import LinkType from aiida.engine import CalcJob from aiida.parsers import Parser from aiida.plugins import CalculationFactory, ParserFactory ArithmeticAddCalculation = CalculationFactory('arithmetic.add') # pylint: disable=invalid-name ArithmeticAddParser = ParserFactory('arithmetic.add') # pylint: disable=invalid-name class CustomCalcJob(CalcJob): """`CalcJob` implementation with output namespace and additional output node that should be passed to parser.""" @classmethod def define(cls, spec): super(CustomCalcJob, cls).define(spec) spec.input('inp', valid_type=orm.Data) spec.output('output', pass_to_parser=True) spec.output_namespace('out.space', dynamic=True) def prepare_for_submission(self): # pylint: disable=arguments-differ pass
def validate_calc_job(inputs): """Validate the entire set of inputs passed to the `CalcJob` constructor. Reasons that will cause this validation to raise an `InputValidationError`: * No `Computer` has been specified, neither directly in `metadata.computer` nor indirectly through the `Code` input * The specified computer is not stored * The `Computer` specified in `metadata.computer` is not the same as that of the specified `Code` * The `metadata.options.parser_name` does not correspond to a loadable `Parser` class. * The `metadata.options.resources` are invalid for the `Scheduler` of the specified `Computer`. :raises `~aiida.common.exceptions.InputValidationError`: if inputs are invalid """ from aiida.plugins import ParserFactory code = inputs['code'] computer_from_code = code.computer computer_from_metadata = inputs['metadata'].get('computer', None) if not computer_from_code and not computer_from_metadata: raise exceptions.InputValidationError( 'no computer has been specified in `metadata.computer` nor via `code`.' ) if computer_from_code and not computer_from_code.is_stored: raise exceptions.InputValidationError( 'the Computer<{}> is not stored'.format(computer_from_code)) if computer_from_metadata and not computer_from_metadata.is_stored: raise exceptions.InputValidationError( 'the Computer<{}> is not stored'.format(computer_from_metadata)) if computer_from_code and computer_from_metadata and computer_from_code.uuid != computer_from_metadata.uuid: raise exceptions.InputValidationError( 'Computer<{}> explicitly defined in `metadata.computer is different from ' 'Computer<{}> which is the computer of Code<{}> defined as the `code` input.' .format(computer_from_metadata, computer_from_code, code)) # At this point at least one computer is defined and if both they are the same so we just pick one computer = computer_from_metadata if computer_from_metadata else computer_from_code options = inputs['metadata'].get('options', {}) parser_name = options.get('parser_name', None) resources = options.get('resources', None) if parser_name is not None: try: ParserFactory(parser_name) except exceptions.EntryPointError as exception: raise exceptions.InputValidationError( 'invalid parser specified: {}'.format(exception)) scheduler = computer.get_scheduler() # pylint: disable=no-member def_cpus_machine = computer.get_default_mpiprocs_per_machine() # pylint: disable=no-member if def_cpus_machine is not None: resources['default_mpiprocs_per_machine'] = def_cpus_machine try: scheduler.create_job_resource(**resources) except (TypeError, ValueError) as exception: raise exceptions.InputValidationError( 'invalid resources for the scheduler of the specified computer<{}>: {}' .format(computer, exception))
def test_siesta_parser_entry_point(): from aiida.plugins import ParserFactory siesta_parser = ParserFactory('siesta.parser') assert siesta_parser is not None
def test_fleur_parser_entry_point(self): from aiida.plugins import ParserFactory from aiida_fleur.parsers.fleur import FleurParser parser = ParserFactory('fleur.fleurparser') assert parser == FleurParser
def test_voronoi_parser_entry_point(self): from aiida.plugins import ParserFactory from aiida_kkr.parsers.voro import VoronoiParser parser = ParserFactory('kkr.voroparser') assert parser == VoronoiParser