Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

115 problematic parsing of traj files from ase #131

Draft
wants to merge 7 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion atomisticparsers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def load(self):
python_package='atomisticparsers.asap',
mainfile_binary_header_re=b'AFFormatASE\\-Trajectory',
mainfile_mime_re='application/octet-stream',
mainfile_name_re=r'.*.traj$',
mainfile_name_re=r'.*.traj$', # can this be specified here? to directly check for the emt calculator maybe? somehow we need to seperate the general ase/traj parser from the asap parser
parser_class_name='atomisticparsers.asap.AsapParser',
code_name='ASAP',
code_homepage='https://wiki.fysik.dtu.dk/asap',
Expand All @@ -94,6 +94,33 @@ def load(self):
},
)

ase_parser_entry_point = EntryPoint(
name='parsers/ase',
aliases=['parsers/ase'],
description='NOMAD parser for ASE.',
python_package='atomisticparsers.ase',
mainfile_binary_header_re=b'.+?ASE\\-Trajectory',
mainfile_mime_re='application/octet-stream',
mainfile_name_re=r'.*.traj$',
parser_class_name='atomisticparsers.ase.AseParser',
code_name='ASE',
code_homepage='https://wiki.fysik.dtu.dk/ase',
code_category='Atomistic code',
metadata={
'codeCategory': 'Atomistic code',
'codeLabel': 'ASE',
'codeLabelStyle': 'all in capitals',
'codeName': 'ase',
'codeUrl': 'https://wiki.fysik.dtu.dk/ase',
'parserDirName': 'dependencies/parsers/atomistic/atomisticparsers/ase/',
'parserGitUrl': 'https://github.com/nomad-coe/atomistic-parsers.git',
'parserSpecific': '',
'preamble': '',
'status': 'production',
'tableOfFiles': '',
},
)

