htpolynet 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- htpolynet/__init__.py +2 -0
- htpolynet/analysis/__init__.py +0 -0
- htpolynet/analysis/analyze.py +256 -0
- htpolynet/analysis/plot.py +588 -0
- htpolynet/analysis/postsim.py +444 -0
- htpolynet/analysis/utils.py +399 -0
- htpolynet/cli.py +488 -0
- htpolynet/core/__init__.py +0 -0
- htpolynet/core/bondtemplate.py +112 -0
- htpolynet/core/configuration.py +83 -0
- htpolynet/core/coordinates.py +644 -0
- htpolynet/core/molecule.py +998 -0
- htpolynet/core/projectfilesystem.py +616 -0
- htpolynet/core/runtime.py +774 -0
- htpolynet/core/topocoord.py +2044 -0
- htpolynet/core/topology.py +1138 -0
- htpolynet/cure/__init__.py +0 -0
- htpolynet/cure/chain.py +135 -0
- htpolynet/cure/curecontroller.py +746 -0
- htpolynet/cure/expandreactions.py +294 -0
- htpolynet/cure/reaction.py +280 -0
- htpolynet/external/__init__.py +0 -0
- htpolynet/external/ambertools.py +97 -0
- htpolynet/external/command.py +73 -0
- htpolynet/external/gromacs.py +308 -0
- htpolynet/external/slurm.py +111 -0
- htpolynet/external/software.py +183 -0
- htpolynet/geometry/__init__.py +0 -0
- htpolynet/geometry/bondlist.py +205 -0
- htpolynet/geometry/lattice.py +32 -0
- htpolynet/geometry/linkcell.py +210 -0
- htpolynet/geometry/matrix4.py +110 -0
- htpolynet/geometry/ring.py +274 -0
- htpolynet/io/__init__.py +6 -0
- htpolynet/io/gro.py +195 -0
- htpolynet/io/mol2.py +210 -0
- htpolynet/io/pdb.py +157 -0
- htpolynet/resources/README.md +20 -0
- htpolynet/resources/__init__.py +0 -0
- htpolynet/resources/cfg/DGE-PAC-hi.yaml +115 -0
- htpolynet/resources/cfg/DGE-PAC-lo.yaml +115 -0
- htpolynet/resources/cfg/FDE-DFDA-hi.yaml +116 -0
- htpolynet/resources/cfg/FDE-DFDA-lo.yaml +116 -0
- htpolynet/resources/cfg/GMASTY-hi.yaml +158 -0
- htpolynet/resources/cfg/GMASTY-lo.yaml +158 -0
- htpolynet/resources/cfg/README.md +1 -0
- htpolynet/resources/cfg/STY.yaml +23 -0
- htpolynet/resources/cfg/pMSTY-hi.yaml +101 -0
- htpolynet/resources/cfg/pMSTY-lo.yaml +101 -0
- htpolynet/resources/cfg/pSTY-hi.yaml +101 -0
- htpolynet/resources/cfg/pSTY-lo.yaml +101 -0
- htpolynet/resources/example_depot/0-liquid-styrene.sh +134 -0
- htpolynet/resources/example_depot/0-liquid-styrene.tgz +0 -0
- htpolynet/resources/example_depot/1-polystyrene.sh +202 -0
- htpolynet/resources/example_depot/1-polystyrene.tgz +0 -0
- htpolynet/resources/example_depot/2-bisgma-styrene-thermoset.sh +353 -0
- htpolynet/resources/example_depot/2-bisgma-styrene-thermoset.tgz +0 -0
- htpolynet/resources/example_depot/3-pacm-dgeba-epoxy-thermoset.sh +242 -0
- htpolynet/resources/example_depot/3-pacm-dgeba-epoxy-thermoset.tgz +0 -0
- htpolynet/resources/example_depot/4-dfda-fde-epoxy-thermoset.sh +216 -0
- htpolynet/resources/example_depot/4-dfda-fde-epoxy-thermoset.tgz +0 -0
- htpolynet/resources/mdp/README.md +1 -0
- htpolynet/resources/mdp/drag-min.mdp +13 -0
- htpolynet/resources/mdp/drag-npt.mdp +36 -0
- htpolynet/resources/mdp/drag-nvt.mdp +31 -0
- htpolynet/resources/mdp/min.mdp +14 -0
- htpolynet/resources/mdp/npt.mdp +37 -0
- htpolynet/resources/mdp/nvt.mdp +29 -0
- htpolynet/resources/mdp/relax-min.mdp +13 -0
- htpolynet/resources/mdp/relax-npt.mdp +37 -0
- htpolynet/resources/mdp/relax-nvt.mdp +31 -0
- htpolynet/resources/mdp/single-molecule-min.mdp +12 -0
- htpolynet/resources/mdp/single-molecule-nvt.mdp +25 -0
- htpolynet/resources/molecules/inputs/DFA.pdb +62 -0
- htpolynet/resources/molecules/inputs/DGE.mol2 +115 -0
- htpolynet/resources/molecules/inputs/EMB.mol2 +50 -0
- htpolynet/resources/molecules/inputs/FDE.pdb +72 -0
- htpolynet/resources/molecules/inputs/GMA.mol2 +163 -0
- htpolynet/resources/molecules/inputs/PAC.mol2 +91 -0
- htpolynet/resources/molecules/inputs/STY.mol2 +44 -0
- htpolynet/resources/molecules/make-monomers.sh +64 -0
- htpolynet/resources/molecules/pics/DFA.png +0 -0
- htpolynet/resources/molecules/pics/DGE.png +0 -0
- htpolynet/resources/molecules/pics/EMB.png +0 -0
- htpolynet/resources/molecules/pics/FDE.png +0 -0
- htpolynet/resources/molecules/pics/GMA.png +0 -0
- htpolynet/resources/molecules/pics/PAC.png +0 -0
- htpolynet/resources/molecules/pics/STY.png +0 -0
- htpolynet/resources/molecules/sample-inputs/DFA.pdb +62 -0
- htpolynet/resources/molecules/sample-inputs/DGE.mol2 +115 -0
- htpolynet/resources/molecules/sample-inputs/EMB.mol2 +50 -0
- htpolynet/resources/molecules/sample-inputs/FDE.pdb +72 -0
- htpolynet/resources/molecules/sample-inputs/GMA.mol2 +163 -0
- htpolynet/resources/molecules/sample-inputs/PAC.mol2 +91 -0
- htpolynet/resources/molecules/sample-inputs/STY.mol2 +44 -0
- htpolynet/resources/tcl/readbonds.tcl +32 -0
- htpolynet/resources/tcl/readgrx.tcl +46 -0
- htpolynet/resources/tcl/render.tcl +74 -0
- htpolynet/utils/__init__.py +0 -0
- htpolynet/utils/banner.py +34 -0
- htpolynet/utils/checkpoint.py +85 -0
- htpolynet/utils/dataframetools.py +92 -0
- htpolynet/utils/inputcheck.py +49 -0
- htpolynet/utils/logsetup.py +44 -0
- htpolynet/utils/stringthings.py +39 -0
- htpolynet-2.0.0.dist-info/METADATA +78 -0
- htpolynet-2.0.0.dist-info/RECORD +110 -0
- htpolynet-2.0.0.dist-info/WHEEL +4 -0
- htpolynet-2.0.0.dist-info/entry_points.txt +2 -0
- htpolynet-2.0.0.dist-info/licenses/LICENSE +21 -0
htpolynet/cli.py
ADDED
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
"""Manages the htpolynet application, provides the command-line interface entry point.
|
|
2
|
+
|
|
3
|
+
Author: Cameron F. Abrams <cfa22@drexel.edu>
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import glob
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
import tarfile
|
|
11
|
+
import textwrap
|
|
12
|
+
|
|
13
|
+
import argparse as ap
|
|
14
|
+
import yaml
|
|
15
|
+
|
|
16
|
+
from importlib.metadata import distribution
|
|
17
|
+
|
|
18
|
+
from .analysis.analyze import analyze
|
|
19
|
+
from .analysis.plot import plots
|
|
20
|
+
from .analysis.postsim import postsim
|
|
21
|
+
from .core import projectfilesystem as pfs
|
|
22
|
+
from .core.runtime import Runtime
|
|
23
|
+
from .external import software
|
|
24
|
+
from .external.slurm import generate_script, _normalize_keys
|
|
25
|
+
from .utils.banner import banner_message
|
|
26
|
+
from .utils.inputcheck import input_check
|
|
27
|
+
from .utils.logsetup import setup_logging
|
|
28
|
+
from .utils.stringthings import my_logger
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _is_editable_install():
|
|
34
|
+
"""Returns True if htpolynet was installed in editable mode (pip install -e .)."""
|
|
35
|
+
try:
|
|
36
|
+
direct_url = distribution('htpolynet').read_text('direct_url.json')
|
|
37
|
+
if direct_url:
|
|
38
|
+
return json.loads(direct_url).get('dir_info', {}).get('editable', False)
|
|
39
|
+
except Exception:
|
|
40
|
+
pass
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def info(args: ap.Namespace):
|
|
45
|
+
"""Handles the info subcommand.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
args (argparse.Namespace): parsed arguments
|
|
49
|
+
"""
|
|
50
|
+
print('This is some information on your installed version of htpolynet')
|
|
51
|
+
l = pfs.lib_setup()
|
|
52
|
+
software.sw_setup()
|
|
53
|
+
print(l.info())
|
|
54
|
+
print(software.to_string())
|
|
55
|
+
system_mols, cached_mols = pfs.get_molecule_info()
|
|
56
|
+
print('Available molecules in system library:')
|
|
57
|
+
for m in system_mols:
|
|
58
|
+
print(f' {m}')
|
|
59
|
+
if cached_mols:
|
|
60
|
+
print('Cached parameterized molecules:')
|
|
61
|
+
for m in cached_mols:
|
|
62
|
+
print(f' {m}')
|
|
63
|
+
possibles = l.get_example_names()
|
|
64
|
+
print('Available examples using htpolynet fetch-example')
|
|
65
|
+
for p in possibles:
|
|
66
|
+
if p[0].isdecimal():
|
|
67
|
+
print(f' {p}')
|
|
68
|
+
|
|
69
|
+
def run(args: ap.Namespace):
|
|
70
|
+
"""Handles the run subcommand.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
args (argparse.Namespace): parsed arguments
|
|
74
|
+
"""
|
|
75
|
+
setup_logging(args.loglevel, diag=args.diag, no_banner=args.no_banner)
|
|
76
|
+
my_logger('htpolynet runtime begins', logger.info)
|
|
77
|
+
userlib = args.lib if os.path.exists(args.lib) else None
|
|
78
|
+
software.sw_setup()
|
|
79
|
+
my_logger(software.to_string(), logger.info)
|
|
80
|
+
pfs.pfs_setup(root=os.getcwd(), topdirs=pfs.Dirs.run_topdirs, verbose=True, projdir=args.proj, reProject=args.restart, userlibrary=userlib)
|
|
81
|
+
a = Runtime(cfgfile=args.config, restart=args.restart)
|
|
82
|
+
if args.param_only:
|
|
83
|
+
a.generate_molecules(force_checkin=args.force_checkin, force_parameterization=args.force_parameterization)
|
|
84
|
+
else:
|
|
85
|
+
a.do_workflow(force_checkin=args.force_checkin, force_parameterization=args.force_parameterization)
|
|
86
|
+
my_logger('htpolynet runtime ends', logger.info)
|
|
87
|
+
|
|
88
|
+
def _unpack_example(fullpath):
|
|
89
|
+
"""Extracts a single example tarball into the current directory.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
fullpath (str): absolute path to the .tgz file in the system library
|
|
93
|
+
"""
|
|
94
|
+
with tarfile.open(fullpath) as tf:
|
|
95
|
+
tf.extractall('.')
|
|
96
|
+
|
|
97
|
+
def _fetch_one(depot, fullname):
|
|
98
|
+
"""Fetches one example by name, preferring a .sh script over a .tgz tarball.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
depot (str): path to the example depot directory
|
|
102
|
+
fullname (str): example name without extension
|
|
103
|
+
"""
|
|
104
|
+
import shutil
|
|
105
|
+
sh_path = os.path.join(depot, f'{fullname}.sh')
|
|
106
|
+
tgz_path = os.path.join(depot, f'{fullname}.tgz')
|
|
107
|
+
if os.path.exists(sh_path):
|
|
108
|
+
dest = os.path.basename(sh_path)
|
|
109
|
+
shutil.copy(sh_path, dest)
|
|
110
|
+
print(f'Fetched {dest} (run with: bash {dest})')
|
|
111
|
+
elif os.path.exists(tgz_path):
|
|
112
|
+
_unpack_example(tgz_path)
|
|
113
|
+
else:
|
|
114
|
+
raise FileNotFoundError(f'No example found for {fullname}')
|
|
115
|
+
|
|
116
|
+
def fetch_example(args):
|
|
117
|
+
"""Handles the fetch-example subcommand.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
args (argparse.Namespace): parsed arguments
|
|
121
|
+
"""
|
|
122
|
+
l = pfs.system()
|
|
123
|
+
depot = l.get_example_depot_location()
|
|
124
|
+
possibles = l.get_example_names()
|
|
125
|
+
if args.n == 'all':
|
|
126
|
+
for fullname in possibles:
|
|
127
|
+
_fetch_one(depot, fullname)
|
|
128
|
+
return
|
|
129
|
+
if args.n.isdecimal():
|
|
130
|
+
fullname = [x for x in possibles if x.startswith(args.n)][0]
|
|
131
|
+
else:
|
|
132
|
+
fullname = args.n
|
|
133
|
+
_fetch_one(depot, fullname)
|
|
134
|
+
|
|
135
|
+
def pack_example(args):
|
|
136
|
+
l = pfs.system()
|
|
137
|
+
requires = ['README.md', 'run.sh', 'lib/molecules']
|
|
138
|
+
for r in requires:
|
|
139
|
+
assert os.path.exists(r), f'{r}: not found.'
|
|
140
|
+
ls = glob.glob('*.yaml') + glob.glob('*.yml')
|
|
141
|
+
assert len(ls)>0, f'No yaml config file found.'
|
|
142
|
+
bn = os.path.basename(os.getcwd())
|
|
143
|
+
inspectname = bn.split('-')
|
|
144
|
+
if len(inspectname) > 0:
|
|
145
|
+
firstfield = inspectname[0]
|
|
146
|
+
if firstfield.isdigit():
|
|
147
|
+
existing_n = int(firstfield)
|
|
148
|
+
use_n = False
|
|
149
|
+
else:
|
|
150
|
+
use_n = True
|
|
151
|
+
n = args.n
|
|
152
|
+
overwrite = args.overwrite
|
|
153
|
+
existing_examples = l.get_example_names()
|
|
154
|
+
numbers = [int(x.split('-')[0]) for x in existing_examples]
|
|
155
|
+
if not overwrite:
|
|
156
|
+
assert not n in numbers, f'Choose an index other than {n}; an example with this index already exists.'
|
|
157
|
+
if not use_n:
|
|
158
|
+
assert not existing_n in numbers, f'Choose an index other than {n}; an example with this index already exists. To do this, rename the directory.'
|
|
159
|
+
if n == -1:
|
|
160
|
+
n = str(max(numbers) + 1)
|
|
161
|
+
if use_n:
|
|
162
|
+
newname = f'{n}-{os.path.basename(os.getcwd())}'
|
|
163
|
+
else:
|
|
164
|
+
newname = f'{os.path.basename(os.getcwd())}'
|
|
165
|
+
depot_location = l.get_example_depot_location()
|
|
166
|
+
outpath = os.path.join(depot_location, f'{newname}.tgz')
|
|
167
|
+
if overwrite and os.path.exists(outpath):
|
|
168
|
+
logger.debug(f'Warning: overwriting example {outpath}')
|
|
169
|
+
|
|
170
|
+
def _shallow(tarinfo):
|
|
171
|
+
# mirror --exclude="*/*/*/*/*": omit paths deeper than 4 components
|
|
172
|
+
from pathlib import Path
|
|
173
|
+
return tarinfo if len(Path(tarinfo.name).parts) <= 4 else None
|
|
174
|
+
|
|
175
|
+
with tarfile.open(outpath, 'w:gz') as tf:
|
|
176
|
+
for fname in ['README.md', 'run.sh'] + ls:
|
|
177
|
+
tf.add(fname, arcname=f'{bn}/{fname}', recursive=False)
|
|
178
|
+
tf.add('lib/molecules', arcname=f'{bn}/lib/molecules', filter=_shallow)
|
|
179
|
+
logger.debug(f'Packed {outpath} -- consider a pull request!')
|
|
180
|
+
|
|
181
|
+
def pack_example_sh(args):
|
|
182
|
+
"""Generates a self-contained bash setup script for the current example directory.
|
|
183
|
+
|
|
184
|
+
Scans the current directory for YAML configs and lib/molecules/inputs/ files,
|
|
185
|
+
embeds them as heredocs, and writes the resulting .sh script to the example depot.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
args (argparse.Namespace): parsed arguments
|
|
189
|
+
"""
|
|
190
|
+
import textwrap as _tw
|
|
191
|
+
|
|
192
|
+
l = pfs.system()
|
|
193
|
+
yamls = sorted(glob.glob('*.yaml') + glob.glob('*.yml'))
|
|
194
|
+
assert yamls, 'No YAML config file found in the current directory.'
|
|
195
|
+
inputs_dir = 'lib/molecules/inputs'
|
|
196
|
+
assert os.path.isdir(inputs_dir), f'{inputs_dir}: not found.'
|
|
197
|
+
|
|
198
|
+
# Collect text-format monomer input files (mol2, pdb); skip binaries like png
|
|
199
|
+
input_files = sorted(
|
|
200
|
+
f for f in glob.glob(os.path.join(inputs_dir, '*'))
|
|
201
|
+
if os.path.splitext(f)[1].lower() in ('.mol2', '.pdb')
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
bn = os.path.basename(os.getcwd())
|
|
205
|
+
inspectname = bn.split('-')
|
|
206
|
+
firstfield = inspectname[0] if inspectname else ''
|
|
207
|
+
use_n = not firstfield.isdigit()
|
|
208
|
+
existing_n = int(firstfield) if not use_n else None
|
|
209
|
+
|
|
210
|
+
n = args.n
|
|
211
|
+
overwrite = args.overwrite
|
|
212
|
+
existing_examples = l.get_example_names()
|
|
213
|
+
numbers = [int(x.split('-')[0]) for x in existing_examples]
|
|
214
|
+
if not overwrite:
|
|
215
|
+
if n != -1:
|
|
216
|
+
assert n not in numbers, f'Index {n} already exists; use --overwrite or choose another.'
|
|
217
|
+
if not use_n:
|
|
218
|
+
assert existing_n not in numbers, (
|
|
219
|
+
f'Index {existing_n} (from directory name) already exists; '
|
|
220
|
+
f'use --overwrite or rename the directory.'
|
|
221
|
+
)
|
|
222
|
+
if n == -1:
|
|
223
|
+
n = max(numbers) + 1 if numbers else 0
|
|
224
|
+
newname = f'{n}-{bn}' if use_n else bn
|
|
225
|
+
display_n = n if use_n else existing_n
|
|
226
|
+
|
|
227
|
+
depot_location = l.get_example_depot_location()
|
|
228
|
+
outpath = os.path.join(depot_location, f'{newname}.sh')
|
|
229
|
+
if overwrite and os.path.exists(outpath):
|
|
230
|
+
logger.debug(f'Warning: overwriting {outpath}')
|
|
231
|
+
|
|
232
|
+
# Determine the primary config (first yaml alphabetically) for the --run line
|
|
233
|
+
primary_yaml = yamls[0]
|
|
234
|
+
|
|
235
|
+
lines = [
|
|
236
|
+
'#!/bin/bash',
|
|
237
|
+
f'# htpolynet -- Example {display_n} -- {bn}',
|
|
238
|
+
'#',
|
|
239
|
+
f'# Writes all input files into ./{newname}/ and optionally launches the build.',
|
|
240
|
+
'#',
|
|
241
|
+
'# Usage:',
|
|
242
|
+
f'# bash {newname}.sh # set up directory only',
|
|
243
|
+
f'# bash {newname}.sh --run # set up and launch htpolynet',
|
|
244
|
+
'#',
|
|
245
|
+
'# Generated by: htpolynet pack-example-sh',
|
|
246
|
+
'#',
|
|
247
|
+
'# Cameron F. Abrams — cfa22@drexel.edu',
|
|
248
|
+
'',
|
|
249
|
+
'set -euo pipefail',
|
|
250
|
+
'',
|
|
251
|
+
f'EXDIR="{newname}"',
|
|
252
|
+
'if [ -d "$EXDIR" ]; then',
|
|
253
|
+
' echo "Error: directory \'$EXDIR\' already exists. Remove it first." >&2',
|
|
254
|
+
' exit 1',
|
|
255
|
+
'fi',
|
|
256
|
+
'',
|
|
257
|
+
'mkdir -p "$EXDIR/lib/molecules/inputs" "$EXDIR/lib/molecules/parameterized"',
|
|
258
|
+
'cd "$EXDIR"',
|
|
259
|
+
'',
|
|
260
|
+
]
|
|
261
|
+
|
|
262
|
+
# Embed each monomer input file as a heredoc
|
|
263
|
+
if input_files:
|
|
264
|
+
lines.append('# ---------------------------------------------------------------------------')
|
|
265
|
+
lines.append('# Monomer input files')
|
|
266
|
+
lines.append('# ---------------------------------------------------------------------------')
|
|
267
|
+
for fpath in input_files:
|
|
268
|
+
dest = os.path.join('lib/molecules/inputs', os.path.basename(fpath))
|
|
269
|
+
token = f'HTPOLYNET_{os.path.basename(fpath).replace(".", "_").upper()}_EOF'
|
|
270
|
+
lines.append(f"cat > {dest} << '{token}'")
|
|
271
|
+
with open(fpath) as fh:
|
|
272
|
+
lines.append(fh.read().rstrip())
|
|
273
|
+
lines.append(token)
|
|
274
|
+
lines.append('')
|
|
275
|
+
|
|
276
|
+
# Embed each YAML config as a heredoc
|
|
277
|
+
lines.append('# ---------------------------------------------------------------------------')
|
|
278
|
+
lines.append('# Configuration file(s)')
|
|
279
|
+
lines.append('# ---------------------------------------------------------------------------')
|
|
280
|
+
for yaml_file in yamls:
|
|
281
|
+
token = f'HTPOLYNET_{yaml_file.replace(".", "_").upper()}_EOF'
|
|
282
|
+
lines.append(f"cat > {yaml_file} << '{token}'")
|
|
283
|
+
with open(yaml_file) as fh:
|
|
284
|
+
lines.append(fh.read().rstrip())
|
|
285
|
+
lines.append(token)
|
|
286
|
+
lines.append('')
|
|
287
|
+
|
|
288
|
+
# --run launcher
|
|
289
|
+
lines += [
|
|
290
|
+
'# ---------------------------------------------------------------------------',
|
|
291
|
+
'# Optionally launch the build',
|
|
292
|
+
'# ---------------------------------------------------------------------------',
|
|
293
|
+
'if [[ "${1:-}" == "--run" ]]; then',
|
|
294
|
+
f' htpolynet run -diag diagnostics.log {primary_yaml} &> console.log',
|
|
295
|
+
'else',
|
|
296
|
+
' echo "Setup complete. To run:"',
|
|
297
|
+
f' echo " cd $EXDIR && htpolynet run -diag diagnostics.log {primary_yaml}"',
|
|
298
|
+
'fi',
|
|
299
|
+
]
|
|
300
|
+
|
|
301
|
+
script = '\n'.join(lines) + '\n'
|
|
302
|
+
with open(outpath, 'w') as fh:
|
|
303
|
+
fh.write(script)
|
|
304
|
+
logger.debug(f'Wrote {outpath} -- consider a pull request!')
|
|
305
|
+
print(f'Wrote {outpath}')
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _add_run_options(p, loglevel='debug'):
|
|
309
|
+
"""Adds run options (no positional config) shared by run and gen-slurm-script."""
|
|
310
|
+
p.add_argument('-lib',type=str,default='lib',help='local user library of molecular structures and parameterizations')
|
|
311
|
+
p.add_argument('-proj',type=str,default='next',help='project directory; "next" generates the next available; otherwise creates or resumes (with -restart) a named directory')
|
|
312
|
+
p.add_argument('-diag',type=str,default='htpolynet_runtime_diagnostics.log',help='diagnostic log file')
|
|
313
|
+
p.add_argument('-restart',default=False,action='store_true',help='restart in latest proj dir')
|
|
314
|
+
p.add_argument('--no-banner',default=False,action='store_true',help='suppress the startup banner')
|
|
315
|
+
p.add_argument('--force-parameterization',default=False,action='store_true',help='force GAFF parameterization of any input mol2 structures')
|
|
316
|
+
p.add_argument('--force-checkin',default=False,action='store_true',help='force check-in of generated parameter files to the system library')
|
|
317
|
+
p.add_argument('--param-only',default=False,action='store_true',help='stop after parameterizing molecules without building the full system')
|
|
318
|
+
p.add_argument('--loglevel',type=str,default=loglevel,help='log level for the diagnostic file (debug|info)')
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _add_runtime_args(p, loglevel='debug'):
|
|
322
|
+
"""Adds the common arguments shared by the run and parameterize subcommands."""
|
|
323
|
+
p.add_argument('config',type=str,default=None,help='input configuration file in YAML format')
|
|
324
|
+
_add_run_options(p, loglevel=loglevel)
|
|
325
|
+
|
|
326
|
+
def gen_slurm_script(args: ap.Namespace):
|
|
327
|
+
"""Handles the gen-slurm-script subcommand.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
args (argparse.Namespace): parsed arguments
|
|
331
|
+
"""
|
|
332
|
+
# Load YAML config to pick up the optional slurm: section
|
|
333
|
+
slurm_cfg = {}
|
|
334
|
+
if os.path.exists(args.config):
|
|
335
|
+
with open(args.config) as fh:
|
|
336
|
+
cfg = yaml.safe_load(fh) or {}
|
|
337
|
+
slurm_cfg = _normalize_keys(cfg.get('slurm', {}))
|
|
338
|
+
|
|
339
|
+
# CLI overrides take precedence over values from the YAML slurm: section
|
|
340
|
+
cli_overrides = {
|
|
341
|
+
'job_name': args.job_name,
|
|
342
|
+
'partition': args.partition,
|
|
343
|
+
'account': args.account,
|
|
344
|
+
'nodes': args.nodes,
|
|
345
|
+
'ntasks': args.ntasks,
|
|
346
|
+
'ntasks_per_node': args.ntasks_per_node,
|
|
347
|
+
'cpus_per_task': args.cpus_per_task,
|
|
348
|
+
'time': args.time,
|
|
349
|
+
'mem': args.mem,
|
|
350
|
+
'gres': args.gres,
|
|
351
|
+
}
|
|
352
|
+
for k, v in cli_overrides.items():
|
|
353
|
+
if v is not None:
|
|
354
|
+
slurm_cfg[k] = v
|
|
355
|
+
|
|
356
|
+
# --sif / --apptainer-alias override YAML values
|
|
357
|
+
sif = args.sif or slurm_cfg.get('sif')
|
|
358
|
+
apptainer_alias = args.apptainer_alias or slurm_cfg.get('apptainer_alias', 'apptainer')
|
|
359
|
+
|
|
360
|
+
if not args.native and not sif:
|
|
361
|
+
raise SystemExit(
|
|
362
|
+
'error: containerized mode requires --sif <path/to/image.sif> '
|
|
363
|
+
'(or a sif: entry in the slurm: section of the config file)'
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
run_args = {
|
|
367
|
+
'lib': args.lib,
|
|
368
|
+
'proj': args.proj,
|
|
369
|
+
'diag': args.diag,
|
|
370
|
+
'restart': args.restart,
|
|
371
|
+
'no_banner': args.no_banner,
|
|
372
|
+
'force_parameterization': args.force_parameterization,
|
|
373
|
+
'force_checkin': args.force_checkin,
|
|
374
|
+
'param_only': args.param_only,
|
|
375
|
+
'loglevel': args.loglevel,
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
script = generate_script(
|
|
379
|
+
config_file=args.config,
|
|
380
|
+
slurm_cfg=slurm_cfg,
|
|
381
|
+
run_args=run_args,
|
|
382
|
+
native=args.native,
|
|
383
|
+
sif=sif,
|
|
384
|
+
apptainer_alias=apptainer_alias,
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
outfile = args.output or f'submit_{os.path.splitext(os.path.basename(args.config))[0]}.sh'
|
|
388
|
+
with open(outfile, 'w') as fh:
|
|
389
|
+
fh.write(script)
|
|
390
|
+
print(f'Wrote SLURM script: {outfile}')
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def _add_gen_slurm_args(p):
|
|
394
|
+
"""Adds arguments for the gen-slurm-script subcommand."""
|
|
395
|
+
p.add_argument('config', type=str, help='htpolynet YAML config file')
|
|
396
|
+
# run options (mirrors _add_run_options)
|
|
397
|
+
_add_run_options(p, loglevel='info')
|
|
398
|
+
# execution mode
|
|
399
|
+
p.add_argument('--native', default=False, action='store_true',
|
|
400
|
+
help='run htpolynet natively instead of via Apptainer container')
|
|
401
|
+
p.add_argument('--sif', type=str, default=None,
|
|
402
|
+
help='path to the Apptainer/Singularity .sif image (required unless --native)')
|
|
403
|
+
p.add_argument('--apptainer-alias', type=str, default=None, dest='apptainer_alias',
|
|
404
|
+
help='container runtime command (default: apptainer)')
|
|
405
|
+
# output
|
|
406
|
+
p.add_argument('-o', '--output', type=str, default=None,
|
|
407
|
+
help='output script filename (default: submit_<config>.sh)')
|
|
408
|
+
# common SLURM directives as CLI overrides
|
|
409
|
+
p.add_argument('--job-name', type=str, default=None, dest='job_name')
|
|
410
|
+
p.add_argument('--partition', type=str, default=None)
|
|
411
|
+
p.add_argument('--account', type=str, default=None)
|
|
412
|
+
p.add_argument('--nodes', type=int, default=None)
|
|
413
|
+
p.add_argument('--ntasks', type=int, default=None)
|
|
414
|
+
p.add_argument('--ntasks-per-node', type=int, default=None, dest='ntasks_per_node')
|
|
415
|
+
p.add_argument('--cpus-per-task', type=int, default=None, dest='cpus_per_task')
|
|
416
|
+
p.add_argument('--time', type=str, default=None)
|
|
417
|
+
p.add_argument('--mem', type=str, default=None)
|
|
418
|
+
p.add_argument('--gres', type=str, default=None)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def _add_analysis_args(p):
|
|
422
|
+
"""Adds the common arguments shared by the postsim and analyze subcommands."""
|
|
423
|
+
p.add_argument('-proj',type=str,default='',nargs='+',help='name of project directory')
|
|
424
|
+
p.add_argument('-lib',type=str,default='lib',help='local user library of molecular structures and parameterizations')
|
|
425
|
+
p.add_argument('-ocfg',type=str,default='',help='original htpolynet config file used to generate project(s)')
|
|
426
|
+
p.add_argument('-cfg',type=str,default='',help='config file specifying the operations to perform')
|
|
427
|
+
p.add_argument('--no-banner',default=False,action='store_true',help='suppress the startup banner')
|
|
428
|
+
p.add_argument('--loglevel',type=str,default='info',help='log level for the diagnostic file (debug|info)')
|
|
429
|
+
|
|
430
|
+
def cli():
|
|
431
|
+
"""Command-line interface."""
|
|
432
|
+
l = pfs.lib_setup()
|
|
433
|
+
example_names = l.get_example_names()
|
|
434
|
+
example_ids = [x.split('-')[0] for x in example_names]
|
|
435
|
+
|
|
436
|
+
subcommands = [
|
|
437
|
+
('run', run, 'build a system using instructions in the config file and any required molecular structure inputs (use --param-only to stop after parameterization)'),
|
|
438
|
+
('info', info, 'print version, software, and library information'),
|
|
439
|
+
('plots', plots, 'generate plots summarizing aspects of the current completed build'),
|
|
440
|
+
('fetch-example', fetch_example, 'fetch and unpack example(s): '+', '.join(f'"{x}"' for x in example_names)),
|
|
441
|
+
('input-check', input_check, 'report the number of atoms that would be in the initial system based on config'),
|
|
442
|
+
('postsim', postsim, 'perform specified post-cure MD simulations on final results in one or more project directories'),
|
|
443
|
+
('analyze', analyze, "perform 'gmx <command>' style analyses specified in the config file"),
|
|
444
|
+
('gen-slurm-script', gen_slurm_script, 'generate a SLURM submission script for running htpolynet on a cluster'),
|
|
445
|
+
]
|
|
446
|
+
if _is_editable_install():
|
|
447
|
+
subcommands.append(('pack-example', pack_example, '(dev) pack current directory as a tarball into resources/example_depot'))
|
|
448
|
+
subcommands.append(('pack-example-sh', pack_example_sh, '(dev) generate a self-contained bash setup script from the current example directory'))
|
|
449
|
+
|
|
450
|
+
parser = ap.ArgumentParser(description=textwrap.dedent(banner_message),formatter_class=ap.RawDescriptionHelpFormatter)
|
|
451
|
+
subparsers = parser.add_subparsers()
|
|
452
|
+
subparsers.required = True
|
|
453
|
+
cp = {}
|
|
454
|
+
for name,handler,help_str in subcommands:
|
|
455
|
+
cp[name] = subparsers.add_parser(name,help=help_str)
|
|
456
|
+
cp[name].set_defaults(func=handler)
|
|
457
|
+
|
|
458
|
+
_add_runtime_args(cp['run'])
|
|
459
|
+
_add_gen_slurm_args(cp['gen-slurm-script'])
|
|
460
|
+
|
|
461
|
+
cp['plots'].add_argument('source', type=str, choices=['diag', 'build', 'post'], default='build', help='source of data to plot')
|
|
462
|
+
cp['plots'].add_argument('--diags', type=str, default=[], nargs='+', help='names of diagnostic log files (1 or more)')
|
|
463
|
+
cp['plots'].add_argument('--proj', nargs='+', type=str, default=[], help='name of project director[y/ies]')
|
|
464
|
+
cp['plots'].add_argument('--cfg', type=str, nargs='+', default=[], help='name of input config files')
|
|
465
|
+
cp['plots'].add_argument('--buildplot', type=str, nargs='+', default=['t'], choices=['t', 'g', 'n', 'c'], help='build plot type: t=traces, g=2-D graph, n=homo-N between crosslinks, c=cluster-size distributions')
|
|
466
|
+
cp['plots'].add_argument('--traces', type=str, nargs='+', default=['t', 'd', 'p'], choices=['t', 'd', 'p'], help='trace types: t=temperature, d=density, p=potential energy')
|
|
467
|
+
cp['plots'].add_argument('--n_points', type=int, nargs=2, default=[10, 20], help='number of [cold-side,hot-side] data points for Tg line fits')
|
|
468
|
+
cp['plots'].add_argument('--plotfile', type=str, default='', help='name of output plot file')
|
|
469
|
+
cp['plots'].add_argument('--no-banner', default=False, action='store_true', help='suppress the startup banner')
|
|
470
|
+
cp['plots'].add_argument('--loglevel', type=str, default='info', help='log level for the diagnostic file (debug|info)')
|
|
471
|
+
|
|
472
|
+
cp['fetch-example'].add_argument('n', type=str, choices=example_ids+['all'], help='example to fetch: '+', '.join(example_names))
|
|
473
|
+
|
|
474
|
+
cp['input-check'].add_argument('config', type=str, default=None, help='input configuration file in YAML format')
|
|
475
|
+
cp['input-check'].add_argument('-lib', type=str, default='lib', help='local user library of molecular structures and parameterizations')
|
|
476
|
+
|
|
477
|
+
_add_analysis_args(cp['postsim'])
|
|
478
|
+
_add_analysis_args(cp['analyze'])
|
|
479
|
+
|
|
480
|
+
if 'pack-example' in cp:
|
|
481
|
+
cp['pack-example'].add_argument('-n', type=int, default=-1, help='desired index for this example (default: next available)')
|
|
482
|
+
cp['pack-example'].add_argument('--overwrite', default=False, action='store_true', help='overwrite any existing example with this index in the depot')
|
|
483
|
+
if 'pack-example-sh' in cp:
|
|
484
|
+
cp['pack-example-sh'].add_argument('-n', type=int, default=-1, help='desired index for this example (default: next available)')
|
|
485
|
+
cp['pack-example-sh'].add_argument('--overwrite', default=False, action='store_true', help='overwrite any existing example with this index in the depot')
|
|
486
|
+
|
|
487
|
+
args = parser.parse_args()
|
|
488
|
+
args.func(args)
|
|
File without changes
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Bond-context descriptor classes.
|
|
2
|
+
|
|
3
|
+
These are pure data objects that describe a bond by the chemical
|
|
4
|
+
context (atom names, residue names, order, bystanders, one-aways)
|
|
5
|
+
of its two participating atoms. They carry no cure-specific logic
|
|
6
|
+
and have no htpolynet dependencies.
|
|
7
|
+
|
|
8
|
+
Author: Cameron F. Abrams <cfa22@drexel.edu>
|
|
9
|
+
"""
|
|
10
|
+
import logging
|
|
11
|
+
|
|
12
|
+
from copy import deepcopy
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BondTemplate:
|
|
18
|
+
def __init__(self, names, resnames, intraresidue, order, bystander_resnames, bystander_atomnames, oneaway_resnames, oneaway_atomnames):
|
|
19
|
+
"""Creates a BondTemplate object.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
names (list-like container of two ints): names of atoms in the bond
|
|
23
|
+
resnames (list-like container of two strs): names of the two residues to which the atoms belong
|
|
24
|
+
intraresidue (bool): True if this is an intraresidue bond
|
|
25
|
+
order (int): bond order (1=single, 2=double, ...)
|
|
26
|
+
bystander_resnames (list of two list-like containers of strs): lists of names of bystander residues (residues also bound to one of the atoms), one for each atom in names
|
|
27
|
+
bystander_atomnames (list of two list-like containers of strs): lists of names of atoms in bystander residues (parallel to bystander_resnames)
|
|
28
|
+
oneaway_resnames (list-like container of strs): names of one-away residues (residues bound one bond away from the new interresidue bond; only relevant for C=C free-radical polymerization)
|
|
29
|
+
oneaway_atomnames (list-like container of strs): names of atoms in one-away residues
|
|
30
|
+
"""
|
|
31
|
+
self.names = names
|
|
32
|
+
self.resnames = resnames
|
|
33
|
+
self.intraresidue = intraresidue
|
|
34
|
+
self.bystander_resnames = bystander_resnames
|
|
35
|
+
self.bystander_atomnames = bystander_atomnames
|
|
36
|
+
self.oneaway_resnames = oneaway_resnames
|
|
37
|
+
self.oneaway_atomnames = oneaway_atomnames
|
|
38
|
+
self.order = order
|
|
39
|
+
|
|
40
|
+
def reverse(self):
|
|
41
|
+
"""Reverses the order of all parallel lists in a BondTemplate object."""
|
|
42
|
+
self.names = self.names[::-1]
|
|
43
|
+
self.resnames = self.resnames[::-1]
|
|
44
|
+
self.bystander_resnames = self.bystander_resnames[::-1]
|
|
45
|
+
self.bystander_atomnames = self.bystander_atomnames[::-1]
|
|
46
|
+
self.oneaway_resnames = self.oneaway_resnames[::-1]
|
|
47
|
+
self.oneaway_atomnames = self.oneaway_atomnames[::-1]
|
|
48
|
+
|
|
49
|
+
def __str__(self):
|
|
50
|
+
return f'BondTemplate {self.names} resnames {self.resnames} intraresidue? {self.intraresidue} order {self.order} bystander-resnames {self.bystander_resnames} bystander-atomnames {self.bystander_atomnames} oneaway-resnames {self.oneaway_resnames} oneaway-atomnames {self.oneaway_atomnames}'
|
|
51
|
+
|
|
52
|
+
def __eq__(self,other):
|
|
53
|
+
check = self.names == other.names
|
|
54
|
+
check = check and self.intraresidue == other.intraresidue
|
|
55
|
+
check = check and self.resnames == other.resnames
|
|
56
|
+
check = check and self.bystander_resnames == other.bystander_resnames
|
|
57
|
+
check = check and self.bystander_atomnames == other.bystander_atomnames
|
|
58
|
+
check = check and self.oneaway_resnames == other.oneaway_resnames
|
|
59
|
+
check = check and self.oneaway_atomnames == other.oneaway_atomnames
|
|
60
|
+
return check
|
|
61
|
+
|
|
62
|
+
def is_reverse_of(self,other):
|
|
63
|
+
"""Returns True if self and other are reverse of each other.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
other (BondTemplate): another BondTemplate object
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
bool: True if self and other are reverse copies of each other
|
|
70
|
+
"""
|
|
71
|
+
rb = deepcopy(other)
|
|
72
|
+
rb.reverse()
|
|
73
|
+
return self == rb
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
BondTemplateList = list[BondTemplate]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class ReactionBond:
|
|
80
|
+
def __init__(self, idx, resids, order, bystanders, bystanders_atomidx, oneaways, oneaways_atomidx):
|
|
81
|
+
"""Generates a new ReactionBond object.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
idx (list-like container of two ints): indices of two atoms that form the bond
|
|
85
|
+
resids (list-like container of two ints): resids of the two atoms that form the bond
|
|
86
|
+
order (int): order of the bond (1=single, 2=double, ...)
|
|
87
|
+
bystanders (list of two list-like containers of ints): two lists of indices of interresidue resids already bound to each atom in idx
|
|
88
|
+
bystanders_atomidx (list of two list-like containers of ints): two lists of indices of interresidue atoms already bound to each atom in idx
|
|
89
|
+
oneaways (list of ints): list of one-away resids
|
|
90
|
+
oneaways_atomidx (list of ints): list of one-away atom indices
|
|
91
|
+
"""
|
|
92
|
+
self.idx = idx
|
|
93
|
+
self.resids = resids
|
|
94
|
+
self.bystander_resids = bystanders
|
|
95
|
+
self.bystander_atomidx = bystanders_atomidx
|
|
96
|
+
self.oneaway_resids = oneaways
|
|
97
|
+
self.oneaway_atomidx = oneaways_atomidx
|
|
98
|
+
self.order = order
|
|
99
|
+
|
|
100
|
+
def reverse(self):
|
|
101
|
+
self.idx = self.idx[::-1]
|
|
102
|
+
self.resids = self.resids[::-1]
|
|
103
|
+
self.bystander_resids = self.bystander_resids[::-1]
|
|
104
|
+
self.bystander_atomidx = self.bystander_atomidx[::-1]
|
|
105
|
+
self.oneaway_resids = self.oneaway_resids[::-1]
|
|
106
|
+
self.oneaway_atomidx = self.oneaway_atomidx[::-1]
|
|
107
|
+
|
|
108
|
+
def __str__(self):
|
|
109
|
+
return f'ReactionBond {self.idx} resids {self.resids} order {self.order} bystander-resids {self.bystander_resids} oneaway-resids {self.oneaway_resids} oneaway-atomidx {self.oneaway_atomidx}'
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
ReactionBondList = list[ReactionBond]
|