Skip to content

Commit

Permalink
Merge pull request #73 from nomad-coe/72-add-bond-list-and-generalize…
Browse files Browse the repository at this point in the history
…-interactions-in-gromacs-and-lammps

72 add bond list and generalize interactions in gromacs and lammps
  • Loading branch information
JFRudzinski authored Nov 7, 2023
2 parents 33ae2ef + 992d851 commit 0ef1d68
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 45 deletions.
15 changes: 9 additions & 6 deletions atomisticparsers/gromacs/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
from nomad.parsing.file_parser import TextParser, Quantity, FileParser
from nomad.datamodel.metainfo.simulation.run import Run, Program, TimeRun
from nomad.datamodel.metainfo.simulation.method import (
NeighborSearching, ForceCalculations, Method, ForceField, Model, Interaction, AtomParameters
NeighborSearching, ForceCalculations, Method, ForceField, Model, AtomParameters
)
from nomad.datamodel.metainfo.simulation.system import (
AtomsGroup
Expand All @@ -44,6 +44,7 @@
)
from .metainfo.gromacs import x_gromacs_section_control_parameters, x_gromacs_section_input_output_files
from atomisticparsers.utils import MDAnalysisParser, MDParser
from nomad.atomutils import get_bond_list_from_model_contributions

re_float = r'[-+]?\d+\.*\d*(?:[Ee][-+]\d+)?'
re_n = r'[\n\r]'
Expand Down Expand Up @@ -704,14 +705,19 @@ def get_composition(children_names):
if positions is None:
continue

bond_list = []
if n == 0: # TODO add references to the bond list for other steps
bond_list = get_bond_list_from_model_contributions(sec_run, method_index=-1, model_index=-1)

self.parse_trajectory_step({
'atoms': {
'n_atoms': self.traj_parser.get_n_atoms(n),
'periodic': pbc,
'lattice_vectors': self.traj_parser.get_lattice_vectors(n),
'labels': self.traj_parser.get_atom_labels(n),
'positions': positions,
'velocities': self.traj_parser.get_velocities(n)
'velocities': self.traj_parser.get_velocities(n),
'bond_list': bond_list
}
})

Expand Down Expand Up @@ -827,10 +833,7 @@ def parse_method(self):
self.logger.error('Error parsing interactions.')

interactions = self.traj_parser.get_interactions()
for interaction in interactions:
sec_interaction = sec_model.m_create(Interaction)
for key, val in interaction.items():
setattr(sec_interaction, key, val)
self.parse_interactions(interactions, sec_model)

input_parameters = self.log_parser.get('input_parameters', {})
sec_force_calculations = sec_force_field.m_create(ForceCalculations)
Expand Down
28 changes: 9 additions & 19 deletions atomisticparsers/lammps/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from nomad.parsing.file_parser import Quantity, TextParser
from nomad.datamodel.metainfo.simulation.run import Run, Program
from nomad.datamodel.metainfo.simulation.method import (
NeighborSearching, ForceCalculations, ForceField, Method, Interaction, Model, AtomParameters
NeighborSearching, ForceCalculations, ForceField, Method, Model, AtomParameters
)
from nomad.datamodel.metainfo.simulation.system import (
AtomsGroup
Expand All @@ -36,6 +36,7 @@
)
from .metainfo.lammps import x_lammps_section_input_output_files, x_lammps_section_control_parameters
from atomisticparsers.utils import MDAnalysisParser, MDParser
from nomad.atomutils import get_bond_list_from_model_contributions


re_float = r'[-+]?\d+\.*\d*(?:[Ee][-+]\d+)?'
Expand Down Expand Up @@ -958,14 +959,18 @@ def get_composition(children_names):
velocities = self.traj_parsers.eval('get_velocities', traj_n)
if velocities is not None:
velocities = apply_unit(velocities, 'velocity')
bond_list = []
if traj_n == 0: # TODO add references to the bond list for other steps
bond_list = get_bond_list_from_model_contributions(sec_run, method_index=-1, model_index=-1)
self.parse_trajectory_step({
'atoms': {
'n_atoms': self.traj_parsers.eval('get_n_atoms', traj_n),
'lattice_vectors': lattice_vectors,
'periodic': self.traj_parsers.eval('get_pbc', traj_n),
'positions': apply_unit(self.traj_parsers.eval('get_positions', traj_n), 'distance'),
'labels': self.traj_parsers.eval('get_atom_labels', traj_n),
'velocities': velocities
'velocities': velocities,
'bond_list': bond_list
}
})

