Molassembler  3.0.0
Molecule graph and conformer library
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
EigenRefinement.h
Go to the documentation of this file.
1 
8 #ifndef INCLUDE_MOLASSEMBLER_DG_EIGEN_REFINEMENT_PROBLEM_H
9 #define INCLUDE_MOLASSEMBLER_DG_EIGEN_REFINEMENT_PROBLEM_H
10 
11 #include <Eigen/Dense>
12 
14 
15 namespace Scine {
16 namespace Molassembler {
17 namespace DistanceGeometry {
18 
27 template<unsigned dimensionality, typename FloatType, bool SIMD>
29  static_assert(
30  dimensionality == 3 || dimensionality == 4,
31  "EigenRefinementProblem is only suitable for three or four dimensions"
32  );
33 
34 public:
38  using VectorType = Eigen::Matrix<FloatType, Eigen::Dynamic, 1>;
39 
41  using ThreeDimensionalVector = Eigen::Matrix<FloatType, 3, 1>;
43  using FullDimensionalVector = Eigen::Matrix<FloatType, dimensionality, 1>;
44 
46  using ThreeDimensionalMatrixType = Eigen::Matrix<FloatType, 3, Eigen::Dynamic>;
48  using FullDimensionalMatrixType = Eigen::Matrix<FloatType, dimensionality, Eigen::Dynamic>;
50  using FloatingPointType = FloatType;
51 
54  void distanceTerm(unsigned /* i */, unsigned /* j */, double /* value */) {}
55  void chiralTerm(const ChiralConstraint::SiteSequence& /* sites */, double /* value */) {}
56  void dihedralTerm(const DihedralConstraint::SiteSequence& /* sites */, double /* value */) {}
57  void fourthDimensionTerm(unsigned /* i */, double /* value */) {}
58  };
60 
63 
67  static std::string name() {
68  std::string name = "Eigen";
69 
70  name += "RefinementProblem<dimensionality=";
71  name += std::to_string(dimensionality);
72  name += ", ";
73 
74  if(std::is_same<FloatType, float>::value) {
75  name += "float";
76  } else {
77  name += "double";
78  }
79 
80  if(SIMD) {
81  name += ", SIMD=true>";
82  } else {
83  name += ", SIMD=false>";
84  }
85 
86  return name;
87  }
88 
94  const VectorType& positions,
95  const AtomIndex index
96  ) {
97  return positions.template segment<dimensionality>(dimensionality * index);
98  }
99 
105  const VectorType& positions,
106  const AtomIndex index
107  ) {
108  return positions.template segment<3>(dimensionality * index);
109  }
110 
117  const VectorType& positions,
118  const ChiralConstraint::AtomListType& atomList
119  ) {
120  if(atomList.size() == 1) {
121  return getPosition(positions, atomList.front());
122  }
123 
124  FullDimensionalVector sum = FullDimensionalVector::Zero();
125  for(const AtomIndex i : atomList) {
126  sum += positions.template segment<dimensionality>(dimensionality * i);
127  }
128 
129  return sum / atomList.size();
130  }
131 
138  const VectorType& positions,
139  const ChiralConstraint::AtomListType& atomList
140  ) {
141  if(atomList.size() == 1) {
142  return getPosition3D(positions, atomList.front());
143  }
144 
145  ThreeDimensionalVector sum = ThreeDimensionalVector::Zero();
146  for(const AtomIndex i : atomList) {
147  sum += positions.template segment<3>(dimensionality * i);
148  }
149 
150  return sum / atomList.size();
151  }
153 
169  std::vector<ChiralConstraint> chiralConstraints;
171  std::vector<DihedralConstraint> dihedralConstraints;
175  bool dihedralTerms = false;
177 
180  mutable double proportionChiralConstraintsCorrectSign = 0.0;
182 
186  const Eigen::MatrixXd& squaredBounds,
187  std::vector<ChiralConstraint> passChiralConstraints,
188  std::vector<DihedralConstraint> passDihedralConstraints
189  ) : chiralConstraints(std::move(passChiralConstraints)),
190  dihedralConstraints(std::move(passDihedralConstraints))
191  {
192  const unsigned N = squaredBounds.cols();
193  const unsigned strictlyUpperTriangularElements = N * (N - 1) / 2;
194 
195  // Lineize upper distance bounds squared
196  upperDistanceBoundsSquared.resize(strictlyUpperTriangularElements);
197  lowerDistanceBoundsSquared.resize(strictlyUpperTriangularElements);
198  for(unsigned linearIndex = 0, i = 0; i < N; ++i) {
199  for(unsigned j = i + 1; j < N; ++j, ++linearIndex) {
200  upperDistanceBoundsSquared(linearIndex) = squaredBounds(i, j);
201  lowerDistanceBoundsSquared(linearIndex) = squaredBounds(j, i);
202  }
203  }
204 
205  // Vectorize chiral constraint bounds
206  const unsigned C = chiralConstraints.size();
207  chiralUpperConstraints.resize(C);
208  chiralLowerConstraints.resize(C);
209  for(unsigned i = 0; i < C; ++i) {
210  const ChiralConstraint& constraint = chiralConstraints[i];
211  chiralUpperConstraints(i) = constraint.upper;
212  chiralLowerConstraints(i) = constraint.lower;
213  }
214 
215  // Vectorize dihedral constraint bounds sum halves and diff halves
216  const unsigned D = dihedralConstraints.size();
219  for(unsigned i = 0; i < D; ++i) {
220  const DihedralConstraint& constraint = dihedralConstraints[i];
221  dihedralConstraintSumsHalved(i) = (constraint.upper + constraint.lower) / 2;
222  dihedralConstraintDiffsHalved(i) = (constraint.upper - constraint.lower) / 2;
223  }
224  }
226 
229 
234  const VectorType& positions,
235  FloatType& error,
236  Eigen::Ref<VectorType> gradient
237  ) const {
238  // Delegate to SIMD or non-SIMD implementation
239  distanceContributionsImpl(positions, error, gradient, DefaultTermVisitor {});
240  }
241 
247  const VectorType& positions,
248  FloatType& error,
249  Eigen::Ref<VectorType> gradient
250  ) const {
251  chiralContributionsImpl(positions, error, gradient, DefaultTermVisitor {});
252  }
253 
259  const VectorType& positions,
260  FloatType& error,
261  Eigen::Ref<VectorType> gradient
262  ) const {
263  if(dihedralTerms) {
264  dihedralContributionsImpl(positions, error, gradient, DefaultTermVisitor {});
265  }
266  }
267 
269  const VectorType& positions,
270  FloatType& error,
271  Eigen::Ref<VectorType> gradient
272  ) const {
273  fourthDimensionContributionsImpl(positions, error, gradient, DefaultTermVisitor {});
274  }
276 
285  void operator() (const VectorType& parameters, FloatType& value, Eigen::Ref<VectorType> gradient) const {
286  assert(parameters.size() == gradient.size());
287  assert(parameters.size() % dimensionality == 0);
288 
289  value = 0;
290  gradient.setZero();
291 
292  fourthDimensionContributions(parameters, value, gradient);
293  dihedralContributions(parameters, value, gradient);
294  distanceContributions(parameters, value, gradient);
295  chiralContributions(parameters, value, gradient);
296  }
297 
304  unsigned nonZeroChiralConstraints = 0;
305  unsigned incorrectNonZeroChiralConstraints = 0;
306 
307  for(const auto& constraint : chiralConstraints) {
308  /* Make sure that chiral constraints meant to cause coplanarity of
309  * four indices aren't counted here - Their volume bounds are
310  * usually so tight that they might be slightly outside in any given
311  * refinement step. If they are outside their target values by a large
312  * amount, that is caught by finalStructureAcceptable anyway.
313  */
314  if(!constraint.targetVolumeIsZero()) {
315  nonZeroChiralConstraints += 1;
316 
317  const ThreeDimensionalVector delta = getAveragePosition3D(positions, constraint.sites[3]);
318 
319  const double volume = (
320  getAveragePosition3D(positions, constraint.sites[0]) - delta
321  ).dot(
322  (
323  getAveragePosition3D(positions, constraint.sites[1]) - delta
324  ).cross(
325  getAveragePosition3D(positions, constraint.sites[2]) - delta
326  )
327  );
328 
329  if( // can this be simplified? -> sign bit XOR?
330  (volume < 0 && constraint.lower > 0)
331  || (volume > 0 && constraint.lower < 0)
332  ) {
333  incorrectNonZeroChiralConstraints += 1;
334  }
335  }
336  }
337 
338  if(nonZeroChiralConstraints == 0) {
339  proportionChiralConstraintsCorrectSign = 1.0;
340  } else {
341  proportionChiralConstraintsCorrectSign = static_cast<double>(
342  nonZeroChiralConstraints - incorrectNonZeroChiralConstraints
343  ) / nonZeroChiralConstraints;
344  }
345 
346  return proportionChiralConstraintsCorrectSign;
347  }
348 
349  template<typename Visitor>
350  auto visitTerms(const VectorType& positions, Visitor&& visitor) const {
351  FloatType dummyValue = 0;
352  VectorType dummyGradient;
353  dummyGradient.resize(positions.size());
354  dummyGradient.setZero();
355 
356  fourthDimensionContributionsImpl(positions, dummyValue, dummyGradient, visitor);
357  dihedralContributionsImpl(positions, dummyValue, dummyGradient, visitor);
358  distanceContributionsImpl(positions, dummyValue, dummyGradient, visitor);
359  chiralContributionsImpl(positions, dummyValue, dummyGradient, visitor);
360  }
361 
380  template<typename Visitor>
382  const DistanceBoundsMatrix& bounds,
383  const VectorType& positions,
384  Visitor&& visitor
385  ) const {
386  const unsigned N = positions.size() / dimensionality;
387 
388  // Distance bounds deviations
389  for(unsigned i = 0; i < N; ++i) {
390  for(unsigned j = i + 1; j < N; ++j) {
391  const double ijDistance = (
392  positions.template segment<dimensionality>(dimensionality * j)
393  - positions.template segment<dimensionality>(dimensionality * i)
394  ).norm();
395 
396  if(
397  ijDistance - bounds.upperBound(i, j) > visitor.deviationThreshold
398  || bounds.lowerBound(i, j) - ijDistance > visitor.deviationThreshold
399  ) {
400  visitor.distanceOverThreshold(i, j, ijDistance);
401  if(visitor.earlyExit) {
402  return visitor.value;
403  }
404  }
405  }
406  }
407 
408  // Check chiral bound deviations
409  for(const auto& constraint : chiralConstraints) {
410  const double volume = (
411  getAveragePosition3D(positions, constraint.sites[0])
412  - getAveragePosition3D(positions, constraint.sites[3])
413  ).dot(
414  (
415  getAveragePosition3D(positions, constraint.sites[1])
416  - getAveragePosition3D(positions, constraint.sites[3])
417  ).cross(
418  getAveragePosition3D(positions, constraint.sites[2])
419  - getAveragePosition3D(positions, constraint.sites[3])
420  )
421  );
422 
423  if(
424  volume - constraint.upper > visitor.deviationThreshold
425  || constraint.lower - volume > visitor.deviationThreshold
426  ) {
427  visitor.chiralOverThreshold(constraint, volume);
428  if(visitor.earlyExit) {
429  return visitor.value;
430  }
431  }
432  }
433 
434  // Check dihedral constraints
435  for(const auto& constraint : dihedralConstraints) {
436  const ThreeDimensionalVector alpha = getAveragePosition3D(positions, constraint.sites[0]);
437  const ThreeDimensionalVector beta = getAveragePosition3D(positions, constraint.sites[1]);
438  const ThreeDimensionalVector gamma = getAveragePosition3D(positions, constraint.sites[2]);
439  const ThreeDimensionalVector delta = getAveragePosition3D(positions, constraint.sites[3]);
440 
441  const ThreeDimensionalVector f = alpha - beta;
442  const ThreeDimensionalVector g = beta - gamma;
443  const ThreeDimensionalVector h = delta - gamma;
444 
445  const ThreeDimensionalVector a = f.cross(g);
446  const ThreeDimensionalVector b = h.cross(g);
447 
448  const double dihedral = std::atan2(
449  a.cross(b).dot(-g.normalized()),
450  a.dot(b)
451  );
452 
453  const double constraintSumHalved = (constraint.lower + constraint.upper) / 2;
454 
455  const double phi = [&]() {
456  if(dihedral < constraintSumHalved - M_PI) {
457  return dihedral + 2 * M_PI;
458  }
459 
460  if(dihedral > constraintSumHalved + M_PI) {
461  return dihedral - 2 * M_PI;
462  }
463 
464  return dihedral;
465  }();
466 
467  const double term = std::fabs(phi - constraintSumHalved) - (constraint.upper - constraint.lower) / 2;
468 
469  if(term > visitor.deviationThreshold) {
470  visitor.dihedralOverThreshold(constraint, dihedral, term);
471  if(visitor.earlyExit) {
472  return visitor.value;
473  }
474  }
475  }
476 
477  return visitor.value;
478  }
479 
480 private:
482 #if SIMD
483 
487  template<class Visitor>
489  const VectorType& positions,
490  FloatType& error,
491  Eigen::Ref<VectorType> gradient,
492  Visitor&& /* visitor */
493  ) const {
494  assert(positions.size() == gradient.size());
495  /* Using the squared distance bounds to avoid unneeded square-root calculations
496  *
497  * NOTE: positions are full-dimensional
498  *
499  * for each nonredundant pair of atoms (i < j)
500  * Calculate a squared vector distance:
501  * sq_distance = (position[j] - position[i]).array().square().sum()
502  *
503  * upper_term = (sq_distance / upperBoundSquared) - 1
504  *
505  * if upper_term > 0:
506  * error += upper_term.square()
507  *
508  * lower_term = (
509  * 2 * lowerBoundSquared / (lowerBoundSquared + sq_distance)
510  * ) - 1
511  *
512  * if lowerTerm > 0:
513  * error += lower_term.square()
514  *
515  * SIMDize:
516  * sq_distances = list of upper triangular square distance terms
517  * upper_bounds_sq = list of corresponding upper bounds (identical across runs, no need to gather multiple times)
518  * lower_bounds_sq = list of corresponding lower bounds (same as above)
519  *
520  * upper_terms = (sq_distances / upper_bounds_sq) - 1
521  * error += upper_terms.max(0.0).square().sum()
522  *
523  * lower_terms = (2 * lower_bounds_sq / (lower_bounds_sq + sq_distances)) - 1
524  * error += lower_terms.max(0.0).square().sum()
525  *
526  */
527 
528  // Calculate all full-dimensional N(N-1) / 2 position differences
529  const unsigned N = positions.size() / dimensionality;
530  const unsigned differencesCount = N * (N - 1) / 2;
531 
532  FullDimensionalMatrixType positionDifferences(dimensionality, differencesCount);
533  {
534  unsigned offset = 0;
535  for(unsigned i = 0; i < N - 1; ++i) {
536  auto iPosition = positions.template segment<dimensionality>(dimensionality * i);
537  for(unsigned j = i + 1; j < N; ++j) {
538  positionDifferences.col(offset) = (
539  iPosition
540  - positions.template segment<dimensionality>(dimensionality * j)
541  );
542  ++offset;
543  }
544  }
545  }
546 
547  // SIMD
548  const VectorType squareDistances = positionDifferences.colwise().squaredNorm();
549 
550  // SIMD
551  const VectorType upperTerms = squareDistances.array() / upperDistanceBoundsSquared.array() - 1;
552 
553 #ifndef NDEBUG
554  {
555  // Check correctness of results so far
556  unsigned offset = 0;
557  for(unsigned i = 0; i < N - 1; ++i) {
558  for(unsigned j = i + 1; j < N; ++j) {
559  const FullDimensionalVector positionDifference = (
560  positions.template segment<dimensionality>(dimensionality * i)
561  - positions.template segment<dimensionality>(dimensionality * j)
562  );
563 
564  assert(positionDifferences.col(offset) == positionDifference);
565 
566  const FloatType squareDistance = positionDifference.squaredNorm();
567 
568  assert(squareDistance == squareDistances(offset));
569 
570  // Upper term
571  const FloatType upperTerm = squareDistance / upperDistanceBoundsSquared(offset) - 1;
572 
573  assert(upperTerm == upperTerms(offset));
574 
575  ++offset;
576  }
577  }
578  }
579 #endif
580 
581  // Traverse upper terms and calculate gradients
582  for(unsigned iOffset = 0, i = 0; i < N - 1; ++i) {
583  const unsigned crossTerms = N - i - 1;
584  for(unsigned jOffset = 0, j = i + 1; j < N; ++j, ++jOffset) {
585  const FloatType upperTerm = upperTerms(iOffset + jOffset);
586  if(upperTerm > 0) {
587  error += upperTerm * upperTerm;
588 
589  /* This i-j combination has an upper value contribution
590  *
591  * calculate gradient and apply to i and j
592  */
593  const FullDimensionalVector f = (
594  4 * positionDifferences.col(iOffset + jOffset) * upperTerm
595  / upperDistanceBoundsSquared(iOffset + jOffset)
596  );
597 
598  // position difference is i - j, not j - i (!)
599  gradient.template segment<dimensionality>(dimensionality * i) += f;
600  gradient.template segment<dimensionality>(dimensionality * j) -= f;
601  } else {
602  /* This i-j combination MAYBE has a lower value contribution
603  */
604  const auto& lowerBoundSquared = lowerDistanceBoundsSquared(iOffset + jOffset);
605  const FloatType quotient = lowerBoundSquared + squareDistances(iOffset + jOffset);
606  const FloatType lowerTerm = 2 * lowerBoundSquared / quotient - 1;
607 
608  if(lowerTerm > 0) {
609  // Add it to the error
610  error += lowerTerm * lowerTerm;
611 
612  // Calculate gradient and apply
613  const FullDimensionalVector g = (
614  8 * lowerBoundSquared * positionDifferences.col(iOffset + jOffset) * lowerTerm / (
615  quotient * quotient
616  )
617  );
618 
619  // position difference is i - j, not j - i (!)
620  gradient.template segment<dimensionality>(dimensionality * i) -= g;
621  gradient.template segment<dimensionality>(dimensionality * j) += g;
622  }
623  }
624  }
625 
626  iOffset += crossTerms;
627  }
628  }
629 #else
630 
634  template<class Visitor>
636  const VectorType& positions,
637  FloatType& error,
638  Eigen::Ref<VectorType> gradient,
639  Visitor&& visitor
640  ) const {
641  assert(positions.size() == gradient.size());
642  const unsigned N = positions.size() / dimensionality;
643 
644  for(unsigned linearIndex = 0, i = 0; i < N - 1; ++i) {
645  for(unsigned j = i + 1; j < N; ++j, ++linearIndex) {
646  const FloatType lowerBoundSquared = lowerDistanceBoundsSquared(linearIndex);
647  const FloatType upperBoundSquared = upperDistanceBoundsSquared(linearIndex);
648  assert(lowerBoundSquared <= upperBoundSquared);
649 
650  // For both
651  const FullDimensionalVector positionDifference = (
652  positions.template segment<dimensionality>(dimensionality * i)
653  - positions.template segment<dimensionality>(dimensionality * j)
654  );
655 
656  const FloatType squareDistance = positionDifference.squaredNorm();
657 
658  // Upper term
659  const FloatType upperTerm = squareDistance / upperBoundSquared - 1;
660 
661  if(upperTerm > 0) {
662  const FloatType value = upperTerm * upperTerm;
663  error += value;
664  visitor.distanceTerm(i, j, value);
665 
666  const FullDimensionalVector f = 4 * positionDifference * upperTerm / upperBoundSquared;
667 
668  gradient.template segment<dimensionality>(dimensionality * i) += f;
669  gradient.template segment<dimensionality>(dimensionality * j) -= f;
670  } else {
671  // Lower term is only possible if the upper term does not contribute
672  const FloatType quotient = lowerBoundSquared + squareDistance;
673  const FloatType lowerTerm = 2 * lowerBoundSquared / quotient - 1;
674 
675  if(lowerTerm > 0) {
676  const FloatType value = lowerTerm * lowerTerm;
677  error += value;
678  visitor.distanceTerm(i, j, value);
679 
680  const FullDimensionalVector g = 8 * lowerBoundSquared * positionDifference * lowerTerm / (
681  quotient * quotient
682  );
683 
684  /* We use -= because the lower term needs the position vector
685  * difference (j - i), so we reuse positionDifference and just subtract
686  * from the gradient instead of adding to it
687  */
688  gradient.template segment<dimensionality>(dimensionality * i) -= g;
689  gradient.template segment<dimensionality>(dimensionality * j) += g;
690  } else {
691  visitor.distanceTerm(i, j, 0.0);
692  }
693  }
694  }
695  }
696  }
697 #endif
698 
702  template<typename Visitor>
704  const VectorType& positions,
705  FloatType& error,
706  Eigen::Ref<VectorType> gradient,
707  Visitor&& visitor
708  ) const {
709  unsigned nonZeroChiralConstraints = 0;
710  unsigned incorrectNonZeroChiralConstraints = 0;
711 
712  for(const auto& constraint : chiralConstraints) {
713  const ThreeDimensionalVector alpha = getAveragePosition3D(positions, constraint.sites[0]);
714  const ThreeDimensionalVector beta = getAveragePosition3D(positions, constraint.sites[1]);
715  const ThreeDimensionalVector gamma = getAveragePosition3D(positions, constraint.sites[2]);
716  const ThreeDimensionalVector delta = getAveragePosition3D(positions, constraint.sites[3]);
717 
718  const ThreeDimensionalVector alphaMinusDelta = alpha - delta;
719  const ThreeDimensionalVector betaMinusDelta = beta - delta;
720  const ThreeDimensionalVector gammaMinusDelta = gamma - delta;
721 
722  const FloatType volume = (alphaMinusDelta).dot(
723  betaMinusDelta.cross(gammaMinusDelta)
724  );
725 
726  if(!constraint.targetVolumeIsZero()) {
727  ++nonZeroChiralConstraints;
728  if(
729  ( volume < 0 && constraint.lower > 0)
730  || (volume > 0 && constraint.lower < 0)
731  ) {
732  ++incorrectNonZeroChiralConstraints;
733  }
734  }
735 
736  // Upper bound term
737  const FloatType upperTerm = static_cast<FloatType>(constraint.weight) * (volume - static_cast<FloatType>(constraint.upper));
738  if(upperTerm > 0) {
739  const FloatType value = upperTerm * upperTerm;
740  error += value;
741  visitor.chiralTerm(constraint.sites, value);
742  }
743 
744  // Lower bound term
745  const FloatType lowerTerm = static_cast<FloatType>(constraint.weight) * (static_cast<FloatType>(constraint.lower) - volume);
746  if(lowerTerm > 0) {
747  const FloatType value = lowerTerm * lowerTerm;
748  error += value;
749  visitor.chiralTerm(constraint.sites, value);
750  }
751 
752  const FloatType factor = 2 * (
753  std::max(FloatType {0}, upperTerm)
754  - std::max(FloatType {0}, lowerTerm)
755  );
756 
757  /* Make sure computing all cross products is worth it by checking that
758  * one of both terms is actually greater than 0
759  */
760  if(factor != 0) {
761  // Specific to deltaI only but still repeated
762  const ThreeDimensionalVector alphaMinusGamma = alpha - gamma;
763  const ThreeDimensionalVector betaMinusGamma = beta - gamma;
764 
765  const ThreeDimensionalVector iContribution = (
766  factor * betaMinusDelta.cross(gammaMinusDelta)
767  / constraint.sites[0].size()
768  );
769 
770  const ThreeDimensionalVector jContribution = (
771  factor * gammaMinusDelta.cross(alphaMinusDelta)
772  / constraint.sites[1].size()
773  );
774 
775  const ThreeDimensionalVector kContribution = (
776  factor * alphaMinusDelta.cross(betaMinusDelta)
777  / constraint.sites[2].size()
778  );
779 
780  const ThreeDimensionalVector lContribution = (
781  factor * betaMinusGamma.cross(alphaMinusGamma)
782  / constraint.sites[3].size()
783  );
784 
785  for(const AtomIndex alphaI : constraint.sites[0]) {
786  gradient.template segment<3>(dimensionality * alphaI) += iContribution;
787  }
788 
789  for(const AtomIndex betaI : constraint.sites[1]) {
790  gradient.template segment<3>(dimensionality * betaI) += jContribution;
791  }
792 
793  for(const AtomIndex gammaI : constraint.sites[2]) {
794  gradient.template segment<3>(dimensionality * gammaI) += kContribution;
795  }
796 
797  for(const AtomIndex deltaI : constraint.sites[3]) {
798  gradient.template segment<3>(dimensionality * deltaI) += lContribution;
799  }
800  } else {
801  visitor.chiralTerm(constraint.sites, 0.0);
802  }
803  }
804 
805  // Set signaling member
806  if(nonZeroChiralConstraints == 0) {
807  proportionChiralConstraintsCorrectSign = 1;
808  } else {
809  proportionChiralConstraintsCorrectSign = static_cast<double>(
810  nonZeroChiralConstraints - incorrectNonZeroChiralConstraints
811  ) / nonZeroChiralConstraints;
812  }
813  }
814 
818  template<class Visitor>
820  const VectorType& positions,
821  FloatType& error,
822  Eigen::Ref<VectorType> gradient,
823  Visitor&& visitor
824  ) const {
825  assert(positions.size() == gradient.size());
826  constexpr FloatType reductionFactor = 1.0 / 10;
827 
828  for(const DihedralConstraint& constraint : dihedralConstraints) {
829  const ThreeDimensionalVector alpha = getAveragePosition3D(positions, constraint.sites[0]);
830  const ThreeDimensionalVector beta = getAveragePosition3D(positions, constraint.sites[1]);
831  const ThreeDimensionalVector gamma = getAveragePosition3D(positions, constraint.sites[2]);
832  const ThreeDimensionalVector delta = getAveragePosition3D(positions, constraint.sites[3]);
833 
834  const ThreeDimensionalVector f = alpha - beta;
835  const ThreeDimensionalVector g = beta - gamma;
836  const ThreeDimensionalVector h = delta - gamma;
837 
838  ThreeDimensionalVector a = f.cross(g);
839  ThreeDimensionalVector b = h.cross(g);
840 
841  const FloatType constraintSumHalved = (static_cast<FloatType>(constraint.lower) + static_cast<FloatType>(constraint.upper)) / 2;
842  constexpr FloatType pi {M_PI};
843 
844  // Calculate the dihedral angle
845  FloatType phi = std::atan2(
846  a.cross(b).dot(-g.normalized()),
847  a.dot(b)
848  );
849 
850  if(phi < constraintSumHalved - pi) {
851  phi += 2 * pi;
852  } else if(phi > constraintSumHalved + pi) {
853  phi -= 2 * pi;
854  }
855 
856  const FloatType w_phi = phi - constraintSumHalved;
857 
858  FloatType h_phi = std::fabs(w_phi) - (static_cast<FloatType>(constraint.upper) - static_cast<FloatType>(constraint.lower)) / 2;
859 
860  /* "Apply the max function": If h <= 0, then the max function yields zero,
861  * so we can skip this constraint
862  */
863  if(h_phi <= 0) {
864  visitor.dihedralTerm(constraint.sites, 0.0);
865  continue;
866  }
867 
868  // Error contribution
869  const FloatType errorContribution = h_phi * h_phi * reductionFactor;
870  error += errorContribution;
871  visitor.dihedralTerm(constraint.sites, errorContribution);
872 
873  // Multiply with sgn (w)
874  h_phi *= static_cast<int>(0 < w_phi) - static_cast<int>(w_phi < 0);
875 
876  // Multiply in 2 and the reduction factor
877  h_phi *= 2 * reductionFactor;
878 
879  // Precompute some reused expressions
880  const FloatType gLength = g.norm();
881  if(gLength == 0) {
882  throw std::runtime_error("Encountered zero-length g vector in dihedral errors");
883  }
884 
885  const FloatType aLengthSq = a.squaredNorm();
886  if(aLengthSq == 0) {
887  throw std::runtime_error("Encountered zero-length a vector in dihedral gradient contributions");
888  }
889  a /= aLengthSq;
890  const FloatType bLengthSq = b.squaredNorm();
891  if(bLengthSq == 0) {
892  throw std::runtime_error("Encountered zero-length b vector in dihedral gradient contributions");
893  }
894  b /= bLengthSq;
895  const FloatType fDotG = f.dot(g);
896  const FloatType gDotH = g.dot(h);
897 
898  const ThreeDimensionalVector iContribution = (
899  -(h_phi / constraint.sites[0].size()) * gLength * a
900  );
901 
902  const ThreeDimensionalVector jContribution = (
903  (h_phi / constraint.sites[1].size()) * (
904  (gLength + fDotG / gLength) * a
905  - (gDotH / gLength) * b
906  )
907  );
908 
909  const ThreeDimensionalVector kContribution = (
910  (h_phi / constraint.sites[2].size()) * (
911  (gDotH / gLength - gLength) * b
912  - (fDotG / gLength) * a
913  )
914  );
915 
916  const ThreeDimensionalVector lContribution = (
917  (h_phi / constraint.sites[3].size()) * gLength * b
918  );
919 
920  if(
921  !iContribution.array().isFinite().all()
922  || !jContribution.array().isFinite().all()
923  || !kContribution.array().isFinite().all()
924  || !lContribution.array().isFinite().all()
925  ) {
926  throw std::runtime_error("Encountered non-finite dihedral gradient contributions");
927  }
928 
929  /* Distribute contributions among constituting indices */
930  for(const AtomIndex alphaConstitutingIndex : constraint.sites[0]) {
931  gradient.template segment<3>(dimensionality * alphaConstitutingIndex) += iContribution;
932  }
933 
934  for(const AtomIndex betaConstitutingIndex: constraint.sites[1]) {
935  gradient.template segment<3>(dimensionality * betaConstitutingIndex) += jContribution;
936  }
937 
938  for(const AtomIndex gammaConstitutingIndex : constraint.sites[2]) {
939  gradient.template segment<3>(dimensionality * gammaConstitutingIndex) += kContribution;
940  }
941 
942  for(const AtomIndex deltaConstitutingIndex : constraint.sites[3]) {
943  gradient.template segment<3>(dimensionality * deltaConstitutingIndex) += lContribution;
944  }
945  }
946  }
947 
952  template<class Visitor = DefaultTermVisitor>
954  const VectorType& positions,
955  FloatType& error,
956  Eigen::Ref<VectorType> gradient,
957  Visitor&& visitor = {}
958  ) const {
959  if(dimensionality != 4) {
960  return;
961  }
962 
963  // Collect all fourth dimension values, square and sum, add to error
964  const unsigned N = positions.size() / dimensionality;
966  for(unsigned i = 0; i < N; ++i) {
967  const FloatType fourthDimensionalValue = positions(4 * i + 3);
968  const FloatType value = fourthDimensionalValue * fourthDimensionalValue;
969  error += value;
970  visitor.fourthDimensionTerm(i, value);
971  gradient(4 * i + 3) += 2 * fourthDimensionalValue;
972  }
973  } else {
974  for(unsigned i = 0; i < N; ++i) {
975  visitor.fourthDimensionTerm(i, 0.0);
976  }
977  }
978  }
980 };
981 
986 template<typename RefinementType>
988  // Trick to make the compiler tell me the template arguments to RefinementType
989  template<
990  template<unsigned, typename, bool> class BaseClass,
991  unsigned dimensionality,
992  typename FloatingPointType,
993  bool SIMD
994  > static auto templateArgumentHelper(BaseClass<dimensionality, FloatingPointType, SIMD> /* a */)
995  -> std::tuple<
996  std::integral_constant<unsigned, dimensionality>,
997  FloatingPointType,
998  std::integral_constant<bool, SIMD>
999  >
1000  {
1001  return {};
1002  }
1003 
1004  using TemplateArgumentsTuple = decltype(templateArgumentHelper(std::declval<RefinementType>()));
1005  using DimensionalityConstant = std::tuple_element_t<0, TemplateArgumentsTuple>;
1006  using FloatingPointType = std::tuple_element_t<1, TemplateArgumentsTuple>;
1007  using SimdConstant = std::tuple_element_t<2, TemplateArgumentsTuple>;
1008 };
1009 
1010 } // namespace DistanceGeometry
1011 } // namespace Molassembler
1012 } // namespace Scine
1013 
1014 #endif
double upper
Upper bound on signed volume.
Definition: DistanceGeometry.h:41
void chiralContributionsImpl(const VectorType &positions, FloatType &error, Eigen::Ref< VectorType > gradient, Visitor &&visitor) const
Adds chiral error and gradient contributions.
Definition: EigenRefinement.h:703
bool dihedralTerms
Whether to enable dihedral terms.
Definition: EigenRefinement.h:175
bool compressFourthDimension
Whether to compress the fourth dimension.
Definition: EigenRefinement.h:173
static ThreeDimensionalVector getAveragePosition3D(const VectorType &positions, const ChiralConstraint::AtomListType &atomList)
Calculate the average three-dimensional position of several atoms.
Definition: EigenRefinement.h:137
void dihedralContributions(const VectorType &positions, FloatType &error, Eigen::Ref< VectorType > gradient) const
Adds dihedral error and gradient contributions.
Definition: EigenRefinement.h:258
Data struct representing a dihedral constraint.
Definition: DistanceGeometry.h:61
Eigen-based refinement error function.
Definition: EigenRefinement.h:28
VectorType chiralLowerConstraints
Chiral lower constraints, in sequence of chiralConstraints.
Definition: EigenRefinement.h:163
double calculateProportionChiralConstraintsCorrectSign(const VectorType &positions) const
Calculates the number of chiral constraints with correct sign.
Definition: EigenRefinement.h:303
void distanceContributionsImpl(const VectorType &positions, FloatType &error, Eigen::Ref< VectorType > gradient, Visitor &&visitor) const
Adds distance error and gradient contributions (non-SIMD)
Definition: EigenRefinement.h:635
std::vector< DihedralConstraint > dihedralConstraints
List of dihedral constraints.
Definition: EigenRefinement.h:171
VectorType chiralUpperConstraints
Chiral upper constraints, in sequence of chiralConstraints.
Definition: EigenRefinement.h:161
static double & lowerBound(Eigen::Ref< Eigen::MatrixXd > matrix, const AtomIndex i, const AtomIndex j)
Uses Floyd&#39;s algorithm to smooth the matrix.
Definition: DistanceBoundsMatrix.h:33
double lower
Lower bound on signed volume.
Definition: DistanceGeometry.h:39
double lower
Lower bound on dihedral angle.
Definition: DistanceGeometry.h:68
void fourthDimensionContributions(const VectorType &positions, FloatType &error, Eigen::Ref< VectorType > gradient) const
Adds pairwise distance error and gradient contributions.
Definition: EigenRefinement.h:268
Eigen::Matrix< FloatType, 3, 1 > ThreeDimensionalVector
Three dimensional vector.
Definition: EigenRefinement.h:41
static FullDimensionalVector getPosition(const VectorType &positions, const AtomIndex index)
Copy a full dimensional position of an atom.
Definition: EigenRefinement.h:93
VectorType lowerDistanceBoundsSquared
Lower distance bounds squared, linearized in i &lt; j.
Definition: EigenRefinement.h:159
std::size_t AtomIndex
Unsigned integer atom index type. Used to refer to particular atoms.
Definition: Types.h:51
static double & upperBound(Eigen::Ref< Eigen::MatrixXd > matrix, const AtomIndex i, const AtomIndex j)
Uses Floyd&#39;s algorithm to smooth the matrix.
Definition: DistanceBoundsMatrix.h:41
Eigen::Matrix< FloatType, 3, Eigen::Dynamic > ThreeDimensionalMatrixType
Three-row dynamic column matrix.
Definition: EigenRefinement.h:46
auto visitUnfulfilledConstraints(const DistanceBoundsMatrix &bounds, const VectorType &positions, Visitor &&visitor) const
Visit all unfulfilled constraints.
Definition: EigenRefinement.h:381
Eigen::Matrix< FloatType, dimensionality, Eigen::Dynamic > FullDimensionalMatrixType
Three or four-row dynamic column matrix.
Definition: EigenRefinement.h:48
void distanceContributions(const VectorType &positions, FloatType &error, Eigen::Ref< VectorType > gradient) const
Adds pairwise distance error and gradient contributions.
Definition: EigenRefinement.h:233
void chiralContributions(const VectorType &positions, FloatType &error, Eigen::Ref< VectorType > gradient) const
Adds chiral error and gradient contributions.
Definition: EigenRefinement.h:246
Eigen::Matrix< FloatType, Eigen::Dynamic, 1 > VectorType
Vector layout of positions.
Definition: EigenRefinement.h:38
VectorType upperDistanceBoundsSquared
Upper distance bounds squared, linearized in i &lt; j.
Definition: EigenRefinement.h:157
This is for when you have a fully qualified Refinement typename but want to get the template argument...
Definition: EigenRefinement.h:987
Visitor that can be passed to visit all terms.
Definition: EigenRefinement.h:53
VectorType dihedralConstraintDiffsHalved
Dihedral bounds upper minus lower, halved, in sequence.
Definition: EigenRefinement.h:167
void operator()(const VectorType &parameters, FloatType &value, Eigen::Ref< VectorType > gradient) const
Calculates the error value and gradient for all contributions.
Definition: EigenRefinement.h:285
FloatType FloatingPointType
Template argument specifying floating-point type.
Definition: EigenRefinement.h:50
VectorType dihedralConstraintSumsHalved
Dihedral bounds&#39; averages, in sequence of dihedralConstraints.
Definition: EigenRefinement.h:165
std::vector< ChiralConstraint > chiralConstraints
List of chiral constraints.
Definition: EigenRefinement.h:169
static FullDimensionalVector getAveragePosition(const VectorType &positions, const ChiralConstraint::AtomListType &atomList)
Calculate the average full-dimensional position of several atoms.
Definition: EigenRefinement.h:116
constexpr T sum(const ArrayType< T, size > &array)
Sum up all elements of an array-like class.
Definition: Containers.h:112
static std::string name()
Get a string representation of the type name.
Definition: EigenRefinement.h:67
Eigen::Matrix< FloatType, dimensionality, 1 > FullDimensionalVector
Three or four dimensional vector (depending on dimensionality)
Definition: EigenRefinement.h:43
Data struct representing a chiral constraint.
Definition: DistanceGeometry.h:32
static ThreeDimensionalVector getPosition3D(const VectorType &positions, const AtomIndex index)
Copy a three-dimensional position of an atom.
Definition: EigenRefinement.h:104
void dihedralContributionsImpl(const VectorType &positions, FloatType &error, Eigen::Ref< VectorType > gradient, Visitor &&visitor) const
Adds dihedral error and gradient contributions.
Definition: EigenRefinement.h:819
void fourthDimensionContributionsImpl(const VectorType &positions, FloatType &error, Eigen::Ref< VectorType > gradient, Visitor &&visitor={}) const
Adds fourth dimension error and gradient contributions.
Definition: EigenRefinement.h:953
Class storing atom-pairwise distance bounds.
double upper
Upper bound on dihedral angle.
Definition: DistanceGeometry.h:70