bopfox_parser_entry_point = EntryPoint(
name='parsers/bopfox',
aliases=['parsers/bopfox'],
Expand Down
181 changes: 20 additions & 161 deletions atomisticparsers/asap/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,182 +16,41 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
from simulationworkflowschema.geometry_optimization import GeometryOptimization
from simulationworkflowschema.molecular_dynamics import MolecularDynamics
from atomisticparsers.utils import ASETrajParser
from .metainfo import asap # pylint: disable=unused-import

import os
import logging
import numpy as np
from ase.io.trajectory import Trajectory

from nomad.units import ureg
from nomad.parsing.file_parser import FileParser
from runschema.run import Run, Program
from runschema.method import Method, ForceField, Model
from simulationworkflowschema import GeometryOptimization, GeometryOptimizationMethod
from atomisticparsers.utils import MDParser
from .metainfo.asap import MolecularDynamics # pylint: disable=unused-import


class TrajParser(FileParser):
def __init__(self):
super().__init__()

@property
def traj(self):
if self._file_handler is None:
try:
self._file_handler = Trajectory(self.mainfile, 'r')
# check if traj file is really asap
if 'calculator' in self._file_handler.backend.keys():
if self._file_handler.backend.calculator.name != 'emt': # pylint: disable=E1101
self.logger.error('Trajectory is not ASAP.')
self._file_handler = None
except Exception:
self.logger.error('Error reading trajectory file.')
return self._file_handler

def get_version(self):
if hasattr(self.traj, 'ase_version') and self.traj.ase_version:
return self.traj.ase_version
else:
return '3.x.x'

def parse(self):
pass


class AsapParser(MDParser):
def __init__(self):
self.traj_parser = TrajParser()
super().__init__()

class AsapParser(ASETrajParser):
def parse_method(self):
super().parse_method()
traj = self.traj_parser.traj
sec_method = Method()
self.archive.run[0].method.append(sec_method)

if traj[0].calc is not None:
sec_method.force_field = ForceField(model=[Model(name=traj[0].calc.name)])

description = traj.description if hasattr(traj, 'description') else dict()
if not description:
return

calc_type = description.get('type')
if calc_type == 'optimization':
workflow = GeometryOptimization(method=GeometryOptimizationMethod())
workflow = self.archive.workflow2
if isinstance(workflow, GeometryOptimization):
workflow.x_asap_maxstep = description.get('maxstep', 0)
workflow.method.method = description.get('optimizer', '').lower()
self.archive.workflow2 = workflow
elif calc_type == 'molecular-dynamics':
data = {}
data['x_asap_timestep'] = description.get('timestep', 0)
data['x_asap_temperature'] = description.get('temperature', 0)
elif isinstance(workflow, MolecularDynamics):
workflow.x_asap_timestep = description.get('timestep', 0)
workflow.x_asap_temperature = description.get('temperature', 0)
md_type = description.get('md-type', '')
thermodynamic_ensemble = None
if 'Langevin' in md_type:
data['x_asap_langevin_friction'] = description.get('friction', 0)
thermodynamic_ensemble = 'NVT'
elif 'NVT' in md_type:
thermodynamic_ensemble = 'NVT'
elif 'Verlet' in md_type:
thermodynamic_ensemble = 'NVE'
elif 'NPT' in md_type:
thermodynamic_ensemble = 'NPT'
data['method'] = {'thermodynamic_ensemble': thermodynamic_ensemble}
self.parse_md_workflow(data)
workflow.x_asap_langevin_friction = description.get('friction', 0)

def write_to_archive(self) -> None:
self.traj_parser.mainfile = self.mainfile
if self.traj_parser.traj is None:
return

sec_run = Run()
self.archive.run.append(sec_run)
sec_run.program = Program(name='ASAP', version=self.traj_parser.get_version())

# TODO do we build the topology and method for each frame
self.parse_method()

# set up md parser
self.n_atoms = max(
[traj.get_global_number_of_atoms() for traj in self.traj_parser.traj]
)
steps = [
(traj.description if hasattr(traj, 'description') else dict()).get(
'interval', 1
)
* n
for n, traj in enumerate(self.traj_parser.traj)
]
self.trajectory_steps = steps
self.thermodynamics_steps = steps

def get_constraint_name(constraint):
def index():
d = constraint['kwargs'].get('direction')
return ((d / np.linalg.norm(d)) ** 2).argsort()[2]

name = constraint.get('name')
if name == 'FixedPlane':
return ['fix_yz', 'fix_xz', 'fix_xy'][index()]
elif name == 'FixedLine':
return ['fix_x', 'fix_y', 'fix_z'][index()]
elif name == 'FixAtoms':
return 'fix_xyz'
else:
return name

for step in self.trajectory_steps:
traj = self.traj_parser.traj[steps.index(step)]
lattice_vectors = traj.get_cell() * ureg.angstrom
labels = traj.get_chemical_symbols()
positions = traj.get_positions() * ureg.angstrom
periodic = traj.get_pbc()
if (velocities := traj.get_velocities()) is not None:
velocities = velocities * (ureg.angstrom / ureg.fs)
# check if traj file is really asap
if 'calculator' in self.traj_parser.traj.backend.keys():
if self.traj_parser.traj.backend.calculator.name != 'emt': # pylint: disable=E1101
self.logger.error('Trajectory is not ASAP.')
return

constraints = []
for constraint in traj.constraints:
as_dict = constraint.todict()
indices = as_dict['kwargs'].get('a', as_dict['kwargs'].get('indices'))
indices = (
indices
if isinstance(indices, (np.ndarray, list))
else [int(indices)]
)
constraints.append(
dict(
atom_indices=[np.asarray(indices)],
kind=get_constraint_name(as_dict),
)
)
self.parse_trajectory_step(
dict(
atoms=dict(
lattice_vectors=lattice_vectors,
labels=labels,
positions=positions,
periodic=periodic,
velocities=velocities,
),
constraint=constraints,
)
)
super().write_to_archive()

for step in self.thermodynamics_steps:
try:
traj = self.traj_parser.traj[steps.index(step)]
if (total_energy := traj.get_total_energy()) is not None:
total_energy = total_energy * ureg.eV
if (forces := traj.get_forces()) is not None:
forces = forces * ureg.eV / ureg.angstrom
if (forces_raw := traj.get_forces(apply_constraint=False)) is not None:
forces_raw * ureg.eV / ureg.angstrom
self.parse_thermodynamics_step(
dict(
energy=dict(total=dict(value=total_energy)),
forces=dict(total=dict(value=forces, value_raw=forces_raw)),
)
)
except Exception:
pass
if self.archive.run:
self.archive.run[0].program.name = 'ASAP'
19 changes: 19 additions & 0 deletions atomisticparsers/ase/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#
# Copyright The NOMAD Authors.
#
# This file is part of NOMAD.
# See https://nomad-lab.eu for further info.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from .parser import AseParser
31 changes: 31 additions & 0 deletions atomisticparsers/ase/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#
# Copyright The NOMAD Authors.
#
# This file is part of NOMAD.
# See https://nomad-lab.eu for further info.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import sys
import json
import logging

from nomad.utils import configure_logging
from nomad.datamodel import EntryArchive
from atomisticparsers.ase import AseParser

if __name__ == '__main__':
configure_logging(console_log_level=logging.DEBUG)
archive = EntryArchive()
AseParser().parse(sys.argv[1], archive, logging)
json.dump(archive.m_to_dict(), sys.stdout, indent=2)
30 changes: 30 additions & 0 deletions atomisticparsers/ase/parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#
# Copyright The NOMAD Authors.
#
# This file is part of NOMAD.
# See https://nomad-lab.eu for further info.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

from atomisticparsers.utils import ASETrajParser


class AseParser(ASETrajParser):
def write_to_archive(self) -> None:
super().write_to_archive()

if self.archive.run:
self.archive.run[0].program.name = 'ASE'

# TODO add tests
4 changes: 3 additions & 1 deletion atomisticparsers/gromacs/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@
x_gromacs_section_input_output_files,
)
from atomisticparsers.utils import MDAnalysisParser, MDParser
from simulationworkflowschema.molecular_dynamics import get_bond_list_from_model_contributions
from simulationworkflowschema.molecular_dynamics import (
get_bond_list_from_model_contributions,
)

re_float = r'[-+]?\d+\.*\d*(?:[Ee][-+]\d+)?'
re_n = r'[\n\r]'
Expand Down
4 changes: 3 additions & 1 deletion atomisticparsers/lammps/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@
x_lammps_section_control_parameters,
)
from atomisticparsers.utils import MDAnalysisParser, MDParser
from simulationworkflowschema.molecular_dynamics import get_bond_list_from_model_contributions
from simulationworkflowschema.molecular_dynamics import (
get_bond_list_from_model_contributions,
)


re_float = r'[-+]?\d+\.*\d*(?:[Ee][-+]\d+)?'
Expand Down
2 changes: 1 addition & 1 deletion atomisticparsers/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@
# limitations under the License.

from .mdanalysis import MDAnalysisParser
from .parsers import MDParser
from .parsers import MDParser, ASETrajParser

MOL = 6.022140857e23
5 changes: 4 additions & 1 deletion atomisticparsers/utils/mdanalysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@

from nomad.units import ureg
from nomad.parsing.file_parser import FileParser
from simulationworkflowschema.molecular_dynamics import BeadGroup, shifted_correlation_average
from simulationworkflowschema.molecular_dynamics import (
BeadGroup,
shifted_correlation_average,
)


MOL = 6.022140857e23
Expand Down
Loading
Loading