Expand Down Expand Up @@ -1080,18 +1085,6 @@ def parse_method(self):
# Can you add the implementation here, and then we can make the MDA implementation below as a backup?
# Can you also get the charges somehow?

# This is storing the input parameters/command for the interaction at the moment, which is already stored in the "force_calculations" section
# interactions = self.log_parser.get_interactions()
# if not interactions:
# interactions = self.data_parser.get_interactions()

# for interaction in interactions:
# if not interaction[0] or interaction[1] is None or np.size(interaction[1]) == 0:
# continue
# sec_interaction = sec_model.m_create(Interaction)
# sec_interaction.type = str(interaction[0])
# sec_interaction.parameters = [[float(ai) for ai in a] for a in interaction[1]]

# parse method with MDAnalysis (should be a backup for the charges and masses...but the interactions are most easily read from the MDA universe right now)
n_atoms = self.traj_parsers.eval('get_n_atoms', 0)
if n_atoms is not None:
Expand All @@ -1101,12 +1094,9 @@ def parse_method(self):
sec_atom.charge = atoms_info.get('charges', [None] * (n + 1))[n]
sec_atom.mass = atoms_info.get('masses', [None] * (n + 1))[n]

# TODO address case types are numbered instead of giving atom labels (fix tests accordingly)
interactions = self._mdanalysistraj_parser.get_interactions()
interactions = interactions if interactions is not None else []
for interaction in interactions:
sec_interaction = sec_model.m_create(Interaction)
for key, val in interaction.items():
setattr(sec_interaction, key, val)
self.parse_interactions(interactions, sec_model)

# Force Calculation Parameters
sec_force_calculations = sec_force_field.m_create(ForceCalculations)
Expand Down
36 changes: 35 additions & 1 deletion atomisticparsers/utils/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,17 @@

from typing import Any, Dict, List
import numpy as np
import inspect
from collections.abc import Iterable

from nomad.utils import get_logger
from nomad.datamodel import EntryArchive
from nomad.metainfo import MSection, SubSection
from nomad.metainfo import MSection, SubSection, Quantity
from nomad.datamodel.metainfo.simulation.run import Run
from nomad.datamodel.metainfo.simulation.system import System
from nomad.datamodel.metainfo.simulation.calculation import Calculation
from nomad.datamodel.metainfo.simulation.workflow import MolecularDynamics
from nomad.datamodel.metainfo.simulation.method import Interaction


# TODO put this in nomad.parsing
Expand Down Expand Up @@ -200,3 +202,35 @@ def parse_md_workflow(self, data: Dict[str, Any]) -> None:
sec_workflow = MolecularDynamics()
self.parse_section(data, sec_workflow)
self.archive.workflow2 = sec_workflow

def parse_interactions(self, interactions: List[Dict], sec_model: MSection) -> None:

interaction_key_list = [n for n, q in inspect.getmembers(Interaction) if isinstance(q, Quantity)]
interaction_dict = {}
for interaction_key in interaction_key_list:
interaction_dict[interaction_key] = np.array([interaction.get(interaction_key) for interaction in interactions], dtype=object)
interaction_dict = {key: val for key, val in interaction_dict.items()}
interaction_types = np.unique(interaction_dict['type']) if interaction_dict.get('type') is not None else []
for interaction_type in interaction_types:
sec_interaction = sec_model.m_create(Interaction)
interaction_indices = np.where(interaction_dict['type'] == interaction_type)[0]
sec_interaction.type = interaction_type
sec_interaction.n_interactions = len(interaction_indices)
sec_interaction.n_atoms
for key, val in interaction_dict.items():
if key == 'type':
continue
interaction_vals = val[interaction_indices]
if type(interaction_vals[0]).__name__ == 'ndarray':
interaction_vals = np.array([vals.tolist() for vals in interaction_vals], dtype=object)
if interaction_vals.all() is None:
continue
if key == 'parameters':
interaction_vals = interaction_vals.tolist()
elif key == 'n_atoms':
interaction_vals = interaction_vals[0]
if hasattr(sec_interaction, key):
sec_interaction.m_set(sec_interaction.m_get_quantity_definition(key), interaction_vals)

