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

Add serialization for Topology #567

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
91bc168
WIP- #552, Initial serialization attempt
umesh-timalsina Jun 18, 2021
c739c57
WIP- Move serialization code to json.py
umesh-timalsina Jul 1, 2021
ea03505
WIP- Add framework for topology conversion
umesh-timalsina Jul 1, 2021
d29a404
WIP- Full serialization and tests ready
umesh-timalsina Jul 7, 2021
8d3aaee
Merge remote-tracking branch 'upstream/master' into 552-topology-seri…
umesh-timalsina Jul 7, 2021
b43637b
Merge branch 'master' into 552-topology-serialization
umesh-timalsina Jul 13, 2021
f26edf9
Create format registries for loading and saving
umesh-timalsina Jul 13, 2021
e26992d
WIP- Remove unused import; add test for box
umesh-timalsina Jul 13, 2021
af19010
Merge branch 'master' into 552-topology-serialization
umesh-timalsina Jul 13, 2021
cbc606c
Merge remote-tracking branch 'upstream/master' into 552-topology-seri…
umesh-timalsina Jul 13, 2021
2d3af5a
Make PairPotentialTypes serializable
umesh-timalsina Jul 13, 2021
6ef9922
Merge branch 'master' into 552-topology-serialization
justinGilmer Jul 14, 2021
d08af7f
Merge branch 'master' into 552-topology-serialization
umesh-timalsina Jul 16, 2021
e5f0074
WIP- Add more tests for exceptions
umesh-timalsina Jul 16, 2021
b45f34b
Import json moved to top level import in gmso_base.py
umesh-timalsina Jul 16, 2021
d637180
WIP- add are_equivalent_topologies fixture
umesh-timalsina Jul 20, 2021
9784a90
Merge branch 'master' into 552-topology-serialization
umesh-timalsina Jul 20, 2021
5e6d64c
Merge branch 'master' into 552-topology-serialization
justinGilmer Jul 21, 2021
5da5b31
WIP- Adress review comments
umesh-timalsina Jul 27, 2021
5782f32
Merge remote-tracking branch 'origin/552-topology-serialization' into…
umesh-timalsina Jul 27, 2021
da6cf23
WIP- Remove redundant whitespace in format_registries.py
umesh-timalsina Jul 27, 2021
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
6 changes: 6 additions & 0 deletions gmso/abc/gmso_base.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Base model all classes extend."""
import json
import warnings
from abc import ABC
from typing import Any, ClassVar, Type
Expand Down Expand Up @@ -93,6 +94,11 @@ def json(self, **kwargs):

return super(GMSOBase, self).json(**kwargs)

def json_dict(self, **kwargs):
"""Return a JSON serializable dictionary from the object"""
raw_json = self.json(**kwargs)
return json.loads(raw_json)

@classmethod
def validate(cls, value):
"""Ensure that the object is validated before use."""
Expand Down
8 changes: 8 additions & 0 deletions gmso/core/box.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,14 @@ def get_unit_vectors(self):
"""Return the normalized vectors of the box."""
return self._unit_vectors_from_angles()

def json_dict(self):
from gmso.abc.serialization_utils import unyt_to_dict

return {
"lengths": unyt_to_dict(self._lengths),
"angles": unyt_to_dict(self._angles),
}

def __repr__(self):
"""Return formatted representation of the box."""
return "Box(a={}, b={}, c={}, alpha={}, beta={}, gamma={})".format(
Expand Down
17 changes: 12 additions & 5 deletions gmso/core/pairpotential_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,21 @@ class PairPotentialType(ParametricPotential):
def __init__(
self,
name="PairPotentialType",
expression="4 * eps * ((sigma / r)**12 - (sigma / r)**6)",
expression=None,
parameters=None,
independent_variables=None,
potential_expression=None,
member_types=None,
topology=None,
tags=None,
):
if parameters is None:
parameters = {"eps": 1 * u.Unit("kJ / mol"), "sigma": 1 * u.nm}
if independent_variables is None:
independent_variables = {"r"}
if potential_expression is None:
if expression is None:
expression = "4 * eps * ((sigma / r)**12 - (sigma / r)**6)"
if parameters is None:
parameters = {"eps": 1 * u.Unit("kJ / mol"), "sigma": 1 * u.nm}
if independent_variables is None:
independent_variables = {"r"}

super(PairPotentialType, self).__init__(
name=name,
Expand All @@ -53,7 +58,9 @@ def __init__(
independent_variables=independent_variables,
topology=topology,
member_types=member_types,
potential_expression=potential_expression,
set_ref=PAIRPOTENTIAL_TYPE_DICT,
tags=tags,
)

@property
Expand Down
9 changes: 9 additions & 0 deletions gmso/core/subtopology.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,15 @@ def __str__(self):
f"id: {id(self)}>"
)

def json_dict(self):
"""Return a json serializable dictionary of this subtopology."""
subtop_dict = {"name": self.name, "atoms": []}

for site in self._sites:
subtop_dict["atoms"].append(self.parent.get_index(site))

return subtop_dict


def _validate_parent(parent):
"""Ensure the parent is a topology."""
Expand Down
37 changes: 37 additions & 0 deletions gmso/core/topology.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Base data structure for GMSO chemical systems."""
import warnings
from pathlib import Path

