Skip to content

Commit f6e0d6a

Browse files
committed
Seed perturbed coords from a system clone, not raw arrays.
1 parent e1cb792 commit f6e0d6a

2 files changed

Lines changed: 35 additions & 72 deletions

File tree

src/somd2/runner/_base.py

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -94,28 +94,6 @@ def __init__(self, system, config):
9494
_logger.error(msg)
9595
raise ValueError(msg)
9696

97-
# Make sure the coordinates property is linked.
98-
perturbed_system = _sr.morph.link_to_perturbed(
99-
self._config.perturbed_system
100-
)
101-
102-
# Store the positions.
103-
self._perturbed_positions = _sr.io.get_coords_array(perturbed_system)
104-
105-
# Store the box vectors.
106-
cell = self._config.perturbed_system.space().box_matrix()
107-
c0 = cell.column0()
108-
c1 = cell.column1()
109-
c2 = cell.column2()
110-
self._perturbed_box = (
111-
(c0.x().value(), c0.y().value(), c0.z().value()),
112-
(c1.x().value(), c1.y().value(), c1.z().value()),
113-
(c2.x().value(), c2.y().value(), c2.z().value()),
114-
)
115-
else:
116-
self._perturbed_positions = None
117-
self._perturbed_box = None
118-
11997
# Log the versions of somd2 and sire.
12098
from somd2 import (
12199
__version__,
@@ -523,6 +501,27 @@ def __init__(self, system, config):
523501
# Store the current system as a reference.
524502
self._reference_system = self._system.clone()
525503

504+
# Create a clone of the fully-prepared reference system with the
505+
# perturbed end-state coordinates and periodic space. This is done
506+
# after all system preparation so that the clone inherits the same
507+
# topology and properties. It is used to seed starting coordinates
508+
# for lambda > 0.5 replicas.
509+
if self._config.replica_exchange and self._config.perturbed_system is not None:
510+
from sire.legacy.IO import setCoordinates as _setCoordinates
511+
512+
pert_coords = _sr.io.get_coords_array(
513+
_sr.morph.link_to_perturbed(self._config.perturbed_system)
514+
)
515+
self._perturbed_system = _sr.system.System(
516+
_setCoordinates(self._system._system, pert_coords.tolist())
517+
)
518+
self._perturbed_system.set_space(self._config.perturbed_system.space())
519+
520+
# Link properties to the lambda = 0 end state.
521+
self._perturbed_system = _sr.morph.link_to_reference(self._perturbed_system)
522+
else:
523+
self._perturbed_system = None
524+
526525
# Check for a valid restart.
527526
if self._config.restart:
528527
if self._config.use_backup:

src/somd2/runner/_repex.py

Lines changed: 14 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,7 @@ def __init__(
5252
dynamics_kwargs,
5353
gcmc_kwargs=None,
5454
output_directory=None,
55-
perturbed_positions=None,
56-
perturbed_box=None,
55+
perturbed_system=None,
5756
):
5857
"""
5958
Constructor.
@@ -82,13 +81,9 @@ def __init__(
8281
output_directory: pathlib.Path
8382
The directory for simulation output.
8483
85-
perturbed_positions: numpy.ndarray
86-
The positions for the perturbed state. If None, then the perturbed state
87-
is not used.
88-
89-
perturbed_box: numpy.ndarray
90-
The box vectors for the perturbed state. If None, then the perturbed state
91-
is not used.
84+
perturbed_system: :class: `System <sire.system.System>`
85+
The perturbed end-state system used to seed starting coordinates for
86+
lambda > 0.5 replicas. If None, the perturbed state is not used.
9287
"""
9388

9489
# Warn if the number of replicas is not a multiple of the number of GPUs.
@@ -121,8 +116,7 @@ def __init__(
121116
dynamics_kwargs,
122117
gcmc_kwargs=gcmc_kwargs,
123118
output_directory=output_directory,
124-
perturbed_positions=perturbed_positions,
125-
perturbed_box=perturbed_box,
119+
perturbed_system=perturbed_system,
126120
)
127121

128122
def __setstate__(self, state):
@@ -173,8 +167,7 @@ def _create_dynamics(
173167
dynamics_kwargs,
174168
gcmc_kwargs=None,
175169
output_directory=None,
176-
perturbed_positions=None,
177-
perturbed_box=None,
170+
perturbed_system=None,
178171
):
179172
"""
180173
Create the dynamics objects.
@@ -203,13 +196,9 @@ def _create_dynamics(
203196
output_directory: pathlib.Path
204197
The directory for simulation output.
205198
206-
perturbed_positions: numpy.ndarray
207-
The positions for the perturbed state. If None, then the perturbed state
208-
is not used.
209-
210-
perturbed_box: numpy.ndarray
211-
The box vectors for the perturbed state. If None, then the perturbed state
212-
is not used.
199+
perturbed_system: :class: `System <sire.system.System>`
200+
The perturbed end-state system used to seed starting coordinates for
201+
lambda > 0.5 replicas. If None, the perturbed state is not used.
213202
"""
214203

215204
from math import floor
@@ -256,7 +245,10 @@ def _create_dynamics(
256245
# This is a restart, get the system for this replica.
257246
if isinstance(system, list):
258247
mols = system[i]
259-
# This is a new simulation.
248+
# This is a new simulation. For lambda > 0.5, use the perturbed
249+
# system to seed the starting coordinates and periodic space.
250+
elif perturbed_system is not None and lam > 0.5:
251+
mols = perturbed_system
260252
else:
261253
mols = system
262254

@@ -314,33 +306,6 @@ def _create_dynamics(
314306
_logger.error(msg)
315307
raise RuntimeError(msg) from e
316308

317-
# Update the box vectors and positions if the perturbed state is used.
318-
if (
319-
perturbed_positions is not None
320-
and perturbed_box is not None
321-
and lam > 0.5
322-
):
323-
from openmm.unit import angstrom
324-
325-
# Get the positions from the context.
326-
positions = (
327-
dynamics.context()
328-
.getState(getPositions=True)
329-
.getPositions(asNumpy=True)
330-
) / angstrom
331-
332-
# The positions array also contains the ghost water atoms that
333-
# were added during the GCMC setup. We need to make sure that
334-
# we copy these over to the perturbed positions array.
335-
diff = len(positions) - len(perturbed_positions)
336-
if diff != 0:
337-
perturbed_positions = _np.concatenate(
338-
[perturbed_positions, positions[-diff:]]
339-
)
340-
341-
dynamics.context().setPeriodicBoxVectors(*perturbed_box * angstrom)
342-
dynamics.context().setPositions(perturbed_positions * angstrom)
343-
344309
# Bind the GCMC sampler to the dynamics object. This allows the
345310
# dynamics object to reset the water state in its internal OpenMM
346311
# context following a crash recovery.
@@ -782,8 +747,7 @@ def __init__(self, system, config):
782747
self._num_gpus,
783748
dynamics_kwargs,
784749
gcmc_kwargs=self._gcmc_kwargs,
785-
perturbed_positions=self._perturbed_positions,
786-
perturbed_box=self._perturbed_box,
750+
perturbed_system=self._perturbed_system,
787751
output_directory=self._config.output_directory,
788752
)
789753
else:

0 commit comments

Comments
 (0)