Skip to content

Commit 8f6a797

Browse files
authored
Merge pull request #43 from dwhswenson/sim-setup
`compile` command: Simulation setup with YAML or JSON
2 parents 8217eea + a43b2c0 commit 8f6a797

48 files changed

Lines changed: 2749 additions & 67 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎docs/compiling.rst‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
2+
Compiling
3+
=========
4+
5+
Let's start with an overview of terminology.:
6+
7+
* **Compiling**: The process of converting input in a text format,
8+
such as YAML or JSON, into OPS objects. Here we're focused on non-Python
9+
alternatives to using the standard Python interpreter to compile objects.
10+
* **Category**: The base category of object to be created (engine, CV,
11+
volume, etc.)
12+
* **Builder**: We will refer to a builder function, which creates an
13+
instance of a specific
14+
15+
Everything is created with plugins. There are two types of plugins used in
16+
the ``compiling`` subpackage:
17+
18+
* ``InstanceCompilerPlugin``: This is what you'll normally work with. These
19+
convert the input text to an instance of a specific OPS object (for
20+
example, an ``OpenMMEngine`` or an ``MDTrajFunctionCV``. In general, you
21+
do not create subclasses of ``InstanceCompilerPlugin`` -- there are
22+
subclasses specialized to engines (``EngineCompilerPlugin``), to CVs
23+
(``CVCompilerPlugin``), etc. You create *instances* of those subclasses.
24+
You write your builder function, and wrap in with in an instance of an
25+
``InstanceCompilerPlugin``.
26+
* ``CategoryPlugin``: These manage the plugins associated with a given
27+
features. Contributors will almost never need to create one of these.
28+
The only case in which you would need to create one of these is if you're
29+
creating a new *category* of object, i.e., something like an engine where
30+
users will have multiple options at the command line.
31+
32+
Other useful classes defined in the ``compiling`` subpackage include:
33+
34+
* ``Builder``: The ``Builder`` class is a convenience for creating builder
35+
functions. It takes either a callable or a string as input, where a string
36+
is treated as a path to an object to import at runtime. It also allows
37+
takes parameters ``remapper`` and ``after_build``, which are callables
38+
that act on the input dictionary before object creation (``remapper``) and
39+
on the created object after object creations (``after_build``).
40+
* ``CategoryCompiler``: This class manages plugins for a given category, as
41+
well as tracking named objects of that type. These are created
42+
automatically when plugins are registered; users do not need to create
43+
these.

‎paths_cli/cli.py‎

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
from .plugin_management import (FilePluginLoader, NamespacePluginLoader,
1717
OPSCommandPlugin)
18+
from .utils import get_installed_plugins
1819

1920
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
2021

@@ -26,20 +27,10 @@ class OpenPathSamplingCLI(click.MultiCommand):
2627
def __init__(self, *args, **kwargs):
2728
# the logic here is all about loading the plugins
2829
commands = str(pathlib.Path(__file__).parent.resolve() / 'commands')
29-
def app_dir_plugins(posix):
30-
return str(pathlib.Path(
31-
click.get_app_dir("OpenPathSampling", force_posix=posix)
32-
).resolve() / 'cli-plugins')
33-
34-
self.plugin_loaders = [
35-
FilePluginLoader(commands, OPSCommandPlugin),
36-
FilePluginLoader(app_dir_plugins(posix=False), OPSCommandPlugin),
37-
FilePluginLoader(app_dir_plugins(posix=True), OPSCommandPlugin),
38-
NamespacePluginLoader('paths_cli_plugins', OPSCommandPlugin)
39-
]
40-
41-
plugins = sum([loader() for loader in self.plugin_loaders], [])
42-
30+
plugins = get_installed_plugins(
31+
default_loader=FilePluginLoader(commands, OPSCommandPlugin),
32+
plugin_types=OPSCommandPlugin
33+
)
4334
self._get_command = {}
4435
self._sections = collections.defaultdict(list)
4536
self.plugins = []

‎paths_cli/commands/compile.py‎

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import click
2+
3+
from paths_cli.compiling.root_compiler import do_compile, register_plugins
4+
from paths_cli.parameters import OUTPUT_FILE
5+
from paths_cli.errors import MissingIntegrationError
6+
from paths_cli import OPSCommandPlugin
7+
from paths_cli.compiling.plugins import (
8+
CategoryPlugin, InstanceCompilerPlugin
9+
)
10+
from paths_cli.plugin_management import NamespacePluginLoader
11+
import importlib
12+
from paths_cli.utils import get_installed_plugins
13+
from paths_cli.commands.contents import report_all_tables
14+
15+
# this is just to handle a nicer error
16+
def import_module(module_name, format_type=None, install=None):
17+
try:
18+
mod = importlib.import_module(module_name)
19+
except ImportError:
20+
if format_type is None:
21+
format_type = module_name
22+
23+
msg = f"Unable to find a compiler for {format_type} on your system."
24+
if install is not None:
25+
msg += f" Please install {install} to use this format."
26+
27+
raise MissingIntegrationError(msg)
28+
return mod
29+
30+
def load_yaml(f):
31+
yaml = import_module('yaml', format_type="YAML", install="PyYAML")
32+
return yaml.load(f.read(), Loader=yaml.FullLoader)
33+
34+
def load_json(f):
35+
json = import_module('json') # this should never fail... std lib!
36+
return json.loads(f.read())
37+
38+
# This is why we can't use TOML:
39+
# https://github.com/toml-lang/toml/issues/553#issuecomment-444814690
40+
# Conflicts with rules preventing mixed types in arrays. This seems to have
41+
# relaxed in TOML 1.0, but the toml package doesn't support the 1.0
42+
# standard. We'll add toml in once the package supports the standard.
43+
44+
EXTENSIONS = {
45+
'yaml': load_yaml,
46+
'yml': load_yaml,
47+
'json': load_json,
48+
'jsn': load_json,
49+
# 'toml': load_toml,
50+
}
51+
52+
def select_loader(filename):
53+
ext = filename.split('.')[-1]
54+
try:
55+
return EXTENSIONS[ext]
56+
except KeyError:
57+
raise RuntimeError(f"Unknown file extension: {ext}")
58+
59+
@click.command(
60+
'compile',
61+
)
62+
@click.argument('input_file')
63+
@OUTPUT_FILE.clicked(required=True)
64+
def compile_(input_file, output_file):
65+
loader = select_loader(input_file)
66+
with open(input_file, mode='r') as f:
67+
dct = loader(f)
68+
69+
plugin_types = (InstanceCompilerPlugin, CategoryPlugin)
70+
plugins = get_installed_plugins(
71+
default_loader=NamespacePluginLoader('paths_cli.compiling',
72+
plugin_types),
73+
plugin_types=plugin_types
74+
)
75+
register_plugins(plugins)
76+
77+
objs = do_compile(dct)
78+
print(f"Saving {len(objs)} user-specified objects to {output_file}....")
79+
storage = OUTPUT_FILE.get(output_file)
80+
storage.save(objs)
81+
report_all_tables(storage)
82+
83+
84+
PLUGIN = OPSCommandPlugin(
85+
command=compile_,
86+
section="Debug",
87+
requires_ops=(1, 0),
88+
requires_cli=(0, 3)
89+
)

‎paths_cli/commands/contents.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
'Snapshots': 'snapshots'
2121
}
2222

