Skip to content

Commit 63591b0

Browse files
committed
Fixes to convergence plot rendering. Also, updates and improvements to documentation.
1 parent 16e85c2 commit 63591b0

File tree

11 files changed

+97
-29
lines changed

11 files changed

+97
-29
lines changed

README.md

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,14 @@ OpenMM is recommended for the molecular dynamics (MD) stage of SEEKR2. SEEKR2
5252
also needs the SEEKR2 OpenMM Plugin in order to use OpenMM for MD simulations.
5353

5454
The easiest, quickest way to install the SEEKR2 OpenMM Plugin is to use
55-
Conda. If you don't already have Conda installed, Download Conda with
56-
Python version 3.8 from
57-
https://conda.io/en/latest/miniconda.html and run the downloaded script and
58-
fill out the prompts.
55+
Mamba. If you don't already have Mamba installed, Download Mamba from
56+
https://github.com/conda-forge/miniforge/releases/latest/download/Mambaforge-Linux-x86_64.sh
57+
and run the downloaded script and fill out the prompts.
5958

60-
With Conda working, install the SEEKR2 OpenMM Plugin:
59+
With Mamba working, install the SEEKR2 OpenMM Plugin:
6160

6261
```
63-
conda install -c conda-forge seekr2_openmm_plugin
62+
mamba install seekr2_openmm_plugin
6463
```
6564
One can test the installation by opening a Python terminal and typing:
6665

@@ -72,14 +71,21 @@ If there is a problem related to not being able to find libOpenMM8.1, one
7271
can try specifying the OpenMM version:
7372

7473
```
75-
conda install -c conda-forge seekr2_openmm_plugin openmm=8.1
74+
mamba install seekr2_openmm_plugin openmm=8.1
75+
```
76+
77+
If there is an error such as "CUDA_ERROR_UNSUPPORTED_PTX_VERSION", one can
78+
see if a different version of CudaToolKit will work:
79+
80+
```
81+
mamba install seekr2_openmm_plugin cudatoolkit=11.7
7682
```
7783

7884
If there is an error such as "No module named seekr2plugin", one can always
7985
try installing an older version of OpenMM and CUDA:
8086

8187
```
82-
conda install -c conda-forge seekr2_openmm_plugin cudatoolkit=10.2 openmm=7.7
88+
mamba install seekr2_openmm_plugin cudatoolkit=10.2 openmm=7.7
8389
```
8490

8591
Alternatively, NAMD2 may be used for MD if desired. See the NAMD2 section

docs/installation.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ can try specifying the OpenMM version:
7474
If you get an error such as "CUDA_ERROR_UNSUPPORTED_PTX_VERSION", you might
7575
need to install with a different CUDA Toolkit version:
7676

77-
``conda install seekr2_openmm_plugin cudatoolkit=11.7``
77+
``mamba install seekr2_openmm_plugin cudatoolkit=11.7``
7878

7979

8080
Installation of SEEKR2 itself begins with cloning and installing the SEEKR2
@@ -401,4 +401,4 @@ Additional continuous integration tests may be run from the Python scripts in
401401
the seekr2/seekr2/continuous_integration/ directory if extra testing is
402402
desired.
403403

404-
You should now be able to use SEEKR2.
404+
You should now be able to use SEEKR2.

seekr2/analyze.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -697,27 +697,32 @@ def save_plots(self, image_directory):
697697
plt.xticks(anchor_values, anchor_values, rotation=90)
698698
plt.ylabel("\u03C0_{\u03B1}")
699699
plt.xlabel("anchor value")
700+
plt.yscale("log", nonpositive="mask")
700701
plt.tight_layout()
701702
pi_fig.savefig(os.path.join(image_directory, "pi_alpha.png"))
702703