if not sec_interaction.n_atoms:
sec_interaction.n_atoms = len(sec_interaction.get('atom_indices')[0]) if sec_interaction.get('atom_indices') is not None else None
6 changes: 3 additions & 3 deletions tests/test_bopfoxparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ def test_energy(parser):
sec_model = sec_method.tb.model[0]
assert sec_model.name == 'test'
assert sec_model.hamiltonian[0].name == 'ddsigma'
assert sec_model.hamiltonian[1].atom_labels == ['W', 'W']
assert (sec_model.hamiltonian[1].atom_labels == ['W', 'W']).all()
assert sec_model.hamiltonian[2].functional_form == 'screenedpowerlaw'
assert sec_model.hamiltonian[3].x_bopfox_cutoff == approx(4.4)
assert sec_model.hamiltonian[4].x_bopfox_dcutoff == approx(1.3)
assert sec_model.hamiltonian[5].x_bopfox_valence == ['d', 'd']
assert sec_model.hamiltonian[6].atom_labels == ['W', 'Mo']
assert (sec_model.hamiltonian[6].atom_labels == ['W', 'Mo']).all()
assert sec_model.hamiltonian[7].parameters[0] == approx(0.8359765)
assert sec_model.repulsion[2].atom_labels == ['Mo', 'Mo']
assert (sec_model.repulsion[2].atom_labels == ['Mo', 'Mo']).all()
assert sec_model.repulsion[0].x_bopfox_cutoff == approx(5.0)
assert sec_model.repulsion[1].parameters[2] == approx(-2.909290858)
assert sec_model.repulsion[2].functional_form == 'env_Yukawa'
Expand Down
4 changes: 2 additions & 2 deletions tests/test_dlpolyparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def test_0(parser):
assert sec_atom_parameters[0].x_dl_poly_nrept == 500
sec_model = sec_method.force_field.model[0]
assert len(sec_model.contributions) == 3
assert sec_model.contributions[0].atom_labels == ['Na+', 'Na+']
assert (sec_model.contributions[0].atom_labels == ['Na+', 'Na+']).all()
assert sec_model.contributions[1].functional_form == 'Born-Huggins-Meyer'
assert sec_model.contributions[2].parameters[1] == approx(3.1545)

Expand Down Expand Up @@ -91,7 +91,7 @@ def test_1(parser):

sec_model = archive.run[0].method[0].force_field.model[0]
assert len(sec_model.contributions) == 1
assert sec_model.contributions[0].atom_labels == ['Al', 'Al']
assert (sec_model.contributions[0].atom_labels == ['Al', 'Al']).all()
assert sec_model.contributions[0].functional_form == 'Sutton-Chen'
assert sec_model.contributions[0].parameters[4] == approx(16.399)

Expand Down
11 changes: 8 additions & 3 deletions tests/test_gromacsparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,17 @@ def test_md_verbose(parser):
assert sec_systems[1].atoms.positions[800][1].magnitude == approx(2.4740036e-09)
assert sec_systems[0].atoms.velocities[500][0].magnitude == approx(869.4773)
assert sec_systems[1].atoms.lattice_vectors[2][2].magnitude == approx(2.469158e-09)
assert sec_systems[0].atoms.bond_list[200][0] == 289