23+
import logging
24+
logger = logging.getLogger(__name__)
25+
2326
@click.command(
2427
'contents',
2528
short_help="list named objects from an OPS storage file",
@@ -49,6 +52,7 @@ def contents(input_file, table):
4952

5053
def get_section_string(label, store):
5154
attr = NAME_TO_ATTR.get(label, label.lower())
55+
logger.debug(f"Working on {attr}")
5256
if attr in UNNAMED_SECTIONS:
5357
string = get_unnamed_section_string(label, store)
5458
elif attr in ['tag', 'tags']:

‎paths_cli/compat/openmm.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# should be able to remove this try block when we drop OpenMM < 7.6
2+
try:
3+
import openmm as mm
4+
from openmm import unit
5+
except ImportError:
6+
try:
7+
from simtk import openmm as mm
8+
from simtk import unit # -no-cov-
9+
except ImportError:
10+
HAS_OPENMM = False
11+
mm = None
12+
unit = None
13+
else: # -no-cov-
14+
HAS_OPENMM = True
15+
else:
16+
HAS_OPENMM = True
17+
18+

‎paths_cli/compiling/__init__.py‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from .core import Parameter, Builder, InstanceCompilerPlugin
2+
from .root_compiler import (clean_input_key, compiler_for, register_plugins,
3+
do_compile)
4+
5+
from .plugins import CategoryPlugin
6+
from .errors import InputError
7+
8+
# OPS-specific
9+
from .plugins import (
10+
EngineCompilerPlugin, CVCompilerPlugin, VolumeCompilerPlugin,
11+
NetworkCompilerPlugin, SchemeCompilerPlugin, StrategyCompilerPlugin,
12+
)

0 commit comments

Comments
 (0)