From dfc13c2011ecc2104b599f414bf5224f3cc78f8d Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Wed, 19 Jun 2019 22:31:05 +0100 Subject: [PATCH 01/28] lint fixes --- aiida_lammps/calculations/lammps/md.py | 39 +++++++++++++++++--------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/aiida_lammps/calculations/lammps/md.py b/aiida_lammps/calculations/lammps/md.py index 6933cb9..61ee441 100644 --- a/aiida_lammps/calculations/lammps/md.py +++ b/aiida_lammps/calculations/lammps/md.py @@ -6,7 +6,8 @@ from aiida_lammps.validation import validate_with_json import six -def generate_LAMMPS_input(calc, + +def generate_lammps_input(calc, parameters, potential_obj, structure_file='potential.pot', @@ -19,12 +20,14 @@ def generate_LAMMPS_input(calc, names_str = ' '.join(potential_obj.kind_names) - #lammps_date = convert_date_string(pdict.get("lammps_version", None)) + # lammps_date = convert_date_string(pdict.get("lammps_version", None)) - lammps_input_file = 'units {0}\n'.format(potential_obj.default_units) + lammps_input_file = 'units {0}\n'.format( + potential_obj.default_units) lammps_input_file += 'boundary p p p\n' lammps_input_file += 'box tilt large\n' - lammps_input_file += 'atom_style {0}\n'.format(potential_obj.atom_style) + lammps_input_file += 'atom_style {0}\n'.format( + potential_obj.atom_style) lammps_input_file += 'read_data {}\n'.format(structure_file) lammps_input_file += potential_obj.get_input_potential_lines() @@ -33,7 +36,8 @@ def generate_LAMMPS_input(calc, lammps_input_file += "neighbor {0} {1}\n".format(pdict["neighbor"][0], pdict["neighbor"][1]) if "neigh_modify" in pdict: - lammps_input_file += "neigh_modify {}\n".format(join_keywords(pdict["neigh_modify"])) + lammps_input_file += "neigh_modify {}\n".format( + join_keywords(pdict["neigh_modify"])) # lammps_input_file += 'neighbor 0.3 bin\n' # lammps_input_file += 'neigh_modify every 1 delay 0 check no\n' @@ -44,22 +48,25 @@ def generate_LAMMPS_input(calc, lammps_input_file += 'thermo 1000\n' initial_temp, _, _ = get_path(pdict, ["integration", "constraints", "temp"], - default=[None, None, None], raise_error=False) + default=[None, None, None], raise_error=False) if initial_temp is not None: lammps_input_file += 'velocity all create {0} {1} dist gaussian mom yes\n'.format( initial_temp, random_number) - lammps_input_file += 'velocity all scale {}\n'.format(initial_temp) + lammps_input_file += 'velocity all scale {}\n'.format( + initial_temp) lammps_input_file += 'fix int all {0} {1} {2}\n'.format( get_path(pdict, ["integration", "style"]), - join_keywords(get_path(pdict, ["integration", "constraints"], {}, raise_error=False)), + join_keywords( + get_path(pdict, ["integration", "constraints"], {}, raise_error=False)), join_keywords(get_path(pdict, ["integration", "keywords"], {}, raise_error=False))) # lammps_input_file += 'fix int all nvt temp {0} {0} {1}\n'.format(parameters.dict.temperature, # parameters.dict.thermostat_variable) - lammps_input_file += 'run {}\n'.format(parameters.dict.equilibrium_steps) + lammps_input_file += 'run {}\n'.format( + parameters.dict.equilibrium_steps) lammps_input_file += 'reset_timestep 0\n' lammps_input_file += 'dump aiida all custom {0} {1} element x y z\n'.format(parameters.dict.dump_rate, @@ -74,21 +81,24 @@ def generate_LAMMPS_input(calc, lammps_input_file += 'dump_modify aiida sort id\n' lammps_input_file += 'dump_modify aiida element {}\n'.format(names_str) - lammps_input_file += 'run {}\n'.format(parameters.dict.total_steps) + lammps_input_file += 'run {}\n'.format( + parameters.dict.total_steps) return lammps_input_file class MdCalculation(BaseLammpsCalculation): _OUTPUT_TRAJECTORY_FILE_NAME = 'trajectory.lammpstrj' - _generate_input_function = generate_LAMMPS_input + _generate_input_function = generate_lammps_input @classmethod def define(cls, spec): super(MdCalculation, cls).define(spec) - spec.input('metadata.options.trajectory_name', valid_type=six.string_types, default=cls._OUTPUT_TRAJECTORY_FILE_NAME) - spec.input('metadata.options.parser_name', valid_type=six.string_types, default='lammps.md') + spec.input('metadata.options.trajectory_name', + valid_type=six.string_types, default=cls._OUTPUT_TRAJECTORY_FILE_NAME) + spec.input('metadata.options.parser_name', + valid_type=six.string_types, default='lammps.md') spec.default_output_port = 'results' # spec.input('settings', valid_type=six.string_types, default='lammps.optimize') @@ -112,6 +122,7 @@ def validate_parameters(self, param_data, potential_object): self._retrieve_list += [] if self._OUTPUT_TRAJECTORY_FILE_NAME not in self._retrieve_temporary_list: - self._retrieve_temporary_list += [self._OUTPUT_TRAJECTORY_FILE_NAME] + self._retrieve_temporary_list += [ + self._OUTPUT_TRAJECTORY_FILE_NAME] return True From 3ead7f3ab743524e598ef8bed87bc172d2db73c8 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Thu, 20 Jun 2019 00:36:47 +0100 Subject: [PATCH 02/28] add restart file output option for `lammps.md` --- aiida_lammps/calculations/lammps/__init__.py | 19 +++++++++++---- aiida_lammps/calculations/lammps/force.py | 8 +++---- aiida_lammps/calculations/lammps/md.py | 14 ++++++----- aiida_lammps/calculations/lammps/optimize.py | 25 ++++++++++++-------- aiida_lammps/validation/md.schema.json | 13 ++++++---- 5 files changed, 50 insertions(+), 29 deletions(-) diff --git a/aiida_lammps/calculations/lammps/__init__.py b/aiida_lammps/calculations/lammps/__init__.py index ac6ba7c..325b602 100644 --- a/aiida_lammps/calculations/lammps/__init__.py +++ b/aiida_lammps/calculations/lammps/__init__.py @@ -47,7 +47,8 @@ def get_FORCE_CONSTANTS_txt(force_constants): def structure_to_poscar(structure): - atom_type_unique = np.unique([site.kind_name for site in structure.sites], return_index=True)[1] + atom_type_unique = np.unique( + [site.kind_name for site in structure.sites], return_index=True)[1] labels = np.diff(np.append(atom_type_unique, [len(structure.sites)])) poscar = ' '.join(np.unique([site.kind_name for site in structure.sites])) @@ -59,7 +60,8 @@ def structure_to_poscar(structure): poscar += ' '.join(np.array(labels, dtype=str)) + '\n' poscar += 'Cartesian\n' for site in structure.sites: - poscar += '{0: 22.16f} {1: 22.16f} {2: 22.16f}\n'.format(*site.position) + poscar += '{0: 22.16f} {1: 22.16f} {2: 22.16f}\n'.format( + *site.position) return poscar @@ -102,7 +104,9 @@ class BaseLammpsCalculation(CalcJob): _INPUT_FILE_NAME = 'input.in' _INPUT_STRUCTURE = 'input.data' - _OUTPUT_FILE_NAME = 'log.lammps' + _DEFAULT_OUTPUT_FILE_NAME = 'log.lammps' + _DEFAULT_TRAJECTORY_FILE_NAME = 'trajectory.lammpstrj' + _DEFAULT_OUTPUT_RESTART_FILE_NAME = 'lammps.restart' _retrieve_list = ['log.lammps'] _retrieve_temporary_list = [] @@ -118,7 +122,11 @@ def define(cls, spec): spec.input('parameters', valid_type=Dict, help='the parameters', required=False) spec.input('metadata.options.output_filename', - valid_type=six.string_types, default=cls._OUTPUT_FILE_NAME) + valid_type=six.string_types, default=cls._DEFAULT_OUTPUT_FILE_NAME) + spec.input('metadata.options.trajectory_name', + valid_type=six.string_types, default=cls._DEFAULT_TRAJECTORY_FILE_NAME) + spec.input('metadata.options.restart_filename', + valid_type=six.string_types, default=cls._DEFAULT_OUTPUT_RESTART_FILE_NAME) spec.output('results', valid_type=DataFactory('dict'), @@ -190,7 +198,8 @@ def prepare_for_submission(self, tempfolder): input_txt = self._generate_input_function(parameters, self.inputs.potential, self._INPUT_STRUCTURE, - self._OUTPUT_TRAJECTORY_FILE_NAME, + self.options.trajectory_name, + self.options.restart_filename, version_date=lammps_date) input_filename = tempfolder.get_abs_path(self._INPUT_FILE_NAME) diff --git a/aiida_lammps/calculations/lammps/force.py b/aiida_lammps/calculations/lammps/force.py index 9cc0044..4b9f4fb 100644 --- a/aiida_lammps/calculations/lammps/force.py +++ b/aiida_lammps/calculations/lammps/force.py @@ -9,11 +9,12 @@ def generate_LAMMPS_input(calc, potential_obj, structure_file='data.gan', trajectory_file='trajectory.lammpstr', + restart_filename="lammps.restart", version_date='11 Aug 2017'): names_str = ' '.join(potential_obj.kind_names) - lammps_input_file = 'units {0}\n'.format(potential_obj.default_units) + lammps_input_file = 'units {0}\n'.format(potential_obj.default_units) lammps_input_file += 'boundary p p p\n' lammps_input_file += 'box tilt large\n' lammps_input_file += 'atom_style {0}\n'.format(potential_obj.atom_style) @@ -42,14 +43,12 @@ def generate_LAMMPS_input(calc, class ForceCalculation(BaseLammpsCalculation): - _OUTPUT_TRAJECTORY_FILE_NAME = 'trajectory.lammpstrj' _generate_input_function = generate_LAMMPS_input @classmethod def define(cls, spec): super(ForceCalculation, cls).define(spec) - spec.input('metadata.options.trajectory_name', valid_type=six.string_types, default=cls._OUTPUT_TRAJECTORY_FILE_NAME) spec.input('metadata.options.parser_name', valid_type=six.string_types, default='lammps.force') # spec.input('settings', valid_type=six.string_types, default='lammps.optimize') @@ -60,5 +59,6 @@ def define(cls, spec): help='force data per step') def validate_parameters(self, param_data, potential_object): - self._retrieve_list += [self._OUTPUT_TRAJECTORY_FILE_NAME] + if self.options.trajectory_name not in self._retrieve_list: + self._retrieve_list += [self.options.trajectory_name] self._retrieve_temporary_list += [] diff --git a/aiida_lammps/calculations/lammps/md.py b/aiida_lammps/calculations/lammps/md.py index 61ee441..2d9da74 100644 --- a/aiida_lammps/calculations/lammps/md.py +++ b/aiida_lammps/calculations/lammps/md.py @@ -12,6 +12,7 @@ def generate_lammps_input(calc, potential_obj, structure_file='potential.pot', trajectory_file='trajectory.lammpstr', + restart_filename="lammps.restart", version_date='11 Aug 2017'): pdict = parameters.get_dict() @@ -47,6 +48,10 @@ def generate_lammps_input(calc, lammps_input_file += 'thermo_style custom step etotal temp vol press\n' lammps_input_file += 'thermo 1000\n' + restart = pdict.get("restart", False) + if restart: + lammps_input_file += 'restart {0} {1}\n'.format(restart, restart_filename) + initial_temp, _, _ = get_path(pdict, ["integration", "constraints", "temp"], default=[None, None, None], raise_error=False) @@ -88,15 +93,13 @@ def generate_lammps_input(calc, class MdCalculation(BaseLammpsCalculation): - _OUTPUT_TRAJECTORY_FILE_NAME = 'trajectory.lammpstrj' + _generate_input_function = generate_lammps_input @classmethod def define(cls, spec): super(MdCalculation, cls).define(spec) - spec.input('metadata.options.trajectory_name', - valid_type=six.string_types, default=cls._OUTPUT_TRAJECTORY_FILE_NAME) spec.input('metadata.options.parser_name', valid_type=six.string_types, default='lammps.md') spec.default_output_port = 'results' @@ -121,8 +124,7 @@ def validate_parameters(self, param_data, potential_object): )) self._retrieve_list += [] - if self._OUTPUT_TRAJECTORY_FILE_NAME not in self._retrieve_temporary_list: - self._retrieve_temporary_list += [ - self._OUTPUT_TRAJECTORY_FILE_NAME] + if self.options.trajectory_name not in self._retrieve_temporary_list: + self._retrieve_temporary_list += [self.options.trajectory_name] return True diff --git a/aiida_lammps/calculations/lammps/optimize.py b/aiida_lammps/calculations/lammps/optimize.py index 781e986..1e67f04 100644 --- a/aiida_lammps/calculations/lammps/optimize.py +++ b/aiida_lammps/calculations/lammps/optimize.py @@ -11,6 +11,7 @@ def generate_LAMMPS_input(calc, potential_obj, structure_file='data.gan', trajectory_file='path.lammpstrj', + restart_filename="lammps.restart", version_date='11 Aug 2017'): names_str = ' '.join(potential_obj.kind_names) @@ -19,10 +20,12 @@ def generate_LAMMPS_input(calc, # lammps_date = convert_date_string(parameters.get("lammps_version", None)) - lammps_input_file = 'units {0}\n'.format(potential_obj.default_units) + lammps_input_file = 'units {0}\n'.format( + potential_obj.default_units) lammps_input_file += 'boundary p p p\n' lammps_input_file += 'box tilt large\n' - lammps_input_file += 'atom_style {0}\n'.format(potential_obj.atom_style) + lammps_input_file += 'atom_style {0}\n'.format( + potential_obj.atom_style) lammps_input_file += 'read_data {}\n'.format(structure_file) lammps_input_file += potential_obj.get_input_potential_lines() @@ -30,7 +33,7 @@ def generate_LAMMPS_input(calc, lammps_input_file += 'fix int all box/relax {} {} {}\n'.format(parameters['relax']['type'], parameters['relax']['pressure'], join_keywords(parameters['relax'], - ignore=['type', 'pressure'])) + ignore=['type', 'pressure'])) # TODO find exact version when changes were made if version_date <= convert_date_string('11 Nov 2013'): @@ -53,7 +56,8 @@ def generate_LAMMPS_input(calc, lammps_input_file += 'dump_modify aiida sort id\n' lammps_input_file += 'dump_modify aiida element {}\n'.format(names_str) - lammps_input_file += 'min_style {}\n'.format(parameters['minimize']['style']) + lammps_input_file += 'min_style {}\n'.format( + parameters['minimize']['style']) # lammps_input_file += 'min_style cg\n' lammps_input_file += 'minimize {} {} {} {}\n'.format(parameters['minimize']['energy_tolerance'], parameters['minimize']['force_tolerance'], @@ -70,16 +74,15 @@ def generate_LAMMPS_input(calc, class OptimizeCalculation(BaseLammpsCalculation): - _OUTPUT_TRAJECTORY_FILE_NAME = 'path.lammpstrj' + _generate_input_function = generate_LAMMPS_input @classmethod def define(cls, spec): super(OptimizeCalculation, cls).define(spec) - spec.input('metadata.options.trajectory_name', valid_type=six.string_types, default=cls._OUTPUT_TRAJECTORY_FILE_NAME) - spec.input('metadata.options.parser_name', valid_type=six.string_types, default='lammps.optimize') - # spec.input('settings', valid_type=six.string_types, default='lammps.optimize') + spec.input('metadata.options.parser_name', + valid_type=six.string_types, default='lammps.optimize') spec.output('structure', valid_type=DataFactory('structure'), @@ -104,10 +107,12 @@ def validate_parameters(self, param_data, potential_object): punits, potential_object.default_units )) else: - self.logger.log('No units defined, using:', potential_object.default_units) + self.logger.log('No units defined, using:', + potential_object.default_units) # Update retrieve list - self._retrieve_list += [self._OUTPUT_TRAJECTORY_FILE_NAME] + if self.options.trajectory_name not in self._retrieve_list: + self._retrieve_list += [self.options.trajectory_name] self._retrieve_temporary_list += [] return True diff --git a/aiida_lammps/validation/md.schema.json b/aiida_lammps/validation/md.schema.json index 8826329..c64b7c0 100644 --- a/aiida_lammps/validation/md.schema.json +++ b/aiida_lammps/validation/md.schema.json @@ -47,25 +47,30 @@ "description": "the timestep (in time units of style used)", "type": "number", "minimum": 0, - "exclusiveMinimum": "true" + "exclusiveMinimum": true }, "equilibrium_steps": { "description": "number of initial time steps to equilibrate", "type": "integer", "minimum": 0, - "exclusiveMinimum": "true" + "exclusiveMinimum": true }, "total_steps": { "description": "number of time steps to record (after equilibration)", "type": "integer", "minimum": 0, - "exclusiveMinimum": "true" + "exclusiveMinimum": true }, "dump_rate": { "description": "record trajectory every x steps", "type": "number", "minimum": 0, - "exclusiveMinimum": "true" + "exclusiveMinimum": true + }, + "restart": { + "description": "Write out a binary restart file with the current state of the simulation every x steps", + "type": "integer", + "minimum": 0 }, "integration": { "description": "the time integration method to update the position and velocity for atoms at each time step", From c869b6b1e424d47c3ae74655d89c5c08f47ad7d5 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Thu, 20 Jun 2019 00:38:15 +0100 Subject: [PATCH 03/28] return exit code, if read_lammps_trajectory fails, for `lammps.md` --- aiida_lammps/parsers/lammps/md.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/aiida_lammps/parsers/lammps/md.py b/aiida_lammps/parsers/lammps/md.py index 5f044f8..5e93c86 100644 --- a/aiida_lammps/parsers/lammps/md.py +++ b/aiida_lammps/parsers/lammps/md.py @@ -1,4 +1,5 @@ import os +import traceback from aiida.parsers.parser import Parser from aiida.common import exceptions from aiida.orm import Dict, TrajectoryData @@ -49,16 +50,15 @@ def parse(self, **kwargs): # Read trajectory from temporal folder trajectory_filepath = temporary_folder + '/' + self.node.get_option('trajectory_name') timestep = self.node.inputs.parameters.dict.timestep - # TODO test if trajectory path is empty, before parsing - positions, step_ids, cells, symbols, time = read_lammps_trajectory(trajectory_filepath, timestep=timestep) # save trajectory into node try: + positions, step_ids, cells, symbols, time = read_lammps_trajectory(trajectory_filepath, timestep=timestep) trajectory_data = TrajectoryData() trajectory_data.set_trajectory(symbols, positions, stepids=step_ids, cells=cells, times=time) self.out('trajectory_data', trajectory_data) - - except KeyError: # keys not found in json + except Exception: + traceback.print_exc() return self.exit_codes.ERROR_TRAJ_PARSING # Read other data from output folder From d5982eed09b7c8d4aba685d6fcd95a8976cf61d2 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Thu, 20 Jun 2019 04:03:29 +0100 Subject: [PATCH 04/28] add optional output of system variables for `lammps.md` --- aiida_lammps/calculations/lammps/__init__.py | 17 ++-- aiida_lammps/calculations/lammps/force.py | 17 ++-- aiida_lammps/calculations/lammps/md.py | 28 +++++-- aiida_lammps/calculations/lammps/optimize.py | 19 ++--- aiida_lammps/parsers/lammps/md.py | 14 +++- aiida_lammps/tests/test_calculations.py | 13 ++- aiida_lammps/validation/md.schema.json | 86 +++++++++++++++++++- 7 files changed, 158 insertions(+), 36 deletions(-) diff --git a/aiida_lammps/calculations/lammps/__init__.py b/aiida_lammps/calculations/lammps/__init__.py index 325b602..e2f594d 100644 --- a/aiida_lammps/calculations/lammps/__init__.py +++ b/aiida_lammps/calculations/lammps/__init__.py @@ -106,6 +106,7 @@ class BaseLammpsCalculation(CalcJob): _DEFAULT_OUTPUT_FILE_NAME = 'log.lammps' _DEFAULT_TRAJECTORY_FILE_NAME = 'trajectory.lammpstrj' + _DEFAULT_OUTPUT_INFO_FILE_NAME = "system_info.dump" _DEFAULT_OUTPUT_RESTART_FILE_NAME = 'lammps.restart' _retrieve_list = ['log.lammps'] @@ -125,6 +126,8 @@ def define(cls, spec): valid_type=six.string_types, default=cls._DEFAULT_OUTPUT_FILE_NAME) spec.input('metadata.options.trajectory_name', valid_type=six.string_types, default=cls._DEFAULT_TRAJECTORY_FILE_NAME) + spec.input('metadata.options.info_filename', + valid_type=six.string_types, default=cls._DEFAULT_OUTPUT_INFO_FILE_NAME) spec.input('metadata.options.restart_filename', valid_type=six.string_types, default=cls._DEFAULT_OUTPUT_RESTART_FILE_NAME) @@ -195,12 +198,14 @@ def prepare_for_submission(self, tempfolder): parameters.get_dict().get("lammps_version", '11 Aug 2017')) # Setup input parameters - input_txt = self._generate_input_function(parameters, - self.inputs.potential, - self._INPUT_STRUCTURE, - self.options.trajectory_name, - self.options.restart_filename, - version_date=lammps_date) + input_txt = self._generate_input_function( + parameters=parameters, + potential_obj=self.inputs.potential, + structure_filename=self._INPUT_STRUCTURE, + trajectory_filename=self.options.trajectory_name, + info_filename=self.options.info_filename, + restart_filename=self.options.restart_filename, + version_date=lammps_date) input_filename = tempfolder.get_abs_path(self._INPUT_FILE_NAME) diff --git a/aiida_lammps/calculations/lammps/force.py b/aiida_lammps/calculations/lammps/force.py index 4b9f4fb..6a25892 100644 --- a/aiida_lammps/calculations/lammps/force.py +++ b/aiida_lammps/calculations/lammps/force.py @@ -4,13 +4,12 @@ import six -def generate_LAMMPS_input(calc, - parameters_data, +def generate_lammps_input(calc, + parameters, potential_obj, - structure_file='data.gan', - trajectory_file='trajectory.lammpstr', - restart_filename="lammps.restart", - version_date='11 Aug 2017'): + structure_filename, + trajectory_filename, + version_date='11 Aug 2017', **kwargs): names_str = ' '.join(potential_obj.kind_names) @@ -19,13 +18,13 @@ def generate_LAMMPS_input(calc, lammps_input_file += 'box tilt large\n' lammps_input_file += 'atom_style {0}\n'.format(potential_obj.atom_style) - lammps_input_file += 'read_data {}\n'.format(structure_file) + lammps_input_file += 'read_data {}\n'.format(structure_filename) lammps_input_file += potential_obj.get_input_potential_lines() lammps_input_file += 'neighbor 0.3 bin\n' lammps_input_file += 'neigh_modify every 1 delay 0 check no\n' - lammps_input_file += 'dump aiida all custom 1 {0} element fx fy fz\n'.format(trajectory_file) + lammps_input_file += 'dump aiida all custom 1 {0} element fx fy fz\n'.format(trajectory_filename) # TODO find exact version when changes were made if version_date <= convert_date_string('10 Feb 2015'): @@ -43,7 +42,7 @@ def generate_LAMMPS_input(calc, class ForceCalculation(BaseLammpsCalculation): - _generate_input_function = generate_LAMMPS_input + _generate_input_function = generate_lammps_input @classmethod def define(cls, spec): diff --git a/aiida_lammps/calculations/lammps/md.py b/aiida_lammps/calculations/lammps/md.py index 2d9da74..858ab06 100644 --- a/aiida_lammps/calculations/lammps/md.py +++ b/aiida_lammps/calculations/lammps/md.py @@ -10,9 +10,10 @@ def generate_lammps_input(calc, parameters, potential_obj, - structure_file='potential.pot', - trajectory_file='trajectory.lammpstr', - restart_filename="lammps.restart", + structure_filename, + trajectory_filename, + restart_filename, + info_filename, version_date='11 Aug 2017'): pdict = parameters.get_dict() @@ -29,7 +30,7 @@ def generate_lammps_input(calc, lammps_input_file += 'box tilt large\n' lammps_input_file += 'atom_style {0}\n'.format( potential_obj.atom_style) - lammps_input_file += 'read_data {}\n'.format(structure_file) + lammps_input_file += 'read_data {}\n'.format(structure_filename) lammps_input_file += potential_obj.get_input_potential_lines() @@ -75,7 +76,7 @@ def generate_lammps_input(calc, lammps_input_file += 'reset_timestep 0\n' lammps_input_file += 'dump aiida all custom {0} {1} element x y z\n'.format(parameters.dict.dump_rate, - trajectory_file) + trajectory_filename) # TODO find exact version when changes were made if version_date <= convert_date_string('10 Feb 2015'): @@ -86,6 +87,15 @@ def generate_lammps_input(calc, lammps_input_file += 'dump_modify aiida sort id\n' lammps_input_file += 'dump_modify aiida element {}\n'.format(names_str) + variables = pdict.get("output_variables", []) + for var in variables: + lammps_input_file += 'variable {0} equal {0}\n'.format(var) + if variables: + # TODO dump includes the initial configuration, whereas print does not + lammps_input_file += 'fix sys_info all print {0} "{1}" title "{2}" file {3} screen no\n'.format( + parameters.dict.dump_rate, " ".join(["${{{0}}}".format(v) for v in variables]), + " ".join(variables), info_filename) + lammps_input_file += 'run {}\n'.format( parameters.dict.total_steps) @@ -108,7 +118,11 @@ def define(cls, spec): spec.output('trajectory_data', valid_type=DataFactory('array.trajectory'), required=True, - help='forces, stresses and positions data per step') + help='atomic configuration data per dump step') + spec.output('system_data', + valid_type=DataFactory('array'), + required=False, + help='selected system data per dump step') def validate_parameters(self, param_data, potential_object): if param_data is None: @@ -126,5 +140,7 @@ def validate_parameters(self, param_data, potential_object): self._retrieve_list += [] if self.options.trajectory_name not in self._retrieve_temporary_list: self._retrieve_temporary_list += [self.options.trajectory_name] + if self.options.info_filename not in self._retrieve_temporary_list: + self._retrieve_temporary_list += [self.options.info_filename] return True diff --git a/aiida_lammps/calculations/lammps/optimize.py b/aiida_lammps/calculations/lammps/optimize.py index 1e67f04..099ff7b 100644 --- a/aiida_lammps/calculations/lammps/optimize.py +++ b/aiida_lammps/calculations/lammps/optimize.py @@ -6,17 +6,16 @@ import six -def generate_LAMMPS_input(calc, - parameters_data, +def generate_lammps_input(calc, + parameters, potential_obj, - structure_file='data.gan', - trajectory_file='path.lammpstrj', - restart_filename="lammps.restart", - version_date='11 Aug 2017'): + structure_filename, + trajectory_filename, + version_date, **kwargs): names_str = ' '.join(potential_obj.kind_names) - parameters = parameters_data.get_dict() + parameters = parameters.get_dict() # lammps_date = convert_date_string(parameters.get("lammps_version", None)) @@ -26,7 +25,7 @@ def generate_LAMMPS_input(calc, lammps_input_file += 'box tilt large\n' lammps_input_file += 'atom_style {0}\n'.format( potential_obj.atom_style) - lammps_input_file += 'read_data {}\n'.format(structure_file) + lammps_input_file += 'read_data {}\n'.format(structure_filename) lammps_input_file += potential_obj.get_input_potential_lines() @@ -46,7 +45,7 @@ def generate_LAMMPS_input(calc, lammps_input_file += 'variable pr equal -(c_stgb[1]+c_stgb[2]+c_stgb[3])/(3*vol)\n' lammps_input_file += 'thermo_style custom step temp press v_pr etotal c_stgb[1] c_stgb[2] c_stgb[3] c_stgb[4] c_stgb[5] c_stgb[6]\n' - lammps_input_file += 'dump aiida all custom 1 {0} element x y z fx fy fz\n'.format(trajectory_file) + lammps_input_file += 'dump aiida all custom 1 {0} element x y z fx fy fz\n'.format(trajectory_filename) # TODO find exact version when changes were made if version_date <= convert_date_string('10 Feb 2015'): @@ -75,7 +74,7 @@ def generate_LAMMPS_input(calc, class OptimizeCalculation(BaseLammpsCalculation): - _generate_input_function = generate_LAMMPS_input + _generate_input_function = generate_lammps_input @classmethod def define(cls, spec): diff --git a/aiida_lammps/parsers/lammps/md.py b/aiida_lammps/parsers/lammps/md.py index 5e93c86..e085bbf 100644 --- a/aiida_lammps/parsers/lammps/md.py +++ b/aiida_lammps/parsers/lammps/md.py @@ -1,8 +1,9 @@ import os import traceback +import numpy as np from aiida.parsers.parser import Parser from aiida.common import exceptions -from aiida.orm import Dict, TrajectoryData +from aiida.orm import Dict, TrajectoryData, ArrayData from aiida_lammps import __version__ as aiida_lammps_version from aiida_lammps.common.raw_parsers import read_lammps_trajectory, get_units_dict, read_log_file @@ -77,3 +78,14 @@ def parse(self, **kwargs): parameters_data = Dict(dict=output_data) self.out('results', parameters_data) + + # parse the system data file + info_filename = self.node.get_option('info_filename') + if info_filename in list_of_temp_files: + info_filepath = temporary_folder + '/' + self.node.get_option('info_filename') + with open(info_filepath) as handle: + names = handle.readline().strip().split() + sys_data = ArrayData() + for i, col in enumerate(np.loadtxt(info_filepath, skiprows=1, unpack=True)): + sys_data.set_array(names[i], col) + self.out('system_data', sys_data) diff --git a/aiida_lammps/tests/test_calculations.py b/aiida_lammps/tests/test_calculations.py index 609cb2d..0af5e3a 100644 --- a/aiida_lammps/tests/test_calculations.py +++ b/aiida_lammps/tests/test_calculations.py @@ -46,7 +46,10 @@ def get_calc_parameters(plugin_name, units): "neigh_modify": {"every": 1, "delay": 0, "check": False}, 'equilibrium_steps': 100, 'total_steps': 1000, - 'dump_rate': 1} + 'dump_rate': 10, + 'restart': 100, + 'output_variables': ["step", "temp", "etotal"] + } else: raise ValueError(plugin_name) @@ -274,7 +277,7 @@ def test_md_process(db_test_app, get_potential_data, potential_type): link_labels = calc_node.get_outgoing().all_link_labels() assert set(link_labels).issuperset( - ['results', 'trajectory_data']) + ['results', 'trajectory_data', 'system_data']) pdict = calc_node.outputs.results.get_dict() assert set(pdict.keys()).issuperset( @@ -284,3 +287,9 @@ def test_md_process(db_test_app, get_potential_data, potential_type): assert set(calc_node.outputs.trajectory_data.get_arraynames()).issuperset( ['cells', 'positions', 'steps', 'times'] ) + assert calc_node.outputs.trajectory_data.numsteps == 101 + + assert set(calc_node.outputs.system_data.get_arraynames()) == set( + ['step', 'temp', 'etotal'] + ) + assert calc_node.outputs.system_data.get_shape('temp') == (100,) diff --git a/aiida_lammps/validation/md.schema.json b/aiida_lammps/validation/md.schema.json index c64b7c0..e34b9da 100644 --- a/aiida_lammps/validation/md.schema.json +++ b/aiida_lammps/validation/md.schema.json @@ -41,7 +41,15 @@ "units": { "description": "the unit system used", "type": "string", - "enum": ["real", "nano", "metal", "electron", "micro", "si", "cgs"] + "enum": [ + "real", + "nano", + "metal", + "electron", + "micro", + "si", + "cgs" + ] }, "timestep": { "description": "the timestep (in time units of style used)", @@ -92,7 +100,6 @@ "description": "external constraints to set", "type": "object", "additionalProperties": false, - "properties": { "temp": { "description": "the temperature to run at", @@ -242,6 +249,81 @@ "minimum": 0 } } + }, + "output_variables": { + "description": "output variables, per dump_rate, to an array (see thermo_style)", + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "step", + "elapsed", + "elaplong", + "dt", + "time", + "cpu", + "tpcpu", + "spcpu", + "cpuremain", + "part", + "timeremain", + "atoms", + "temp", + "press", + "pe", + "ke", + "etotal", + "enthalpy", + "evdwl", + "ecoul", + "epair", + "ebond", + "eangle", + "edihed", + "eimp", + "emol", + "elong", + "etail", + "vol", + "density", + "lx", + "ly", + "lz", + "xlo", + "xhi", + "ylo", + "yhi", + "zlo", + "zhi", + "xy", + "xz", + "yz", + "xlat", + "ylat", + "zlat", + "bonds", + "angles", + "dihedrals", + "impropers", + "pxx", + "pyy", + "pzz", + "pxy", + "pxz", + "pyz", + "fmax", + "fnorm", + "nbuild", + "ndanger", + "cella", + "cellb", + "cellc", + "cellalpha", + "cellbeta", + "cellgamma" + ] + } } } } \ No newline at end of file From c3314347c249d6705246af4a9457b440360efeb4 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Thu, 20 Jun 2019 04:07:23 +0100 Subject: [PATCH 05/28] version bump --- aiida_lammps/__init__.py | 2 +- setup.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aiida_lammps/__init__.py b/aiida_lammps/__init__.py index 90b9238..342dbf2 100644 --- a/aiida_lammps/__init__.py +++ b/aiida_lammps/__init__.py @@ -1 +1 @@ -__version__ = "0.3.0b3" +__version__ = "0.4.0b3" diff --git a/setup.json b/setup.json index 0202bd2..b092f0e 100644 --- a/setup.json +++ b/setup.json @@ -1,6 +1,6 @@ { "name": "aiida-lammps", - "version": "0.3.0b3", + "version": "0.4.0b3", "description": "AiiDA plugin for LAMMPS", "url": "https://github.com/abelcarreras/aiida_extensions", "author": "Abel Carreras", From ddc2ffef66a1506f1a8c3c10df82e35dd13b552a Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Thu, 20 Jun 2019 12:17:19 +0100 Subject: [PATCH 06/28] ensure step is included in `system_data` --- aiida_lammps/calculations/lammps/md.py | 5 ++++- aiida_lammps/tests/test_calculations.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/aiida_lammps/calculations/lammps/md.py b/aiida_lammps/calculations/lammps/md.py index 858ab06..c43439a 100644 --- a/aiida_lammps/calculations/lammps/md.py +++ b/aiida_lammps/calculations/lammps/md.py @@ -88,10 +88,13 @@ def generate_lammps_input(calc, lammps_input_file += 'dump_modify aiida element {}\n'.format(names_str) variables = pdict.get("output_variables", []) + if variables and 'step' not in variables: + # always include 'step', so we can sync with the `dump` data + # NOTE `dump` includes step 0, whereas `print` starts from step 1 + variables.append('step') for var in variables: lammps_input_file += 'variable {0} equal {0}\n'.format(var) if variables: - # TODO dump includes the initial configuration, whereas print does not lammps_input_file += 'fix sys_info all print {0} "{1}" title "{2}" file {3} screen no\n'.format( parameters.dict.dump_rate, " ".join(["${{{0}}}".format(v) for v in variables]), " ".join(variables), info_filename) diff --git a/aiida_lammps/tests/test_calculations.py b/aiida_lammps/tests/test_calculations.py index 0af5e3a..475431f 100644 --- a/aiida_lammps/tests/test_calculations.py +++ b/aiida_lammps/tests/test_calculations.py @@ -48,7 +48,7 @@ def get_calc_parameters(plugin_name, units): 'total_steps': 1000, 'dump_rate': 10, 'restart': 100, - 'output_variables': ["step", "temp", "etotal"] + 'output_variables': ["temp", "etotal"] } else: raise ValueError(plugin_name) From 7a5a1cc1145ecde4b90653dce1ecc4899a5d310d Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Thu, 20 Jun 2019 12:22:31 +0100 Subject: [PATCH 07/28] save the units style in the `system_data` attributes --- aiida_lammps/parsers/lammps/md.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aiida_lammps/parsers/lammps/md.py b/aiida_lammps/parsers/lammps/md.py index e085bbf..b8f6c5b 100644 --- a/aiida_lammps/parsers/lammps/md.py +++ b/aiida_lammps/parsers/lammps/md.py @@ -88,4 +88,5 @@ def parse(self, **kwargs): sys_data = ArrayData() for i, col in enumerate(np.loadtxt(info_filepath, skiprows=1, unpack=True)): sys_data.set_array(names[i], col) + sys_data.set_attribute('units', units) self.out('system_data', sys_data) From d796e29ebc4993f056b8fc8f86a258d52ffda951 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Thu, 20 Jun 2019 13:03:22 +0100 Subject: [PATCH 08/28] add reaxff energy contribution variables --- aiida_lammps/calculations/lammps/md.py | 2 ++ aiida_lammps/data/potential/reaxff.py | 15 +++++++++++++++ aiida_lammps/validation/md.schema.json | 16 +++++++++++++++- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/aiida_lammps/calculations/lammps/md.py b/aiida_lammps/calculations/lammps/md.py index c43439a..8c7afe9 100644 --- a/aiida_lammps/calculations/lammps/md.py +++ b/aiida_lammps/calculations/lammps/md.py @@ -93,6 +93,8 @@ def generate_lammps_input(calc, # NOTE `dump` includes step 0, whereas `print` starts from step 1 variables.append('step') for var in variables: + if var.startswith("reax_"): + continue # these variables are set in the potential lines lammps_input_file += 'variable {0} equal {0}\n'.format(var) if variables: lammps_input_file += 'fix sys_info all print {0} "{1}" title "{2}" file {3} screen no\n'.format( diff --git a/aiida_lammps/data/potential/reaxff.py b/aiida_lammps/data/potential/reaxff.py index 57be9d9..14b1963 100644 --- a/aiida_lammps/data/potential/reaxff.py +++ b/aiida_lammps/data/potential/reaxff.py @@ -25,6 +25,21 @@ def get_input_potential_lines(data, names=None, potential_filename='potential.po lammps_input_text += "fix qeq all qeq/reax 1 0.0 10.0 1e-6 reax/c\n" lammps_input_text += "fix_modify qeq energy yes\n" + lammps_input_text += "variable reax_eb equal c_reax[1]\n" + lammps_input_text += "variable reax_ea equal c_reax[2]\n" + lammps_input_text += "variable reax_elp equal c_reax[3]\n" + lammps_input_text += "variable reax_emol equal c_reax[4]\n" + lammps_input_text += "variable reax_ev equal c_reax[5]\n" + lammps_input_text += "variable reax_epen equal c_reax[6]\n" + lammps_input_text += "variable reax_ecoa equal c_reax[7]\n" + lammps_input_text += "variable reax_ehb equal c_reax[8]\n" + lammps_input_text += "variable reax_et equal c_reax[9]\n" + lammps_input_text += "variable reax_eco equal c_reax[10]\n" + lammps_input_text += "variable reax_ew equal c_reax[11]\n" + lammps_input_text += "variable reax_ep equal c_reax[12]\n" + lammps_input_text += "variable reax_efi equal c_reax[13]\n" + lammps_input_text += "variable reax_eqeq equal c_reax[14]\n" + return lammps_input_text diff --git a/aiida_lammps/validation/md.schema.json b/aiida_lammps/validation/md.schema.json index e34b9da..e54ac8b 100644 --- a/aiida_lammps/validation/md.schema.json +++ b/aiida_lammps/validation/md.schema.json @@ -321,7 +321,21 @@ "cellc", "cellalpha", "cellbeta", - "cellgamma" + "cellgamma", + "reax_eb", + "reax_ea", + "reax_elp", + "reax_emol", + "reax_ev", + "reax_epen", + "reax_ecoa", + "reax_ehb", + "reax_et", + "reax_eco", + "reax_ew", + "reax_ep", + "reax_efi", + "reax_eqeq" ] } } From 1d824e98ffd555f31614a8ba1286d97ddfc9a163 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Thu, 20 Jun 2019 14:32:21 +0100 Subject: [PATCH 09/28] add option to output final thermodynamic variables for `lammps.force` and `lammps.optimize` calculations also: - add exit code return, if log file parsing fails - lint fixes - file_regression test fixes --- .travis.yml | 2 +- aiida_lammps/calculations/lammps/__init__.py | 8 +- aiida_lammps/calculations/lammps/force.py | 14 +- aiida_lammps/calculations/lammps/md.py | 1 - aiida_lammps/calculations/lammps/optimize.py | 7 + aiida_lammps/common/raw_parsers.py | 445 +++++++++--------- aiida_lammps/parsers/lammps/force.py | 8 +- aiida_lammps/parsers/lammps/md.py | 24 +- aiida_lammps/parsers/lammps/optimize.py | 7 +- aiida_lammps/tests/__init__.py | 2 +- aiida_lammps/tests/test_calculations.py | 12 +- .../test_potential_data/test_init_reaxff_.yml | 2 +- .../test_input_lines_reaxff_.txt | 14 + aiida_lammps/validation/__init__.py | 2 +- aiida_lammps/validation/force.schema.json | 101 ++++ aiida_lammps/validation/optimize.schema.json | 89 ++++ 16 files changed, 496 insertions(+), 242 deletions(-) create mode 100644 aiida_lammps/validation/force.schema.json diff --git a/.travis.yml b/.travis.yml index edabe35..3ece177 100644 --- a/.travis.yml +++ b/.travis.yml @@ -72,7 +72,7 @@ script: fi - | if [[ "$TEST_TYPE" == "code-style" ]]; then - flake8 . + flake8 aiida_lammps fi - | if [[ "$TEST_TYPE" == "version" ]]; then diff --git a/aiida_lammps/calculations/lammps/__init__.py b/aiida_lammps/calculations/lammps/__init__.py index e2f594d..db62450 100644 --- a/aiida_lammps/calculations/lammps/__init__.py +++ b/aiida_lammps/calculations/lammps/__init__.py @@ -155,13 +155,17 @@ def define(cls, spec): # Unrecoverable errors: required retrieved files could not be read, parsed or are otherwise incomplete spec.exit_code( - 300, 'ERROR_OUTPUT_PARSING', + 300, 'ERROR_LOG_PARSING', message=('An error was flagged trying to parse the ' - 'main lammps output file')) + 'main lammps output log file')) spec.exit_code( 310, 'ERROR_TRAJ_PARSING', message=('An error was flagged trying to parse the ' 'trajectory output file')) + spec.exit_code( + 320, 'ERROR_INFO_PARSING', + message=('An error was flagged trying to parse the ' + 'system info output file')) # Significant errors but calculation can be used to restart spec.exit_code( diff --git a/aiida_lammps/calculations/lammps/force.py b/aiida_lammps/calculations/lammps/force.py index 6a25892..206096b 100644 --- a/aiida_lammps/calculations/lammps/force.py +++ b/aiida_lammps/calculations/lammps/force.py @@ -1,5 +1,6 @@ from aiida.plugins import DataFactory from aiida_lammps.calculations.lammps import BaseLammpsCalculation +from aiida_lammps.validation import validate_with_json from aiida_lammps.common.utils import convert_date_string import six @@ -35,7 +36,14 @@ def generate_lammps_input(calc, lammps_input_file += 'dump_modify aiida sort id\n' lammps_input_file += 'dump_modify aiida element {}\n'.format(names_str) - lammps_input_file += 'run 0' + lammps_input_file += 'run 0\n' + + variables = parameters.get_attribute("output_variables", []) + for var in variables: + if var.startswith("reax_"): + continue # these variables are set in the potential lines + lammps_input_file += 'variable {0} equal {0}\n'.format(var) + lammps_input_file += 'print "final_variable: {0} = ${{{0}}}"\n'.format(var) return lammps_input_file @@ -55,9 +63,11 @@ def define(cls, spec): spec.output('arrays', valid_type=DataFactory('array'), required=True, - help='force data per step') + help='force data per atom') def validate_parameters(self, param_data, potential_object): + if param_data is not None: + validate_with_json(param_data.get_dict(), "force") if self.options.trajectory_name not in self._retrieve_list: self._retrieve_list += [self.options.trajectory_name] self._retrieve_temporary_list += [] diff --git a/aiida_lammps/calculations/lammps/md.py b/aiida_lammps/calculations/lammps/md.py index 8c7afe9..5b50a89 100644 --- a/aiida_lammps/calculations/lammps/md.py +++ b/aiida_lammps/calculations/lammps/md.py @@ -118,7 +118,6 @@ def define(cls, spec): spec.input('metadata.options.parser_name', valid_type=six.string_types, default='lammps.md') spec.default_output_port = 'results' - # spec.input('settings', valid_type=six.string_types, default='lammps.optimize') spec.output('trajectory_data', valid_type=DataFactory('array.trajectory'), diff --git a/aiida_lammps/calculations/lammps/optimize.py b/aiida_lammps/calculations/lammps/optimize.py index 099ff7b..3801c19 100644 --- a/aiida_lammps/calculations/lammps/optimize.py +++ b/aiida_lammps/calculations/lammps/optimize.py @@ -69,6 +69,13 @@ def generate_lammps_input(calc, lammps_input_file += 'print "$(ylo) $(yhi) $(xz)"\n' lammps_input_file += 'print "$(zlo) $(zhi) $(yz)"\n' + variables = parameters.get("output_variables", []) + for var in variables: + if var.startswith("reax_"): + continue # these variables are set in the potential lines + lammps_input_file += 'variable {0} equal {0}\n'.format(var) + lammps_input_file += 'print "final_variable: {0} = ${{{0}}}"\n'.format(var) + return lammps_input_file diff --git a/aiida_lammps/common/raw_parsers.py b/aiida_lammps/common/raw_parsers.py index 5d25314..dac60cc 100644 --- a/aiida_lammps/common/raw_parsers.py +++ b/aiida_lammps/common/raw_parsers.py @@ -25,34 +25,35 @@ def parse_dynaphopy_output(file): q_points = [] for i, line in enumerate(data_lines): if 'Q-point' in line: - # print i, np.array(line.replace(']', '').replace('[', '').split()[4:8], dtype=float) + # print i, np.array(line.replace(']', '').replace('[', '').split()[4:8], dtype=float) indices.append(i) - q_points.append(np.array(line.replace(']', '').replace('[', '').split()[4:8],dtype=float)) + q_points.append(np.array(line.replace( + ']', '').replace('[', '').split()[4:8], dtype=float)) indices.append(len(data_lines)) phonons = {} for i, index in enumerate(indices[:-1]): - fragment = data_lines[indices[i]: indices[i+1]] + fragment = data_lines[indices[i]: indices[i + 1]] if 'kipped' in fragment: continue phonon_modes = {} for j, line in enumerate(fragment): if 'Peak' in line: number = line.split()[2] - phonon_mode = {'width': float(fragment[j+2].split()[1]), - 'positions': float(fragment[j+3].split()[1]), - 'shift': float(fragment[j+12].split()[2])} + phonon_mode = {'width': float(fragment[j + 2].split()[1]), + 'positions': float(fragment[j + 3].split()[1]), + 'shift': float(fragment[j + 12].split()[2])} phonon_modes.update({number: phonon_mode}) if 'Thermal' in line: - free_energy = float(fragment[j+4].split()[4]) - entropy = float(fragment[j+5].split()[3]) - cv = float(fragment[j+6].split()[3]) - total_energy = float(fragment[j+7].split()[4]) + free_energy = float(fragment[j + 4].split()[4]) + entropy = float(fragment[j + 5].split()[3]) + cv = float(fragment[j + 6].split()[3]) + total_energy = float(fragment[j + 7].split()[4]) - temperature = float(fragment[j].split()[5].replace('(','')) + temperature = float(fragment[j].split()[5].replace('(', '')) thermal_properties = {'temperature': temperature, 'free_energy': free_energy, @@ -60,12 +61,9 @@ def parse_dynaphopy_output(file): 'cv': cv, 'total_energy': total_energy} - phonon_modes.update({'q_point': q_points[i].tolist()}) - phonons.update({'wave_vector_'+str(i): phonon_modes}) - - f.close() + phonons.update({'wave_vector_' + str(i): phonon_modes}) return thermal_properties @@ -88,48 +86,44 @@ def read_lammps_forces(file_name): file_map = mmap.mmap(f.fileno(), 0) # Read time steps - position_number=file_map.find('TIMESTEP') + position_number = file_map.find('TIMESTEP') file_map.seek(position_number) file_map.readline() - - #Read number of atoms - position_number=file_map.find('NUMBER OF ATOMS') + # Read number of atoms + position_number = file_map.find('NUMBER OF ATOMS') file_map.seek(position_number) file_map.readline() number_of_atoms = int(file_map.readline()) - - #Read cell - position_number=file_map.find('ITEM: BOX') + # Read cell + position_number = file_map.find('ITEM: BOX') file_map.seek(position_number) file_map.readline() - bounds = [] for i in range(3): bounds.append(file_map.readline().split()) bounds = np.array(bounds, dtype=float) if bounds.shape[1] == 2: - bounds = np.append(bounds, np.array([0, 0, 0])[None].T ,axis=1) - + bounds = np.append(bounds, np.array([0, 0, 0])[None].T, axis=1) xy = bounds[0, 2] xz = bounds[1, 2] yz = bounds[2, 2] - xlo = bounds[0, 0] - np.min([0.0, xy, xz, xy+xz]) - xhi = bounds[0, 1] - np.max([0.0, xy, xz, xy+xz]) + xlo = bounds[0, 0] - np.min([0.0, xy, xz, xy + xz]) + xhi = bounds[0, 1] - np.max([0.0, xy, xz, xy + xz]) ylo = bounds[1, 0] - np.min([0.0, yz]) yhi = bounds[1, 1] - np.max([0.0, yz]) zlo = bounds[2, 0] zhi = bounds[2, 1] - super_cell = np.array([[xhi-xlo, xy, xz], - [0, yhi-ylo, yz], - [0, 0, zhi-zlo]]) + super_cell = np.array([[xhi - xlo, xy, xz], + [0, yhi - ylo, yz], + [0, 0, zhi - zlo]]) cells.append(super_cell.T) @@ -137,12 +131,12 @@ def read_lammps_forces(file_name): file_map.seek(position_number) file_map.readline() - #Reading forces + # Reading forces forces = [] read_elements = [] - for i in range (number_of_atoms): - line = file_map.readline().split()[0:number_of_dimensions+1] - forces.append(line[1:number_of_dimensions+1]) + for i in range(number_of_atoms): + line = file_map.readline().split()[0:number_of_dimensions + 1] + forces.append(line[1:number_of_dimensions + 1]) read_elements.append(line[0]) file_map.close() @@ -154,33 +148,38 @@ def read_lammps_forces(file_name): def read_log_file(logdata_txt): - data = logdata_txt.split('\n') + data = logdata_txt.splitlines() data_dict = {} units = None for i, line in enumerate(data): if 'Loop time' in line: - energy = float(data[i-1].split()[4]) + energy = float(data[i - 1].split()[4]) data_dict['energy'] = energy if 'units' in line: units = line.split()[1] + data_dict['units_style'] = units + if line.startswith("final_variable:"): + if 'final_variables' not in data_dict: + data_dict['final_variables'] = {} + data_dict['final_variables'][line.split()[1]] = float(line.split()[3]) return data_dict, units def read_lammps_trajectory_txt(data_txt, - limit_number_steps=100000000, - initial_cut=1, - timestep=1): + limit_number_steps=100000000, + initial_cut=1, + timestep=1): # Dimensionality of LAMMP calculation number_of_dimensions = 3 import re blocks = [m.start() for m in re.finditer('TIMESTEP', data_txt)] - blocks = [(blocks[i], blocks[i+1]) for i in range(len(blocks)-1)] + blocks = [(blocks[i], blocks[i + 1]) for i in range(len(blocks) - 1)] - blocks = blocks[initial_cut:initial_cut+limit_number_steps] + blocks = blocks[initial_cut:initial_cut + limit_number_steps] step_ids = [] position_list = [] @@ -193,10 +192,10 @@ def read_lammps_trajectory_txt(data_txt, # Read number of atoms block_lines = data_txt[ini:end].split('\n') id = block_lines.index('TIMESTEP') - time_steps.append(block_lines[id+1]) + time_steps.append(block_lines[id + 1]) id = get_index('NUMBER OF ATOMS', block_lines) - number_of_atoms = int(block_lines[id+1]) + number_of_atoms = int(block_lines[id + 1]) id = get_index('ITEM: BOX', block_lines) bounds = [line.split() for line in block_lines[id + 1:id + 4]] @@ -220,7 +219,7 @@ def read_lammps_trajectory_txt(data_txt, [0, 0, zhi - zlo]]) cell = super_cell.T - #id = [i for i, s in enumerate(block_lines) if 'ITEM: BOX BOUNDS' in s][0] + # id = [i for i, s in enumerate(block_lines) if 'ITEM: BOX BOUNDS' in s][0] # Reading positions id = get_index('ITEM: ATOMS', block_lines) @@ -228,7 +227,7 @@ def read_lammps_trajectory_txt(data_txt, positions = [] read_elements = [] for i in range(number_of_atoms): - line = block_lines[id + i+ 1].split() + line = block_lines[id + i + 1].split() positions.append(line[1:number_of_dimensions + 1]) read_elements.append(line[0]) @@ -275,8 +274,9 @@ def read_lammps_trajectory(file_name, counter += 1 # Read time steps - position_number=file_map.find(b'TIMESTEP') - if position_number < 0: break + position_number = file_map.find(b'TIMESTEP') + if position_number < 0: + break file_map.seek(position_number) file_map.readline() @@ -284,45 +284,46 @@ def read_lammps_trajectory(file_name, if number_of_atoms is None: # Read number of atoms - position_number=file_map.find(b'NUMBER OF ATOMS') + position_number = file_map.find(b'NUMBER OF ATOMS') file_map.seek(position_number) file_map.readline() number_of_atoms = int(file_map.readline()) if True: # Read cell - position_number=file_map.find(b'ITEM: BOX') + position_number = file_map.find(b'ITEM: BOX') file_map.seek(position_number) file_map.readline() - bounds = [] for i in range(3): bounds.append(file_map.readline().split()) bounds = np.array(bounds, dtype=float) if bounds.shape[1] == 2: - bounds = np.append(bounds, np.array([0, 0, 0])[None].T ,axis=1) + bounds = np.append(bounds, np.array( + [0, 0, 0])[None].T, axis=1) xy = bounds[0, 2] xz = bounds[1, 2] yz = bounds[2, 2] - xlo = bounds[0, 0] - np.min([0.0, xy, xz, xy+xz]) - xhi = bounds[0, 1] - np.max([0.0, xy, xz, xy+xz]) + xlo = bounds[0, 0] - np.min([0.0, xy, xz, xy + xz]) + xhi = bounds[0, 1] - np.max([0.0, xy, xz, xy + xz]) ylo = bounds[1, 0] - np.min([0.0, yz]) yhi = bounds[1, 1] - np.max([0.0, yz]) zlo = bounds[2, 0] zhi = bounds[2, 1] - super_cell = np.array([[xhi-xlo, xy, xz], - [0, yhi-ylo, yz], - [0, 0, zhi-zlo]]) + super_cell = np.array([[xhi - xlo, xy, xz], + [0, yhi - ylo, yz], + [0, 0, zhi - zlo]]) cells.append(super_cell.T) position_number = file_map.find(b'ITEM: ATOMS') file_map.seek(position_number) - lammps_labels=file_map.readline() # lammps_labels not used now but call is necessary! + # lammps_labels not used now but call is necessary! + lammps_labels = file_map.readline() # noqa: F841 # Initial cut control if initial_cut > counter: @@ -331,18 +332,20 @@ def read_lammps_trajectory(file_name, # Reading coordinates read_coordinates = [] read_elements = [] - for i in range (number_of_atoms): - line = file_map.readline().split()[0:number_of_dimensions+1] - read_coordinates.append(line[1:number_of_dimensions+1]) + for i in range(number_of_atoms): + line = file_map.readline().split()[0:number_of_dimensions + 1] + read_coordinates.append(line[1:number_of_dimensions + 1]) read_elements.append(line[0]) try: - data.append(np.array(read_coordinates, dtype=float)) #in angstroms + # in angstroms + data.append(np.array(read_coordinates, dtype=float)) except ValueError: print("Error reading step {0}".format(counter)) break # security routine to limit maximum of steps to read and put in memory - if limit_number_steps+initial_cut < counter: - print("Warning! maximum number of steps reached! No more steps will be read") + if limit_number_steps + initial_cut < counter: + print( + "Warning! maximum number of steps reached! No more steps will be read") break if end_cut is not None and end_cut <= counter: @@ -355,19 +358,18 @@ def read_lammps_trajectory(file_name, cells = np.array(cells) elements = np.array(read_elements, dtype='str') - time = np.array(step_ids)*timestep + time = np.array(step_ids) * timestep return data, step_ids, cells, elements, time def read_log_file_long(logdata_txt): - # Dimensionality of LAMMP calculation number_of_dimensions = 3 - data = logdata_txt.split('\n') + data = logdata_txt.splitlines() - #with open(logfile, 'r') as f: + # with open(logfile, 'r') as f: # data = f.readlines() if not data: @@ -378,21 +380,25 @@ def read_log_file_long(logdata_txt): for i, line in enumerate(data): if 'units' in line: units = line.split()[1] - + data_dict['units_style'] = units if 'Loop time' in line: - energy = float(data[i-1].split()[4]) + energy = float(data[i - 1].split()[4]) data_dict['energy'] = energy - xx, yy, zz, xy, xz, yz = data[i-1].split()[5:11] + xx, yy, zz, xy, xz, yz = data[i - 1].split()[5:11] stress = np.array([[xx, xy, xz], [xy, yy, yz], [xz, yz, zz]], dtype=float) if '$(xlo)' in line: - a = data[i+1].split() + a = data[i + 1].split() if '$(ylo)' in line: - b = data[i+1].split() + b = data[i + 1].split() if '$(zlo)' in line: - c = data[i+1].split() + c = data[i + 1].split() + if line.startswith("final_variable:"): + if 'final_variables' not in data_dict: + data_dict['final_variables'] = {} + data_dict['final_variables'][line.split()[1]] = float(line.split()[3]) bounds = np.array([a, b, c], dtype=float) @@ -407,17 +413,17 @@ def read_log_file_long(logdata_txt): zlo = bounds[2, 0] zhi = bounds[2, 1] - super_cell = np.array([[xhi-xlo, xy, xz], - [0, yhi-ylo, yz], - [0, 0, zhi-zlo]]) + super_cell = np.array([[xhi - xlo, xy, xz], + [0, yhi - ylo, yz], + [0, 0, zhi - zlo]]) cell = super_cell.T if np.linalg.det(cell) < 0: - cell = -1.0*cell + cell = -1.0 * cell volume = np.linalg.det(cell) - stress = -stress/volume * 1.e-3 # bar*A^3 -> kbar + stress = -stress / volume * 1.e-3 # bar*A^3 -> kbar return data_dict, cell, stress, units @@ -433,29 +439,27 @@ def read_lammps_positions_and_forces(file_name): # Dimensionality of LAMMP calculation number_of_dimensions = 3 - with open(file_name, "r+") as f: file_map = mmap.mmap(f.fileno(), 0) # Read time steps while True: - position_number=file_map.find('TIMESTEP') + position_number = file_map.find('TIMESTEP') try: file_map.seek(position_number) file_map.readline() except ValueError: break - #Read number of atoms - position_number=file_map.find('NUMBER OF ATOMS') + # Read number of atoms + position_number = file_map.find('NUMBER OF ATOMS') file_map.seek(position_number) file_map.readline() number_of_atoms = int(file_map.readline()) - - #Read cell - position_number=file_map.find('ITEM: BOX') + # Read cell + position_number = file_map.find('ITEM: BOX') file_map.seek(position_number) file_map.readline() @@ -465,38 +469,38 @@ def read_lammps_positions_and_forces(file_name): bounds = np.array(bounds, dtype=float) if bounds.shape[1] == 2: - bounds = np.append(bounds, np.array([0, 0, 0])[None].T ,axis=1) + bounds = np.append(bounds, np.array([0, 0, 0])[None].T, axis=1) xy = bounds[0, 2] xz = bounds[1, 2] yz = bounds[2, 2] - xlo = bounds[0, 0] - np.min([0.0, xy, xz, xy+xz]) - xhi = bounds[0, 1] - np.max([0.0, xy, xz, xy+xz]) + xlo = bounds[0, 0] - np.min([0.0, xy, xz, xy + xz]) + xhi = bounds[0, 1] - np.max([0.0, xy, xz, xy + xz]) ylo = bounds[1, 0] - np.min([0.0, yz]) yhi = bounds[1, 1] - np.max([0.0, yz]) zlo = bounds[2, 0] zhi = bounds[2, 1] - super_cell = np.array([[xhi-xlo, xy, xz], - [0, yhi-ylo, yz], - [0, 0, zhi-zlo]]) - - cell=super_cell.T + super_cell = np.array([[xhi - xlo, xy, xz], + [0, yhi - ylo, yz], + [0, 0, zhi - zlo]]) + cell = super_cell.T position_number = file_map.find('ITEM: ATOMS') file_map.seek(position_number) file_map.readline() - #Reading positions + # Reading positions positions = [] forces = [] read_elements = [] - for i in range (number_of_atoms): - line = file_map.readline().split()[0:number_of_dimensions*2+1] - positions.append(line[1:number_of_dimensions+1]) - forces.append(line[1+number_of_dimensions:number_of_dimensions*2+1]) + for i in range(number_of_atoms): + line = file_map.readline().split()[0:number_of_dimensions * 2 + 1] + positions.append(line[1:number_of_dimensions + 1]) + forces.append( + line[1 + number_of_dimensions:number_of_dimensions * 2 + 1]) read_elements.append(line[0]) file_map.close() @@ -520,10 +524,10 @@ def read_lammps_positions_and_forces_txt(data_txt): import re blocks = [m.start() for m in re.finditer('TIMESTEP', data_txt)] - blocks = [(blocks[i], blocks[i+1]) for i in range(len(blocks)-1)] + blocks = [(blocks[i], blocks[i + 1]) for i in range(len(blocks) - 1)] position_list = [] - forces_list=[] + forces_list = [] read_elements = None cell = None @@ -534,10 +538,10 @@ def read_lammps_positions_and_forces_txt(data_txt): block_lines = data_txt[ini:end].split('\n') id = block_lines.index('TIMESTEP') - time_steps.append(block_lines[id+1]) + time_steps.append(block_lines[id + 1]) id = get_index('NUMBER OF ATOMS', block_lines) - number_of_atoms = int(block_lines[id+1]) + number_of_atoms = int(block_lines[id + 1]) id = get_index('ITEM: BOX', block_lines) bounds = [line.split() for line in block_lines[id + 1:id + 4]] @@ -570,10 +574,11 @@ def read_lammps_positions_and_forces_txt(data_txt): forces = [] read_elements = [] for i in range(number_of_atoms): - line = block_lines[id + i+ 1].split() + line = block_lines[id + i + 1].split() positions.append(line[1:number_of_dimensions + 1]) - forces.append(line[1 + number_of_dimensions:number_of_dimensions * 2 + 1]) + forces.append( + line[1 + number_of_dimensions:number_of_dimensions * 2 + 1]) read_elements.append(line[0]) position_list.append(positions) @@ -596,124 +601,124 @@ def get_units_dict(style, quantities): """ units_dict = { - 'real': + 'real': { - 'mass': 'grams/mole', - 'distance': 'Angstroms', - 'time': 'femtoseconds', - 'energy': 'Kcal/mole', - 'velocity': 'Angstroms/femtosecond', - 'force': 'Kcal/mole-Angstrom', - 'torque': 'Kcal/mole', - 'temperature': 'Kelvin', - 'pressure': 'atmospheres', - 'dynamic_viscosity': 'Poise', - 'charge': 'e', # multiple of electron charge (1.0 is a proton) - 'dipole': 'charge*Angstroms', - 'electric field': 'volts/Angstrom', - 'density': 'gram/cm^dim', - }, - 'metal': { - - 'mass': 'grams/mole', - 'distance': 'Angstroms', - 'time': 'picoseconds', - 'energy': 'eV', - 'velocity': 'Angstroms/picosecond', - 'force': 'eV/Angstrom', - 'torque': 'eV', - 'temperature': 'Kelvin', - 'pressure': 'bars', - 'dynamic_viscosity': 'Poise', - 'charge': 'e', # multiple of electron charge (1.0 is a proton) - 'dipole': 'charge*Angstroms', - 'electric field': 'volts/Angstrom', - 'density': 'gram/cm^dim', - }, - 'si': { - 'mass': 'kilograms', - 'distance': 'meters', - 'time': 'seconds', - 'energy': 'Joules', - 'velocity': 'meters/second', - 'force': 'Newtons', - 'torque': 'Newton-meters', - 'temperature': 'Kelvin', - 'pressure': 'Pascals', - 'dynamic_viscosity': 'Pascal*second', - 'charge': 'Coulombs', # (1.6021765e-19 is a proton) - 'dipole': 'Coulombs*meters', - 'electric field': 'volts/meter', - 'density': 'kilograms/meter^dim', - }, - 'cgs': { - - 'mass': 'grams', - 'distance': 'centimeters', - 'time': 'seconds', - 'energy': 'ergs', - 'velocity': 'centimeters/second', - 'force': 'dynes', - 'torque': 'dyne-centimeters', - 'temperature': 'Kelvin', - 'pressure': 'dyne/cm^2', # or barye': '1.0e-6 bars - 'dynamic_viscosity': 'Poise', - 'charge': 'statcoulombs', # or esu (4.8032044e-10 is a proton) - 'dipole': 'statcoul-cm', #: '10^18 debye - 'electric_field': 'statvolt/cm', # or dyne/esu - 'density': 'grams/cm^dim', - }, - 'electron':{ - - 'mass': 'amu', - 'distance': 'Bohr', - 'time': 'femtoseconds', - 'energy': 'Hartrees', - 'velocity': 'Bohr/atu', #[1.03275e-15 seconds] - 'force': 'Hartrees/Bohr', - 'temperature': 'Kelvin', - 'pressure': 'Pascals', - 'charge': 'e', # multiple of electron charge (1.0 is a proton) - 'dipole_moment': 'Debye', - 'electric_field': 'volts/cm', - }, - 'micro': { - - 'mass': 'picograms', - 'distance': 'micrometers', - 'time': 'microseconds', - 'energy': 'picogram-micrometer^2/microsecond^2', - 'velocity': 'micrometers/microsecond', - 'force': 'picogram-micrometer/microsecond^2', - 'torque': 'picogram-micrometer^2/microsecond^2', - 'temperature': 'Kelvin', - 'pressure': 'picogram/(micrometer-microsecond^2)', - 'dynamic_viscosity': 'picogram/(micrometer-microsecond)', - 'charge': 'picocoulombs', # (1.6021765e-7 is a proton) - 'dipole': 'picocoulomb-micrometer', - 'electric field': 'volt/micrometer', - 'density': 'picograms/micrometer^dim', - }, - - 'nano': { - - 'mass': 'attograms', - 'distance': 'nanometers', - 'time': 'nanoseconds', - 'energy': 'attogram-nanometer^2/nanosecond^2', - 'velocity': 'nanometers/nanosecond', - 'force': 'attogram-nanometer/nanosecond^2', - 'torque': 'attogram-nanometer^2/nanosecond^2', - 'temperature': 'Kelvin', - 'pressure': 'attogram/(nanometer-nanosecond^2)', - 'dynamic_viscosity': 'attogram/(nanometer-nanosecond)', - 'charge': 'e', # multiple of electron charge (1.0 is a proton) - 'dipole': 'charge-nanometer', - 'electric_field': 'volt/nanometer', - 'density': 'attograms/nanometer^dim' - } + 'mass': 'grams/mole', + 'distance': 'Angstroms', + 'time': 'femtoseconds', + 'energy': 'Kcal/mole', + 'velocity': 'Angstroms/femtosecond', + 'force': 'Kcal/mole-Angstrom', + 'torque': 'Kcal/mole', + 'temperature': 'Kelvin', + 'pressure': 'atmospheres', + 'dynamic_viscosity': 'Poise', + 'charge': 'e', # multiple of electron charge (1.0 is a proton) + 'dipole': 'charge*Angstroms', + 'electric field': 'volts/Angstrom', + 'density': 'gram/cm^dim', + }, + 'metal': { + + 'mass': 'grams/mole', + 'distance': 'Angstroms', + 'time': 'picoseconds', + 'energy': 'eV', + 'velocity': 'Angstroms/picosecond', + 'force': 'eV/Angstrom', + 'torque': 'eV', + 'temperature': 'Kelvin', + 'pressure': 'bars', + 'dynamic_viscosity': 'Poise', + 'charge': 'e', # multiple of electron charge (1.0 is a proton) + 'dipole': 'charge*Angstroms', + 'electric field': 'volts/Angstrom', + 'density': 'gram/cm^dim', + }, + 'si': { + 'mass': 'kilograms', + 'distance': 'meters', + 'time': 'seconds', + 'energy': 'Joules', + 'velocity': 'meters/second', + 'force': 'Newtons', + 'torque': 'Newton-meters', + 'temperature': 'Kelvin', + 'pressure': 'Pascals', + 'dynamic_viscosity': 'Pascal*second', + 'charge': 'Coulombs', # (1.6021765e-19 is a proton) + 'dipole': 'Coulombs*meters', + 'electric field': 'volts/meter', + 'density': 'kilograms/meter^dim', + }, + 'cgs': { + + 'mass': 'grams', + 'distance': 'centimeters', + 'time': 'seconds', + 'energy': 'ergs', + 'velocity': 'centimeters/second', + 'force': 'dynes', + 'torque': 'dyne-centimeters', + 'temperature': 'Kelvin', + 'pressure': 'dyne/cm^2', # or barye': '1.0e-6 bars + 'dynamic_viscosity': 'Poise', + 'charge': 'statcoulombs', # or esu (4.8032044e-10 is a proton) + 'dipole': 'statcoul-cm', #: '10^18 debye + 'electric_field': 'statvolt/cm', # or dyne/esu + 'density': 'grams/cm^dim', + }, + 'electron': { + + 'mass': 'amu', + 'distance': 'Bohr', + 'time': 'femtoseconds', + 'energy': 'Hartrees', + 'velocity': 'Bohr/atu', # [1.03275e-15 seconds] + 'force': 'Hartrees/Bohr', + 'temperature': 'Kelvin', + 'pressure': 'Pascals', + 'charge': 'e', # multiple of electron charge (1.0 is a proton) + 'dipole_moment': 'Debye', + 'electric_field': 'volts/cm', + }, + 'micro': { + + 'mass': 'picograms', + 'distance': 'micrometers', + 'time': 'microseconds', + 'energy': 'picogram-micrometer^2/microsecond^2', + 'velocity': 'micrometers/microsecond', + 'force': 'picogram-micrometer/microsecond^2', + 'torque': 'picogram-micrometer^2/microsecond^2', + 'temperature': 'Kelvin', + 'pressure': 'picogram/(micrometer-microsecond^2)', + 'dynamic_viscosity': 'picogram/(micrometer-microsecond)', + 'charge': 'picocoulombs', # (1.6021765e-7 is a proton) + 'dipole': 'picocoulomb-micrometer', + 'electric field': 'volt/micrometer', + 'density': 'picograms/micrometer^dim', + }, + + 'nano': { + + 'mass': 'attograms', + 'distance': 'nanometers', + 'time': 'nanoseconds', + 'energy': 'attogram-nanometer^2/nanosecond^2', + 'velocity': 'nanometers/nanosecond', + 'force': 'attogram-nanometer/nanosecond^2', + 'torque': 'attogram-nanometer^2/nanosecond^2', + 'temperature': 'Kelvin', + 'pressure': 'attogram/(nanometer-nanosecond^2)', + 'dynamic_viscosity': 'attogram/(nanometer-nanosecond)', + 'charge': 'e', # multiple of electron charge (1.0 is a proton) + 'dipole': 'charge-nanometer', + 'electric_field': 'volt/nanometer', + 'density': 'attograms/nanometer^dim' + } } out_dict = {} for quantity in quantities: out_dict[quantity + "_units"] = units_dict[style][quantity] - return out_dict \ No newline at end of file + return out_dict diff --git a/aiida_lammps/parsers/lammps/force.py b/aiida_lammps/parsers/lammps/force.py index 5a58e37..48fdc16 100644 --- a/aiida_lammps/parsers/lammps/force.py +++ b/aiida_lammps/parsers/lammps/force.py @@ -1,3 +1,4 @@ +import traceback from aiida.parsers.parser import Parser from aiida.common import exceptions @@ -41,7 +42,12 @@ def parse(self, **kwargs): return self.exit_codes.ERROR_TRAJ_FILE_MISSING output_txt = out_folder.get_object_content(output_filename) - output_data, units = read_log_file(output_txt) + + try: + output_data, units = read_log_file(output_txt) + except Exception: + traceback.print_exc() + return self.exit_codes.ERROR_LOG_PARSING # output_data, cell, stress_tensor, units = read_log_file(output_txt) diff --git a/aiida_lammps/parsers/lammps/md.py b/aiida_lammps/parsers/lammps/md.py index b8f6c5b..d03668e 100644 --- a/aiida_lammps/parsers/lammps/md.py +++ b/aiida_lammps/parsers/lammps/md.py @@ -67,7 +67,11 @@ def parse(self, **kwargs): output_txt = out_folder.get_object_content(output_filename) - output_data, units = read_log_file(output_txt) + try: + output_data, units = read_log_file(output_txt) + except Exception: + traceback.print_exc() + return self.exit_codes.ERROR_LOG_PARSING output_data.update(get_units_dict(units, ["distance", "time"])) # add the dictionary with warnings @@ -83,10 +87,14 @@ def parse(self, **kwargs): info_filename = self.node.get_option('info_filename') if info_filename in list_of_temp_files: info_filepath = temporary_folder + '/' + self.node.get_option('info_filename') - with open(info_filepath) as handle: - names = handle.readline().strip().split() - sys_data = ArrayData() - for i, col in enumerate(np.loadtxt(info_filepath, skiprows=1, unpack=True)): - sys_data.set_array(names[i], col) - sys_data.set_attribute('units', units) - self.out('system_data', sys_data) + try: + with open(info_filepath) as handle: + names = handle.readline().strip().split() + sys_data = ArrayData() + for i, col in enumerate(np.loadtxt(info_filepath, skiprows=1, unpack=True)): + sys_data.set_array(names[i], col) + sys_data.set_attribute('units_style', units) + self.out('system_data', sys_data) + except Exception: + traceback.print_exc() + return self.exit_codes.ERROR_INFO_PARSING diff --git a/aiida_lammps/parsers/lammps/optimize.py b/aiida_lammps/parsers/lammps/optimize.py index ff864c7..0d7e581 100644 --- a/aiida_lammps/parsers/lammps/optimize.py +++ b/aiida_lammps/parsers/lammps/optimize.py @@ -1,3 +1,4 @@ +import traceback from aiida.parsers.parser import Parser from aiida.common import exceptions from aiida.orm import ArrayData, Dict, StructureData @@ -42,7 +43,11 @@ def parse(self, **kwargs): # Get files and do the parsing output_txt = out_folder.get_object_content(output_filename) - output_data, cell, stress_tensor, units = read_log_file(output_txt) + try: + output_data, cell, stress_tensor, units = read_log_file(output_txt) + except Exception: + traceback.print_exc() + return self.exit_codes.ERROR_LOG_PARSING trajectory_txt = out_folder.get_object_content(trajectory_filename) positions, forces, symbols, cell2 = read_lammps_positions_and_forces_txt(trajectory_txt) diff --git a/aiida_lammps/tests/__init__.py b/aiida_lammps/tests/__init__.py index f58c627..f0a74b4 100644 --- a/aiida_lammps/tests/__init__.py +++ b/aiida_lammps/tests/__init__.py @@ -1,2 +1,2 @@ """ tests for the plugin that does not pollute your profiles/databases. -""" \ No newline at end of file +""" diff --git a/aiida_lammps/tests/test_calculations.py b/aiida_lammps/tests/test_calculations.py index 475431f..c7ec130 100644 --- a/aiida_lammps/tests/test_calculations.py +++ b/aiida_lammps/tests/test_calculations.py @@ -13,6 +13,7 @@ def get_calc_parameters(plugin_name, units): if plugin_name == 'lammps.force': parameters_opt = { 'lammps_version': tests.lammps_version(), + 'output_variables': ["temp", "etotal"] } elif plugin_name == 'lammps.optimize': parameters_opt = { @@ -28,7 +29,8 @@ def get_calc_parameters(plugin_name, units): 'energy_tolerance': 1.0e-25, 'force_tolerance': 1.0e-25, 'max_evaluations': 100000, - 'max_iterations': 50000} + 'max_iterations': 50000}, + 'output_variables': ["temp", "etotal"] } elif plugin_name == "lammps.md": @@ -183,9 +185,12 @@ def test_force_process(db_test_app, get_potential_data, potential_type): assert set(link_labels).issuperset( ['results', 'arrays']) + # raise ValueError(calc_node.outputs.retrieved.get_object_content('log.lammps')) + pdict = calc_node.outputs.results.get_dict() assert set(pdict.keys()).issuperset( - ['energy', 'warnings', 'energy_units', 'force_units', 'parser_class', 'parser_version']) + ['energy', 'warnings', 'final_variables', 'units_style', + 'energy_units', 'force_units', 'parser_class', 'parser_version']) assert pdict['warnings'].strip() == pot_data.output["warnings"] assert pdict['energy'] == pytest.approx(pot_data.output['initial_energy']) @@ -233,7 +238,8 @@ def test_optimize_process(db_test_app, get_potential_data, potential_type): pdict = calc_node.outputs.results.get_dict() assert set(pdict.keys()).issuperset( - ['energy', 'warnings', 'energy_units', 'force_units', 'parser_class', 'parser_version']) + ['energy', 'warnings', 'final_variables', 'units_style', + 'energy_units', 'force_units', 'parser_class', 'parser_version']) assert pdict['warnings'].strip() == pot_data.output["warnings"] assert pdict['energy'] == pytest.approx(pot_data.output['energy']) diff --git a/aiida_lammps/tests/test_potential_data/test_init_reaxff_.yml b/aiida_lammps/tests/test_potential_data/test_init_reaxff_.yml index d07d568..a893b5c 100644 --- a/aiida_lammps/tests/test_potential_data/test_init_reaxff_.yml +++ b/aiida_lammps/tests/test_potential_data/test_init_reaxff_.yml @@ -1,6 +1,6 @@ atom_style: charge default_units: real -input_lines_md5: 6564bc40d8841625b0e790161f59ec5d +input_lines_md5: e3416e4f9226f1e4058dd46047833011 kind_names: - Fe - S diff --git a/aiida_lammps/tests/test_potential_data/test_input_lines_reaxff_.txt b/aiida_lammps/tests/test_potential_data/test_input_lines_reaxff_.txt index 2e49e8d..e5cf5c3 100644 --- a/aiida_lammps/tests/test_potential_data/test_input_lines_reaxff_.txt +++ b/aiida_lammps/tests/test_potential_data/test_input_lines_reaxff_.txt @@ -2,3 +2,17 @@ pair_style reax/c NULL safezone 1.6 pair_coeff * * potential.pot Fe S fix qeq all qeq/reax 1 0.0 10.0 1e-6 reax/c fix_modify qeq energy yes +variable reax_eb equal c_reax[1] +variable reax_ea equal c_reax[2] +variable reax_elp equal c_reax[3] +variable reax_emol equal c_reax[4] +variable reax_ev equal c_reax[5] +variable reax_epen equal c_reax[6] +variable reax_ecoa equal c_reax[7] +variable reax_ehb equal c_reax[8] +variable reax_et equal c_reax[9] +variable reax_eco equal c_reax[10] +variable reax_ew equal c_reax[11] +variable reax_ep equal c_reax[12] +variable reax_efi equal c_reax[13] +variable reax_eqeq equal c_reax[14] diff --git a/aiida_lammps/validation/__init__.py b/aiida_lammps/validation/__init__.py index e7e699d..41ee6b2 100644 --- a/aiida_lammps/validation/__init__.py +++ b/aiida_lammps/validation/__init__.py @@ -40,4 +40,4 @@ def validate_with_dict(data, schema): validator = jsonschema.Draft4Validator # by default, only validates lists - validator(schema, types={"array": (list, tuple)}).validate(data) \ No newline at end of file + validator(schema, types={"array": (list, tuple)}).validate(data) diff --git a/aiida_lammps/validation/force.schema.json b/aiida_lammps/validation/force.schema.json new file mode 100644 index 0000000..a69effe --- /dev/null +++ b/aiida_lammps/validation/force.schema.json @@ -0,0 +1,101 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "LAMMPS MD Run Parameters", + "type": "object", + "additionalProperties": false, + "properties": { + "lammps_version": { + "description": "the version of lammps in date format, e.g 10 Nov 2015", + "type": "string" + }, + "output_variables": { + "description": "output variables to record as attributes (see thermo_style)", + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "step", + "elapsed", + "elaplong", + "dt", + "time", + "cpu", + "tpcpu", + "spcpu", + "cpuremain", + "part", + "timeremain", + "atoms", + "temp", + "press", + "pe", + "ke", + "etotal", + "enthalpy", + "evdwl", + "ecoul", + "epair", + "ebond", + "eangle", + "edihed", + "eimp", + "emol", + "elong", + "etail", + "vol", + "density", + "lx", + "ly", + "lz", + "xlo", + "xhi", + "ylo", + "yhi", + "zlo", + "zhi", + "xy", + "xz", + "yz", + "xlat", + "ylat", + "zlat", + "bonds", + "angles", + "dihedrals", + "impropers", + "pxx", + "pyy", + "pzz", + "pxy", + "pxz", + "pyz", + "fmax", + "fnorm", + "nbuild", + "ndanger", + "cella", + "cellb", + "cellc", + "cellalpha", + "cellbeta", + "cellgamma", + "reax_eb", + "reax_ea", + "reax_elp", + "reax_emol", + "reax_ev", + "reax_epen", + "reax_ecoa", + "reax_ehb", + "reax_et", + "reax_eco", + "reax_ew", + "reax_ep", + "reax_efi", + "reax_eqeq" + ] + } + } + } +} \ No newline at end of file diff --git a/aiida_lammps/validation/optimize.schema.json b/aiida_lammps/validation/optimize.schema.json index 64da28d..7210fa4 100644 --- a/aiida_lammps/validation/optimize.schema.json +++ b/aiida_lammps/validation/optimize.schema.json @@ -100,6 +100,95 @@ ] } } + }, + "output_variables": { + "description": "optimized output variables to record as attributes (see thermo_style)", + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "step", + "elapsed", + "elaplong", + "dt", + "time", + "cpu", + "tpcpu", + "spcpu", + "cpuremain", + "part", + "timeremain", + "atoms", + "temp", + "press", + "pe", + "ke", + "etotal", + "enthalpy", + "evdwl", + "ecoul", + "epair", + "ebond", + "eangle", + "edihed", + "eimp", + "emol", + "elong", + "etail", + "vol", + "density", + "lx", + "ly", + "lz", + "xlo", + "xhi", + "ylo", + "yhi", + "zlo", + "zhi", + "xy", + "xz", + "yz", + "xlat", + "ylat", + "zlat", + "bonds", + "angles", + "dihedrals", + "impropers", + "pxx", + "pyy", + "pzz", + "pxy", + "pxz", + "pyz", + "fmax", + "fnorm", + "nbuild", + "ndanger", + "cella", + "cellb", + "cellc", + "cellalpha", + "cellbeta", + "cellgamma", + "reax_eb", + "reax_ea", + "reax_elp", + "reax_emol", + "reax_ev", + "reax_epen", + "reax_ecoa", + "reax_ehb", + "reax_et", + "reax_eco", + "reax_ew", + "reax_ep", + "reax_efi", + "reax_eqeq" + ] + } } } } From cec3c74ee67de1116cb2c2d4955889dec8102500 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Thu, 20 Jun 2019 16:48:18 +0100 Subject: [PATCH 10/28] remove reax variable output (not working) and add reading of errors from stdout --- aiida_lammps/calculations/lammps/force.py | 2 -- aiida_lammps/calculations/lammps/md.py | 2 -- aiida_lammps/calculations/lammps/optimize.py | 2 -- aiida_lammps/data/potential/reaxff.py | 33 +++++++++++-------- aiida_lammps/parsers/lammps/force.py | 15 ++++++--- aiida_lammps/parsers/lammps/md.py | 14 ++++++-- aiida_lammps/parsers/lammps/optimize.py | 14 ++++++-- aiida_lammps/tests/test_calculations.py | 1 + .../test_potential_data/test_init_reaxff_.yml | 2 +- .../test_input_lines_reaxff_.txt | 14 -------- aiida_lammps/validation/force.schema.json | 16 +-------- aiida_lammps/validation/md.schema.json | 16 +-------- aiida_lammps/validation/optimize.schema.json | 16 +-------- 13 files changed, 57 insertions(+), 90 deletions(-) diff --git a/aiida_lammps/calculations/lammps/force.py b/aiida_lammps/calculations/lammps/force.py index 206096b..efa8f16 100644 --- a/aiida_lammps/calculations/lammps/force.py +++ b/aiida_lammps/calculations/lammps/force.py @@ -40,8 +40,6 @@ def generate_lammps_input(calc, variables = parameters.get_attribute("output_variables", []) for var in variables: - if var.startswith("reax_"): - continue # these variables are set in the potential lines lammps_input_file += 'variable {0} equal {0}\n'.format(var) lammps_input_file += 'print "final_variable: {0} = ${{{0}}}"\n'.format(var) diff --git a/aiida_lammps/calculations/lammps/md.py b/aiida_lammps/calculations/lammps/md.py index 5b50a89..a4071fe 100644 --- a/aiida_lammps/calculations/lammps/md.py +++ b/aiida_lammps/calculations/lammps/md.py @@ -93,8 +93,6 @@ def generate_lammps_input(calc, # NOTE `dump` includes step 0, whereas `print` starts from step 1 variables.append('step') for var in variables: - if var.startswith("reax_"): - continue # these variables are set in the potential lines lammps_input_file += 'variable {0} equal {0}\n'.format(var) if variables: lammps_input_file += 'fix sys_info all print {0} "{1}" title "{2}" file {3} screen no\n'.format( diff --git a/aiida_lammps/calculations/lammps/optimize.py b/aiida_lammps/calculations/lammps/optimize.py index 3801c19..797f951 100644 --- a/aiida_lammps/calculations/lammps/optimize.py +++ b/aiida_lammps/calculations/lammps/optimize.py @@ -71,8 +71,6 @@ def generate_lammps_input(calc, variables = parameters.get("output_variables", []) for var in variables: - if var.startswith("reax_"): - continue # these variables are set in the potential lines lammps_input_file += 'variable {0} equal {0}\n'.format(var) lammps_input_file += 'print "final_variable: {0} = ${{{0}}}"\n'.format(var) diff --git a/aiida_lammps/data/potential/reaxff.py b/aiida_lammps/data/potential/reaxff.py index 14b1963..619167b 100644 --- a/aiida_lammps/data/potential/reaxff.py +++ b/aiida_lammps/data/potential/reaxff.py @@ -25,20 +25,25 @@ def get_input_potential_lines(data, names=None, potential_filename='potential.po lammps_input_text += "fix qeq all qeq/reax 1 0.0 10.0 1e-6 reax/c\n" lammps_input_text += "fix_modify qeq energy yes\n" - lammps_input_text += "variable reax_eb equal c_reax[1]\n" - lammps_input_text += "variable reax_ea equal c_reax[2]\n" - lammps_input_text += "variable reax_elp equal c_reax[3]\n" - lammps_input_text += "variable reax_emol equal c_reax[4]\n" - lammps_input_text += "variable reax_ev equal c_reax[5]\n" - lammps_input_text += "variable reax_epen equal c_reax[6]\n" - lammps_input_text += "variable reax_ecoa equal c_reax[7]\n" - lammps_input_text += "variable reax_ehb equal c_reax[8]\n" - lammps_input_text += "variable reax_et equal c_reax[9]\n" - lammps_input_text += "variable reax_eco equal c_reax[10]\n" - lammps_input_text += "variable reax_ew equal c_reax[11]\n" - lammps_input_text += "variable reax_ep equal c_reax[12]\n" - lammps_input_text += "variable reax_efi equal c_reax[13]\n" - lammps_input_text += "variable reax_eqeq equal c_reax[14]\n" + # lammps_input_text += "compute reax all pair reax/c\n" + # lammps_input_text += "variable reax_eb equal c_reax[1]\n" + # lammps_input_text += "variable reax_ea equal c_reax[2]\n" + # lammps_input_text += "variable reax_elp equal c_reax[3]\n" + # lammps_input_text += "variable reax_emol equal c_reax[4]\n" + # lammps_input_text += "variable reax_ev equal c_reax[5]\n" + # lammps_input_text += "variable reax_epen equal c_reax[6]\n" + # lammps_input_text += "variable reax_ecoa equal c_reax[7]\n" + # lammps_input_text += "variable reax_ehb equal c_reax[8]\n" + # lammps_input_text += "variable reax_et equal c_reax[9]\n" + # lammps_input_text += "variable reax_eco equal c_reax[10]\n" + # lammps_input_text += "variable reax_ew equal c_reax[11]\n" + # lammps_input_text += "variable reax_ep equal c_reax[12]\n" + # lammps_input_text += "variable reax_efi equal c_reax[13]\n" + # lammps_input_text += "variable reax_eqeq equal c_reax[14]\n" + + # TODO to access these variables, the compute must be triggered, + # for example by adding c_reax[1] to the thermo_style + # but how to do this in a generalised manner? return lammps_input_text diff --git a/aiida_lammps/parsers/lammps/force.py b/aiida_lammps/parsers/lammps/force.py index 48fdc16..97ae3db 100644 --- a/aiida_lammps/parsers/lammps/force.py +++ b/aiida_lammps/parsers/lammps/force.py @@ -49,13 +49,14 @@ def parse(self, **kwargs): traceback.print_exc() return self.exit_codes.ERROR_LOG_PARSING - # output_data, cell, stress_tensor, units = read_log_file(output_txt) - trajectory_txt = out_folder.get_object_content(trajectory_filename) positions, forces, symbols, cell2 = read_lammps_positions_and_forces_txt(trajectory_txt) - warnings = out_folder.get_object_content('_scheduler-stderr.txt') + warnings = out_folder.get_object_content(self.node.get_option("scheduler_stderr")) + # for some reason, errors may be in the stdout, but not the log.lammps + stdout = out_folder.get_object_content(self.node.get_option("scheduler_stdout")) + errors = [line for line in stdout.splitlines() if line.startswith("ERROR")] # ====================== prepare the output node ====================== @@ -67,8 +68,9 @@ def parse(self, **kwargs): array_data.set_array('forces', forces) self.out('arrays', array_data) - # add the dictionary with warnings + # add the dictionary with warnings and errors output_data.update({'warnings': warnings}) + output_data.update({'errors': errors}) output_data["parser_class"] = self.__class__.__name__ output_data["parser_version"] = aiida_lammps_version @@ -76,3 +78,8 @@ def parse(self, **kwargs): # self.out(self.node.get_linkname_outparams(), parameters_data) self.out('results', parameters_data) + + if output_data["errors"]: + for error in output_data["errors"]: + self.logger.error(error) + return self.exit_codes.ERROR_LAMMPS_RUN diff --git a/aiida_lammps/parsers/lammps/md.py b/aiida_lammps/parsers/lammps/md.py index d03668e..5c72b75 100644 --- a/aiida_lammps/parsers/lammps/md.py +++ b/aiida_lammps/parsers/lammps/md.py @@ -62,8 +62,10 @@ def parse(self, **kwargs): traceback.print_exc() return self.exit_codes.ERROR_TRAJ_PARSING - # Read other data from output folder - warnings = out_folder.get_object_content('_scheduler-stderr.txt') + warnings = out_folder.get_object_content(self.node.get_option("scheduler_stderr")) + # for some reason, errors may be in the stdout, but not the log.lammps + stdout = out_folder.get_object_content(self.node.get_option("scheduler_stdout")) + errors = [line for line in stdout.splitlines() if line.startswith("ERROR")] output_txt = out_folder.get_object_content(output_filename) @@ -74,8 +76,9 @@ def parse(self, **kwargs): return self.exit_codes.ERROR_LOG_PARSING output_data.update(get_units_dict(units, ["distance", "time"])) - # add the dictionary with warnings + # add the dictionary with warnings and errors output_data.update({'warnings': warnings}) + output_data.update({'errors': errors}) output_data["parser_class"] = self.__class__.__name__ output_data["parser_version"] = aiida_lammps_version @@ -98,3 +101,8 @@ def parse(self, **kwargs): except Exception: traceback.print_exc() return self.exit_codes.ERROR_INFO_PARSING + + if output_data["errors"]: + for error in output_data["errors"]: + self.logger.error(error) + return self.exit_codes.ERROR_LAMMPS_RUN \ No newline at end of file diff --git a/aiida_lammps/parsers/lammps/optimize.py b/aiida_lammps/parsers/lammps/optimize.py index 0d7e581..3edbf16 100644 --- a/aiida_lammps/parsers/lammps/optimize.py +++ b/aiida_lammps/parsers/lammps/optimize.py @@ -52,7 +52,10 @@ def parse(self, **kwargs): trajectory_txt = out_folder.get_object_content(trajectory_filename) positions, forces, symbols, cell2 = read_lammps_positions_and_forces_txt(trajectory_txt) - warnings = out_folder.get_object_content('_scheduler-stderr.txt') + warnings = out_folder.get_object_content(self.node.get_option("scheduler_stderr")) + # for some reason, errors may be in the stdout, but not the log.lammps + stdout = out_folder.get_object_content(self.node.get_option("scheduler_stdout")) + errors = [line for line in stdout.splitlines() if line.startswith("ERROR")] # ====================== prepare the output node ====================== @@ -75,12 +78,17 @@ def parse(self, **kwargs): array_data.set_array('positions', positions) self.out('arrays', array_data) - # add the dictionary with warnings + # add the dictionary with warnings and errors output_data.update({'warnings': warnings}) + output_data.update({'errors': errors}) output_data["parser_class"] = self.__class__.__name__ output_data["parser_version"] = aiida_lammps_version parameters_data = Dict(dict=output_data) - # self.out(self.node.get_linkname_outparams(), parameters_data) self.out('results', parameters_data) + + if output_data["errors"]: + for error in output_data["errors"]: + self.logger.error(error) + return self.exit_codes.ERROR_LAMMPS_RUN diff --git a/aiida_lammps/tests/test_calculations.py b/aiida_lammps/tests/test_calculations.py index c7ec130..dec88fd 100644 --- a/aiida_lammps/tests/test_calculations.py +++ b/aiida_lammps/tests/test_calculations.py @@ -185,6 +185,7 @@ def test_force_process(db_test_app, get_potential_data, potential_type): assert set(link_labels).issuperset( ['results', 'arrays']) + # raise ValueError(calc_node.outputs.retrieved.get_object_content('_scheduler-stdout.txt')) # raise ValueError(calc_node.outputs.retrieved.get_object_content('log.lammps')) pdict = calc_node.outputs.results.get_dict() diff --git a/aiida_lammps/tests/test_potential_data/test_init_reaxff_.yml b/aiida_lammps/tests/test_potential_data/test_init_reaxff_.yml index a893b5c..d07d568 100644 --- a/aiida_lammps/tests/test_potential_data/test_init_reaxff_.yml +++ b/aiida_lammps/tests/test_potential_data/test_init_reaxff_.yml @@ -1,6 +1,6 @@ atom_style: charge default_units: real -input_lines_md5: e3416e4f9226f1e4058dd46047833011 +input_lines_md5: 6564bc40d8841625b0e790161f59ec5d kind_names: - Fe - S diff --git a/aiida_lammps/tests/test_potential_data/test_input_lines_reaxff_.txt b/aiida_lammps/tests/test_potential_data/test_input_lines_reaxff_.txt index e5cf5c3..2e49e8d 100644 --- a/aiida_lammps/tests/test_potential_data/test_input_lines_reaxff_.txt +++ b/aiida_lammps/tests/test_potential_data/test_input_lines_reaxff_.txt @@ -2,17 +2,3 @@ pair_style reax/c NULL safezone 1.6 pair_coeff * * potential.pot Fe S fix qeq all qeq/reax 1 0.0 10.0 1e-6 reax/c fix_modify qeq energy yes -variable reax_eb equal c_reax[1] -variable reax_ea equal c_reax[2] -variable reax_elp equal c_reax[3] -variable reax_emol equal c_reax[4] -variable reax_ev equal c_reax[5] -variable reax_epen equal c_reax[6] -variable reax_ecoa equal c_reax[7] -variable reax_ehb equal c_reax[8] -variable reax_et equal c_reax[9] -variable reax_eco equal c_reax[10] -variable reax_ew equal c_reax[11] -variable reax_ep equal c_reax[12] -variable reax_efi equal c_reax[13] -variable reax_eqeq equal c_reax[14] diff --git a/aiida_lammps/validation/force.schema.json b/aiida_lammps/validation/force.schema.json index a69effe..a2f7a66 100644 --- a/aiida_lammps/validation/force.schema.json +++ b/aiida_lammps/validation/force.schema.json @@ -79,21 +79,7 @@ "cellc", "cellalpha", "cellbeta", - "cellgamma", - "reax_eb", - "reax_ea", - "reax_elp", - "reax_emol", - "reax_ev", - "reax_epen", - "reax_ecoa", - "reax_ehb", - "reax_et", - "reax_eco", - "reax_ew", - "reax_ep", - "reax_efi", - "reax_eqeq" + "cellgamma" ] } } diff --git a/aiida_lammps/validation/md.schema.json b/aiida_lammps/validation/md.schema.json index e54ac8b..e34b9da 100644 --- a/aiida_lammps/validation/md.schema.json +++ b/aiida_lammps/validation/md.schema.json @@ -321,21 +321,7 @@ "cellc", "cellalpha", "cellbeta", - "cellgamma", - "reax_eb", - "reax_ea", - "reax_elp", - "reax_emol", - "reax_ev", - "reax_epen", - "reax_ecoa", - "reax_ehb", - "reax_et", - "reax_eco", - "reax_ew", - "reax_ep", - "reax_efi", - "reax_eqeq" + "cellgamma" ] } } diff --git a/aiida_lammps/validation/optimize.schema.json b/aiida_lammps/validation/optimize.schema.json index 7210fa4..a2521b5 100644 --- a/aiida_lammps/validation/optimize.schema.json +++ b/aiida_lammps/validation/optimize.schema.json @@ -172,21 +172,7 @@ "cellc", "cellalpha", "cellbeta", - "cellgamma", - "reax_eb", - "reax_ea", - "reax_elp", - "reax_emol", - "reax_ev", - "reax_epen", - "reax_ecoa", - "reax_ehb", - "reax_et", - "reax_eco", - "reax_ew", - "reax_ep", - "reax_efi", - "reax_eqeq" + "cellgamma" ] } } From 788a25fe1c6a1d6fe1a67a2ea7b0043e8d1e454b Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Thu, 20 Jun 2019 18:28:00 +0100 Subject: [PATCH 11/28] create base abstract parser class --- aiida_lammps/parsers/lammps/base.py | 104 ++++++++++++++++++++++++ aiida_lammps/parsers/lammps/force.py | 73 +++++------------ aiida_lammps/parsers/lammps/md.py | 100 +++++++---------------- aiida_lammps/parsers/lammps/optimize.py | 70 ++++------------ 4 files changed, 170 insertions(+), 177 deletions(-) create mode 100644 aiida_lammps/parsers/lammps/base.py diff --git a/aiida_lammps/parsers/lammps/base.py b/aiida_lammps/parsers/lammps/base.py new file mode 100644 index 0000000..82fb62f --- /dev/null +++ b/aiida_lammps/parsers/lammps/base.py @@ -0,0 +1,104 @@ +import os +import traceback + +from aiida.parsers.parser import Parser +from aiida.common import exceptions + +from aiida_lammps import __version__ as aiida_lammps_version +from aiida_lammps.common.raw_parsers import read_log_file, read_log_file_long + + +class LAMMPSBaseParser(Parser): + """ + Abstract Base Parser for LAMMPS, supplying common methods + """ + + def __init__(self, node): + """ + Initialize the instance of Force LammpsParser + """ + super(LAMMPSBaseParser, self).__init__(node) + + def get_parsing_resources(self, kwargs, traj_in_temp=False, sys_info=False): + # Check that the retrieved folder is there + try: + out_folder = self.retrieved + except exceptions.NotExistent: + return None, self.exit_codes.ERROR_NO_RETRIEVED_FOLDER + + if traj_in_temp or sys_info: + if 'retrieved_temporary_folder' not in kwargs: + return None, self.exit_codes.ERROR_NO_RETRIEVED_TEMP_FOLDER + temporary_folder = kwargs['retrieved_temporary_folder'] + list_of_temp_files = os.listdir(temporary_folder) + + # check what is inside the folder + list_of_files = out_folder.list_object_names() + + # check outout folder + output_filename = self.node.get_option('output_filename') + if output_filename not in list_of_files: + return None, self.exit_codes.ERROR_OUTPUT_FILE_MISSING + + # check temporal folder + trajectory_filename = self.node.get_option('trajectory_name') + + if traj_in_temp: + # check trajectory in temporal folder + if trajectory_filename not in list_of_temp_files: + return None, self.exit_codes.ERROR_TRAJ_FILE_MISSING + trajectory_filepath = os.path.join(temporary_folder, trajectory_filename) + else: + # check trajectory in retrived folder + if trajectory_filename not in list_of_files: + return None, self.exit_codes.ERROR_TRAJ_FILE_MISSING + trajectory_filepath = None + + info_filepath = None + if sys_info: + info_filename = self.node.get_option('info_filename') + if info_filename in list_of_temp_files: + info_filepath = os.path.join(temporary_folder, self.node.get_option('info_filename')) + + return (trajectory_filename, trajectory_filepath, info_filepath), None + + def parse_log_file(self): + """ parse the log file """ + output_filename = self.node.get_option('output_filename') + output_txt = self.retrieved.get_object_content(output_filename) + try: + output_data, units = read_log_file(output_txt) + except Exception: + traceback.print_exc() + return None, None, self.exit_codes.ERROR_LOG_PARSING + return output_data, units, None + + def parse_log_file_long(self, long=False): + """ parse the log file """ + output_filename = self.node.get_option('output_filename') + output_txt = self.retrieved.get_object_content(output_filename) + try: + output_data, cell, stress_tensor, units = read_log_file_long(output_txt) + except Exception: + traceback.print_exc() + return None, None, None, None, self.exit_codes.ERROR_LOG_PARSING + return output_data, cell, stress_tensor, units, None + + def add_warnings_and_errors(self, output_data): + """ add warning and errors to the output data """ + # add the dictionary with warnings and errors + warnings = self.retrieved.get_object_content(self.node.get_option("scheduler_stderr")) + # for some reason, errors may be in the stdout, but not the log.lammps + stdout = self.retrieved.get_object_content(self.node.get_option("scheduler_stdout")) + errors = [line for line in stdout.splitlines() if line.startswith("ERROR")] + + for error in errors: + self.logger.error(error) + + output_data.update({'warnings': warnings}) + output_data.update({'errors': errors}) + + def add_standard_info(self, output_data): + """ add standard information to output data """ + output_data["parser_class"] = self.__class__.__name__ + output_data["parser_version"] = aiida_lammps_version diff --git a/aiida_lammps/parsers/lammps/force.py b/aiida_lammps/parsers/lammps/force.py index 97ae3db..8de4ce6 100644 --- a/aiida_lammps/parsers/lammps/force.py +++ b/aiida_lammps/parsers/lammps/force.py @@ -1,14 +1,10 @@ -import traceback -from aiida.parsers.parser import Parser -from aiida.common import exceptions - from aiida.orm import Dict, ArrayData -from aiida_lammps import __version__ as aiida_lammps_version -from aiida_lammps.common.raw_parsers import read_lammps_positions_and_forces_txt, read_log_file, get_units_dict +from aiida_lammps.parsers.lammps.base import LAMMPSBaseParser +from aiida_lammps.common.raw_parsers import read_lammps_positions_and_forces_txt, get_units_dict -class ForceParser(Parser): +class ForceParser(LAMMPSBaseParser): """ Simple Parser for LAMMPS. """ @@ -23,63 +19,32 @@ def parse(self, **kwargs): """ Parses the datafolder, stores results. """ + # retrieve resources + resources, exit_code = self.get_parsing_resources(kwargs) + if exit_code is not None: + return exit_code + trajectory_filename, trajectory_filepath, info_filepath = resources + + # parse log file + output_data, units, exit_code = self.parse_log_file() + if exit_code is not None: + return exit_code + output_data.update(get_units_dict(units, ["energy", "force", "distance"])) - # Check that the retrieved folder is there - try: - out_folder = self.retrieved - except exceptions.NotExistent: - return self.exit_codes.ERROR_NO_RETRIEVED_FOLDER - - # check what is inside the folder - list_of_files = out_folder.list_object_names() - - output_filename = self.node.get_option('output_filename') - if output_filename not in list_of_files: - return self.exit_codes.ERROR_OUTPUT_FILE_MISSING - - trajectory_filename = self.node.get_option('trajectory_name') - if trajectory_filename not in list_of_files: - return self.exit_codes.ERROR_TRAJ_FILE_MISSING - - output_txt = out_folder.get_object_content(output_filename) - - try: - output_data, units = read_log_file(output_txt) - except Exception: - traceback.print_exc() - return self.exit_codes.ERROR_LOG_PARSING - - trajectory_txt = out_folder.get_object_content(trajectory_filename) - + # parse trajectory file + trajectory_txt = self.retrieved.get_object_content(trajectory_filename) positions, forces, symbols, cell2 = read_lammps_positions_and_forces_txt(trajectory_txt) - warnings = out_folder.get_object_content(self.node.get_option("scheduler_stderr")) - # for some reason, errors may be in the stdout, but not the log.lammps - stdout = out_folder.get_object_content(self.node.get_option("scheduler_stdout")) - errors = [line for line in stdout.splitlines() if line.startswith("ERROR")] - - # ====================== prepare the output node ====================== - - # add units used - output_data.update(get_units_dict(units, ["energy", "force", "distance"])) - # save forces and stresses into node array_data = ArrayData() array_data.set_array('forces', forces) self.out('arrays', array_data) - # add the dictionary with warnings and errors - output_data.update({'warnings': warnings}) - output_data.update({'errors': errors}) - output_data["parser_class"] = self.__class__.__name__ - output_data["parser_version"] = aiida_lammps_version - + # save results into node + self.add_warnings_and_errors(output_data) + self.add_standard_info(output_data) parameters_data = Dict(dict=output_data) - - # self.out(self.node.get_linkname_outparams(), parameters_data) self.out('results', parameters_data) if output_data["errors"]: - for error in output_data["errors"]: - self.logger.error(error) return self.exit_codes.ERROR_LAMMPS_RUN diff --git a/aiida_lammps/parsers/lammps/md.py b/aiida_lammps/parsers/lammps/md.py index 5c72b75..1338150 100644 --- a/aiida_lammps/parsers/lammps/md.py +++ b/aiida_lammps/parsers/lammps/md.py @@ -1,14 +1,12 @@ -import os import traceback import numpy as np -from aiida.parsers.parser import Parser -from aiida.common import exceptions from aiida.orm import Dict, TrajectoryData, ArrayData -from aiida_lammps import __version__ as aiida_lammps_version -from aiida_lammps.common.raw_parsers import read_lammps_trajectory, get_units_dict, read_log_file +from aiida_lammps.parsers.lammps.base import LAMMPSBaseParser +from aiida_lammps.common.raw_parsers import read_lammps_trajectory, get_units_dict -class MdParser(Parser): + +class MdParser(LAMMPSBaseParser): """ Simple Parser for LAMMPS. """ @@ -23,86 +21,50 @@ def parse(self, **kwargs): """ Parses the datafolder, stores results. """ + # retrieve resources + resources, exit_code = self.get_parsing_resources(kwargs, traj_in_temp=True, sys_info=True) + if exit_code is not None: + return exit_code + trajectory_filename, trajectory_filepath, info_filepath = resources + + # parse log file + output_data, units, exit_code = self.parse_log_file() + if exit_code is not None: + return exit_code + output_data.update(get_units_dict(units, ["distance", "time"])) - # Check that the retrieved folder is there - try: - out_folder = self.retrieved - except exceptions.NotExistent: - return self.exit_codes.ERROR_NO_RETRIEVED_FOLDER - - if 'retrieved_temporary_folder' not in kwargs: - return self.exit_codes.ERROR_NO_RETRIEVED_TEMP_FOLDER - temporary_folder = kwargs['retrieved_temporary_folder'] - - # check what is inside the folder - list_of_files = out_folder.list_object_names() - list_of_temp_files = os.listdir(temporary_folder) - - # check outout folder - output_filename = self.node.get_option('output_filename') - if output_filename not in list_of_files: - return self.exit_codes.ERROR_OUTPUT_FILE_MISSING - - # check temporal folder - trajectory_filename = self.node.get_option('trajectory_name') - if trajectory_filename not in list_of_temp_files: - return self.exit_codes.ERROR_TRAJ_FILE_MISSING - - # Read trajectory from temporal folder - trajectory_filepath = temporary_folder + '/' + self.node.get_option('trajectory_name') - timestep = self.node.inputs.parameters.dict.timestep - - # save trajectory into node + # parse trajectory file try: + timestep = self.node.inputs.parameters.dict.timestep positions, step_ids, cells, symbols, time = read_lammps_trajectory(trajectory_filepath, timestep=timestep) - trajectory_data = TrajectoryData() - trajectory_data.set_trajectory(symbols, positions, stepids=step_ids, cells=cells, times=time) - self.out('trajectory_data', trajectory_data) except Exception: traceback.print_exc() return self.exit_codes.ERROR_TRAJ_PARSING - warnings = out_folder.get_object_content(self.node.get_option("scheduler_stderr")) - # for some reason, errors may be in the stdout, but not the log.lammps - stdout = out_folder.get_object_content(self.node.get_option("scheduler_stdout")) - errors = [line for line in stdout.splitlines() if line.startswith("ERROR")] - - output_txt = out_folder.get_object_content(output_filename) - - try: - output_data, units = read_log_file(output_txt) - except Exception: - traceback.print_exc() - return self.exit_codes.ERROR_LOG_PARSING - output_data.update(get_units_dict(units, ["distance", "time"])) - - # add the dictionary with warnings and errors - output_data.update({'warnings': warnings}) - output_data.update({'errors': errors}) - output_data["parser_class"] = self.__class__.__name__ - output_data["parser_version"] = aiida_lammps_version - + # save results into node + self.add_warnings_and_errors(output_data) + self.add_standard_info(output_data) parameters_data = Dict(dict=output_data) - self.out('results', parameters_data) + # save trajectories into node + trajectory_data = TrajectoryData() + trajectory_data.set_trajectory(symbols, positions, stepids=step_ids, cells=cells, times=time) + self.out('trajectory_data', trajectory_data) + # parse the system data file - info_filename = self.node.get_option('info_filename') - if info_filename in list_of_temp_files: - info_filepath = temporary_folder + '/' + self.node.get_option('info_filename') + if info_filepath: + sys_data = ArrayData() try: with open(info_filepath) as handle: names = handle.readline().strip().split() - sys_data = ArrayData() for i, col in enumerate(np.loadtxt(info_filepath, skiprows=1, unpack=True)): sys_data.set_array(names[i], col) - sys_data.set_attribute('units_style', units) - self.out('system_data', sys_data) except Exception: traceback.print_exc() - return self.exit_codes.ERROR_INFO_PARSING + return self.exit_codes.ERROR_INFO_PARSING + sys_data.set_attribute('units_style', units) + self.out('system_data', sys_data) if output_data["errors"]: - for error in output_data["errors"]: - self.logger.error(error) - return self.exit_codes.ERROR_LAMMPS_RUN \ No newline at end of file + return self.exit_codes.ERROR_LAMMPS_RUN diff --git a/aiida_lammps/parsers/lammps/optimize.py b/aiida_lammps/parsers/lammps/optimize.py index 3edbf16..8bbf166 100644 --- a/aiida_lammps/parsers/lammps/optimize.py +++ b/aiida_lammps/parsers/lammps/optimize.py @@ -1,14 +1,10 @@ -import traceback -from aiida.parsers.parser import Parser -from aiida.common import exceptions from aiida.orm import ArrayData, Dict, StructureData -from aiida_lammps import __version__ as aiida_lammps_version -from aiida_lammps.common.raw_parsers import read_log_file_long as read_log_file, read_lammps_positions_and_forces_txt, \ - get_units_dict +from aiida_lammps.parsers.lammps.base import LAMMPSBaseParser +from aiida_lammps.common.raw_parsers import read_lammps_positions_and_forces_txt, get_units_dict -class OptimizeParser(Parser): +class OptimizeParser(LAMMPSBaseParser): """ Simple Optimize Parser for LAMMPS. """ @@ -23,52 +19,24 @@ def parse(self, **kwargs): """ Parses the datafolder, stores results. """ + resources, exit_code = self.get_parsing_resources(kwargs) + if exit_code is not None: + return exit_code + trajectory_filename, trajectory_filepath, info_filepath = resources + + output_data, cell, stress_tensor, units, exit_code = self.parse_log_file_long() + if exit_code is not None: + return exit_code + output_data.update(get_units_dict(units, ["energy", "force", "distance"])) - # Check that the retrieved folder is there - try: - out_folder = self.retrieved - except exceptions.NotExistent: - return self.exit_codes.ERROR_NO_RETRIEVED_FOLDER - - # check what is inside the folder - list_of_files = out_folder.list_object_names() - - output_filename = self.node.get_option('output_filename') - if output_filename not in list_of_files: - return self.exit_codes.ERROR_OUTPUT_FILE_MISSING - - trajectory_filename = self.node.get_option('trajectory_name') - if trajectory_filename not in list_of_files: - return self.exit_codes.ERROR_TRAJ_FILE_MISSING - - # Get files and do the parsing - output_txt = out_folder.get_object_content(output_filename) - try: - output_data, cell, stress_tensor, units = read_log_file(output_txt) - except Exception: - traceback.print_exc() - return self.exit_codes.ERROR_LOG_PARSING - - trajectory_txt = out_folder.get_object_content(trajectory_filename) + trajectory_txt = self.retrieved.get_object_content(trajectory_filename) positions, forces, symbols, cell2 = read_lammps_positions_and_forces_txt(trajectory_txt) - warnings = out_folder.get_object_content(self.node.get_option("scheduler_stderr")) - # for some reason, errors may be in the stdout, but not the log.lammps - stdout = out_folder.get_object_content(self.node.get_option("scheduler_stdout")) - errors = [line for line in stdout.splitlines() if line.startswith("ERROR")] - - # ====================== prepare the output node ====================== - - # add units used - output_data.update(get_units_dict(units, ["energy", "force", "distance"])) - # save optimized structure into node structure = StructureData(cell=cell) - for i, position in enumerate(positions[-1]): structure.append_atom(position=position.tolist(), symbols=symbols[i]) - self.out('structure', structure) # save forces and stresses into node @@ -78,17 +46,11 @@ def parse(self, **kwargs): array_data.set_array('positions', positions) self.out('arrays', array_data) - # add the dictionary with warnings and errors - output_data.update({'warnings': warnings}) - output_data.update({'errors': errors}) - output_data["parser_class"] = self.__class__.__name__ - output_data["parser_version"] = aiida_lammps_version - + # save results into node + self.add_warnings_and_errors(output_data) + self.add_standard_info(output_data) parameters_data = Dict(dict=output_data) - self.out('results', parameters_data) if output_data["errors"]: - for error in output_data["errors"]: - self.logger.error(error) return self.exit_codes.ERROR_LAMMPS_RUN From 7fbee4760d3435d890383ca323a2b83af8813298 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Thu, 20 Jun 2019 18:32:37 +0100 Subject: [PATCH 12/28] add docstring --- aiida_lammps/parsers/lammps/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aiida_lammps/parsers/lammps/base.py b/aiida_lammps/parsers/lammps/base.py index 82fb62f..81f5d27 100644 --- a/aiida_lammps/parsers/lammps/base.py +++ b/aiida_lammps/parsers/lammps/base.py @@ -20,6 +20,7 @@ def __init__(self, node): super(LAMMPSBaseParser, self).__init__(node) def get_parsing_resources(self, kwargs, traj_in_temp=False, sys_info=False): + """ check that all resources, required for parsing, are present """ # Check that the retrieved folder is there try: out_folder = self.retrieved From df5d0d9c00d96385559d222395ce5472d9cc9dd5 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Thu, 20 Jun 2019 19:54:55 +0100 Subject: [PATCH 13/28] add parser tests --- aiida_lammps/calculations/lammps/__init__.py | 12 +- aiida_lammps/common/raw_parsers.py | 3 + aiida_lammps/parsers/lammps/base.py | 14 +- aiida_lammps/parsers/lammps/force.py | 5 +- aiida_lammps/parsers/lammps/md.py | 3 +- aiida_lammps/parsers/lammps/optimize.py | 5 +- aiida_lammps/tests/test_parsers.py | 156 +++++++++++++++++++ 7 files changed, 187 insertions(+), 11 deletions(-) create mode 100644 aiida_lammps/tests/test_parsers.py diff --git a/aiida_lammps/calculations/lammps/__init__.py b/aiida_lammps/calculations/lammps/__init__.py index db62450..3af7d46 100644 --- a/aiida_lammps/calculations/lammps/__init__.py +++ b/aiida_lammps/calculations/lammps/__init__.py @@ -147,11 +147,17 @@ def define(cls, spec): 201, 'ERROR_NO_RETRIEVED_TEMP_FOLDER', message='The retrieved temporary folder data node could not be accessed.') spec.exit_code( - 210, 'ERROR_OUTPUT_FILE_MISSING', - message='the main output file was not found') + 202, 'ERROR_LOG_FILE_MISSING', + message='the main log output file was not found') spec.exit_code( - 220, 'ERROR_TRAJ_FILE_MISSING', + 203, 'ERROR_TRAJ_FILE_MISSING', message='the trajectory output file was not found') + spec.exit_code( + 204, 'ERROR_STDOUT_FILE_MISSING', + message='the stdout output file was not found') + spec.exit_code( + 205, 'ERROR_STDERR_FILE_MISSING', + message='the stderr output file was not found') # Unrecoverable errors: required retrieved files could not be read, parsed or are otherwise incomplete spec.exit_code( diff --git a/aiida_lammps/common/raw_parsers.py b/aiida_lammps/common/raw_parsers.py index dac60cc..46eb870 100644 --- a/aiida_lammps/common/raw_parsers.py +++ b/aiida_lammps/common/raw_parsers.py @@ -150,6 +150,9 @@ def read_log_file(logdata_txt): data = logdata_txt.splitlines() + if not data: + raise IOError('The logfile is empty') + data_dict = {} units = None for i, line in enumerate(data): diff --git a/aiida_lammps/parsers/lammps/base.py b/aiida_lammps/parsers/lammps/base.py index 81f5d27..355348b 100644 --- a/aiida_lammps/parsers/lammps/base.py +++ b/aiida_lammps/parsers/lammps/base.py @@ -36,12 +36,10 @@ def get_parsing_resources(self, kwargs, traj_in_temp=False, sys_info=False): # check what is inside the folder list_of_files = out_folder.list_object_names() - # check outout folder - output_filename = self.node.get_option('output_filename') - if output_filename not in list_of_files: - return None, self.exit_codes.ERROR_OUTPUT_FILE_MISSING + # check log file + if self.node.get_option('output_filename') not in list_of_files: + return None, self.exit_codes.ERROR_LOG_FILE_MISSING - # check temporal folder trajectory_filename = self.node.get_option('trajectory_name') if traj_in_temp: @@ -55,6 +53,12 @@ def get_parsing_resources(self, kwargs, traj_in_temp=False, sys_info=False): return None, self.exit_codes.ERROR_TRAJ_FILE_MISSING trajectory_filepath = None + # check stdin and stdout + if self.node.get_option('scheduler_stdout') not in list_of_files: + return None, self.exit_codes.ERROR_STDOUT_FILE_MISSING + if self.node.get_option('scheduler_stderr') not in list_of_files: + return None, self.exit_codes.ERROR_STDERR_FILE_MISSING + info_filepath = None if sys_info: info_filename = self.node.get_option('info_filename') diff --git a/aiida_lammps/parsers/lammps/force.py b/aiida_lammps/parsers/lammps/force.py index 8de4ce6..10ea17d 100644 --- a/aiida_lammps/parsers/lammps/force.py +++ b/aiida_lammps/parsers/lammps/force.py @@ -29,10 +29,11 @@ def parse(self, **kwargs): output_data, units, exit_code = self.parse_log_file() if exit_code is not None: return exit_code - output_data.update(get_units_dict(units, ["energy", "force", "distance"])) # parse trajectory file trajectory_txt = self.retrieved.get_object_content(trajectory_filename) + if not trajectory_txt: + return self.exit_codes.ERROR_TRAJ_PARSING positions, forces, symbols, cell2 = read_lammps_positions_and_forces_txt(trajectory_txt) # save forces and stresses into node @@ -41,6 +42,8 @@ def parse(self, **kwargs): self.out('arrays', array_data) # save results into node + if units is not None: + output_data.update(get_units_dict(units, ["energy", "force", "distance"])) self.add_warnings_and_errors(output_data) self.add_standard_info(output_data) parameters_data = Dict(dict=output_data) diff --git a/aiida_lammps/parsers/lammps/md.py b/aiida_lammps/parsers/lammps/md.py index 1338150..c77367c 100644 --- a/aiida_lammps/parsers/lammps/md.py +++ b/aiida_lammps/parsers/lammps/md.py @@ -31,7 +31,6 @@ def parse(self, **kwargs): output_data, units, exit_code = self.parse_log_file() if exit_code is not None: return exit_code - output_data.update(get_units_dict(units, ["distance", "time"])) # parse trajectory file try: @@ -42,6 +41,8 @@ def parse(self, **kwargs): return self.exit_codes.ERROR_TRAJ_PARSING # save results into node + if units is not None: + output_data.update(get_units_dict(units, ["distance", "time"])) self.add_warnings_and_errors(output_data) self.add_standard_info(output_data) parameters_data = Dict(dict=output_data) diff --git a/aiida_lammps/parsers/lammps/optimize.py b/aiida_lammps/parsers/lammps/optimize.py index 8bbf166..672adb3 100644 --- a/aiida_lammps/parsers/lammps/optimize.py +++ b/aiida_lammps/parsers/lammps/optimize.py @@ -27,9 +27,10 @@ def parse(self, **kwargs): output_data, cell, stress_tensor, units, exit_code = self.parse_log_file_long() if exit_code is not None: return exit_code - output_data.update(get_units_dict(units, ["energy", "force", "distance"])) trajectory_txt = self.retrieved.get_object_content(trajectory_filename) + if not trajectory_txt: + return self.exit_codes.ERROR_TRAJ_PARSING positions, forces, symbols, cell2 = read_lammps_positions_and_forces_txt(trajectory_txt) # save optimized structure into node @@ -47,6 +48,8 @@ def parse(self, **kwargs): self.out('arrays', array_data) # save results into node + if units is not None: + output_data.update(get_units_dict(units, ["energy", "force", "distance"])) self.add_warnings_and_errors(output_data) self.add_standard_info(output_data) parameters_data = Dict(dict=output_data) diff --git a/aiida_lammps/tests/test_parsers.py b/aiida_lammps/tests/test_parsers.py new file mode 100644 index 0000000..09553fc --- /dev/null +++ b/aiida_lammps/tests/test_parsers.py @@ -0,0 +1,156 @@ +from textwrap import dedent +import pytest +from aiida.orm import FolderData +from aiida.cmdline.utils.common import get_calcjob_report + + +def get_log(): + return dedent("""\ + units metal + 0 0 0 0 1 1 1 1 0 0 0 + Loop time + print "$(xlo) $(xhi) $(xy)" + 0 1 0 + print "$(ylo) $(yhi) $(xz)" + 0 1 0 + print "$(zlo) $(zhi) $(yz)" + 0 1 0 + """) + + +def get_traj_force(): + return dedent("""\ + ITEM: TIMESTEP + 0 + ITEM: NUMBER OF ATOMS + 6 + ITEM: BOX BOUNDS pp pp pp + 0 4.44 + 0 5.39 + 0 3.37 + ITEM: ATOMS element fx fy fz + Fe 0.0000000000 0.0000000000 -0.0000000000 + Fe 0.0000000000 -0.0000000000 0.0000000000 + S -25.5468278966 20.6615772179 -0.0000000000 + S -25.5468278966 -20.6615772179 -0.0000000000 + S 25.5468278966 20.6615772179 -0.0000000000 + S 25.5468278966 -20.6615772179 0.0000000000 + """) + + +@pytest.mark.parametrize('plugin_name', [ + "lammps.force", + "lammps.optimize", + # "lammps.md", # requires retrieved_temporary_folder (awaiting aiidateam/aiida_core#3061) +]) +def test_missing_log(db_test_app, plugin_name): + + retrieved = FolderData() + + calc_node = db_test_app.generate_calcjob_node(plugin_name, retrieved) + parser = db_test_app.get_parser_cls(plugin_name) + results, calcfunction = parser.parse_from_node(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_LOG_FILE_MISSING.status + + +@pytest.mark.parametrize('plugin_name', [ + "lammps.force", + "lammps.optimize", + # "lammps.md", # requires retrieved_temporary_folder (awaiting aiidateam/aiida_core#3061) +]) +def test_missing_traj(db_test_app, plugin_name): + + retrieved = FolderData() + with retrieved.open('log.lammps', 'w'): + pass + + calc_node = db_test_app.generate_calcjob_node(plugin_name, retrieved) + parser = db_test_app.get_parser_cls(plugin_name) + results, calcfunction = parser.parse_from_node(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_TRAJ_FILE_MISSING.status + + +@pytest.mark.parametrize('plugin_name', [ + "lammps.force", + "lammps.optimize", + # "lammps.md", # requires retrieved_temporary_folder (awaiting aiidateam/aiida_core#3061) +]) +def test_empty_log(db_test_app, plugin_name): + + retrieved = FolderData() + with retrieved.open('log.lammps', 'w'): + pass + with retrieved.open('trajectory.lammpstrj', 'w'): + pass + with retrieved.open('_scheduler-stdout.txt', 'w'): + pass + with retrieved.open('_scheduler-stderr.txt', 'w'): + pass + + calc_node = db_test_app.generate_calcjob_node(plugin_name, retrieved) + parser = db_test_app.get_parser_cls(plugin_name) + results, calcfunction = parser.parse_from_node(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_LOG_PARSING.status + + +@pytest.mark.parametrize('plugin_name', [ + "lammps.force", + "lammps.optimize", + # "lammps.md", # requires retrieved_temporary_folder (awaiting aiidateam/aiida_core#3061) +]) +def test_empty_traj(db_test_app, plugin_name): + + retrieved = FolderData() + with retrieved.open('log.lammps', 'w') as handle: + handle.write(get_log()) + with retrieved.open('trajectory.lammpstrj', 'w') as handle: + pass + with retrieved.open('_scheduler-stdout.txt', 'w'): + pass + with retrieved.open('_scheduler-stderr.txt', 'w'): + pass + + calc_node = db_test_app.generate_calcjob_node(plugin_name, retrieved) + parser = db_test_app.get_parser_cls(plugin_name) + results, calcfunction = parser.parse_from_node(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_TRAJ_PARSING.status + + +@pytest.mark.parametrize('plugin_name', [ + "lammps.force", + # "lammps.optimize", + # "lammps.md", # requires retrieved_temporary_folder (awaiting aiidateam/aiida_core#3061) +]) +def test_run_error(db_test_app, plugin_name): + + retrieved = FolderData() + with retrieved.open('log.lammps', 'w') as handle: + handle.write(get_log()) + with retrieved.open('trajectory.lammpstrj', 'w') as handle: + handle.write(get_traj_force()) + with retrieved.open('_scheduler-stdout.txt', 'w') as handle: + handle.write('ERROR description') + with retrieved.open('_scheduler-stderr.txt', 'w'): + pass + + calc_node = db_test_app.generate_calcjob_node(plugin_name, retrieved) + parser = db_test_app.get_parser_cls(plugin_name) + results, calcfunction = parser.parse_from_node(calc_node) + + 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 From faca19cefb735032077c40b479153ec1875b4bfd Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Thu, 20 Jun 2019 20:03:27 +0100 Subject: [PATCH 14/28] add logger messages --- aiida_lammps/parsers/lammps/force.py | 3 +++ aiida_lammps/parsers/lammps/md.py | 2 ++ aiida_lammps/parsers/lammps/optimize.py | 3 +++ 3 files changed, 8 insertions(+) diff --git a/aiida_lammps/parsers/lammps/force.py b/aiida_lammps/parsers/lammps/force.py index 10ea17d..8c340a5 100644 --- a/aiida_lammps/parsers/lammps/force.py +++ b/aiida_lammps/parsers/lammps/force.py @@ -33,6 +33,7 @@ def parse(self, **kwargs): # parse trajectory file trajectory_txt = self.retrieved.get_object_content(trajectory_filename) if not trajectory_txt: + self.logger.error("trajectory file empty") return self.exit_codes.ERROR_TRAJ_PARSING positions, forces, symbols, cell2 = read_lammps_positions_and_forces_txt(trajectory_txt) @@ -44,6 +45,8 @@ def parse(self, **kwargs): # save results into node if units is not None: output_data.update(get_units_dict(units, ["energy", "force", "distance"])) + else: + self.logger.warning("units missing in log") self.add_warnings_and_errors(output_data) self.add_standard_info(output_data) parameters_data = Dict(dict=output_data) diff --git a/aiida_lammps/parsers/lammps/md.py b/aiida_lammps/parsers/lammps/md.py index c77367c..07aed88 100644 --- a/aiida_lammps/parsers/lammps/md.py +++ b/aiida_lammps/parsers/lammps/md.py @@ -43,6 +43,8 @@ def parse(self, **kwargs): # save results into node if units is not None: output_data.update(get_units_dict(units, ["distance", "time"])) + else: + self.logger.warning("units missing in log") self.add_warnings_and_errors(output_data) self.add_standard_info(output_data) parameters_data = Dict(dict=output_data) diff --git a/aiida_lammps/parsers/lammps/optimize.py b/aiida_lammps/parsers/lammps/optimize.py index 672adb3..5d003e2 100644 --- a/aiida_lammps/parsers/lammps/optimize.py +++ b/aiida_lammps/parsers/lammps/optimize.py @@ -30,6 +30,7 @@ def parse(self, **kwargs): trajectory_txt = self.retrieved.get_object_content(trajectory_filename) if not trajectory_txt: + self.logger.error("trajectory file empty") return self.exit_codes.ERROR_TRAJ_PARSING positions, forces, symbols, cell2 = read_lammps_positions_and_forces_txt(trajectory_txt) @@ -50,6 +51,8 @@ def parse(self, **kwargs): # save results into node if units is not None: output_data.update(get_units_dict(units, ["energy", "force", "distance"])) + else: + self.logger.warning("units missing in log") self.add_warnings_and_errors(output_data) self.add_standard_info(output_data) parameters_data = Dict(dict=output_data) From 067faa8b4fdca2db5b8f1e0a9a82c6f624c6803e Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Thu, 20 Jun 2019 20:15:35 +0100 Subject: [PATCH 15/28] python 2.7 test fix --- aiida_lammps/tests/test_parsers.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/aiida_lammps/tests/test_parsers.py b/aiida_lammps/tests/test_parsers.py index 09553fc..f2c4250 100644 --- a/aiida_lammps/tests/test_parsers.py +++ b/aiida_lammps/tests/test_parsers.py @@ -1,11 +1,12 @@ from textwrap import dedent import pytest +import six from aiida.orm import FolderData from aiida.cmdline.utils.common import get_calcjob_report def get_log(): - return dedent("""\ + return six.ensure_text(dedent("""\ units metal 0 0 0 0 1 1 1 1 0 0 0 Loop time @@ -15,11 +16,11 @@ def get_log(): 0 1 0 print "$(zlo) $(zhi) $(yz)" 0 1 0 - """) + """)) def get_traj_force(): - return dedent("""\ + return six.ensure_text(dedent("""\ ITEM: TIMESTEP 0 ITEM: NUMBER OF ATOMS @@ -35,7 +36,7 @@ def get_traj_force(): S -25.5468278966 -20.6615772179 -0.0000000000 S 25.5468278966 20.6615772179 -0.0000000000 S 25.5468278966 -20.6615772179 0.0000000000 - """) + """)) @pytest.mark.parametrize('plugin_name', [ From 08586ee2d1a9c75496169dc983cc184ac6d4ba50 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Thu, 20 Jun 2019 21:00:59 +0100 Subject: [PATCH 16/28] python 2.7 fix --- aiida_lammps/tests/test_parsers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiida_lammps/tests/test_parsers.py b/aiida_lammps/tests/test_parsers.py index f2c4250..2188a68 100644 --- a/aiida_lammps/tests/test_parsers.py +++ b/aiida_lammps/tests/test_parsers.py @@ -142,7 +142,7 @@ def test_run_error(db_test_app, plugin_name): with retrieved.open('trajectory.lammpstrj', 'w') as handle: handle.write(get_traj_force()) with retrieved.open('_scheduler-stdout.txt', 'w') as handle: - handle.write('ERROR description') + handle.write(six.ensure_text('ERROR description')) with retrieved.open('_scheduler-stderr.txt', 'w'): pass From 773e28c9044ccdcf5f77cc81dab890c65614f551 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Thu, 20 Jun 2019 22:53:46 +0100 Subject: [PATCH 17/28] refactored variable output and log parsing - expressely print final variables (rather than reading from thermo) - combine `read_log_file` and `read_log_file_long` --- aiida_lammps/calculations/lammps/force.py | 5 + aiida_lammps/calculations/lammps/md.py | 5 +- aiida_lammps/calculations/lammps/optimize.py | 18 ++- aiida_lammps/common/raw_parsers.py | 115 ++++++------------ aiida_lammps/data/potential/reaxff.py | 4 +- aiida_lammps/parsers/lammps/base.py | 21 +--- aiida_lammps/parsers/lammps/force.py | 11 +- aiida_lammps/parsers/lammps/md.py | 19 +-- aiida_lammps/parsers/lammps/optimize.py | 15 ++- aiida_lammps/tests/test_parsers.py | 11 +- .../test_potential_data/test_init_reaxff_.yml | 2 +- .../test_input_lines_reaxff_.txt | 1 + 12 files changed, 101 insertions(+), 126 deletions(-) diff --git a/aiida_lammps/calculations/lammps/force.py b/aiida_lammps/calculations/lammps/force.py index efa8f16..710edf1 100644 --- a/aiida_lammps/calculations/lammps/force.py +++ b/aiida_lammps/calculations/lammps/force.py @@ -25,6 +25,8 @@ def generate_lammps_input(calc, lammps_input_file += 'neighbor 0.3 bin\n' lammps_input_file += 'neigh_modify every 1 delay 0 check no\n' + + lammps_input_file += 'thermo_style custom step temp epair emol etotal press\n' lammps_input_file += 'dump aiida all custom 1 {0} element fx fy fz\n'.format(trajectory_filename) # TODO find exact version when changes were made @@ -43,6 +45,9 @@ def generate_lammps_input(calc, lammps_input_file += 'variable {0} equal {0}\n'.format(var) lammps_input_file += 'print "final_variable: {0} = ${{{0}}}"\n'.format(var) + lammps_input_file += 'variable final_energy equal etotal\n' + lammps_input_file += 'print "final_energy: ${final_energy}"\n' + return lammps_input_file diff --git a/aiida_lammps/calculations/lammps/md.py b/aiida_lammps/calculations/lammps/md.py index a4071fe..cce8c73 100644 --- a/aiida_lammps/calculations/lammps/md.py +++ b/aiida_lammps/calculations/lammps/md.py @@ -46,7 +46,7 @@ def generate_lammps_input(calc, lammps_input_file += 'timestep {}\n'.format(pdict["timestep"]) - lammps_input_file += 'thermo_style custom step etotal temp vol press\n' + lammps_input_file += 'thermo_style custom step temp epair emol etotal press\n' lammps_input_file += 'thermo 1000\n' restart = pdict.get("restart", False) @@ -102,6 +102,9 @@ def generate_lammps_input(calc, lammps_input_file += 'run {}\n'.format( parameters.dict.total_steps) + lammps_input_file += 'variable final_energy equal etotal\n' + lammps_input_file += 'print "final_energy: ${final_energy}"\n' + return lammps_input_file diff --git a/aiida_lammps/calculations/lammps/optimize.py b/aiida_lammps/calculations/lammps/optimize.py index 797f951..c925601 100644 --- a/aiida_lammps/calculations/lammps/optimize.py +++ b/aiida_lammps/calculations/lammps/optimize.py @@ -43,6 +43,12 @@ def generate_lammps_input(calc, # xx, yy, zz, xy, xz, yz lammps_input_file += 'compute stgb all reduce sum c_stpa[1] c_stpa[2] c_stpa[3] c_stpa[4] c_stpa[5] c_stpa[6]\n' lammps_input_file += 'variable pr equal -(c_stgb[1]+c_stgb[2]+c_stgb[3])/(3*vol)\n' + lammps_input_file += 'variable stress_xx equal c_stgb[1]\n' + lammps_input_file += 'variable stress_yy equal c_stgb[2]\n' + lammps_input_file += 'variable stress_zz equal c_stgb[3]\n' + lammps_input_file += 'variable stress_xy equal c_stgb[4]\n' + lammps_input_file += 'variable stress_xz equal c_stgb[5]\n' + lammps_input_file += 'variable stress_yz equal c_stgb[6]\n' lammps_input_file += 'thermo_style custom step temp press v_pr etotal c_stgb[1] c_stgb[2] c_stgb[3] c_stgb[4] c_stgb[5] c_stgb[6]\n' lammps_input_file += 'dump aiida all custom 1 {0} element x y z fx fy fz\n'.format(trajectory_filename) @@ -62,18 +68,18 @@ def generate_lammps_input(calc, parameters['minimize']['force_tolerance'], parameters['minimize']['max_iterations'], parameters['minimize']['max_evaluations']) - # lammps_input_file += 'print "$(xlo - xhi) $(xy) $(xz)"\n' - # lammps_input_file += 'print "0.000 $(yhi - ylo) $(yz)"\n' - # lammps_input_file += 'print "0.000 0.000 $(zhi-zlo)"\n' - lammps_input_file += 'print "$(xlo) $(xhi) $(xy)"\n' - lammps_input_file += 'print "$(ylo) $(yhi) $(xz)"\n' - lammps_input_file += 'print "$(zlo) $(zhi) $(yz)"\n' variables = parameters.get("output_variables", []) for var in variables: lammps_input_file += 'variable {0} equal {0}\n'.format(var) lammps_input_file += 'print "final_variable: {0} = ${{{0}}}"\n'.format(var) + lammps_input_file += 'variable final_energy equal etotal\n' + lammps_input_file += 'print "final_energy: ${final_energy}"\n' + + lammps_input_file += 'print "final_cell: $(xlo) $(xhi) $(xy) $(ylo) $(yhi) $(xz) $(zlo) $(zhi) $(yz)"\n' + lammps_input_file += 'print "final_stress: ${stress_xx} ${stress_yy} ${stress_zz} ${stress_xy} ${stress_xz} ${stress_yz}"\n' + return lammps_input_file diff --git a/aiida_lammps/common/raw_parsers.py b/aiida_lammps/common/raw_parsers.py index 46eb870..32cb162 100644 --- a/aiida_lammps/common/raw_parsers.py +++ b/aiida_lammps/common/raw_parsers.py @@ -146,7 +146,10 @@ def read_lammps_forces(file_name): return forces -def read_log_file(logdata_txt): +def read_log_file(logdata_txt, compute_stress=False): + """ read the log.lammps file """ + # Dimensionality of LAMMP calculation + # number_of_dimensions = 3 data = logdata_txt.splitlines() @@ -154,20 +157,48 @@ def read_log_file(logdata_txt): raise IOError('The logfile is empty') data_dict = {} - units = None + cell_params = None + stress_params = None for i, line in enumerate(data): - if 'Loop time' in line: - energy = float(data[i - 1].split()[4]) - data_dict['energy'] = energy if 'units' in line: - units = line.split()[1] - data_dict['units_style'] = units + data_dict['units_style'] = line.split()[1] + if line.startswith("final_energy:"): + data_dict['energy'] = float(line.split()[1]) if line.startswith("final_variable:"): if 'final_variables' not in data_dict: data_dict['final_variables'] = {} - data_dict['final_variables'][line.split()[1]] = float(line.split()[3]) + data_dict['final_variables'][line.split()[1]] = float( + line.split()[3]) + + if line.startswith("final_cell:"): + cell_params = [float(v) for v in line.split()[1:10]] + if line.startswith("final_stress:"): + stress_params = [float(v) for v in line.split()[1:7]] + + if not compute_stress: + return {"data": data_dict} + + if cell_params is None: + raise IOError("'final_cell' could not be found") + if stress_params is None: + raise IOError("'final_stress' could not be found") + + xlo, xhi, xy, ylo, yhi, xz, zlo, zhi, yz = cell_params + super_cell = np.array([[xhi - xlo, xy, xz], + [0, yhi - ylo, yz], + [0, 0, zhi - zlo]]) + cell = super_cell.T + if np.linalg.det(cell) < 0: + cell = -1.0 * cell + volume = np.linalg.det(cell) - return data_dict, units + xx, yy, zz, xy, xz, yz = stress_params + stress = np.array([[xx, xy, xz], + [xy, yy, yz], + [xz, yz, zz]], dtype=float) + stress = -stress / volume * 1.e-3 # bar*A^3 -> kbar + + return {"data": data_dict, "cell": cell, "stress": stress} def read_lammps_trajectory_txt(data_txt, @@ -365,72 +396,6 @@ def read_lammps_trajectory(file_name, return data, step_ids, cells, elements, time -def read_log_file_long(logdata_txt): - - # Dimensionality of LAMMP calculation - number_of_dimensions = 3 - - data = logdata_txt.splitlines() - - # with open(logfile, 'r') as f: - # data = f.readlines() - - if not data: - raise IOError('The logfile is empty') - - data_dict = {} - units = None - for i, line in enumerate(data): - if 'units' in line: - units = line.split()[1] - data_dict['units_style'] = units - if 'Loop time' in line: - energy = float(data[i - 1].split()[4]) - data_dict['energy'] = energy - xx, yy, zz, xy, xz, yz = data[i - 1].split()[5:11] - stress = np.array([[xx, xy, xz], - [xy, yy, yz], - [xz, yz, zz]], dtype=float) - - if '$(xlo)' in line: - a = data[i + 1].split() - if '$(ylo)' in line: - b = data[i + 1].split() - if '$(zlo)' in line: - c = data[i + 1].split() - if line.startswith("final_variable:"): - if 'final_variables' not in data_dict: - data_dict['final_variables'] = {} - data_dict['final_variables'][line.split()[1]] = float(line.split()[3]) - - bounds = np.array([a, b, c], dtype=float) - - xy = bounds[0, 2] - xz = bounds[1, 2] - yz = bounds[2, 2] - - xlo = bounds[0, 0] - xhi = bounds[0, 1] - ylo = bounds[1, 0] - yhi = bounds[1, 1] - zlo = bounds[2, 0] - zhi = bounds[2, 1] - - super_cell = np.array([[xhi - xlo, xy, xz], - [0, yhi - ylo, yz], - [0, 0, zhi - zlo]]) - - cell = super_cell.T - - if np.linalg.det(cell) < 0: - cell = -1.0 * cell - - volume = np.linalg.det(cell) - stress = -stress / volume * 1.e-3 # bar*A^3 -> kbar - - return data_dict, cell, stress, units - - def read_lammps_positions_and_forces(file_name): import mmap diff --git a/aiida_lammps/data/potential/reaxff.py b/aiida_lammps/data/potential/reaxff.py index 619167b..e015708 100644 --- a/aiida_lammps/data/potential/reaxff.py +++ b/aiida_lammps/data/potential/reaxff.py @@ -25,7 +25,7 @@ def get_input_potential_lines(data, names=None, potential_filename='potential.po lammps_input_text += "fix qeq all qeq/reax 1 0.0 10.0 1e-6 reax/c\n" lammps_input_text += "fix_modify qeq energy yes\n" - # lammps_input_text += "compute reax all pair reax/c\n" + lammps_input_text += "compute reax all pair reax/c\n" # lammps_input_text += "variable reax_eb equal c_reax[1]\n" # lammps_input_text += "variable reax_ea equal c_reax[2]\n" # lammps_input_text += "variable reax_elp equal c_reax[3]\n" @@ -40,7 +40,7 @@ def get_input_potential_lines(data, names=None, potential_filename='potential.po # lammps_input_text += "variable reax_ep equal c_reax[12]\n" # lammps_input_text += "variable reax_efi equal c_reax[13]\n" # lammps_input_text += "variable reax_eqeq equal c_reax[14]\n" - + # TODO to access these variables, the compute must be triggered, # for example by adding c_reax[1] to the thermo_style # but how to do this in a generalised manner? diff --git a/aiida_lammps/parsers/lammps/base.py b/aiida_lammps/parsers/lammps/base.py index 355348b..a682a4d 100644 --- a/aiida_lammps/parsers/lammps/base.py +++ b/aiida_lammps/parsers/lammps/base.py @@ -5,7 +5,7 @@ from aiida.common import exceptions from aiida_lammps import __version__ as aiida_lammps_version -from aiida_lammps.common.raw_parsers import read_log_file, read_log_file_long +from aiida_lammps.common.raw_parsers import read_log_file class LAMMPSBaseParser(Parser): @@ -67,27 +67,16 @@ def get_parsing_resources(self, kwargs, traj_in_temp=False, sys_info=False): return (trajectory_filename, trajectory_filepath, info_filepath), None - def parse_log_file(self): + def parse_log_file(self, compute_stress=False): """ parse the log file """ output_filename = self.node.get_option('output_filename') output_txt = self.retrieved.get_object_content(output_filename) try: - output_data, units = read_log_file(output_txt) + output_data = read_log_file(output_txt, compute_stress=compute_stress) except Exception: traceback.print_exc() - return None, None, self.exit_codes.ERROR_LOG_PARSING - return output_data, units, None - - def parse_log_file_long(self, long=False): - """ parse the log file """ - output_filename = self.node.get_option('output_filename') - output_txt = self.retrieved.get_object_content(output_filename) - try: - output_data, cell, stress_tensor, units = read_log_file_long(output_txt) - except Exception: - traceback.print_exc() - return None, None, None, None, self.exit_codes.ERROR_LOG_PARSING - return output_data, cell, stress_tensor, units, None + return None, self.exit_codes.ERROR_LOG_PARSING + return output_data, None def add_warnings_and_errors(self, output_data): """ add warning and errors to the output data """ diff --git a/aiida_lammps/parsers/lammps/force.py b/aiida_lammps/parsers/lammps/force.py index 8c340a5..e6b7498 100644 --- a/aiida_lammps/parsers/lammps/force.py +++ b/aiida_lammps/parsers/lammps/force.py @@ -26,7 +26,7 @@ def parse(self, **kwargs): trajectory_filename, trajectory_filepath, info_filepath = resources # parse log file - output_data, units, exit_code = self.parse_log_file() + log_data, exit_code = self.parse_log_file() if exit_code is not None: return exit_code @@ -35,7 +35,8 @@ def parse(self, **kwargs): if not trajectory_txt: self.logger.error("trajectory file empty") return self.exit_codes.ERROR_TRAJ_PARSING - positions, forces, symbols, cell2 = read_lammps_positions_and_forces_txt(trajectory_txt) + positions, forces, symbols, cell2 = read_lammps_positions_and_forces_txt( + trajectory_txt) # save forces and stresses into node array_data = ArrayData() @@ -43,8 +44,10 @@ def parse(self, **kwargs): self.out('arrays', array_data) # save results into node - if units is not None: - output_data.update(get_units_dict(units, ["energy", "force", "distance"])) + output_data = log_data["data"] + if 'units_style' in output_data: + output_data.update(get_units_dict(output_data['units_style'], + ["energy", "force", "distance"])) else: self.logger.warning("units missing in log") self.add_warnings_and_errors(output_data) diff --git a/aiida_lammps/parsers/lammps/md.py b/aiida_lammps/parsers/lammps/md.py index 07aed88..e4f5a2b 100644 --- a/aiida_lammps/parsers/lammps/md.py +++ b/aiida_lammps/parsers/lammps/md.py @@ -22,27 +22,31 @@ def parse(self, **kwargs): Parses the datafolder, stores results. """ # retrieve resources - resources, exit_code = self.get_parsing_resources(kwargs, traj_in_temp=True, sys_info=True) + resources, exit_code = self.get_parsing_resources( + kwargs, traj_in_temp=True, sys_info=True) if exit_code is not None: return exit_code trajectory_filename, trajectory_filepath, info_filepath = resources # parse log file - output_data, units, exit_code = self.parse_log_file() + log_data, exit_code = self.parse_log_file() if exit_code is not None: return exit_code # parse trajectory file try: timestep = self.node.inputs.parameters.dict.timestep - positions, step_ids, cells, symbols, time = read_lammps_trajectory(trajectory_filepath, timestep=timestep) + positions, step_ids, cells, symbols, time = read_lammps_trajectory( + trajectory_filepath, timestep=timestep) except Exception: traceback.print_exc() return self.exit_codes.ERROR_TRAJ_PARSING # save results into node - if units is not None: - output_data.update(get_units_dict(units, ["distance", "time"])) + output_data = log_data["data"] + if 'units_style' in output_data: + output_data.update(get_units_dict(output_data['units_style'], + ["distance", "time"])) else: self.logger.warning("units missing in log") self.add_warnings_and_errors(output_data) @@ -52,7 +56,8 @@ def parse(self, **kwargs): # save trajectories into node trajectory_data = TrajectoryData() - trajectory_data.set_trajectory(symbols, positions, stepids=step_ids, cells=cells, times=time) + trajectory_data.set_trajectory( + symbols, positions, stepids=step_ids, cells=cells, times=time) self.out('trajectory_data', trajectory_data) # parse the system data file @@ -66,7 +71,7 @@ def parse(self, **kwargs): except Exception: traceback.print_exc() return self.exit_codes.ERROR_INFO_PARSING - sys_data.set_attribute('units_style', units) + sys_data.set_attribute('units_style', output_data.get('units_style', None)) self.out('system_data', sys_data) if output_data["errors"]: diff --git a/aiida_lammps/parsers/lammps/optimize.py b/aiida_lammps/parsers/lammps/optimize.py index 5d003e2..1fb1a24 100644 --- a/aiida_lammps/parsers/lammps/optimize.py +++ b/aiida_lammps/parsers/lammps/optimize.py @@ -24,7 +24,7 @@ def parse(self, **kwargs): return exit_code trajectory_filename, trajectory_filepath, info_filepath = resources - output_data, cell, stress_tensor, units, exit_code = self.parse_log_file_long() + log_data, exit_code = self.parse_log_file(compute_stress=True) if exit_code is not None: return exit_code @@ -32,10 +32,11 @@ def parse(self, **kwargs): if not trajectory_txt: self.logger.error("trajectory file empty") return self.exit_codes.ERROR_TRAJ_PARSING - positions, forces, symbols, cell2 = read_lammps_positions_and_forces_txt(trajectory_txt) + positions, forces, symbols, cell2 = read_lammps_positions_and_forces_txt( + trajectory_txt) # save optimized structure into node - structure = StructureData(cell=cell) + structure = StructureData(cell=log_data["cell"]) for i, position in enumerate(positions[-1]): structure.append_atom(position=position.tolist(), symbols=symbols[i]) @@ -44,13 +45,15 @@ def parse(self, **kwargs): # save forces and stresses into node array_data = ArrayData() array_data.set_array('forces', forces) - array_data.set_array('stress', stress_tensor) + array_data.set_array('stress', log_data["stress"]) array_data.set_array('positions', positions) self.out('arrays', array_data) # save results into node - if units is not None: - output_data.update(get_units_dict(units, ["energy", "force", "distance"])) + output_data = log_data["data"] + if 'units_style' in output_data: + output_data.update(get_units_dict(output_data['units_style'], + ["energy", "force", "distance"])) else: self.logger.warning("units missing in log") self.add_warnings_and_errors(output_data) diff --git a/aiida_lammps/tests/test_parsers.py b/aiida_lammps/tests/test_parsers.py index 2188a68..12366f5 100644 --- a/aiida_lammps/tests/test_parsers.py +++ b/aiida_lammps/tests/test_parsers.py @@ -8,14 +8,9 @@ def get_log(): return six.ensure_text(dedent("""\ units metal - 0 0 0 0 1 1 1 1 0 0 0 - Loop time - print "$(xlo) $(xhi) $(xy)" - 0 1 0 - print "$(ylo) $(yhi) $(xz)" - 0 1 0 - print "$(zlo) $(zhi) $(yz)" - 0 1 0 + final_energy: 2.0 + final_cell: 0 1 0 0 1 0 0 1 0 + final_stress: 0 0 0 0 0 0 """)) diff --git a/aiida_lammps/tests/test_potential_data/test_init_reaxff_.yml b/aiida_lammps/tests/test_potential_data/test_init_reaxff_.yml index d07d568..e9aafe1 100644 --- a/aiida_lammps/tests/test_potential_data/test_init_reaxff_.yml +++ b/aiida_lammps/tests/test_potential_data/test_init_reaxff_.yml @@ -1,6 +1,6 @@ atom_style: charge default_units: real -input_lines_md5: 6564bc40d8841625b0e790161f59ec5d +input_lines_md5: b8bf88bb00f01f63a4fcf912086acf3f kind_names: - Fe - S diff --git a/aiida_lammps/tests/test_potential_data/test_input_lines_reaxff_.txt b/aiida_lammps/tests/test_potential_data/test_input_lines_reaxff_.txt index 2e49e8d..677d2fa 100644 --- a/aiida_lammps/tests/test_potential_data/test_input_lines_reaxff_.txt +++ b/aiida_lammps/tests/test_potential_data/test_input_lines_reaxff_.txt @@ -2,3 +2,4 @@ pair_style reax/c NULL safezone 1.6 pair_coeff * * potential.pot Fe S fix qeq all qeq/reax 1 0.0 10.0 1e-6 reax/c fix_modify qeq energy yes +compute reax all pair reax/c From 8c2c60b3edbd9a2acb7b515f154681d60f237ff0 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Thu, 20 Jun 2019 23:06:57 +0100 Subject: [PATCH 18/28] return correct units of stress --- aiida_lammps/common/raw_parsers.py | 2 +- aiida_lammps/parsers/lammps/optimize.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/aiida_lammps/common/raw_parsers.py b/aiida_lammps/common/raw_parsers.py index 32cb162..0338e57 100644 --- a/aiida_lammps/common/raw_parsers.py +++ b/aiida_lammps/common/raw_parsers.py @@ -196,7 +196,7 @@ def read_log_file(logdata_txt, compute_stress=False): stress = np.array([[xx, xy, xz], [xy, yy, yz], [xz, yz, zz]], dtype=float) - stress = -stress / volume * 1.e-3 # bar*A^3 -> kbar + stress = -stress / volume # to get stress in units of pressure return {"data": data_dict, "cell": cell, "stress": stress} diff --git a/aiida_lammps/parsers/lammps/optimize.py b/aiida_lammps/parsers/lammps/optimize.py index 1fb1a24..d0c98ec 100644 --- a/aiida_lammps/parsers/lammps/optimize.py +++ b/aiida_lammps/parsers/lammps/optimize.py @@ -53,7 +53,8 @@ def parse(self, **kwargs): output_data = log_data["data"] if 'units_style' in output_data: output_data.update(get_units_dict(output_data['units_style'], - ["energy", "force", "distance"])) + ["energy", "force", "distance", "pressure"])) + output_data["stress_units"] = output_data.pop("pressure_units") else: self.logger.warning("units missing in log") self.add_warnings_and_errors(output_data) From 7974693853759810f2c6dfcfb8e88f8947fed349 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Fri, 21 Jun 2019 00:34:53 +0100 Subject: [PATCH 19/28] allow additional keywords to be added to thermo_style and allow array component values to be recorded --- aiida_lammps/calculations/lammps/__init__.py | 4 +- aiida_lammps/calculations/lammps/force.py | 12 ++- aiida_lammps/calculations/lammps/md.py | 19 +++-- aiida_lammps/calculations/lammps/optimize.py | 13 +++- aiida_lammps/data/potential/reaxff.py | 38 +++++----- aiida_lammps/tests/test_calculations.py | 63 +++++++++++----- aiida_lammps/validation/force.schema.json | 79 +++----------------- aiida_lammps/validation/md.schema.json | 79 +++----------------- aiida_lammps/validation/optimize.schema.json | 79 +++----------------- 9 files changed, 132 insertions(+), 254 deletions(-) diff --git a/aiida_lammps/calculations/lammps/__init__.py b/aiida_lammps/calculations/lammps/__init__.py index 3af7d46..4030b32 100644 --- a/aiida_lammps/calculations/lammps/__init__.py +++ b/aiida_lammps/calculations/lammps/__init__.py @@ -202,10 +202,11 @@ def prepare_for_submission(self, tempfolder): parameters = self.inputs.parameters else: parameters = Dict() + pdict = parameters.get_dict() # Check lammps version date in parameters lammps_date = convert_date_string( - parameters.get_dict().get("lammps_version", '11 Aug 2017')) + pdict.get("lammps_version", '11 Aug 2017')) # Setup input parameters input_txt = self._generate_input_function( @@ -215,6 +216,7 @@ def prepare_for_submission(self, tempfolder): trajectory_filename=self.options.trajectory_name, info_filename=self.options.info_filename, restart_filename=self.options.restart_filename, + add_thermo_keywords=pdict.get("thermo_keywords", []), version_date=lammps_date) input_filename = tempfolder.get_abs_path(self._INPUT_FILE_NAME) diff --git a/aiida_lammps/calculations/lammps/force.py b/aiida_lammps/calculations/lammps/force.py index 710edf1..220d6d8 100644 --- a/aiida_lammps/calculations/lammps/force.py +++ b/aiida_lammps/calculations/lammps/force.py @@ -10,6 +10,7 @@ def generate_lammps_input(calc, potential_obj, structure_filename, trajectory_filename, + add_thermo_keywords, version_date='11 Aug 2017', **kwargs): names_str = ' '.join(potential_obj.kind_names) @@ -26,7 +27,11 @@ def generate_lammps_input(calc, lammps_input_file += 'neighbor 0.3 bin\n' lammps_input_file += 'neigh_modify every 1 delay 0 check no\n' - lammps_input_file += 'thermo_style custom step temp epair emol etotal press\n' + thermo_keywords = ["step", "temp", "epair", "emol", "etotal", "press"] + for kwd in add_thermo_keywords: + if kwd not in thermo_keywords: + thermo_keywords.append(kwd) + lammps_input_file += 'thermo_style custom {}\n'.format(" ".join(thermo_keywords)) lammps_input_file += 'dump aiida all custom 1 {0} element fx fy fz\n'.format(trajectory_filename) # TODO find exact version when changes were made @@ -42,8 +47,9 @@ def generate_lammps_input(calc, variables = parameters.get_attribute("output_variables", []) for var in variables: - lammps_input_file += 'variable {0} equal {0}\n'.format(var) - lammps_input_file += 'print "final_variable: {0} = ${{{0}}}"\n'.format(var) + var_alias = var.replace("[", "_").replace("]", "_") + lammps_input_file += 'variable {0} equal {1}\n'.format(var_alias, var) + lammps_input_file += 'print "final_variable: {0} = ${{{0}}}"\n'.format(var_alias) lammps_input_file += 'variable final_energy equal etotal\n' lammps_input_file += 'print "final_energy: ${final_energy}"\n' diff --git a/aiida_lammps/calculations/lammps/md.py b/aiida_lammps/calculations/lammps/md.py index cce8c73..77f6871 100644 --- a/aiida_lammps/calculations/lammps/md.py +++ b/aiida_lammps/calculations/lammps/md.py @@ -14,7 +14,8 @@ def generate_lammps_input(calc, trajectory_filename, restart_filename, info_filename, - version_date='11 Aug 2017'): + add_thermo_keywords, + version_date='11 Aug 2017', **kwargs): pdict = parameters.get_dict() @@ -46,7 +47,11 @@ def generate_lammps_input(calc, lammps_input_file += 'timestep {}\n'.format(pdict["timestep"]) - lammps_input_file += 'thermo_style custom step temp epair emol etotal press\n' + thermo_keywords = ["step", "temp", "epair", "emol", "etotal", "press"] + for kwd in add_thermo_keywords: + if kwd not in thermo_keywords: + thermo_keywords.append(kwd) + lammps_input_file += 'thermo_style custom {}\n'.format(" ".join(thermo_keywords)) lammps_input_file += 'thermo 1000\n' restart = pdict.get("restart", False) @@ -92,12 +97,16 @@ def generate_lammps_input(calc, # always include 'step', so we can sync with the `dump` data # NOTE `dump` includes step 0, whereas `print` starts from step 1 variables.append('step') + var_aliases = [] for var in variables: - lammps_input_file += 'variable {0} equal {0}\n'.format(var) + var_alias = var.replace("[", "_").replace("]", "_") + var_aliases.append(var_alias) + lammps_input_file += 'variable {0} equal {1}\n'.format(var_alias, var) if variables: lammps_input_file += 'fix sys_info all print {0} "{1}" title "{2}" file {3} screen no\n'.format( - parameters.dict.dump_rate, " ".join(["${{{0}}}".format(v) for v in variables]), - " ".join(variables), info_filename) + parameters.dict.dump_rate, + " ".join(["${{{0}}}".format(v) for v in var_aliases]), + " ".join(var_aliases), info_filename) lammps_input_file += 'run {}\n'.format( parameters.dict.total_steps) diff --git a/aiida_lammps/calculations/lammps/optimize.py b/aiida_lammps/calculations/lammps/optimize.py index c925601..c59b6c8 100644 --- a/aiida_lammps/calculations/lammps/optimize.py +++ b/aiida_lammps/calculations/lammps/optimize.py @@ -11,6 +11,7 @@ def generate_lammps_input(calc, potential_obj, structure_filename, trajectory_filename, + add_thermo_keywords, version_date, **kwargs): names_str = ' '.join(potential_obj.kind_names) @@ -49,7 +50,12 @@ def generate_lammps_input(calc, lammps_input_file += 'variable stress_xy equal c_stgb[4]\n' lammps_input_file += 'variable stress_xz equal c_stgb[5]\n' lammps_input_file += 'variable stress_yz equal c_stgb[6]\n' - lammps_input_file += 'thermo_style custom step temp press v_pr etotal c_stgb[1] c_stgb[2] c_stgb[3] c_stgb[4] c_stgb[5] c_stgb[6]\n' + + thermo_keywords = ["step", "temp", "press", "v_pr", "etotal", "c_stgb[1]", "c_stgb[2]", "c_stgb[3]", "c_stgb[4]", "c_stgb[5]", "c_stgb[6]"] + for kwd in add_thermo_keywords: + if kwd not in thermo_keywords: + thermo_keywords.append(kwd) + lammps_input_file += 'thermo_style custom {}\n'.format(" ".join(thermo_keywords)) lammps_input_file += 'dump aiida all custom 1 {0} element x y z fx fy fz\n'.format(trajectory_filename) @@ -71,8 +77,9 @@ def generate_lammps_input(calc, variables = parameters.get("output_variables", []) for var in variables: - lammps_input_file += 'variable {0} equal {0}\n'.format(var) - lammps_input_file += 'print "final_variable: {0} = ${{{0}}}"\n'.format(var) + var_alias = var.replace("[", "_").replace("]", "_") + lammps_input_file += 'variable {0} equal {1}\n'.format(var_alias, var) + lammps_input_file += 'print "final_variable: {0} = ${{{0}}}"\n'.format(var_alias) lammps_input_file += 'variable final_energy equal etotal\n' lammps_input_file += 'print "final_energy: ${final_energy}"\n' diff --git a/aiida_lammps/data/potential/reaxff.py b/aiida_lammps/data/potential/reaxff.py index e015708..b21a133 100644 --- a/aiida_lammps/data/potential/reaxff.py +++ b/aiida_lammps/data/potential/reaxff.py @@ -24,26 +24,26 @@ def get_input_potential_lines(data, names=None, potential_filename='potential.po lammps_input_text += 'pair_coeff * * {} {}\n'.format(potential_filename, ' '.join(names)) lammps_input_text += "fix qeq all qeq/reax 1 0.0 10.0 1e-6 reax/c\n" lammps_input_text += "fix_modify qeq energy yes\n" - lammps_input_text += "compute reax all pair reax/c\n" - # lammps_input_text += "variable reax_eb equal c_reax[1]\n" - # lammps_input_text += "variable reax_ea equal c_reax[2]\n" - # lammps_input_text += "variable reax_elp equal c_reax[3]\n" - # lammps_input_text += "variable reax_emol equal c_reax[4]\n" - # lammps_input_text += "variable reax_ev equal c_reax[5]\n" - # lammps_input_text += "variable reax_epen equal c_reax[6]\n" - # lammps_input_text += "variable reax_ecoa equal c_reax[7]\n" - # lammps_input_text += "variable reax_ehb equal c_reax[8]\n" - # lammps_input_text += "variable reax_et equal c_reax[9]\n" - # lammps_input_text += "variable reax_eco equal c_reax[10]\n" - # lammps_input_text += "variable reax_ew equal c_reax[11]\n" - # lammps_input_text += "variable reax_ep equal c_reax[12]\n" - # lammps_input_text += "variable reax_efi equal c_reax[13]\n" - # lammps_input_text += "variable reax_eqeq equal c_reax[14]\n" - - # TODO to access these variables, the compute must be triggered, - # for example by adding c_reax[1] to the thermo_style - # but how to do this in a generalised manner? + + # NOTE: to use c_reax[1] - c_reax[14], + # c_reax[1] must be added to `thermo_style custom`, to trigger the compute + + # The array values correspond to: + # eb = bond energy + # ea = atom energy + # elp = lone-pair energy + # emol = molecule energy (always 0.0) + # ev = valence angle energy + # epen = double-bond valence angle penalty + # ecoa = valence angle conjugation energy + # ehb = hydrogen bond energy + # et = torsion energy + # eco = conjugation energy + # ew = van der Waals energy + # ep = Coulomb energy + # efi = electric field energy (always 0.0) + # eqeq = charge equilibration energy return lammps_input_text diff --git a/aiida_lammps/tests/test_calculations.py b/aiida_lammps/tests/test_calculations.py index dec88fd..c5ef74a 100644 --- a/aiida_lammps/tests/test_calculations.py +++ b/aiida_lammps/tests/test_calculations.py @@ -8,12 +8,20 @@ import aiida_lammps.tests.utils as tests -def get_calc_parameters(plugin_name, units): +def get_calc_parameters(plugin_name, units, potential_type): + + if potential_type == "reaxff": + output_variables = ["temp", "etotal", "c_reax[1]"] + thermo_keywords = ["c_reax[1]"] + else: + output_variables = ["temp", "etotal"] + thermo_keywords = [] if plugin_name == 'lammps.force': parameters_opt = { 'lammps_version': tests.lammps_version(), - 'output_variables': ["temp", "etotal"] + 'output_variables': output_variables, + 'thermo_keywords': thermo_keywords } elif plugin_name == 'lammps.optimize': parameters_opt = { @@ -30,7 +38,8 @@ def get_calc_parameters(plugin_name, units): 'force_tolerance': 1.0e-25, 'max_evaluations': 100000, 'max_iterations': 50000}, - 'output_variables': ["temp", "etotal"] + 'output_variables': output_variables, + 'thermo_keywords': thermo_keywords } elif plugin_name == "lammps.md": @@ -50,7 +59,8 @@ def get_calc_parameters(plugin_name, units): 'total_steps': 1000, 'dump_rate': 10, 'restart': 100, - 'output_variables': ["temp", "etotal"] + 'output_variables': output_variables, + 'thermo_keywords': thermo_keywords } else: raise ValueError(plugin_name) @@ -71,7 +81,8 @@ def test_force_submission(db_test_app, get_potential_data, potential_type): potential = DataFactory("lammps.potential")( structure=pot_data.structure, type=pot_data.type, data=pot_data.data ) - parameters = get_calc_parameters(calc_plugin, potential.default_units) + parameters = get_calc_parameters( + calc_plugin, potential.default_units, potential_type) builder = code.get_builder() builder._update({ "metadata": tests.get_default_metadata(), @@ -85,7 +96,8 @@ def test_force_submission(db_test_app, get_potential_data, potential_type): calc_info = db_test_app.generate_calcinfo(calc_plugin, folder, builder) assert calc_info.codes_info[0].cmdline_params == ['-in', 'input.in'] - assert set(folder.get_content_list()).issuperset(['input.data', 'input.in']) + assert set(folder.get_content_list()).issuperset( + ['input.data', 'input.in']) @pytest.mark.parametrize('potential_type', [ @@ -101,7 +113,8 @@ def test_optimize_submission(db_test_app, get_potential_data, potential_type): potential = DataFactory("lammps.potential")( structure=pot_data.structure, type=pot_data.type, data=pot_data.data ) - parameters = get_calc_parameters(calc_plugin, potential.default_units) + parameters = get_calc_parameters( + calc_plugin, potential.default_units, potential_type) builder = code.get_builder() builder._update({ "metadata": tests.get_default_metadata(), @@ -115,7 +128,8 @@ def test_optimize_submission(db_test_app, get_potential_data, potential_type): calc_info = db_test_app.generate_calcinfo(calc_plugin, folder, builder) assert calc_info.codes_info[0].cmdline_params == ['-in', 'input.in'] - assert set(folder.get_content_list()).issuperset(['input.data', 'input.in']) + assert set(folder.get_content_list()).issuperset( + ['input.data', 'input.in']) @pytest.mark.parametrize('potential_type', [ @@ -131,7 +145,8 @@ def test_md_submission(db_test_app, get_potential_data, potential_type): potential = DataFactory("lammps.potential")( structure=pot_data.structure, type=pot_data.type, data=pot_data.data ) - parameters = get_calc_parameters(calc_plugin, potential.default_units) + parameters = get_calc_parameters( + calc_plugin, potential.default_units, potential_type) builder = code.get_builder() builder._update({ "metadata": tests.get_default_metadata(), @@ -145,7 +160,8 @@ def test_md_submission(db_test_app, get_potential_data, potential_type): calc_info = db_test_app.generate_calcinfo(calc_plugin, folder, builder) assert calc_info.codes_info[0].cmdline_params == ['-in', 'input.in'] - assert set(folder.get_content_list()).issuperset(['input.data', 'input.in']) + assert set(folder.get_content_list()).issuperset( + ['input.data', 'input.in']) @pytest.mark.lammps_call @@ -162,7 +178,8 @@ def test_force_process(db_test_app, get_potential_data, potential_type): potential = DataFactory("lammps.potential")( structure=pot_data.structure, type=pot_data.type, data=pot_data.data ) - parameters = get_calc_parameters(calc_plugin, potential.default_units) + parameters = get_calc_parameters( + calc_plugin, potential.default_units, potential_type) builder = code.get_builder() builder._update({ "metadata": tests.get_default_metadata(), @@ -175,6 +192,10 @@ def test_force_process(db_test_app, get_potential_data, potential_type): output = run_get_node(builder) calc_node = output.node + # raise ValueError(calc_node.get_object_content('input.in')) + # raise ValueError(calc_node.outputs.retrieved.get_object_content('_scheduler-stdout.txt')) + # raise ValueError(calc_node.outputs.retrieved.get_object_content('log.lammps')) + if not calc_node.is_finished_ok: print(calc_node.attributes) print(get_calcjob_report(calc_node)) @@ -185,9 +206,6 @@ def test_force_process(db_test_app, get_potential_data, potential_type): assert set(link_labels).issuperset( ['results', 'arrays']) - # raise ValueError(calc_node.outputs.retrieved.get_object_content('_scheduler-stdout.txt')) - # raise ValueError(calc_node.outputs.retrieved.get_object_content('log.lammps')) - pdict = calc_node.outputs.results.get_dict() assert set(pdict.keys()).issuperset( ['energy', 'warnings', 'final_variables', 'units_style', @@ -214,7 +232,8 @@ def test_optimize_process(db_test_app, get_potential_data, potential_type): potential = DataFactory("lammps.potential")( structure=pot_data.structure, type=pot_data.type, data=pot_data.data ) - parameters = get_calc_parameters(calc_plugin, potential.default_units) + parameters = get_calc_parameters( + calc_plugin, potential.default_units, potential_type) builder = code.get_builder() builder._update({ "metadata": tests.get_default_metadata(), @@ -263,7 +282,8 @@ def test_md_process(db_test_app, get_potential_data, potential_type): potential = DataFactory("lammps.potential")( structure=pot_data.structure, type=pot_data.type, data=pot_data.data ) - parameters = get_calc_parameters(calc_plugin, potential.default_units) + parameters = get_calc_parameters( + calc_plugin, potential.default_units, potential_type) builder = code.get_builder() builder._update({ "metadata": tests.get_default_metadata(), @@ -296,7 +316,12 @@ def test_md_process(db_test_app, get_potential_data, potential_type): ) assert calc_node.outputs.trajectory_data.numsteps == 101 - assert set(calc_node.outputs.system_data.get_arraynames()) == set( - ['step', 'temp', 'etotal'] - ) + if potential_type == "reaxff": + assert set(calc_node.outputs.system_data.get_arraynames()) == set( + ['step', 'temp', 'etotal', 'c_reax_1_'] + ) + else: + assert set(calc_node.outputs.system_data.get_arraynames()) == set( + ['step', 'temp', 'etotal'] + ) assert calc_node.outputs.system_data.get_shape('temp') == (100,) diff --git a/aiida_lammps/validation/force.schema.json b/aiida_lammps/validation/force.schema.json index a2f7a66..b3c4266 100644 --- a/aiida_lammps/validation/force.schema.json +++ b/aiida_lammps/validation/force.schema.json @@ -8,79 +8,22 @@ "description": "the version of lammps in date format, e.g 10 Nov 2015", "type": "string" }, + "thermo_keywords": { + "description": "additional keywords to append to 'thermo_style custom'", + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "pattern": "^[a-zA-Z1-9\\_\\[\\]]+$" + } + }, "output_variables": { - "description": "output variables to record as attributes (see thermo_style)", + "description": "output variables to record as attributes (see `thermo_style` for valid keywords)", "type": "array", "uniqueItems": true, "items": { "type": "string", - "enum": [ - "step", - "elapsed", - "elaplong", - "dt", - "time", - "cpu", - "tpcpu", - "spcpu", - "cpuremain", - "part", - "timeremain", - "atoms", - "temp", - "press", - "pe", - "ke", - "etotal", - "enthalpy", - "evdwl", - "ecoul", - "epair", - "ebond", - "eangle", - "edihed", - "eimp", - "emol", - "elong", - "etail", - "vol", - "density", - "lx", - "ly", - "lz", - "xlo", - "xhi", - "ylo", - "yhi", - "zlo", - "zhi", - "xy", - "xz", - "yz", - "xlat", - "ylat", - "zlat", - "bonds", - "angles", - "dihedrals", - "impropers", - "pxx", - "pyy", - "pzz", - "pxy", - "pxz", - "pyz", - "fmax", - "fnorm", - "nbuild", - "ndanger", - "cella", - "cellb", - "cellc", - "cellalpha", - "cellbeta", - "cellgamma" - ] + "pattern": "^[a-zA-Z1-9\\_\\[\\]]+$" } } } diff --git a/aiida_lammps/validation/md.schema.json b/aiida_lammps/validation/md.schema.json index e34b9da..c20455b 100644 --- a/aiida_lammps/validation/md.schema.json +++ b/aiida_lammps/validation/md.schema.json @@ -250,79 +250,22 @@ } } }, + "thermo_keywords": { + "description": "additional keywords to append to 'thermo_style custom'", + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "pattern": "^[a-zA-Z1-9\\_\\[\\]]+$" + } + }, "output_variables": { - "description": "output variables, per dump_rate, to an array (see thermo_style)", + "description": "output variables, per dump_rate, to an array (see `thermo_style` for valid keywords)", "type": "array", "uniqueItems": true, "items": { "type": "string", - "enum": [ - "step", - "elapsed", - "elaplong", - "dt", - "time", - "cpu", - "tpcpu", - "spcpu", - "cpuremain", - "part", - "timeremain", - "atoms", - "temp", - "press", - "pe", - "ke", - "etotal", - "enthalpy", - "evdwl", - "ecoul", - "epair", - "ebond", - "eangle", - "edihed", - "eimp", - "emol", - "elong", - "etail", - "vol", - "density", - "lx", - "ly", - "lz", - "xlo", - "xhi", - "ylo", - "yhi", - "zlo", - "zhi", - "xy", - "xz", - "yz", - "xlat", - "ylat", - "zlat", - "bonds", - "angles", - "dihedrals", - "impropers", - "pxx", - "pyy", - "pzz", - "pxy", - "pxz", - "pyz", - "fmax", - "fnorm", - "nbuild", - "ndanger", - "cella", - "cellb", - "cellc", - "cellalpha", - "cellbeta", - "cellgamma" - ] + "pattern": "^[a-zA-Z1-9\\_\\[\\]]+$" } } } diff --git a/aiida_lammps/validation/optimize.schema.json b/aiida_lammps/validation/optimize.schema.json index a2521b5..96bb7d4 100644 --- a/aiida_lammps/validation/optimize.schema.json +++ b/aiida_lammps/validation/optimize.schema.json @@ -101,79 +101,22 @@ } } }, + "thermo_keywords": { + "description": "additional keywords to append to 'thermo_style custom'", + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "pattern": "^[a-zA-Z1-9\\_\\[\\]]+$" + } + }, "output_variables": { - "description": "optimized output variables to record as attributes (see thermo_style)", + "description": "optimized output variables to record as attributes (see `thermo_style` for valid keywords)", "type": "array", "uniqueItems": true, "items": { "type": "string", - "enum": [ - "step", - "elapsed", - "elaplong", - "dt", - "time", - "cpu", - "tpcpu", - "spcpu", - "cpuremain", - "part", - "timeremain", - "atoms", - "temp", - "press", - "pe", - "ke", - "etotal", - "enthalpy", - "evdwl", - "ecoul", - "epair", - "ebond", - "eangle", - "edihed", - "eimp", - "emol", - "elong", - "etail", - "vol", - "density", - "lx", - "ly", - "lz", - "xlo", - "xhi", - "ylo", - "yhi", - "zlo", - "zhi", - "xy", - "xz", - "yz", - "xlat", - "ylat", - "zlat", - "bonds", - "angles", - "dihedrals", - "impropers", - "pxx", - "pyy", - "pzz", - "pxy", - "pxz", - "pyz", - "fmax", - "fnorm", - "nbuild", - "ndanger", - "cella", - "cellb", - "cellc", - "cellalpha", - "cellbeta", - "cellgamma" - ] + "pattern": "^[a-zA-Z1-9\\_\\[\\]]+$" } } } From 6311a18075aeebd4bb2a8c338d729759c708f88f Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Fri, 21 Jun 2019 01:28:03 +0100 Subject: [PATCH 20/28] fix potentials to use kind elements, rather than kind names kind names only works, if it is equal to the kind symbol, which is not always the case. This is now fixed to use kind symbols (names kind_elements for clarity) --- aiida_lammps/calculations/lammps/__init__.py | 4 ++ aiida_lammps/calculations/lammps/force.py | 4 +- aiida_lammps/calculations/lammps/md.py | 4 +- aiida_lammps/calculations/lammps/optimize.py | 4 +- aiida_lammps/data/potential/__init__.py | 47 +++++++++++-------- aiida_lammps/data/potential/eam.py | 4 +- aiida_lammps/data/potential/lennard_jones.py | 2 +- aiida_lammps/data/potential/reaxff.py | 4 +- aiida_lammps/data/potential/tersoff.py | 4 +- aiida_lammps/tests/conftest.py | 19 ++++---- aiida_lammps/tests/test_generate_structure.py | 1 + .../test_generate_Fe_.txt | 17 +++++++ .../test_potential_data/test_init_eam_.yml | 5 +- .../test_init_lennard_jones_.yml | 2 +- .../test_potential_data/test_init_reaxff_.yml | 2 +- .../test_init_tersoff_.yml | 2 +- .../test_input_lines_eam_.txt | 2 +- 17 files changed, 77 insertions(+), 50 deletions(-) create mode 100644 aiida_lammps/tests/test_generate_structure/test_generate_Fe_.txt diff --git a/aiida_lammps/calculations/lammps/__init__.py b/aiida_lammps/calculations/lammps/__init__.py index 4030b32..65a5c7e 100644 --- a/aiida_lammps/calculations/lammps/__init__.py +++ b/aiida_lammps/calculations/lammps/__init__.py @@ -1,4 +1,5 @@ from aiida.engine import CalcJob +from aiida.common.exceptions import ValidationError from aiida.common import CalcInfo, CodeInfo from aiida.orm import StructureData, Dict from aiida.plugins import DataFactory @@ -190,6 +191,9 @@ def prepare_for_submission(self, tempfolder): :param tempfolder: an `aiida.common.folders.Folder` to temporarily write files on disk :return: `aiida.common.CalcInfo` instance """ + # assert that the potential and structure have the same kind elements + if [k.symbol for k in self.inputs.structure.kinds] != self.inputs.potential.kind_elements: + raise ValidationError("the structure and potential are not compatible (different kind elements)") # Setup potential potential_txt = self.inputs.potential.get_potential_file() diff --git a/aiida_lammps/calculations/lammps/force.py b/aiida_lammps/calculations/lammps/force.py index 220d6d8..995c893 100644 --- a/aiida_lammps/calculations/lammps/force.py +++ b/aiida_lammps/calculations/lammps/force.py @@ -13,8 +13,6 @@ def generate_lammps_input(calc, add_thermo_keywords, version_date='11 Aug 2017', **kwargs): - names_str = ' '.join(potential_obj.kind_names) - lammps_input_file = 'units {0}\n'.format(potential_obj.default_units) lammps_input_file += 'boundary p p p\n' lammps_input_file += 'box tilt large\n' @@ -41,7 +39,7 @@ def generate_lammps_input(calc, lammps_input_file += 'dump_modify aiida format line "%4s %16.10f %16.10f %16.10f"\n' lammps_input_file += 'dump_modify aiida sort id\n' - lammps_input_file += 'dump_modify aiida element {}\n'.format(names_str) + lammps_input_file += 'dump_modify aiida element {}\n'.format(' '.join(potential_obj.kind_elements)) lammps_input_file += 'run 0\n' diff --git a/aiida_lammps/calculations/lammps/md.py b/aiida_lammps/calculations/lammps/md.py index 77f6871..7b8aff1 100644 --- a/aiida_lammps/calculations/lammps/md.py +++ b/aiida_lammps/calculations/lammps/md.py @@ -21,8 +21,6 @@ def generate_lammps_input(calc, random_number = np.random.randint(10000000) - names_str = ' '.join(potential_obj.kind_names) - # lammps_date = convert_date_string(pdict.get("lammps_version", None)) lammps_input_file = 'units {0}\n'.format( @@ -90,7 +88,7 @@ def generate_lammps_input(calc, lammps_input_file += 'dump_modify aiida format line "%4s %16.10f %16.10f %16.10f"\n' lammps_input_file += 'dump_modify aiida sort id\n' - lammps_input_file += 'dump_modify aiida element {}\n'.format(names_str) + lammps_input_file += 'dump_modify aiida element {}\n'.format(' '.join(potential_obj.kind_elements)) variables = pdict.get("output_variables", []) if variables and 'step' not in variables: diff --git a/aiida_lammps/calculations/lammps/optimize.py b/aiida_lammps/calculations/lammps/optimize.py index c59b6c8..bc67c8c 100644 --- a/aiida_lammps/calculations/lammps/optimize.py +++ b/aiida_lammps/calculations/lammps/optimize.py @@ -14,8 +14,6 @@ def generate_lammps_input(calc, add_thermo_keywords, version_date, **kwargs): - names_str = ' '.join(potential_obj.kind_names) - parameters = parameters.get_dict() # lammps_date = convert_date_string(parameters.get("lammps_version", None)) @@ -66,7 +64,7 @@ def generate_lammps_input(calc, lammps_input_file += 'dump_modify aiida format line "%4s %16.10f %16.10f %16.10f %16.10f %16.10f %16.10f"\n' lammps_input_file += 'dump_modify aiida sort id\n' - lammps_input_file += 'dump_modify aiida element {}\n'.format(names_str) + lammps_input_file += 'dump_modify aiida element {}\n'.format(' '.join(potential_obj.kind_elements)) lammps_input_file += 'min_style {}\n'.format( parameters['minimize']['style']) # lammps_input_file += 'min_style cg\n' diff --git a/aiida_lammps/data/potential/__init__.py b/aiida_lammps/data/potential/__init__.py index 81c3c7d..834d52e 100644 --- a/aiida_lammps/data/potential/__init__.py +++ b/aiida_lammps/data/potential/__init__.py @@ -21,28 +21,37 @@ def list_types(cls): def load_type(cls, entry_name): return load_entry_point(cls.entry_name, entry_name) - def __init__(self, **kwargs): - - structure = kwargs.pop('structure', None) - kind_names = kwargs.pop('kind_names', None) - potential_type = kwargs.pop('type', None) - potential_data = kwargs.pop('data', None) + def __init__(self, type, data=None, structure=None, kind_elements=None, **kwargs): + """ empirical potential data, used to create LAMMPS input files + + NB: one of structure or kind_elements is required input + + Parameters + ---------- + type: str + the type of potential (should map to a `lammps.potential` entry point) + data: dict + data required to create the potential file and input lines + structure: StructureData + kind_elements: list[str] + a list of elements for each Kind of the structure + """ super(EmpiricalPotential, self).__init__(**kwargs) - self._set_kind_names(structure, kind_names) - self.set_data(potential_type, potential_data) + self._set_kind_elements(structure, kind_elements) + self.set_data(type, data) - def _set_kind_names(self, structure=None, kind_names=None): - if structure is not None and kind_names is not None: - raise ValueError("only one of 'structure' or 'kind_names' must be provided") + def _set_kind_elements(self, structure=None, kind_elements=None): + if structure is not None and kind_elements is not None: + raise ValueError("only one of 'structure' or 'kind_elements' must be provided") elif structure is not None: - names = [site.name for site in structure.kinds] - self.set_attribute('kind_names', names) - elif kind_names is not None: - self.set_attribute('kind_names', kind_names) + names = [kind.symbol for kind in structure.kinds] + self.set_attribute('kind_elements', names) + elif kind_elements is not None: + self.set_attribute('kind_elements', kind_elements) else: - raise ValueError("one of 'structure' or 'kind_names' must be provided") + raise ValueError("one of 'structure' or 'kind_elements' must be provided") def set_data(self, potential_type, data=None): """ @@ -60,7 +69,7 @@ def set_data(self, potential_type, data=None): data = {} if data is None else data pot_contents = module.generate_LAMMPS_potential(data) pot_lines = module.get_input_potential_lines( - data, names=self.kind_names, potential_filename=self.potential_filename) + data, kind_elements=self.kind_elements, potential_filename=self.potential_filename) self.set_attribute("potential_type", potential_type) self.set_attribute("atom_style", atom_style) @@ -78,8 +87,8 @@ def set_data(self, potential_type, data=None): handle.write(six.ensure_text(pot_lines)) @property - def kind_names(self): - return self.get_attribute('kind_names') + def kind_elements(self): + return self.get_attribute('kind_elements') @property def potential_type(self): diff --git a/aiida_lammps/data/potential/eam.py b/aiida_lammps/data/potential/eam.py index d77c8c8..6448519 100644 --- a/aiida_lammps/data/potential/eam.py +++ b/aiida_lammps/data/potential/eam.py @@ -9,10 +9,10 @@ def generate_LAMMPS_potential(data): return potential_file -def get_input_potential_lines(data, names=None, potential_filename='potential.pot'): +def get_input_potential_lines(data, kind_elements=None, potential_filename='potential.pot'): lammps_input_text = 'pair_style eam/{}\n'.format(data['type']) - lammps_input_text += 'pair_coeff * * {} {}\n'.format(potential_filename, ' '.join(names)) + lammps_input_text += 'pair_coeff * * {} {}\n'.format(potential_filename, ' '.join(kind_elements)) return lammps_input_text diff --git a/aiida_lammps/data/potential/lennard_jones.py b/aiida_lammps/data/potential/lennard_jones.py index 7215c97..39d1cab 100644 --- a/aiida_lammps/data/potential/lennard_jones.py +++ b/aiida_lammps/data/potential/lennard_jones.py @@ -5,7 +5,7 @@ def generate_LAMMPS_potential(data): return None -def get_input_potential_lines(data, names=None, potential_filename='potential.pot'): +def get_input_potential_lines(data, kind_elements=None, potential_filename='potential.pot'): cut = np.max([float(i.split()[2]) for i in data.values()]) diff --git a/aiida_lammps/data/potential/reaxff.py b/aiida_lammps/data/potential/reaxff.py index b21a133..66c0292 100644 --- a/aiida_lammps/data/potential/reaxff.py +++ b/aiida_lammps/data/potential/reaxff.py @@ -15,13 +15,13 @@ def generate_LAMMPS_potential(data): return potential_file -def get_input_potential_lines(data, names=None, potential_filename='potential.pot'): +def get_input_potential_lines(data, kind_elements=None, potential_filename='potential.pot'): lammps_input_text = 'pair_style reax/c NULL ' if 'safezone' in data: lammps_input_text += 'safezone {0} '.format(data['safezone']) lammps_input_text += "\n" - lammps_input_text += 'pair_coeff * * {} {}\n'.format(potential_filename, ' '.join(names)) + lammps_input_text += 'pair_coeff * * {} {}\n'.format(potential_filename, ' '.join(kind_elements)) lammps_input_text += "fix qeq all qeq/reax 1 0.0 10.0 1e-6 reax/c\n" lammps_input_text += "fix_modify qeq energy yes\n" lammps_input_text += "compute reax all pair reax/c\n" diff --git a/aiida_lammps/data/potential/tersoff.py b/aiida_lammps/data/potential/tersoff.py index 082387c..1c4f95e 100644 --- a/aiida_lammps/data/potential/tersoff.py +++ b/aiida_lammps/data/potential/tersoff.py @@ -8,10 +8,10 @@ def generate_LAMMPS_potential(data): return potential_file -def get_input_potential_lines(data, names=None, potential_filename='potential.pot'): +def get_input_potential_lines(data, kind_elements=None, potential_filename='potential.pot'): lammps_input_text = 'pair_style tersoff\n' - lammps_input_text += 'pair_coeff * * {} {}\n'.format(potential_filename, ' '.join(names)) + lammps_input_text += 'pair_coeff * * {} {}\n'.format(potential_filename, ' '.join(kind_elements)) return lammps_input_text diff --git a/aiida_lammps/tests/conftest.py b/aiida_lammps/tests/conftest.py index 0a918d5..bb7a151 100644 --- a/aiida_lammps/tests/conftest.py +++ b/aiida_lammps/tests/conftest.py @@ -59,6 +59,7 @@ def _get_structure_data(pkey): fractional = True symbols = ['Fe', 'Fe'] + names = ['Fe1', 'Fe2'] elif pkey == "Ar": @@ -66,7 +67,8 @@ def _get_structure_data(pkey): [-1.993797, 3.453358, 0.000000], [0.000000, 0.000000, 6.538394]] - symbols = ['Ar'] * 2 + symbols = names = ['Ar'] * 2 + positions = [(0.33333, 0.66666, 0.25000), (0.66667, 0.33333, 0.75000)] fractional = True @@ -83,7 +85,7 @@ def _get_structure_data(pkey): (0.3333331, 0.6666663, 0.8750000)] fractional = True - symbols = ['Ga', 'Ga', 'N', 'N'] + symbols = names = ['Ga', 'Ga', 'N', 'N'] elif pkey == "pyrite": @@ -106,13 +108,13 @@ def _get_structure_data(pkey): [0.162, 0.838, 0.338]] fractional = True - symbols = ['Fe'] * 4 + ['S'] * 8 + symbols = names = ['Fe'] * 4 + ['S'] * 8 elif pkey == "fes_cubic-zincblende": cell = [[2.71, -2.71, 0.0], [2.71, 0.0, 2.71], [0.0, -2.71, 2.71]] - symbols = ['Fe', 'S'] + symbols = names = ['Fe', 'S'] positions = [ [0, 0, 0], [4.065, -4.065, 4.065] @@ -137,20 +139,19 @@ def _get_structure_data(pkey): (4.942938, 4.942938, 7.402062), (4.933062, 4.933062, 2.473938)] fractional = False - symbols = ['Fe', 'Fe', 'Fe', 'Fe', 'Fe', 'Fe', - 'S', 'S', 'S', 'S', 'S', 'S', 'S', 'S'] + symbols = names = ['Fe', 'Fe', 'Fe', 'Fe', 'Fe', 'Fe', + 'S', 'S', 'S', 'S', 'S', 'S', 'S', 'S'] else: raise ValueError('Unknown structure key: {}'.format(pkey)) # create structure structure = DataFactory('structure')(cell=cell) - for position, symbols in zip(positions, symbols): + for position, symbol, name in zip(positions, symbols, names): if fractional: position = np.dot(position, cell).tolist() structure.append_atom( - position=position, - symbols=symbols) + position=position, symbols=symbol, name=name) return structure diff --git a/aiida_lammps/tests/test_generate_structure.py b/aiida_lammps/tests/test_generate_structure.py index 87f214f..5288c70 100644 --- a/aiida_lammps/tests/test_generate_structure.py +++ b/aiida_lammps/tests/test_generate_structure.py @@ -4,6 +4,7 @@ @pytest.mark.parametrize('structure', [ + "Fe", "pyrite", "fes_cubic-zincblende", "greigite" diff --git a/aiida_lammps/tests/test_generate_structure/test_generate_Fe_.txt b/aiida_lammps/tests/test_generate_structure/test_generate_Fe_.txt new file mode 100644 index 0000000..e749747 --- /dev/null +++ b/aiida_lammps/tests/test_generate_structure/test_generate_Fe_.txt @@ -0,0 +1,17 @@ +# generated by aiida_lammps + +2 atoms +2 atom types + +0.0 2.8481160000 xlo xhi +0.0 2.8481160000 ylo yhi +0.0 2.8481160000 zlo zhi +Masses + +1 55.8450000000 +2 55.8450000000 + +Atoms + +1 1 0.0000000000 0.0000000000 0.0000000000 +2 2 1.4240580000 1.4240580000 1.4240580000 diff --git a/aiida_lammps/tests/test_potential_data/test_init_eam_.yml b/aiida_lammps/tests/test_potential_data/test_init_eam_.yml index 754fdd8..ac28a74 100644 --- a/aiida_lammps/tests/test_potential_data/test_init_eam_.yml +++ b/aiida_lammps/tests/test_potential_data/test_init_eam_.yml @@ -1,7 +1,8 @@ atom_style: atomic default_units: metal -input_lines_md5: d0848c95d8e01c6d65fe918fdf7efd71 -kind_names: +input_lines_md5: 540d444ce5def35557bca11074642d35 +kind_elements: +- Fe - Fe potential_md5: efea67b5f9c76b50476dcac1a053ac14 potential_type: eam diff --git a/aiida_lammps/tests/test_potential_data/test_init_lennard_jones_.yml b/aiida_lammps/tests/test_potential_data/test_init_lennard_jones_.yml index 8b6ed81..e0f7c44 100644 --- a/aiida_lammps/tests/test_potential_data/test_init_lennard_jones_.yml +++ b/aiida_lammps/tests/test_potential_data/test_init_lennard_jones_.yml @@ -1,6 +1,6 @@ atom_style: atomic default_units: metal input_lines_md5: ba2f62709eddc687159644c4e0325bd9 -kind_names: +kind_elements: - Ar potential_type: lennard_jones diff --git a/aiida_lammps/tests/test_potential_data/test_init_reaxff_.yml b/aiida_lammps/tests/test_potential_data/test_init_reaxff_.yml index e9aafe1..af32d49 100644 --- a/aiida_lammps/tests/test_potential_data/test_init_reaxff_.yml +++ b/aiida_lammps/tests/test_potential_data/test_init_reaxff_.yml @@ -1,7 +1,7 @@ atom_style: charge default_units: real input_lines_md5: b8bf88bb00f01f63a4fcf912086acf3f -kind_names: +kind_elements: - Fe - S potential_md5: 0296f86bcdfae0e931fa80977fb5fcb2 diff --git a/aiida_lammps/tests/test_potential_data/test_init_tersoff_.yml b/aiida_lammps/tests/test_potential_data/test_init_tersoff_.yml index 7fcf63a..2d6feac 100644 --- a/aiida_lammps/tests/test_potential_data/test_init_tersoff_.yml +++ b/aiida_lammps/tests/test_potential_data/test_init_tersoff_.yml @@ -1,7 +1,7 @@ atom_style: atomic default_units: metal input_lines_md5: 3145644a408a6d464e80866b833115a2 -kind_names: +kind_elements: - Ga - N potential_md5: b3b7d45ae7b92eba05ed99ffe69810d0 diff --git a/aiida_lammps/tests/test_potential_data/test_input_lines_eam_.txt b/aiida_lammps/tests/test_potential_data/test_input_lines_eam_.txt index e68bd09..1c252bb 100644 --- a/aiida_lammps/tests/test_potential_data/test_input_lines_eam_.txt +++ b/aiida_lammps/tests/test_potential_data/test_input_lines_eam_.txt @@ -1,2 +1,2 @@ pair_style eam/fs -pair_coeff * * potential.pot Fe +pair_coeff * * potential.pot Fe Fe From b4619935529f4c05e0b945288e72b4ca65d47c9e Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Fri, 21 Jun 2019 01:54:46 +0100 Subject: [PATCH 21/28] fix thermo keyword validation regex --- aiida_lammps/validation/force.schema.json | 4 ++-- aiida_lammps/validation/md.schema.json | 4 ++-- aiida_lammps/validation/optimize.schema.json | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/aiida_lammps/validation/force.schema.json b/aiida_lammps/validation/force.schema.json index b3c4266..3d96ce3 100644 --- a/aiida_lammps/validation/force.schema.json +++ b/aiida_lammps/validation/force.schema.json @@ -14,7 +14,7 @@ "uniqueItems": true, "items": { "type": "string", - "pattern": "^[a-zA-Z1-9\\_\\[\\]]+$" + "pattern": "^[a-zA-Z0-9\\_\\[\\]]+$" } }, "output_variables": { @@ -23,7 +23,7 @@ "uniqueItems": true, "items": { "type": "string", - "pattern": "^[a-zA-Z1-9\\_\\[\\]]+$" + "pattern": "^[a-zA-Z0-9\\_\\[\\]]+$" } } } diff --git a/aiida_lammps/validation/md.schema.json b/aiida_lammps/validation/md.schema.json index c20455b..7f0d670 100644 --- a/aiida_lammps/validation/md.schema.json +++ b/aiida_lammps/validation/md.schema.json @@ -256,7 +256,7 @@ "uniqueItems": true, "items": { "type": "string", - "pattern": "^[a-zA-Z1-9\\_\\[\\]]+$" + "pattern": "^[a-zA-Z0-9\\_\\[\\]]+$" } }, "output_variables": { @@ -265,7 +265,7 @@ "uniqueItems": true, "items": { "type": "string", - "pattern": "^[a-zA-Z1-9\\_\\[\\]]+$" + "pattern": "^[a-zA-Z0-9\\_\\[\\]]+$" } } } diff --git a/aiida_lammps/validation/optimize.schema.json b/aiida_lammps/validation/optimize.schema.json index 96bb7d4..1bc1815 100644 --- a/aiida_lammps/validation/optimize.schema.json +++ b/aiida_lammps/validation/optimize.schema.json @@ -107,7 +107,7 @@ "uniqueItems": true, "items": { "type": "string", - "pattern": "^[a-zA-Z1-9\\_\\[\\]]+$" + "pattern": "^[a-zA-Z0-9\\_\\[\\]]+$" } }, "output_variables": { @@ -116,7 +116,7 @@ "uniqueItems": true, "items": { "type": "string", - "pattern": "^[a-zA-Z1-9\\_\\[\\]]+$" + "pattern": "^[a-zA-Z0-9\\_\\[\\]]+$" } } } From 02c1810bdc5deb44da980b436d297a5adb3f9bf3 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Fri, 21 Jun 2019 02:25:47 +0100 Subject: [PATCH 22/28] add energy units output fo `lammps.md` --- aiida_lammps/parsers/lammps/md.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiida_lammps/parsers/lammps/md.py b/aiida_lammps/parsers/lammps/md.py index e4f5a2b..950504d 100644 --- a/aiida_lammps/parsers/lammps/md.py +++ b/aiida_lammps/parsers/lammps/md.py @@ -46,7 +46,7 @@ def parse(self, **kwargs): output_data = log_data["data"] if 'units_style' in output_data: output_data.update(get_units_dict(output_data['units_style'], - ["distance", "time"])) + ["distance", "time", "energy"])) else: self.logger.warning("units missing in log") self.add_warnings_and_errors(output_data) From 5b8d205b66d16ce8b293b46bcafcf251f134ed96 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Fri, 21 Jun 2019 03:20:31 +0100 Subject: [PATCH 23/28] update readme documentation with examples --- README.md | 416 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 393 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index e95ade3..b16410b 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,398 @@ [![Build Status](https://travis-ci.org/abelcarreras/aiida-lammps.svg?branch=master)](https://travis-ci.org/abelcarreras/aiida-lammps) -AiiDA LAMMPS plugin -==================== +# AiiDA LAMMPS plugin -This a LAMMPS plugin for AiiDA. +This a LAMMPS plugin for [AiiDA](http://aiida-core.readthedocs.io/). This plugin contains 4 code types: -- lammps.forces: Atomic forces calculation -- lammps.md: Molecular dynamics calculation -- lammps.optimize: Crystal structure optimization -- lammps.combinate: DynaPhoPy calculation using LAMMPS MD trajectory - - -Note: lammps.combinate requires aiida-phonopy (https://github.com/abelcarreras/aiida-phonopy) -plugin to work DynaPhoPy can be found in: https://github.com/abelcarreras/aiida-phonopy - -Currently supported Potentials ------------------------------- - - EAM - - Lennad Jones - - Tersoff - - ReaxFF - - -Examples --------- -Some test calculations are found in the folder **/examples** +- `lammps.forces`: Atomic single-point forces calculation +- `lammps.optimize`: Crystal structure optimization +- `lammps.md`: Molecular dynamics calculation +- `lammps.combinate`: DynaPhoPy calculation using LAMMPS MD trajectory (currently untested) + +Note: `lammps.combinate` requires `aiida-phonopy` (https://github.com/abelcarreras/aiida-phonopy) +plugin to work, DynaPhoPy can be found in: https://github.com/abelcarreras/aiida-phonopy + +## Built-in Potential Support + +- EAM +- Lennad Jones +- Tersoff +- ReaxFF + +## Examples + +More example calculations are found in the folder **/examples**, +and there are many test examples in **/aiida_lammps/tests/test_calculations**. + +### Code Setup + +```python +from aiida_lammps.tests.utils import ( + get_or_create_local_computer, get_or_create_code) +from aiida_lammps.tests.utils import lammps_version + +computer_local = get_or_create_local_computer('work_directory', 'localhost') +code_lammps_force = get_or_create_code('lammps.force', computer_local, 'lammps') +code_lammps_opt = get_or_create_code('lammps.optimize', computer_local, 'lammps') +code_lammps_md = get_or_create_code('lammps.md', computer_local, 'lammps') + +meta_options = { + "resources": { + "num_machines": 1, + "num_mpiprocs_per_machine": 1} +} +``` + +### Structure Setup + +```python +from aiida.plugins import DataFactory +import numpy as np + +cell = [[3.1900000572, 0, 0], + [-1.5950000286, 2.762621076, 0], + [0.0, 0, 5.1890001297]] + +positions = [(0.6666669, 0.3333334, 0.0000000), + (0.3333331, 0.6666663, 0.5000000), + (0.6666669, 0.3333334, 0.3750000), + (0.3333331, 0.6666663, 0.8750000)] + +symbols = names = ['Ga', 'Ga', 'N', 'N'] + +structure = DataFactory('structure')(cell=cell) +for position, symbol, name in zip(positions, symbols, names): + position = np.dot(position, cell).tolist() + structure.append_atom( + position=position, symbols=symbol, name=name) + +structure +``` + +```console + +``` + +### Potential Setup + +```python +pair_style = 'tersoff' +potential_dict = { + 'Ga Ga Ga': '1.0 0.007874 1.846 1.918000 0.75000 -0.301300 1.0 1.0 1.44970 410.132 2.87 0.15 1.60916 535.199', + 'N N N': '1.0 0.766120 0.000 0.178493 0.20172 -0.045238 1.0 1.0 2.38426 423.769 2.20 0.20 3.55779 1044.77', + 'Ga Ga N': '1.0 0.001632 0.000 65.20700 2.82100 -0.518000 1.0 0.0 0.00000 0.00000 2.90 0.20 0.00000 0.00000', + 'Ga N N': '1.0 0.001632 0.000 65.20700 2.82100 -0.518000 1.0 1.0 2.63906 3864.27 2.90 0.20 2.93516 6136.44', + 'N Ga Ga': '1.0 0.001632 0.000 65.20700 2.82100 -0.518000 1.0 1.0 2.63906 3864.27 2.90 0.20 2.93516 6136.44', + 'N Ga N ': '1.0 0.766120 0.000 0.178493 0.20172 -0.045238 1.0 0.0 0.00000 0.00000 2.20 0.20 0.00000 0.00000', + 'N N Ga': '1.0 0.001632 0.000 65.20700 2.82100 -0.518000 1.0 0.0 0.00000 0.00000 2.90 0.20 0.00000 0.00000', + 'Ga N Ga': '1.0 0.007874 1.846 1.918000 0.75000 -0.301300 1.0 0.0 0.00000 0.00000 2.87 0.15 0.00000 0.00000'} +potential = DataFactory("lammps.potential")( + structure=structure, type=pair_style, data=potential_dict +) +potential.attributes +``` + +```python +{'kind_elements': ['Ga', 'N'], + 'potential_type': 'tersoff', + 'atom_style': 'atomic', + 'default_units': 'metal', + 'potential_md5': 'b3b7d45ae7b92eba05ed99ffe69810d0', + 'input_lines_md5': '3145644a408a6d464e80866b833115a2'} +``` + +### Force Calculation + +```python +from aiida.engine import run_get_node +parameters = DataFactory('dict')(dict={ + 'lammps_version': lammps_version(), + 'output_variables': ["temp", "etotal", "pe", "ke"], + 'thermo_keywords': [] +}) +builder = code_lammps_force.get_builder() +builder.metadata.options = meta_options +builder.structure = structure +builder.potential = potential +builder.parameters = parameters +result, calc_node = run_get_node(builder) +``` + +```console +$ verdi process list -D desc -a -l 1 + PK Created Process label Process State Process status +---- --------- ---------------- --------------- ---------------- +2480 32s ago ForceCalculation Finished [0] + +Total results: 1 + +Info: last time an entry changed state: 28s ago (at 02:02:36 on 2019-06-21) + +$ verdi process show 2480 +Property Value +------------- ------------------------------------ +type CalcJobNode +pk 2480 +uuid c754f044-b190-4505-b121-776b79d2d1c8 +label +description +ctime 2019-06-21 02:02:32.894858+00:00 +mtime 2019-06-21 02:02:33.297377+00:00 +process state Finished +exit status 0 +computer [2] localhost + +Inputs PK Type +---------- ---- ------------------ +code 1351 Code +parameters 2479 Dict +potential 2478 EmpiricalPotential +structure 2477 StructureData + +Outputs PK Type +------------- ---- ---------- +arrays 2483 ArrayData +remote_folder 2481 RemoteData +results 2484 Dict +retrieved 2482 FolderData +``` + +```python +calc_node.outputs.results.attributes +``` + +```python +{'parser_version': '0.4.0b3', + 'parser_class': 'ForceParser', + 'errors': [], + 'warnings': '', + 'distance_units': 'Angstroms', + 'force_units': 'eV/Angstrom', + 'energy_units': 'eV', + 'energy': -18.1098859130104, + 'final_variables': {'ke': 0.0, + 'pe': -18.1098859130104, + 'etotal': -18.1098859130104, + 'temp': 0.0}, + 'units_style': 'metal'} +``` + +```python +calc_node.outputs.arrays.attributes +``` + +```python +{'array|forces': [0]} +``` + +## Optimisation Calculation + +```python +from aiida.engine import run_get_node +parameters = DataFactory('dict')(dict={ + 'lammps_version': lammps_version(), + 'output_variables': ["temp", "etotal", "pe", "ke"], + 'thermo_keywords': [], + 'units': 'metal', + 'relax': { + 'type': 'iso', + 'pressure': 0.0, + 'vmax': 0.001, + }, + "minimize": { + 'style': 'cg', + 'energy_tolerance': 1.0e-25, + 'force_tolerance': 1.0e-25, + 'max_evaluations': 100000, + 'max_iterations': 50000} +}) +builder = code_lammps_opt.get_builder() +builder.metadata.options = meta_options +builder.structure = structure +builder.potential = potential +builder.parameters = parameters +result, calc_node = run_get_node(builder) +``` + +```console +$ verdi process list -D desc -a -l 1 + PK Created Process label Process State Process status +---- --------- ------------------- --------------- ---------------- +2486 1m ago OptimizeCalculation ⏹ Finished [0] + +Total results: 1 + +Info: last time an entry changed state: 1m ago (at 02:09:54 on 2019-06-21) + +$ verdi process show 2486 +Property Value +------------- ------------------------------------ +type CalcJobNode +pk 2486 +uuid 5c64433d-6337-4352-a0a8-0acb4083a0c3 +label +description +ctime 2019-06-21 02:09:50.872336+00:00 +mtime 2019-06-21 02:09:51.128639+00:00 +process state Finished +exit status 0 +computer [2] localhost + +Inputs PK Type +---------- ---- ------------------ +code 1344 Code +parameters 2485 Dict +potential 2478 EmpiricalPotential +structure 2477 StructureData + +Outputs PK Type +------------- ---- ------------- +arrays 2490 ArrayData +remote_folder 2487 RemoteData +results 2491 Dict +retrieved 2488 FolderData +structure 2489 StructureData +``` + +```python +calc_node.outputs.results.attributes +``` + +```python +{'parser_version': '0.4.0b3', + 'parser_class': 'OptimizeParser', + 'errors': [], + 'warnings': '', + 'stress_units': 'bars', + 'distance_units': 'Angstroms', + 'force_units': 'eV/Angstrom', + 'energy_units': 'eV', + 'energy': -18.1108516231423, + 'final_variables': {'ke': 0.0, + 'pe': -18.1108516231423, + 'etotal': -18.1108516231423, + 'temp': 0.0}, + 'units_style': 'metal'} +``` + +```python +calc_node.outputs.arrays.attributes +``` + +```python +{'array|positions': [56, 4, 3], + 'array|stress': [3, 3], + 'array|forces': [56, 4, 3]} +``` + +## MD Calculation + +```python +from aiida.engine import submit +parameters = DataFactory('dict')(dict={ + 'lammps_version': lammps_version(), + 'output_variables': ["temp", "etotal", "pe", "ke"], + 'thermo_keywords': [], + 'units': 'metal', + 'timestep': 0.001, + 'integration': { + 'style': 'nvt', + 'constraints': { + 'temp': [300, 300, 0.5] + } + }, + "neighbor": [0.3, "bin"], + "neigh_modify": {"every": 1, "delay": 0, "check": False}, + 'equilibrium_steps': 100, + 'total_steps': 1000, + 'dump_rate': 10, + 'restart': 100 +}) +builder = code_lammps_md.get_builder() +builder.metadata.options = meta_options +builder.structure = structure +builder.potential = potential +builder.parameters = parameters +result, calc_node = run_get_node(builder) +``` + +```console +$ verdi process list -D desc -a -l 1 + PK Created Process label Process State Process status +---- --------- --------------- --------------- ---------------- +2493 12s ago MdCalculation ⏹ Finished [0] + +Total results: 1 + +Info: last time an entry changed state: 4s ago (at 02:15:02 on 2019-06-21) + +$ verdi process show 2493 +Property Value +------------- ------------------------------------ +type CalcJobNode +pk 2493 +uuid 351b4721-10ff-406c-8f1c-951317091524 +label +description +ctime 2019-06-21 02:14:54.986384+00:00 +mtime 2019-06-21 02:14:55.282272+00:00 +process state Finished +exit status 0 +computer [2] localhost + +Inputs PK Type +---------- ---- ------------------ +code 1540 Code +parameters 2492 Dict +potential 2478 EmpiricalPotential +structure 2477 StructureData + +Outputs PK Type +--------------- ---- -------------- +remote_folder 2494 RemoteData +results 2496 Dict +retrieved 2495 FolderData +system_data 2498 ArrayData +trajectory_data 2497 TrajectoryData +``` + +```python +calc_node.outputs.results.attributes +``` + +```python +{'parser_version': '0.4.0b3', + 'parser_class': 'MdParser', + 'errors': [], + 'warnings': '', + 'time_units': 'picoseconds', + 'distance_units': 'Angstroms', + 'energy': -17.8464193488116, + 'units_style': 'metal'} +``` + +```python +calc_node.outputs.system_data.attributes +``` + +```python +{'units_style': 'metal', + 'array|step': [100], + 'array|ke': [100], + 'array|pe': [100], + 'array|etotal': [100], + 'array|temp': [100]} +``` + +```python +calc_node.outputs.trajectory_data.attributes +``` + +```python +{'array|times': [101], + 'array|cells': [101, 3, 3], + 'array|steps': [101], + 'array|positions': [101, 4, 3], + 'symbols': ['Ga', 'Ga', 'N', 'N']} +``` From 28df659173b04dd68014da4b5a38040333baba9c Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Fri, 21 Jun 2019 03:23:24 +0100 Subject: [PATCH 24/28] add table of contents --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index b16410b..13ca56e 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,16 @@ This plugin contains 4 code types: Note: `lammps.combinate` requires `aiida-phonopy` (https://github.com/abelcarreras/aiida-phonopy) plugin to work, DynaPhoPy can be found in: https://github.com/abelcarreras/aiida-phonopy +- [AiiDA LAMMPS plugin](#AiiDA-LAMMPS-plugin) + - [Built-in Potential Support](#Built-in-Potential-Support) + - [Examples](#Examples) + - [Code Setup](#Code-Setup) + - [Structure Setup](#Structure-Setup) + - [Potential Setup](#Potential-Setup) + - [Force Calculation](#Force-Calculation) + - [Optimisation Calculation](#Optimisation-Calculation) + - [MD Calculation](#MD-Calculation) + ## Built-in Potential Support - EAM From d6be7c06e21f8ac2124945c432ecf1917c9bc4b5 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Fri, 21 Jun 2019 03:24:31 +0100 Subject: [PATCH 25/28] fix header levels --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 13ca56e..cce9429 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,8 @@ plugin to work, DynaPhoPy can be found in: https://github.com/abelcarreras/aiida - [Structure Setup](#Structure-Setup) - [Potential Setup](#Potential-Setup) - [Force Calculation](#Force-Calculation) - - [Optimisation Calculation](#Optimisation-Calculation) - - [MD Calculation](#MD-Calculation) + - [Optimisation Calculation](#Optimisation-Calculation) + - [MD Calculation](#MD-Calculation) ## Built-in Potential Support @@ -196,7 +196,7 @@ calc_node.outputs.arrays.attributes {'array|forces': [0]} ``` -## Optimisation Calculation +### Optimisation Calculation ```python from aiida.engine import run_get_node @@ -296,7 +296,7 @@ calc_node.outputs.arrays.attributes 'array|forces': [56, 4, 3]} ``` -## MD Calculation +### MD Calculation ```python from aiida.engine import submit From 5ff213fa4e2604e635a87f07c05db8a9fd44d8e5 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Fri, 21 Jun 2019 06:25:10 +0100 Subject: [PATCH 26/28] fix a bug in trajectory parsing, and allow for charge to be parsed in `lammps.force` and `lammps.optimize`, the final trajectory was not be parsed, because of a bug in `read_lammps_positions_and_forces_txt`. Alos the wrong `dump` command was being set for `lammps.force`. Also, if the potential denotes `atom_style='charge'`, then `q` will be appended to the `dump` keywords. --- README.md | 2 +- aiida_lammps/calculations/lammps/force.py | 16 +++- aiida_lammps/calculations/lammps/md.py | 16 +++- aiida_lammps/calculations/lammps/optimize.py | 15 +++- aiida_lammps/common/raw_parsers.py | 83 +++++++++++++------- aiida_lammps/data/potential/__init__.py | 2 +- aiida_lammps/parsers/lammps/force.py | 4 +- aiida_lammps/parsers/lammps/md.py | 7 +- aiida_lammps/parsers/lammps/optimize.py | 4 +- aiida_lammps/tests/test_calculations.py | 35 ++++++--- 10 files changed, 128 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index cce9429..945b45f 100644 --- a/README.md +++ b/README.md @@ -193,7 +193,7 @@ calc_node.outputs.arrays.attributes ``` ```python -{'array|forces': [0]} +{'array|forces': [1, 4, 3]} ``` ### Optimisation Calculation diff --git a/aiida_lammps/calculations/lammps/force.py b/aiida_lammps/calculations/lammps/force.py index 995c893..b1064b4 100644 --- a/aiida_lammps/calculations/lammps/force.py +++ b/aiida_lammps/calculations/lammps/force.py @@ -30,13 +30,23 @@ def generate_lammps_input(calc, if kwd not in thermo_keywords: thermo_keywords.append(kwd) lammps_input_file += 'thermo_style custom {}\n'.format(" ".join(thermo_keywords)) - lammps_input_file += 'dump aiida all custom 1 {0} element fx fy fz\n'.format(trajectory_filename) + + if potential_obj.atom_style == 'charge': + dump_variables = "element x y z fx fy fz q" + dump_format = "%4s %16.10f %16.10f %16.10f %16.10f %16.10f %16.10f %16.10f" + else: + dump_variables = "element x y z fx fy fz" + dump_format = "%4s %16.10f %16.10f %16.10f %16.10f %16.10f %16.10f" + + lammps_input_file += 'dump aiida all custom 1 {0} {1}\n'.format(trajectory_filename, dump_variables) # TODO find exact version when changes were made if version_date <= convert_date_string('10 Feb 2015'): - lammps_input_file += 'dump_modify aiida format "%4s %16.10f %16.10f %16.10f"\n' + dump_mod_cmnd = "format" else: - lammps_input_file += 'dump_modify aiida format line "%4s %16.10f %16.10f %16.10f"\n' + dump_mod_cmnd = "format line" + + lammps_input_file += 'dump_modify aiida {0} "{1}"\n'.format(dump_mod_cmnd, dump_format) lammps_input_file += 'dump_modify aiida sort id\n' lammps_input_file += 'dump_modify aiida element {}\n'.format(' '.join(potential_obj.kind_elements)) diff --git a/aiida_lammps/calculations/lammps/md.py b/aiida_lammps/calculations/lammps/md.py index 7b8aff1..c1d6fb3 100644 --- a/aiida_lammps/calculations/lammps/md.py +++ b/aiida_lammps/calculations/lammps/md.py @@ -78,15 +78,23 @@ def generate_lammps_input(calc, parameters.dict.equilibrium_steps) lammps_input_file += 'reset_timestep 0\n' - lammps_input_file += 'dump aiida all custom {0} {1} element x y z\n'.format(parameters.dict.dump_rate, - trajectory_filename) + if potential_obj.atom_style == 'charge': + dump_variables = "element x y z q" + dump_format = "%4s %16.10f %16.10f %16.10f %16.10f" + else: + dump_variables = "element x y z" + dump_format = "%4s %16.10f %16.10f %16.10f" + + lammps_input_file += 'dump aiida all custom {0} {1} {2}\n'.format( + parameters.dict.dump_rate, trajectory_filename, dump_variables) # TODO find exact version when changes were made if version_date <= convert_date_string('10 Feb 2015'): - lammps_input_file += 'dump_modify aiida format "%4s %16.10f %16.10f %16.10f"\n' + dump_mod_cmnd = "format" else: - lammps_input_file += 'dump_modify aiida format line "%4s %16.10f %16.10f %16.10f"\n' + dump_mod_cmnd = "format line" + lammps_input_file += 'dump_modify aiida {0} "{1}"\n'.format(dump_mod_cmnd, dump_format) lammps_input_file += 'dump_modify aiida sort id\n' lammps_input_file += 'dump_modify aiida element {}\n'.format(' '.join(potential_obj.kind_elements)) diff --git a/aiida_lammps/calculations/lammps/optimize.py b/aiida_lammps/calculations/lammps/optimize.py index bc67c8c..52000d6 100644 --- a/aiida_lammps/calculations/lammps/optimize.py +++ b/aiida_lammps/calculations/lammps/optimize.py @@ -55,13 +55,22 @@ def generate_lammps_input(calc, thermo_keywords.append(kwd) lammps_input_file += 'thermo_style custom {}\n'.format(" ".join(thermo_keywords)) - lammps_input_file += 'dump aiida all custom 1 {0} element x y z fx fy fz\n'.format(trajectory_filename) + if potential_obj.atom_style == 'charge': + dump_variables = "element x y z fx fy fz q" + dump_format = "%4s %16.10f %16.10f %16.10f %16.10f %16.10f %16.10f %16.10f" + else: + dump_variables = "element x y z fx fy fz" + dump_format = "%4s %16.10f %16.10f %16.10f %16.10f %16.10f %16.10f" + + lammps_input_file += 'dump aiida all custom 1 {0} {1}\n'.format(trajectory_filename, dump_variables) # TODO find exact version when changes were made if version_date <= convert_date_string('10 Feb 2015'): - lammps_input_file += 'dump_modify aiida format "%4s %16.10f %16.10f %16.10f %16.10f %16.10f %16.10f"\n' + dump_mod_cmnd = "format" else: - lammps_input_file += 'dump_modify aiida format line "%4s %16.10f %16.10f %16.10f %16.10f %16.10f %16.10f"\n' + dump_mod_cmnd = "format line" + + lammps_input_file += 'dump_modify aiida {0} "{1}"\n'.format(dump_mod_cmnd, dump_format) lammps_input_file += 'dump_modify aiida sort id\n' lammps_input_file += 'dump_modify aiida element {}\n'.format(' '.join(potential_obj.kind_elements)) diff --git a/aiida_lammps/common/raw_parsers.py b/aiida_lammps/common/raw_parsers.py index 0338e57..83e1523 100644 --- a/aiida_lammps/common/raw_parsers.py +++ b/aiida_lammps/common/raw_parsers.py @@ -1,4 +1,6 @@ +import re import numpy as np +import six def parse_quasiparticle_data(qp_file): @@ -209,7 +211,6 @@ def read_lammps_trajectory_txt(data_txt, # Dimensionality of LAMMP calculation number_of_dimensions = 3 - import re blocks = [m.start() for m in re.finditer('TIMESTEP', data_txt)] blocks = [(blocks[i], blocks[i + 1]) for i in range(len(blocks) - 1)] @@ -279,9 +280,11 @@ def read_lammps_trajectory_txt(data_txt, def read_lammps_trajectory(file_name, limit_number_steps=100000000, - initial_cut=1, - end_cut=None, - timestep=1): + initial_cut=1, end_cut=None, + timestep=1, log_warning_func=print): + """ should be used with: + `dump name all custom n element x y z q`, where q is optional + """ import mmap # Time in picoseconds # Coordinates in Angstroms @@ -290,14 +293,15 @@ def read_lammps_trajectory(file_name, number_of_dimensions = 3 step_ids = [] - data = [] + positions = [] + charges = [] cells = [] read_elements = [] counter = 0 bounds = None number_of_atoms = None - lammps_labels = False + field_names = False with open(file_name, "r+") as f: @@ -356,8 +360,8 @@ def read_lammps_trajectory(file_name, position_number = file_map.find(b'ITEM: ATOMS') file_map.seek(position_number) - # lammps_labels not used now but call is necessary! - lammps_labels = file_map.readline() # noqa: F841 + field_names = six.ensure_str(file_map.readline()).split()[2:] + has_charge = field_names[-1] == 'q' # Initial cut control if initial_cut > counter: @@ -366,19 +370,22 @@ def read_lammps_trajectory(file_name, # Reading coordinates read_coordinates = [] read_elements = [] + read_charges = [] for i in range(number_of_atoms): - line = file_map.readline().split()[0:number_of_dimensions + 1] - read_coordinates.append(line[1:number_of_dimensions + 1]) - read_elements.append(line[0]) + fields = file_map.readline().split() + read_coordinates.append(fields[1:number_of_dimensions + 1]) + read_elements.append(fields[0]) + read_charges.append(fields[-1]) try: - # in angstroms - data.append(np.array(read_coordinates, dtype=float)) + positions.append(np.array(read_coordinates, dtype=float)) except ValueError: - print("Error reading step {0}".format(counter)) + log_warning_func("Error reading step {0}".format(counter)) break + if has_charge: + charges.append(read_charges) # security routine to limit maximum of steps to read and put in memory if limit_number_steps + initial_cut < counter: - print( + log_warning_func( "Warning! maximum number of steps reached! No more steps will be read") break @@ -387,13 +394,17 @@ def read_lammps_trajectory(file_name, file_map.close() - data = np.array(data) + positions = np.array(positions) + if charges: + charges = np.array(charges, dtype=float) + else: + charges = None step_ids = np.array(step_ids, dtype=int) cells = np.array(cells) elements = np.array(read_elements, dtype='str') time = np.array(step_ids) * timestep - return data, step_ids, cells, elements, time + return positions, charges, step_ids, cells, elements, time def read_lammps_positions_and_forces(file_name): @@ -426,7 +437,7 @@ def read_lammps_positions_and_forces(file_name): file_map.readline() number_of_atoms = int(file_map.readline()) - # Read cell + # Read cell position_number = file_map.find('ITEM: BOX') file_map.seek(position_number) file_map.readline() @@ -486,22 +497,29 @@ def get_index(string, lines): def read_lammps_positions_and_forces_txt(data_txt): + """ should be used with: + `dump name all custom n element x y z fx fy fz q`, + where q is optional + """ # Dimensionality of LAMMP calculation number_of_dimensions = 3 - import re - blocks = [m.start() for m in re.finditer('TIMESTEP', data_txt)] - blocks = [(blocks[i], blocks[i + 1]) for i in range(len(blocks) - 1)] + block_start = [m.start() for m in re.finditer('TIMESTEP', data_txt)] + blocks = [(block_start[i], block_start[i + 1]) for i in range(len(block_start) - 1)] + # add last block + blocks.append((block_start[-1], len(data_txt))) position_list = [] forces_list = [] + charge_list = [] read_elements = None cell = None time_steps = [] for ini, end in blocks: + # Read number of atoms block_lines = data_txt[ini:end].split('\n') @@ -533,29 +551,38 @@ def read_lammps_positions_and_forces_txt(data_txt): [0, 0, zhi - zlo]]) cell = super_cell.T - #id = [i for i, s in enumerate(block_lines) if 'ITEM: BOX BOUNDS' in s][0] + # id = [i for i, s in enumerate(block_lines) if 'ITEM: BOX BOUNDS' in s][0] # Reading positions id = get_index('ITEM: ATOMS', block_lines) + field_names = block_lines[id].split()[2:] # noqa: F841 positions = [] forces = [] + charges = [] read_elements = [] for i in range(number_of_atoms): - line = block_lines[id + i + 1].split() - positions.append(line[1:number_of_dimensions + 1]) - + fields = block_lines[id + i + 1].split() + positions.append(fields[1:number_of_dimensions + 1]) forces.append( - line[1 + number_of_dimensions:number_of_dimensions * 2 + 1]) - read_elements.append(line[0]) + fields[1 + number_of_dimensions:number_of_dimensions * 2 + 1]) + if field_names[-1] == "q": + charges.append(fields[-1]) + read_elements.append(fields[0]) position_list.append(positions) forces_list.append(forces) + if field_names[-1] == "q": + charge_list.append(charges) positions = np.array(position_list, dtype=float) forces = np.array(forces_list, dtype=float) + if charge_list: + charges = np.array(charge_list, dtype=float) + else: + charges = None - return positions, forces, read_elements, cell + return positions, forces, charges, read_elements, cell def get_units_dict(style, quantities): diff --git a/aiida_lammps/data/potential/__init__.py b/aiida_lammps/data/potential/__init__.py index 834d52e..e1dfef4 100644 --- a/aiida_lammps/data/potential/__init__.py +++ b/aiida_lammps/data/potential/__init__.py @@ -25,7 +25,7 @@ def __init__(self, type, data=None, structure=None, kind_elements=None, **kwargs """ empirical potential data, used to create LAMMPS input files NB: one of structure or kind_elements is required input - + Parameters ---------- type: str diff --git a/aiida_lammps/parsers/lammps/force.py b/aiida_lammps/parsers/lammps/force.py index e6b7498..916a24e 100644 --- a/aiida_lammps/parsers/lammps/force.py +++ b/aiida_lammps/parsers/lammps/force.py @@ -35,12 +35,14 @@ def parse(self, **kwargs): if not trajectory_txt: self.logger.error("trajectory file empty") return self.exit_codes.ERROR_TRAJ_PARSING - positions, forces, symbols, cell2 = read_lammps_positions_and_forces_txt( + positions, forces, charges, symbols, cell2 = read_lammps_positions_and_forces_txt( trajectory_txt) # save forces and stresses into node array_data = ArrayData() array_data.set_array('forces', forces) + if charges is not None: + array_data.set_array('charges', charges) self.out('arrays', array_data) # save results into node diff --git a/aiida_lammps/parsers/lammps/md.py b/aiida_lammps/parsers/lammps/md.py index 950504d..f47843b 100644 --- a/aiida_lammps/parsers/lammps/md.py +++ b/aiida_lammps/parsers/lammps/md.py @@ -36,8 +36,9 @@ def parse(self, **kwargs): # parse trajectory file try: timestep = self.node.inputs.parameters.dict.timestep - positions, step_ids, cells, symbols, time = read_lammps_trajectory( - trajectory_filepath, timestep=timestep) + positions, charges, step_ids, cells, symbols, time = read_lammps_trajectory( + trajectory_filepath, timestep=timestep, + log_warning_func=self.logger.warning) except Exception: traceback.print_exc() return self.exit_codes.ERROR_TRAJ_PARSING @@ -58,6 +59,8 @@ def parse(self, **kwargs): trajectory_data = TrajectoryData() trajectory_data.set_trajectory( symbols, positions, stepids=step_ids, cells=cells, times=time) + if charges is not None: + trajectory_data.set_array('charges', charges) self.out('trajectory_data', trajectory_data) # parse the system data file diff --git a/aiida_lammps/parsers/lammps/optimize.py b/aiida_lammps/parsers/lammps/optimize.py index d0c98ec..5bd68ac 100644 --- a/aiida_lammps/parsers/lammps/optimize.py +++ b/aiida_lammps/parsers/lammps/optimize.py @@ -32,7 +32,7 @@ def parse(self, **kwargs): if not trajectory_txt: self.logger.error("trajectory file empty") return self.exit_codes.ERROR_TRAJ_PARSING - positions, forces, symbols, cell2 = read_lammps_positions_and_forces_txt( + positions, forces, charges, symbols, cell2 = read_lammps_positions_and_forces_txt( trajectory_txt) # save optimized structure into node @@ -47,6 +47,8 @@ def parse(self, **kwargs): array_data.set_array('forces', forces) array_data.set_array('stress', log_data["stress"]) array_data.set_array('positions', positions) + if charges is not None: + array_data.set_array('charges', charges) self.out('arrays', array_data) # save results into node diff --git a/aiida_lammps/tests/test_calculations.py b/aiida_lammps/tests/test_calculations.py index c5ef74a..49946c5 100644 --- a/aiida_lammps/tests/test_calculations.py +++ b/aiida_lammps/tests/test_calculations.py @@ -194,7 +194,7 @@ def test_force_process(db_test_app, get_potential_data, potential_type): # raise ValueError(calc_node.get_object_content('input.in')) # raise ValueError(calc_node.outputs.retrieved.get_object_content('_scheduler-stdout.txt')) - # raise ValueError(calc_node.outputs.retrieved.get_object_content('log.lammps')) + # raise ValueError(calc_node.outputs.retrieved.get_object_content('trajectory.lammpstrj')) if not calc_node.is_finished_ok: print(calc_node.attributes) @@ -213,9 +213,14 @@ def test_force_process(db_test_app, get_potential_data, potential_type): assert pdict['warnings'].strip() == pot_data.output["warnings"] assert pdict['energy'] == pytest.approx(pot_data.output['initial_energy']) - assert set(calc_node.outputs.arrays.get_arraynames()).issuperset( - ['forces'] - ) + if potential_type == "reaxff": + assert set(calc_node.outputs.arrays.get_arraynames() + ) == set(['forces', 'charges']) + else: + assert set(calc_node.outputs.arrays.get_arraynames() + ) == set(['forces']) + assert calc_node.outputs.arrays.get_shape( + 'forces') == (1, len(pot_data.structure.sites), 3) @pytest.mark.lammps_call @@ -263,9 +268,13 @@ def test_optimize_process(db_test_app, get_potential_data, potential_type): assert pdict['warnings'].strip() == pot_data.output["warnings"] assert pdict['energy'] == pytest.approx(pot_data.output['energy']) - assert set(calc_node.outputs.arrays.get_arraynames()).issuperset( - ['stress', 'forces'] - ) + if potential_type == "reaxff": + assert set(calc_node.outputs.arrays.get_arraynames()) == set( + ['positions', 'forces', 'stress', 'charges']) + else: + assert set(calc_node.outputs.arrays.get_arraynames()) == set( + ['positions', 'forces', 'stress']) + assert len(calc_node.outputs.arrays.get_shape('forces')) == 3 @pytest.mark.lammps_call @@ -311,17 +320,19 @@ def test_md_process(db_test_app, get_potential_data, potential_type): ['warnings', 'parser_class', 'parser_version']) assert pdict['warnings'].strip() == pot_data.output["warnings"] - assert set(calc_node.outputs.trajectory_data.get_arraynames()).issuperset( - ['cells', 'positions', 'steps', 'times'] - ) - assert calc_node.outputs.trajectory_data.numsteps == 101 - if potential_type == "reaxff": + assert set(calc_node.outputs.trajectory_data.get_arraynames()) == set( + ['cells', 'positions', 'steps', 'times', 'charges'] + ) assert set(calc_node.outputs.system_data.get_arraynames()) == set( ['step', 'temp', 'etotal', 'c_reax_1_'] ) else: + assert set(calc_node.outputs.trajectory_data.get_arraynames()) == set( + ['cells', 'positions', 'steps', 'times'] + ) assert set(calc_node.outputs.system_data.get_arraynames()) == set( ['step', 'temp', 'etotal'] ) + assert calc_node.outputs.trajectory_data.numsteps == 101 assert calc_node.outputs.system_data.get_shape('temp') == (100,) From bfb005dee285d9ce6055b004d0e32037e0074320 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Fri, 21 Jun 2019 06:33:27 +0100 Subject: [PATCH 27/28] python 2.7 fix --- aiida_lammps/common/raw_parsers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiida_lammps/common/raw_parsers.py b/aiida_lammps/common/raw_parsers.py index 83e1523..7870b3e 100644 --- a/aiida_lammps/common/raw_parsers.py +++ b/aiida_lammps/common/raw_parsers.py @@ -281,7 +281,7 @@ def read_lammps_trajectory_txt(data_txt, def read_lammps_trajectory(file_name, limit_number_steps=100000000, initial_cut=1, end_cut=None, - timestep=1, log_warning_func=print): + timestep=1, log_warning_func=six.print_): """ should be used with: `dump name all custom n element x y z q`, where q is optional """ From 53c7aeec49b269203eb76e0d16532cf8d7ed05c1 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Fri, 21 Jun 2019 06:40:57 +0100 Subject: [PATCH 28/28] move import to top of file --- aiida_lammps/common/raw_parsers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/aiida_lammps/common/raw_parsers.py b/aiida_lammps/common/raw_parsers.py index 7870b3e..dba88fc 100644 --- a/aiida_lammps/common/raw_parsers.py +++ b/aiida_lammps/common/raw_parsers.py @@ -1,3 +1,4 @@ +import mmap import re import numpy as np import six @@ -72,7 +73,6 @@ def parse_dynaphopy_output(file): def read_lammps_forces(file_name): - import mmap # Time in picoseconds # Coordinates in Angstroms @@ -281,11 +281,12 @@ def read_lammps_trajectory_txt(data_txt, def read_lammps_trajectory(file_name, limit_number_steps=100000000, initial_cut=1, end_cut=None, - timestep=1, log_warning_func=six.print_): + timestep=1, log_warning_func=None): """ should be used with: `dump name all custom n element x y z q`, where q is optional """ - import mmap + if log_warning_func is None: + log_warning_func = six.print_ # Time in picoseconds # Coordinates in Angstroms @@ -409,7 +410,6 @@ def read_lammps_trajectory(file_name, def read_lammps_positions_and_forces(file_name): - import mmap # Time in picoseconds # Coordinates in Angstroms