sec_method = sec_run.method
assert len(sec_method) == 1
assert len(sec_method[0].force_field.model[0].contributions) == 1127
assert sec_method[0].force_field.model[0].contributions[0].type == 'angle'
assert sec_method[0].force_field.model[0].contributions[1120].parameters[1] == 575.0
assert len(sec_method[0].force_field.model[0].contributions) == 8
assert sec_method[0].force_field.model[0].contributions[6].type == 'bond'
assert sec_method[0].force_field.model[0].contributions[6].n_interactions == 1017
assert sec_method[0].force_field.model[0].contributions[6].n_atoms == 2
assert sec_method[0].force_field.model[0].contributions[6].atom_labels[10][0] == 'C'
assert sec_method[0].force_field.model[0].contributions[6].atom_indices[100][1] == 141
assert sec_method[0].force_field.model[0].contributions[6].parameters[858] == approx(0.9999996193044006)
assert sec_method[0].force_field.force_calculations.vdw_cutoff.magnitude == 1.2e-09
assert sec_method[0].force_field.force_calculations.vdw_cutoff.units == 'meter'
assert sec_method[0].force_field.force_calculations.coulomb_type == 'particle_mesh_ewald'
Expand Down
27 changes: 16 additions & 11 deletions tests/test_lammpsparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,13 @@ def test_nvt(parser):
assert sec_workflow.method.thermostat_parameters[0].coupling_constant.units == 'second'

sec_method = sec_run.method[0]
assert len(sec_method.force_field.model[0].contributions) == 3744
assert sec_method.force_field.model[0].contributions[2].type == 'angle'
assert sec_method.force_field.model[0].contributions[5].parameters == approx(109.51999892662283)
assert len(sec_method.force_field.model[0].contributions) == 3
assert sec_method.force_field.model[0].contributions[1].type == 'bond'
assert sec_method.force_field.model[0].contributions[1].n_interactions == 666
assert sec_method.force_field.model[0].contributions[1].n_atoms == 2
assert sec_method.force_field.model[0].contributions[1].atom_indices[100][1] == 103
assert sec_method.force_field.model[0].contributions[1].parameters[200] == approx(1.1147454117684314)
assert sec_method.force_field.model[0].contributions[1].atom_labels[350][0] == '2'
assert sec_method.force_field.force_calculations.coulomb_cutoff.magnitude == 1.2000000000000002e-08
assert sec_method.force_field.force_calculations.coulomb_cutoff.units == 'meter'
assert sec_method.force_field.force_calculations.neighbor_searching.neighbor_update_frequency == 10
Expand All @@ -67,6 +71,7 @@ def test_nvt(parser):
assert sec_system[5].atoms.lattice_vectors[1][1].magnitude == approx(2.24235e-09)
assert False not in sec_system[0].atoms.periodic
assert sec_system[80].atoms.labels[91:96] == ['H', 'H', 'H', 'C', 'C']
assert sec_system[0].atoms.bond_list[200][0] == 194

sec_scc = sec_run.calculation
assert len(sec_scc) == 201
Expand Down Expand Up @@ -123,15 +128,15 @@ def test_unwrapped_pos(parser):
assert sec_systems[1].atoms.positions[452][2].magnitude == approx(5.99898) # JFR - units are incorrect?!
assert sec_systems[2].atoms.velocities[457][-2].magnitude == approx(-0.928553) # JFR - velocities are not being read!!

# TODO Fix dealing with multiple output files with archive_to_universe function, then add back in this test
# def test_multiple_dump(parser):
# archive = EntryArchive()
# parser.parse('tests/data/lammps/2_xyz_files/log.lammps', archive, None)

def test_multiple_dump(parser):
archive = EntryArchive()
parser.parse('tests/data/lammps/2_xyz_files/log.lammps', archive, None)

sec_systems = archive.run[0].system
assert len(sec_systems) == 101
assert sec_systems[2].atoms.positions[468][0].magnitude == approx(3.00831)
assert sec_systems[-1].atoms.velocities[72][1].magnitude == approx(-4.61496) # JFR - universe cannot be built without positions
# sec_systems = archive.run[0].system
# assert len(sec_systems) == 101
# assert sec_systems[2].atoms.positions[468][0].magnitude == approx(3.00831)
# assert sec_systems[-1].atoms.velocities[72][1].magnitude == approx(-4.61496) # JFR - universe cannot be built without positions


def test_md_atomsgroup(parser):
Expand Down

0 comments on commit 0ef1d68

Please sign in to comment.