Skip to content

Commit e99fbdf

Browse files
authored
Merge pull request #163 from OpenBioSim/feature_minimise
Feature minimise
2 parents 5867df0 + b61b9f7 commit e99fbdf

22 files changed

+1503
-593
lines changed

corelib/src/libs/SireBase/progressbar.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -1158,6 +1158,11 @@ static void check_raise_interrupt()
11581158
}
11591159
}
11601160

1161+
void ProgressBar::silentTick()
1162+
{
1163+
check_raise_interrupt();
1164+
}
1165+
11611166
void ProgressBar::tick()
11621167
{
11631168
check_raise_interrupt();

corelib/src/libs/SireBase/progressbar.h

+1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ namespace SireBase
8686

8787
void tick();
8888
void tick(const QString &text);
89+
void silentTick();
8990

9091
void setProgress(quint32 value);
9192
void setProgress(quint32 value, const QString &text);

corelib/src/libs/SireCAS/lambdaschedule.cpp

+57-18
Original file line numberDiff line numberDiff line change
@@ -238,10 +238,12 @@ QString LambdaSchedule::toString() const
238238
auto keys = this->stage_equations[i].keys();
239239
std::sort(keys.begin(), keys.end());
240240

241-
for (auto lever : keys)
241+
for (const auto &lever : keys)
242242
{
243+
auto output_name = lever;
244+
output_name.replace("*::", "");
243245
lines.append(QString(" %1: %2")
244-
.arg(lever.replace("*::", ""))
246+
.arg(output_name)
245247
.arg(this->stage_equations[i][lever].toOpenMMString()));
246248
}
247249
}
@@ -956,20 +958,35 @@ SireCAS::Expression LambdaSchedule::_getEquation(int stage,
956958
CODELOC);
957959

958960
const auto default_lever = _get_lever_name("*", lever);
961+
const auto default_force = _get_lever_name(force, "*");
962+
const auto lever_name = _get_lever_name(force, lever);
959963

960-
if (force == "*")
964+
const auto equations = this->stage_equations[stage];
965+
966+
// search from most specific to least specific
967+
auto it = equations.find(lever_name);
968+
969+
if (it != equations.end())
961970
{
962-
return this->stage_equations[stage].value(
963-
default_lever, this->default_equations[stage]);
971+
return it.value();
964972
}
965-
else
973+
974+
it = equations.find(default_force);
975+
976+
if (it != equations.end())
977+
{
978+
return it.value();
979+
}
980+
981+
it = equations.find(default_lever);
982+
983+
if (it != equations.end())
966984
{
967-
return this->stage_equations[stage].value(
968-
_get_lever_name(force, lever),
969-
this->stage_equations[stage].value(
970-
default_lever,
971-
this->default_equations[stage]));
985+
return it.value();
972986
}
987+
988+
// we don't have any match, so return the default equation for this stage
989+
return this->default_equations[stage];
973990
}
974991

