39 const QMMolecule& mol,
double bond_length_angstrom) {
48 for (
const QMAtom& atom : mol) {
54 std::vector<Index> new_atom_parent_ids(mol.
size(), -1);
58 for (
const QMAtom& atom : mol) {
59 if (!atom.hasExternalBond()) {
67 Eigen::Vector3d new_pos =
68 atom.getPos() + bond_length_bohr * atom.getExternalBondDirection();
69 QMAtom new_h(new_index,
"H", new_pos);
96 result[atom.getId()].AddBondedPartner(new_index);
98 new_atom_parent_ids.push_back(atom.getId());
106 Index n_original_atoms,
119 OpenBabel::OBMol obmol;
122 for (
const QMAtom& atom : mol) {
123 OpenBabel::OBAtom* obatom = obmol.NewAtom();
124 obatom->SetAtomicNum(
int(elements.
getNucCrg(atom.getElement())));
130 obatom->SetVector(pos_angstrom.x(), pos_angstrom.y(), pos_angstrom.z());
140 for (
const QMAtom& atom : mol) {
141 const Index* partners = atom.getBondedPartnerIds();
143 Index partner_id = partners[i];
150 if (partner_id == -1 || partner_id <= atom.getId()) {
153 obmol.AddBond(
int(atom.getId()) + 1,
int(partner_id) + 1, 1);
157 obmol.PerceiveBondOrders();
159 OpenBabel::OBForceField* pFF =
160 OpenBabel::OBForceField::FindForceField(
"MMFF94");
161 if (pFF ==
nullptr || !pFF->Setup(obmol)) {
168 pFF = OpenBabel::OBForceField::FindForceField(
"UFF");
169 if (pFF ==
nullptr || !pFF->Setup(obmol)) {
170 throw std::runtime_error(
171 "FragmentSaturator::RelaxNewAtoms: could not set up either "
172 "MMFF94 or UFF for this fragment.");
185 OpenBabel::OBFFConstraints constraints;
186 for (
Index i = 0; i < n_original_atoms; i++) {
187 constraints.AddAtomConstraint(
int(i) + 1);
189 if (!pFF->Setup(obmol, constraints)) {
190 throw std::runtime_error(
191 "FragmentSaturator::RelaxNewAtoms: could not set up "
224 pFF->ConjugateGradientsInitialize(
int(n_steps), econv);
225 double e_prev = pFF->Energy();
230 const Index check_interval = 10;
231 bool still_running =
true;
233 bool converged =
false;
234 for (; step < n_steps && still_running; step += check_interval) {
235 Index steps_this_round = std::min(check_interval, n_steps - step);
236 still_running = pFF->ConjugateGradientsTakeNSteps(
int(steps_this_round));
237 double e_now = pFF->Energy();
238 if (std::abs(e_now - e_prev) < econv) {
240 step += steps_this_round;
254 std::cerr <<
"[RelaxNewAtoms] took " << step <<
"/" << n_steps
255 <<
" conjugate-gradient steps ("
256 << (converged ?
"converged early"
257 : (still_running ?
"exhausted full budget"
258 :
"OpenBabel itself stopped"))
260 pFF->GetCoordinates(obmol);
269 for (
const QMAtom& atom : mol) {
270 OpenBabel::OBAtom* obatom = obmol.GetAtom(
int(idx) + 1);
271 Eigen::Vector3d pos_bohr =
272 Eigen::Vector3d(obatom->GetX(), obatom->GetY(), obatom->GetZ()) *
274 QMAtom new_atom(atom.getId(), atom.getElement(), pos_bohr);