Skip to content

Commit d7129d2

Browse files
committed
Optimise angles over repeat minimisations. [ref OpenBioSim/cresset#62]
1 parent d7aa325 commit d7129d2

2 files changed

Lines changed: 76 additions & 22 deletions

File tree

src/ghostly/_cli.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,17 @@ def run():
121121
required=False,
122122
)
123123

124+
parser.add_argument(
125+
"--num-optimise",
126+
type=int,
127+
help=(
128+
"The number of repeats to use when optimising the angle terms involving "
129+
"ghost atoms for non-planar triple junctions."
130+
),
131+
default=10,
132+
required=False,
133+
)
134+
124135
parser.add_argument(
125136
"--output-prefix",
126137
type=str,
@@ -257,7 +268,13 @@ def run():
257268
try:
258269
if args.system is None:
259270
system = merged.toSystem()._sire_object
260-
system = modify(system, k_hard.value(), k_soft.value(), args.optimise_angles)
271+
system = modify(
272+
system,
273+
k_hard.value(),
274+
k_soft.value(),
275+
args.optimise_angles,
276+
args.num_optimise,
277+
)
261278
except Exception as e:
262279
logger.error(
263280
f"An error occurred while applying the ghost atom modifications: {e}"

src/ghostly/_ghostly.py

Lines changed: 58 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
del _platform
4545

4646

47-
def modify(system, k_hard=100, k_soft=5, optimise_angles=True):
47+
def modify(system, k_hard=100, k_soft=5, optimise_angles=True, num_optimise=10):
4848
"""
4949
Apply Boresch modifications to ghost atom bonded terms to avoid non-physical
5050
coupling between the ghost atoms and the physical region.
@@ -67,6 +67,11 @@ def modify(system, k_hard=100, k_soft=5, optimise_angles=True):
6767
Whether to optimise the equilibrium value of the angle terms involving
6868
ghost atoms for non-planar triple junctions.
6969
70+
num_optimise : int, optional
71+
The number of repeats to average over when optimising the equilibrium
72+
value of the angle terms involving ghost atoms for non-planar triple
73+
junctions.
74+
7075
Returns
7176
-------
7277
@@ -217,6 +222,7 @@ def modify(system, k_hard=100, k_soft=5, optimise_angles=True):
217222
k_hard=k_hard,
218223
k_soft=k_soft,
219224
optimise_angles=optimise_angles,
225+
num_optimise=num_optimise,
220226
)
221227

222228
# Higher order junction.
@@ -229,6 +235,7 @@ def modify(system, k_hard=100, k_soft=5, optimise_angles=True):
229235
k_hard=k_hard,
230236
k_soft=k_soft,
231237
optimise_angles=optimise_angles,
238+
num_optimise=num_optimise,
232239
)
233240

234241
# Now lambda = 1.
@@ -252,6 +259,7 @@ def modify(system, k_hard=100, k_soft=5, optimise_angles=True):
252259
k_hard=k_hard,
253260
k_soft=k_soft,
254261
optimise_angles=optimise_angles,
262+
num_optimise=num_optimise,
255263
is_lambda1=True,
256264
)
257265

@@ -265,6 +273,7 @@ def modify(system, k_hard=100, k_soft=5, optimise_angles=True):
265273
k_hard=k_hard,
266274
k_soft=k_soft,
267275
optimise_angles=optimise_angles,
276+
num_optimise=num_optimise,
268277
is_lambda1=True,
269278
)
270279

@@ -597,6 +606,7 @@ def _triple(
597606
k_hard=100,
598607
k_soft=5,
599608
optimise_angles=True,
609+
num_optimise=10,
600610
is_lambda1=False,
601611
):
602612
r"""
@@ -639,6 +649,10 @@ def _triple(
639649
Whether to optimise the equilibrium value of the angle terms involving
640650
ghost atoms for non-planar triple junctions.
641651
652+
num_optimise : int, optional
653+
The number of repeats to use when optimising the angle terms involving
654+
ghost atoms for non-planar triple junctions.
655+
642656
is_lambda1 : bool, optional
643657
Whether the junction is at lambda = 1.
644658
@@ -870,32 +884,47 @@ def _triple(
870884
.commit()
871885
)
872886

873-
# Optimise the equilibrium value of theta0 for the softened angle term.
887+
# Optimise the equilibrium value of theta0 for the softened angle terms.
874888
if optimise_angles:
875889
_logger.debug(" Optimising equilibrium values for softened angles.")
876890

877891
import sire.morph as _morph
878892
from sire.units import radian as _radian
879893

880-
# Minimise the molecule.
881-
min_mol = _morph.link_to_reference(mol)
882-
minimiser = min_mol.minimisation(
883-
lambda_value=1.0 if is_lambda1 else 0.0,
884-
constraint="none",
885-
platform="cpu",
886-
)
887-
minimiser.run()
888-
889-
# Commit the changes.
890-
min_mol = minimiser.commit()
891-
892-
# Get the equilibrium angle values.
894+
# Initialise the equilibrium angle values.
893895
theta0s = {}
894896
for idx in angle_idxs:
895-
try:
896-
theta0s[idx] = min_mol.angles(*idx).sizes()[0].to(_radian)
897-
except:
898-
raise ValueError(f"Could not find optimised angle term: {idx}")
897+
theta0s[idx] = []
898+
899+
# Perform multiple minimisations to get an average for the theta0 values.
900+
for _ in range(num_optimise):
901+
# Minimise the molecule.
902+
min_mol = _morph.link_to_reference(mol)
903+
minimiser = min_mol.minimisation(
904+
lambda_value=1.0 if is_lambda1 else 0.0,
905+
constraint="none",
906+
platform="cpu",
907+
)
908+
minimiser.run()
909+
910+
# Commit the changes.
911+
min_mol = minimiser.commit()
912+
913+
# Get the equilibrium angle values.
914+
for idx in angle_idxs:
915+
try:
916+
theta0s[idx].append(min_mol.angles(*idx).sizes()[0].to(_radian))
917+
except:
918+
raise ValueError(f"Could not find optimised angle term: {idx}")
919+
920+
# Compute the mean and standard error.
921+
import numpy as _np
922+
923+
theta0_means = {}
924+
theta0_stds = {}
925+
for idx in theta0s:
926+
theta0_means[idx] = _np.mean(theta0s[idx])
927+
theta0_stds[idx] = _np.std(theta0s[idx]) / _np.sqrt(len(theta0s[idx]))
899928

900929
# Get the existing angles.
901930
angles = mol.property("angle" + suffix)
@@ -913,7 +942,8 @@ def _triple(
913942
# This is the softened angle term.
914943
if idx in angle_idxs:
915944
# Get the optimised equilibrium angle.
916-
theta0 = theta0s[idx]
945+
theta0 = theta0_means[idx]
946+
std = theta0_stds[idx]
917947

918948
# Create the new angle function.
919949
amber_angle = _SireMM.AmberAngle(k_soft, theta0)
@@ -926,7 +956,7 @@ def _triple(
926956

927957
_logger.debug(
928958
f" Optimising angle: [{idx0.value()}-{idx1.value()}-{idx2.value()}], "
929-
f"{p.function()} --> {expression}"
959+
f"{p.function()} --> {expression} (std err: {std:.3f} radian)"
930960
)
931961

932962
else:
@@ -952,6 +982,7 @@ def _higher(
952982
k_hard=100,
953983
k_soft=5,
954984
optimise_angles=True,
985+
num_optimise=10,
955986
is_lambda1=False,
956987
):
957988
r"""
@@ -984,6 +1015,10 @@ def _higher(
9841015
Whether to optimise the equilibrium value of the angle terms involving
9851016
ghost atoms for non-planar triple junctions.
9861017
1018+
num_optimise : int, optional
1019+
The number of repeats to use when optimising the angle terms involving
1020+
ghost atoms for non-planar triple junctions.
1021+
9871022
is_lambda1 : bool, optional
9881023
Whether the junction is at lambda = 1.
9891024
@@ -1072,6 +1107,8 @@ def _higher(
10721107
k_hard=k_hard,
10731108
k_soft=k_soft,
10741109
optimise_angles=optimise_angles,
1110+
num_optimise=num_optimise,
1111+
is_lambda1=is_lambda1,
10751112
)
10761113

10771114

0 commit comments

Comments
 (0)