975992
/** Return the equation used to control the specified 'lever'
@@ -1142,15 +1159,33 @@ QHash<QString, QVector<double>> LambdaSchedule::getLeverValues(
11421159
QVector<double> values(lambda_values.count(), NAN);
11431160

11441161
QHash<QString, QVector<double>> lever_values;
1145-
lever_values.reserve(this->lever_names.count() + 1);
1162+
1163+
// get all of the lever / force combinations in use
1164+
QSet<QString> all_levers;
1165+
1166+
for (const auto &equations : this->stage_equations)
1167+
{
1168+
for (const auto &lever : equations.keys())
1169+
{
1170+
all_levers.insert(lever);
1171+
}
1172+
}
1173+
1174+
QStringList levers = all_levers.values();
1175+
std::sort(levers.begin(), levers.end());
1176+
1177+
lever_values.reserve(levers.count() + 2);
11461178

11471179
lever_values.insert("λ", values);
11481180

11491181
lever_values.insert("default", values);
11501182

1151-
for (const auto &lever_name : this->lever_names)
1183+
for (const auto &lever : levers)
11521184
{
1153-
lever_values.insert(lever_name, values);
1185+
if (lever.startsWith("*::"))
1186+
lever_values.insert(lever.mid(3), values);
1187+
else
1188+
lever_values.insert(lever, values);
11541189
}
11551190

11561191
if (this->nStages() == 0)
@@ -1174,12 +1209,16 @@ QHash<QString, QVector<double>> LambdaSchedule::getLeverValues(
11741209
const auto equation = this->default_equations[stage];
11751210
lever_values["default"][i] = equation(input_values);
11761211

1177-
for (const auto &lever_name : lever_names)
1212+
for (const auto &lever : levers)
11781213
{
1179-
const auto equation = this->stage_equations[stage].value(
1180-
lever_name, this->default_equations[stage]);
1214+
auto parts = lever.split("::");
1215+
1216+
const auto equation = this->_getEquation(stage, parts[0], parts[1]);
11811217

1182-
lever_values[lever_name][i] = equation(input_values);
1218+
if (lever.startsWith("*::"))
1219+
lever_values[lever.mid(3)][i] = equation(input_values);
1220+
else
1221+
lever_values[lever][i] = equation(input_values);
11831222
}
11841223
}
11851224

doc/source/changelog.rst

+14
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,20 @@ organisation on `GitHub <https://github.com/openbiosim/sire>`__.
110110

111111
* Fixed compile error using Python 3.12. This fixes issue #147.
112112

113+
* Optimised the OpenMM minimisation code and making it more robust.
114+
This includes vectorising for Apple Silicon and adding more tests for
115+
convergence so that we can have more confidence that the structures
116+
output are sensible. Also made sure that optimised compilation (-O3) is
117+
used for all of the plugins (SireOpenMM, SireGemmi and SireRDKit).
118+
They were previously compiled with wrapper options (e.g. -Os).
119+
Minimisation now gives better progress updates, using a progress
120+
bar to show progress towards the maximum number of iterations.
121+
This has been reduced to 1500 by default. Also, if the minimisation
122+
fails to create a structure that obeys constraints on the first pass,
123+
then the minimisation is repeated, with the maximum number of
124+
iterations reset. If it fails again, then this structure, with
125+
constraints re-applied, is returned.
126+
113127
* Please add an item to this changelog when you create your PR
114128

115129
`2023.5.1 <https://github.com/openbiosim/sire/compare/2023.5.0...2023.5.1>`__ - January 2024

src/sire/mol/_dynamics.py

+102-5
Original file line numberDiff line numberDiff line change
@@ -564,20 +564,78 @@ def step(self, num_steps: int = 1):
564564

565565
self._omm_mols.getIntegrator().step(num_steps)
566566

567-
def run_minimisation(self, max_iterations: int):
567+
def get_minimisation_log(self):
568+
"""
569+
Return the log from the last minimisation
570+
"""
571+
if self.is_null():
572+
return None
573+
else:
574+
try:
575+
return self._minimisation_log
576+
except Exception:
577+
return None
578+
579+
def run_minimisation(
580+
self,
581+
max_iterations: int = 10000,
582+
tolerance: float = 10.0,
583+
max_restarts: int = 10,
584+
max_ratchets: int = 20,
585+
ratchet_frequency: int = 500,
586+
starting_k: float = 100.0,
587+
ratchet_scale: float = 2.0,
588+
max_constraint_error: float = 0.001,
589+
):
568590
"""
569591
Internal method that runs minimisation on the molecules.
570592
593+
If the system is constrained, then a ratcheting algorithm is used.
594+
The constraints are replaced by harmonic restraints with an
595+
force constant based on `tolerance` and `starting_k`. Minimisation
596+
is performed, with the actual constrained bond lengths checked
597+
whenever minimisation converges, or when ratchet_frequency steps
598+
have completed (whichever is sooner). The force constant of
599+
the restraints is ratcheted up by `ratchet_scale`, and minimisation
600+
continues until there is no large change in energy or the maximum
601+
number of ratchets has been reached. In addition, at each ratchet,
602+
the actual bond lengths of constrained bonds are compared against
603+
the constrained values. If these have drifted too far away from
604+
the constrained values, then the minimisation is restarted,
605+
going back to the starting conformation and starting minimisation
606+
at one higher ratchet level. This will repeat a maximum of
607+
`max_restarts` times.
608+
609+
If a stable structure cannot be reached, then an exception
610+
will be raised.
611+
571612
Parameters:
572613
573614
- max_iterations (int): The maximum number of iterations to run
615+
- tolerance (float): The tolerance to use for the minimisation
616+
- max_restarts (int): The maximum number of restarts before giving up
617+
- max_ratchets (int): The maximum number of ratchets before giving up
618+
- ratchet_frequency (int): The maximum number of steps between ratchets
619+
- starting_k (float): The starting value of k for the minimisation
620+
- ratchet_scale (float): The amount to scale k at each ratchet
621+
- max_constraint_error (float): The maximum error in the constraint in nm
574622
"""
575623
from ..legacy.Convert import minimise_openmm_context
576624

577625
if max_iterations <= 0:
578626
max_iterations = 0
579627

580-
minimise_openmm_context(self._omm_mols, max_iterations=max_iterations)
628+
self._minimisation_log = minimise_openmm_context(
629+
self._omm_mols,
630+
tolerance=tolerance,
631+
max_iterations=max_iterations,
632+
max_restarts=max_restarts,
633+
max_ratchets=max_ratchets,
634+
ratchet_frequency=ratchet_frequency,
635+
starting_k=starting_k,
636+
ratchet_scale=ratchet_scale,
637+
max_constraint_error=max_constraint_error,
638+
)
581639

582640
def _rebuild_and_minimise(self):
583641
if self.is_null():
@@ -600,7 +658,7 @@ def _rebuild_and_minimise(self):
600658

601659
self._omm_mols = to(self._sire_mols, "openmm", map=self._map)
602660

603-
self.run_minimisation(max_iterations=10000)
661+
self.run_minimisation()
604662

605663
def run(
606664
self,
@@ -1032,18 +1090,57 @@ def __repr__(self):
10321090
def minimise(
10331091
self,
10341092
max_iterations: int = 10000,
1093+
tolerance: float = 10.0,
1094+
max_restarts: int = 10,
1095+
max_ratchets: int = 20,
1096+
ratchet_frequency: int = 500,
1097+
starting_k: float = 100.0,
1098+
ratchet_scale: float = 2.0,
1099+
max_constraint_error: float = 0.001,
10351100
):
10361101
"""
1037-
Perform minimisation on the molecules, running a maximum
1038-
of max_iterations iterations.
1102+
Internal method that runs minimisation on the molecules.
1103+
1104+
If the system is constrained, then a ratcheting algorithm is used.
1105+
The constraints are replaced by harmonic restraints with an
1106+
force constant based on `tolerance` and `starting_k`. Minimisation
1107+
is performed, with the actual constrained bond lengths checked
1108+
whenever minimisation converges, or when ratchet_frequency steps
1109+
have completed (whichever is sooner). The force constant of
1110+
the restraints is ratcheted up by `ratchet_scale`, and minimisation
1111+
continues until there is no large change in energy or the maximum
1112+
number of ratchets has been reached. In addition, at each ratchet,
1113+
the actual bond lengths of constrained bonds are compared against
1114+
the constrained values. If these have drifted too far away from
1115+
the constrained values, then the minimisation is restarted,
1116+
going back to the starting conformation and starting minimisation
1117+
at one higher ratchet level. This will repeat a maximum of
1118+
`max_restarts` times.
1119+
1120+
If a stable structure cannot be reached, then an exception
1121+
will be raised.
10391122
10401123
Parameters:
10411124
10421125
- max_iterations (int): The maximum number of iterations to run
1126+
- tolerance (float): The tolerance to use for the minimisation
1127+
- max_restarts (int): The maximum number of restarts before giving up
1128+
- max_ratchets (int): The maximum number of ratchets before giving up
1129+
- ratchet_frequency (int): The maximum number of steps between ratchets
1130+
- starting_k (float): The starting value of k for the minimisation
1131+
- ratchet_scale (float): The amount to scale k at each ratchet
1132+
- max_constraint_error (float): The maximum error in the constraints in nm
10431133
"""
10441134
if not self._d.is_null():
10451135
self._d.run_minimisation(
10461136
max_iterations=max_iterations,
1137+
tolerance=tolerance,
1138+
max_restarts=max_restarts,
1139+
max_ratchets=max_ratchets,
1140+
ratchet_frequency=ratchet_frequency,
1141+
starting_k=starting_k,
1142+
ratchet_scale=ratchet_scale,
1143+
max_constraint_error=max_constraint_error,
10471144
)
10481145

10491146
return self

0 commit comments

Comments
 (0)