703704
# save p_i
704705
pi_fig, ax = plt.subplots()
705-
plt.errorbar(np.round(milestone_values, 3), self.p_i, yerr=self.p_i_error,
706-
ecolor="k", capsize=2)
707-
plt.xticks(np.round(milestone_values, 3), np.round(milestone_values, 3), rotation=90)
706+
plt.errorbar(np.round(milestone_values, 3), self.p_i,
707+
yerr=self.p_i_error, ecolor="k", capsize=2)
708+
plt.xticks(np.round(milestone_values, 3), np.round(milestone_values, 3),
709+
rotation=90)
708710
plt.ylabel("p_i")
709711
plt.xlabel("milestones")
712+
plt.yscale("log", nonpositive="mask")
710713
plt.tight_layout()
711714
pi_fig.savefig(os.path.join(image_directory, "p_i.png"))
712715
# save free energy milestone profile
713716
pi_fig, ax = plt.subplots()
714717
plt.errorbar(np.round(milestone_values, 3), self.free_energy_profile,
715718
yerr=self.free_energy_profile_err, ecolor="k", capsize=2)
716-
plt.xticks(np.round(milestone_values, 3), np.round(milestone_values, 3), rotation=90)
719+
plt.xticks(np.round(milestone_values, 3), np.round(milestone_values, 3),
720+
rotation=90)
717721
plt.ylabel("\u0394G(milestone) (kcal/mol)")
718722
plt.xlabel("milestones")
719723
plt.tight_layout()
720-
pi_fig.savefig(os.path.join(image_directory, "free_energy_profile_milestones.png"))
724+
pi_fig.savefig(os.path.join(
725+
image_directory, "free_energy_profile_milestones.png"))
721726

722727
if self.free_energy_anchors is not None:
723728
# save free energy anchor profile
@@ -728,7 +733,8 @@ def save_plots(self, image_directory):
728733
plt.ylabel("\u0394G(anchor) (kcal/mol)")
729734
plt.xlabel("anchor")
730735
plt.tight_layout()
731-
pi_fig.savefig(os.path.join(image_directory, "free_energy_profile_anchor.png"))
736+
pi_fig.savefig(os.path.join(
737+
image_directory, "free_energy_profile_anchor.png"))
732738
return
733739