import numpy as np
import unyt as u
Expand Down Expand Up @@ -977,6 +978,33 @@ def _reindex_connection_types(self, ref):
for i, ref_member in enumerate(self._set_refs[ref].keys()):
self._index_refs[ref][ref_member] = i

def save(self, filename, overwrite=False, **kwargs):
"""Save the topology to a file.

Parameters
----------
filename: str, pathlib.Path
The file to save the topology as
overwrite: bool, default=True
If True, overwrite the existing file if it exists
**kwargs:
The arguments to specific file savers listed below(as extensions):
* json: types, update, indent
"""
if not isinstance(filename, Path):
filename = Path(filename).resolve()

if filename.exists() and not overwrite:
raise FileExistsError(
f"The file {filename} exists. Please set "
f"overwrite=True if you wish to overwrite the existing file"
)

from gmso.formats import SaversRegistry

saver = SaversRegistry.get_callable(filename.suffix)
saver(self, filename, **kwargs)

def __repr__(self):
"""Return custom format to represent topology."""
return (
Expand All @@ -989,3 +1017,12 @@ def __repr__(self):
def __str__(self):
"""Return custom format to represent topology as a string."""
return f"<Topology {self.name}, {self.n_sites} sites, id: {id(self)}>"

@classmethod
def load(cls, filename, **kwargs):
"""Load a file to a topology"""
filename = Path(filename).resolve()
from gmso.formats import LoadersRegistry

loader = LoadersRegistry.get_callable(filename.suffix)
return loader(filename, **kwargs)
2 changes: 2 additions & 0 deletions gmso/formats/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"""Readers and writers for various file formats."""
from gmso.utils.io import has_ipywidgets

from .formats_registry import LoadersRegistry, SaversRegistry
from .gro import read_gro, write_gro
from .gsd import write_gsd
from .json import save_json
from .lammpsdata import write_lammpsdata
from .top import write_top
from .xyz import read_xyz, write_xyz
Expand Down
50 changes: 50 additions & 0 deletions gmso/formats/formats_registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""Registry utilities to handle formats for gmso Topology."""


class UnsupportedFileFormatError(Exception):
"""Exception to be raised whenever the file loading or saving is not supported."""


class Registry:
"""A registry to incorporate a callable with a file extension."""

def __init__(self):
self.handlers = {}

def _assert_can_process(self, extension):
if extension not in self.handlers:
raise UnsupportedFileFormatError(
f"Extension {extension} cannot be processed as no utility "
f"is defined in the current API to handle {extension} files."
)

def get_callable(self, extension):
"""Get the callable associated with extension."""
self._assert_can_process(extension)
return self.handlers[extension]


SaversRegistry = Registry()
LoadersRegistry = Registry()


class saves_as:
"""Decorator to aid saving."""

def __init__(self, extension):
self.extension = extension

def __call__(self, method):
"""Register the method as saver for an extension."""
SaversRegistry.handlers[self.extension] = method


class loads_as:
"""Decorator to aid loading."""

def __init__(self, extension):
self.extension = extension

def __call__(self, method):
"""Register the method as loader for an extension."""
LoadersRegistry.handlers[self.extension] = method
Loading