734740
def analyze(model, force_warning=False, num_error_samples=1000,

seekr2/converge.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def converge(model, k_on_state=None, image_directory=None, verbose=False,
2929

3030
if image_directory is None or image_directory == "" or not long_converge:
3131
return data_sample_list, times_dict
32-
32+
3333
k_off_fig, ax = common_converge.plot_scalar_conv(
3434
k_off_conv, max_step_list, title="$k_{off}$ Convergence",
3535
label="k_{off} (s^{-1})", timestep_in_ns=timestep_in_ns)

seekr2/modules/check.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,37 @@ def recurse_atoms(atom, _visited_indices=set()):
606606
_visited_indices.update(branch_indices)
607607
return _visited_indices
608608

609+
def check_atom_selections_dont_contain_hydrogen(model):
610+
"""
611+
It has been observed that atom selections (for use as CVs) that
612+
include hydrogen often cause numerical instabilities. This
613+
check will raise an error if any CV atom selections include
614+
hydrogen atoms.
615+
"""
616+
warnstr = """CHECK FAILURE: the atom selection for collective variable
617+
(CV) number {} contains hydrogens, including atom index {}. It has
618+
been observed that CV atom selections including hydrogen are often
619+
unintentional, and tend to produce numerical instabilities, causing
620+
the simulations to blow up. Please ensure that this CV was defined
621+
by the intended atom selection and consider using selections that do
622+
not include any hydrogens.
623+
"""
624+
for anchor in model.anchors:
625+
structure = load_structure_with_parmed(model, anchor)
626+
if structure is None:
627+
continue
628+
for cv in model.collective_variables:
629+
atom_groups = cv.get_atom_groups()
630+
for atom_group in atom_groups:
631+
for atom_index in atom_group:
632+
atom = structure.atoms[atom_index]
633+
if atom.element == 1:
634+
# Then it's a hydrogen
635+
print(warnstr.format(cv.index, atom_index))
636+
return False
637+
638+
return True
639+
609640
def check_atom_selections_on_same_molecule(model):
610641
"""
611642
The user might accidentally define atom selections that span
@@ -621,7 +652,8 @@ def check_atom_selections_on_same_molecule(model):
621652
check the structure for anchor {} to ensure that atom indexing
622653
is correct. Keep in mind: SEEKR2 atom indexing starts at 0 (but
623654
PDB files often start with a different atom serial index, such
624-
as 1 or another number)."""
655+
as 1 or another number). This might also be caused by multimeric
656+
sites or unclosed loops."""
625657
warnstr2 = """CHECK FAILURE: the atom index {} for collective variable
626658
(CV) number {} does not exist in the structure for anchor {}."""
627659
for anchor in model.anchors:
@@ -910,6 +942,7 @@ def check_pre_simulation_all(model):
910942
# Skipping MD/BD salt conc. check because the best results seem to
911943
# come from using no salt in BD.
912944
#check_passed_list.append(check_pre_sim_MD_and_BD_salt_concentration(model))
945+
check_passed_list.append(check_atom_selections_dont_contain_hydrogen(model))
913946
check_passed_list.append(check_atom_selections_on_same_molecule(model))
914947
check_passed_list.append(check_atom_selections_MD_BD(model))
915948
check_passed_list.append(check_pqr_residues(model))

seekr2/modules/common_analyze.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -582,7 +582,10 @@ def calculate_kinetics(self):
582582
continue
583583
bulk_milestones.append(milestone_id)
584584
#bulk_milestone = milestone_id
585-
585+
586+
assert len(end_milestones) > 0, "No end (bound or otherwise) states "\
587+
"defined in this model. Kinetics calculations will not work."
588+
586589
if np.any(self.Q.sum(axis=1) > 1.E-10):
587590
problem_milestone = np.argmin(self.Q.T.sum(axis=1))
588591
error_msg = """The rate matrix Q has a numerically overflowed row

seekr2/modules/common_base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def strBool(bool_str):
3333
raise Exception(
3434
"argument for strBool must be string either 'True' or 'False'.")
3535

36-
def order_files_numerically(file_list, func=int, use_basename=False):
36+
def order_files_numerically(file_list, func=float, use_basename=False):
3737
"""
3838
If there is a list of files, order them numerically, not
3939
alphabetically and return the sorted list of files. Note that

seekr2/modules/common_converge.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
DEFAULT_SKIP = 0
2929

3030
# The threshold beneath which to skip plotting the convergence
31-
MIN_PLOT_NORM = 1e-8
31+
MIN_PLOT_NORM = 1e-18
3232

3333
# The interval between which to update the user on convergence script progress
3434
PROGRESS_UPDATE_INTERVAL = DEFAULT_NUM_POINTS // 10
@@ -394,8 +394,12 @@ def plot_scalar_conv(conv_values, conv_intervals, label, title, timestep_in_ns,
394394
ax : object
395395
matplotlib Axes object
396396
"""
397-
if not np.any(np.isfinite(conv_values)) or np.all(conv_values == 0):
398-
return None, None
397+
#if not np.any(np.isfinite(conv_values)) or np.all(conv_values == 0):
398+
# return None, None
399+
for i, conv_value in enumerate(conv_values):
400+
if not np.isfinite(conv_value) or conv_value == 0:
401+
conv_values[i] = np.NAN
402+
399403
fig, ax = plt.subplots()
400404
ax.plot(np.multiply(conv_intervals, timestep_in_ns), conv_values,
401405
linestyle="-", marker="o", markersize=1)
@@ -460,9 +464,11 @@ def plot_dict_conv(conv_dict, conv_intervals, label_base, unit, timestep_in_ns,
460464
for key in conv_dict:
461465
conv_values = conv_dict[key]
462466
if not np.all(np.isfinite(conv_values)):
467+
print("skipping key:", key, "because values aren't finite")
463468
continue
464469
if skip_null:
465470
if np.linalg.norm(conv_values) < MIN_PLOT_NORM:
471+
print("Skipping key:", key, "because values are too low")
466472
continue
467473
if isinstance(key, tuple):
468474
label = "$" + label_base + "_{" + "\\rightarrow".join(
@@ -473,7 +479,7 @@ def plot_dict_conv(conv_dict, conv_intervals, label_base, unit, timestep_in_ns,
473479
elif isinstance(key, int):
474480
label = "$" + label_base + "_{" + str(key) + "(" + unit + ")}$"
475481
title = "$" + label_base + "_{" + str(key) + "}$"
476-
name = label_base + "_{" + str(key) + "}"
482+
name = label_base + "_" + str(key) + ""
477483
else:
478484
raise Exception("key type not implemented: {}".format(type(key)))
479485

@@ -490,6 +496,7 @@ def plot_dict_conv(conv_dict, conv_intervals, label_base, unit, timestep_in_ns,
490496
ax_list.append(ax)
491497
title_list.append(title)
492498
name_list.append(name)
499+
493500
return fig_list, ax_list, title_list, name_list
494501

495502
def calc_transition_steps(model, data_sample):

seekr2/modules/common_prepare.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1055,6 +1055,7 @@ def modify_model(old_model, new_model, root_directory, force_overwrite=False):
10551055
break
10561056
if not alpha_paired:
10571057
new_anchors_to_create.append(alpha)
1058+
10581059
for beta, anchor2 in enumerate(old_model.anchors):
10591060
if anchor2.bulkstate:
10601061
continue
@@ -1065,7 +1066,7 @@ def modify_model(old_model, new_model, root_directory, force_overwrite=False):
10651066
break
10661067
if not beta_paired:
10671068
old_anchors_to_delete.append(beta)
1068-
1069+
10691070
# Now check all the paired anchors to see if anyone's milestones
10701071
# have changed
10711072
old_anchors_with_changed_milestones = []

seekr2/modules/runner_openmm.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,9 @@ def run_mmvt(self, traj_filename):
522522
if trajectory_reporter_interval is not None:
523523
simulation.reporters.append(traj_reporter(
524524
traj_filename, trajectory_reporter_interval,
525-
enforcePeriodicBox=False))
525+
#enforcePeriodicBox=False))
526+
# Turning this on
527+
enforcePeriodicBox=None))
526528
if self.restart_checkpoint_interval is not None:
527529
assert trajectory_reporter_interval >= \
528530
self.restart_checkpoint_interval
@@ -609,7 +611,8 @@ def run_elber(self, traj_filename):
609611
and not self.umbrellas_already_exist_mode:
610612
umbrella_simulation.reporters.append(umbrella_traj_reporter(
611613
umbrella_traj_filename, umbrella_trajectory_reporter_interval,
612-
enforcePeriodicBox=False))
614+
#enforcePeriodicBox=False))
615+
enforcePeriodicBox=None))
613616
if calc_settings.num_umbrella_stage_steps \
614617
< umbrella_trajectory_reporter_interval:
615618
umbrella_trajectory_reporter_interval \
@@ -748,7 +751,8 @@ def run_elber(self, traj_filename):
748751
print("rev_traj_filename", rev_traj_filename)
749752
rev_simulation.reporters = [rev_traj_reporter(
750753
rev_traj_filename, rev_trajectory_reporter_interval,
751-
enforcePeriodicBox=False)]
754+
#enforcePeriodicBox=False)]
755+
enforcePeriodicBox=None)]
752756
if rev_energy_reporter_interval is not None:
753757
rev_simulation.reporters.append(
754758
self.sim_openmm.rev_energy_reporter(
@@ -806,7 +810,8 @@ def run_elber(self, traj_filename):
806810
"forward_%d.dcd" % crossing_counter)
807811
fwd_simulation.reporters = [fwd_traj_reporter(
808812
fwd_traj_filename, fwd_trajectory_reporter_interval,
809-
enforcePeriodicBox=False)]
813+
#enforcePeriodicBox=False)]
814+
enforcePeriodicBox=None)]
810815
if fwd_energy_reporter_interval is not None:
811816
fwd_simulation.reporters.append(
812817
self.sim_openmm.fwd_energy_reporter(

seekr2/tests/test_common_base.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ def test_order_files_numerically():
5050
for item1, item2 in zip(ordered_list, desired_list):
5151
assert item1==item2
5252

53+
string_list = ["/path/to/anchor_0.1/output0_0",
54+
"/path/to/anchor_0.2/output0_1"]
55+
56+
desired_list = string_list[:]
57+
random.shuffle(string_list)
58+
ordered_list = base.order_files_numerically(string_list)
59+
5360
return
5461

5562
def test_box_vectors():

0 commit comments

Comments
 (0)