Molassembler  1.1.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:
483 
486  template<class Visitor, bool dependent = SIMD, std::enable_if_t<!dependent, int>...>
488  const VectorType& positions,
489  FloatType& error,
490  Eigen::Ref<VectorType> gradient,
491  Visitor&& visitor
492  ) const {
493  assert(positions.size() == gradient.size());
494  const unsigned N = positions.size() / dimensionality;
495 
496  for(unsigned linearIndex = 0, i = 0; i < N - 1; ++i) {
497  for(unsigned j = i + 1; j < N; ++j, ++linearIndex) {
498  const FloatType lowerBoundSquared = lowerDistanceBoundsSquared(linearIndex);
499  const FloatType upperBoundSquared = upperDistanceBoundsSquared(linearIndex);
500  assert(lowerBoundSquared <= upperBoundSquared);
501 
502  // For both
503  const FullDimensionalVector positionDifference = (
504  positions.template segment<dimensionality>(dimensionality * i)
505  - positions.template segment<dimensionality>(dimensionality * j)
506  );
507 
508  const FloatType squareDistance = positionDifference.squaredNorm();
509 
510  // Upper term
511  const FloatType upperTerm = squareDistance / upperBoundSquared - 1;
512 
513  if(upperTerm > 0) {
514  const FloatType value = upperTerm * upperTerm;
515  error += value;
516  visitor.distanceTerm(i, j, value);
517 
518  const FullDimensionalVector f = 4 * positionDifference * upperTerm / upperBoundSquared;
519 
520  gradient.template segment<dimensionality>(dimensionality * i) += f;
521  gradient.template segment<dimensionality>(dimensionality * j) -= f;
522  } else {
523  // Lower term is only possible if the upper term does not contribute
524  const FloatType quotient = lowerBoundSquared + squareDistance;
525  const FloatType lowerTerm = 2 * lowerBoundSquared / quotient - 1;
526 
527  if(lowerTerm > 0) {
528  const FloatType value = lowerTerm * lowerTerm;
529  error += value;
530  visitor.distanceTerm(i, j, value);
531 
532  const FullDimensionalVector g = 8 * lowerBoundSquared * positionDifference * lowerTerm / (
533  quotient * quotient
534  );
535 
536  /* We use -= because the lower term needs the position vector
537  * difference (j - i), so we reuse positionDifference and just subtract
538  * from the gradient instead of adding to it
539  */
540  gradient.template segment<dimensionality>(dimensionality * i) -= g;
541  gradient.template segment<dimensionality>(dimensionality * j) += g;
542  } else {
543  visitor.distanceTerm(i, j, 0.0);
544  }
545  }
546  }
547  }
548  }
549 
553  template<class Visitor, bool dependent = SIMD, std::enable_if_t<dependent, int>...>
555  const VectorType& positions,
556  FloatType& error,
557  Eigen::Ref<VectorType> gradient,
558  Visitor&& /* visitor */
559  ) const {
560  assert(positions.size() == gradient.size());
561  /* Using the squared distance bounds to avoid unneeded square-root calculations
562  *
563  * NOTE: positions are full-dimensional
564  *
565  * for each nonredundant pair of atoms (i < j)
566  * Calculate a squared vector distance:
567  * sq_distance = (position[j] - position[i]).array().square().sum()
568  *
569  * upper_term = (sq_distance / upperBoundSquared) - 1
570  *
571  * if upper_term > 0:
572  * error += upper_term.square()
573  *
574  * lower_term = (
575  * 2 * lowerBoundSquared / (lowerBoundSquared + sq_distance)
576  * ) - 1
577  *
578  * if lowerTerm > 0:
579  * error += lower_term.square()
580  *
581  * SIMDize:
582  * sq_distances = list of upper triangular square distance terms
583  * upper_bounds_sq = list of corresponding upper bounds (identical across runs, no need to gather multiple times)
584  * lower_bounds_sq = list of corresponding lower bounds (same as above)
585  *
586  * upper_terms = (sq_distances / upper_bounds_sq) - 1
587  * error += upper_terms.max(0.0).square().sum()
588  *
589  * lower_terms = (2 * lower_bounds_sq / (lower_bounds_sq + sq_distances)) - 1
590  * error += lower_terms.max(0.0).square().sum()
591  *
592  */
593 
594  // Calculate all full-dimensional N(N-1) / 2 position differences
595  const unsigned N = positions.size() / dimensionality;
596  const unsigned differencesCount = N * (N - 1) / 2;
597 
598  FullDimensionalMatrixType positionDifferences(dimensionality, differencesCount);
599  {
600  unsigned offset = 0;
601  for(unsigned i = 0; i < N - 1; ++i) {
602  auto iPosition = positions.template segment<dimensionality>(dimensionality * i);
603  for(unsigned j = i + 1; j < N; ++j) {
604  positionDifferences.col(offset) = (
605  iPosition
606  - positions.template segment<dimensionality>(dimensionality * j)
607  );
608  ++offset;
609  }
610  }
611  }
612 
613  // SIMD
614  const VectorType squareDistances = positionDifferences.colwise().squaredNorm();
615 
616  // SIMD
617  const VectorType upperTerms = squareDistances.array() / upperDistanceBoundsSquared.array() - 1;
618 
619 #ifndef NDEBUG
620  {
621  // Check correctness of results so far
622  unsigned offset = 0;
623  for(unsigned i = 0; i < N - 1; ++i) {
624  for(unsigned j = i + 1; j < N; ++j) {
625  const FullDimensionalVector positionDifference = (
626  positions.template segment<dimensionality>(dimensionality * i)
627  - positions.template segment<dimensionality>(dimensionality * j)
628  );
629 
630  assert(positionDifferences.col(offset) == positionDifference);
631 
632  const FloatType squareDistance = positionDifference.squaredNorm();
633 
634  assert(squareDistance == squareDistances(offset));
635 
636  // Upper term
637  const FloatType upperTerm = squareDistance / upperDistanceBoundsSquared(offset) - 1;
638 
639  assert(upperTerm == upperTerms(offset));
640 
641  ++offset;
642  }
643  }
644  }
645 #endif
646 
647  // Traverse upper terms and calculate gradients
648  for(unsigned iOffset = 0, i = 0; i < N - 1; ++i) {
649  const unsigned crossTerms = N - i - 1;
650  for(unsigned jOffset = 0, j = i + 1; j < N; ++j, ++jOffset) {
651  const FloatType upperTerm = upperTerms(iOffset + jOffset);
652  if(upperTerm > 0) {
653  error += upperTerm * upperTerm;
654 
655  /* This i-j combination has an upper value contribution
656  *
657  * calculate gradient and apply to i and j
658  */
659  const FullDimensionalVector f = (
660  4 * positionDifferences.col(iOffset + jOffset) * upperTerm
661  / upperDistanceBoundsSquared(iOffset + jOffset)
662  );
663 
664  // position difference is i - j, not j - i (!)
665  gradient.template segment<dimensionality>(dimensionality * i) += f;
666  gradient.template segment<dimensionality>(dimensionality * j) -= f;
667  } else {
668  /* This i-j combination MAYBE has a lower value contribution
669  */
670  const auto& lowerBoundSquared = lowerDistanceBoundsSquared(iOffset + jOffset);
671  const FloatType quotient = lowerBoundSquared + squareDistances(iOffset + jOffset);
672  const FloatType lowerTerm = 2 * lowerBoundSquared / quotient - 1;
673 
674  if(lowerTerm > 0) {
675  // Add it to the error
676  error += lowerTerm * lowerTerm;
677 
678  // Calculate gradient and apply
679  const FullDimensionalVector g = (
680  8 * lowerBoundSquared * positionDifferences.col(iOffset + jOffset) * lowerTerm / (
681  quotient * quotient
682  )
683  );
684 
685  // position difference is i - j, not j - i (!)
686  gradient.template segment<dimensionality>(dimensionality * i) -= g;
687  gradient.template segment<dimensionality>(dimensionality * j) += g;
688  }
689  }
690  }
691 
692  iOffset += crossTerms;
693  }
694  }
695 
699  template<typename Visitor>
701  const VectorType& positions,
702  FloatType& error,
703  Eigen::Ref<VectorType> gradient,
704  Visitor&& visitor
705  ) const {
706  unsigned nonZeroChiralConstraints = 0;
707  unsigned incorrectNonZeroChiralConstraints = 0;
708 
709  for(const auto& constraint : chiralConstraints) {
710  const ThreeDimensionalVector alpha = getAveragePosition3D(positions, constraint.sites[0]);
711  const ThreeDimensionalVector beta = getAveragePosition3D(positions, constraint.sites[1]);
712  const ThreeDimensionalVector gamma = getAveragePosition3D(positions, constraint.sites[2]);
713  const ThreeDimensionalVector delta = getAveragePosition3D(positions, constraint.sites[3]);
714 
715  const ThreeDimensionalVector alphaMinusDelta = alpha - delta;
716  const ThreeDimensionalVector betaMinusDelta = beta - delta;
717  const ThreeDimensionalVector gammaMinusDelta = gamma - delta;
718 
719  const FloatType volume = (alphaMinusDelta).dot(
720  betaMinusDelta.cross(gammaMinusDelta)
721  );
722 
723  if(!constraint.targetVolumeIsZero()) {
724  ++nonZeroChiralConstraints;
725  if(
726  ( volume < 0 && constraint.lower > 0)
727  || (volume > 0 && constraint.lower < 0)
728  ) {
729  ++incorrectNonZeroChiralConstraints;
730  }
731  }
732 
733  // Upper bound term
734  const FloatType upperTerm = static_cast<FloatType>(constraint.weight) * (volume - static_cast<FloatType>(constraint.upper));
735  if(upperTerm > 0) {
736  const FloatType value = upperTerm * upperTerm;
737  error += value;
738  visitor.chiralTerm(constraint.sites, value);
739  }
740 
741  // Lower bound term
742  const FloatType lowerTerm = static_cast<FloatType>(constraint.weight) * (static_cast<FloatType>(constraint.lower) - volume);
743  if(lowerTerm > 0) {
744  const FloatType value = lowerTerm * lowerTerm;
745  error += value;
746  visitor.chiralTerm(constraint.sites, value);
747  }
748 
749  const FloatType factor = 2 * (
750  std::max(FloatType {0}, upperTerm)
751  - std::max(FloatType {0}, lowerTerm)
752  );
753 
754  /* Make sure computing all cross products is worth it by checking that
755  * one of both terms is actually greater than 0
756  */
757  if(factor != 0) {
758  // Specific to deltaI only but still repeated
759  const ThreeDimensionalVector alphaMinusGamma = alpha - gamma;
760  const ThreeDimensionalVector betaMinusGamma = beta - gamma;
761 
762  const ThreeDimensionalVector iContribution = (
763  factor * betaMinusDelta.cross(gammaMinusDelta)
764  / constraint.sites[0].size()
765  );
766 
767  const ThreeDimensionalVector jContribution = (
768  factor * gammaMinusDelta.cross(alphaMinusDelta)
769  / constraint.sites[1].size()
770  );
771 
772  const ThreeDimensionalVector kContribution = (
773  factor * alphaMinusDelta.cross(betaMinusDelta)
774  / constraint.sites[2].size()
775  );
776 
777  const ThreeDimensionalVector lContribution = (
778  factor * betaMinusGamma.cross(alphaMinusGamma)
779  / constraint.sites[3].size()
780  );
781 
782  for(const AtomIndex alphaI : constraint.sites[0]) {
783  gradient.template segment<3>(dimensionality * alphaI) += iContribution;
784  }
785 
786  for(const AtomIndex betaI : constraint.sites[1]) {
787  gradient.template segment<3>(dimensionality * betaI) += jContribution;
788  }
789 
790  for(const AtomIndex gammaI : constraint.sites[2]) {
791  gradient.template segment<3>(dimensionality * gammaI) += kContribution;
792  }
793 
794  for(const AtomIndex deltaI : constraint.sites[3]) {
795  gradient.template segment<3>(dimensionality * deltaI) += lContribution;
796  }
797  } else {
798  visitor.chiralTerm(constraint.sites, 0.0);
799  }
800  }
801 
802  // Set signaling member
803  if(nonZeroChiralConstraints == 0) {
804  proportionChiralConstraintsCorrectSign = 1;
805  } else {
806  proportionChiralConstraintsCorrectSign = static_cast<double>(
807  nonZeroChiralConstraints - incorrectNonZeroChiralConstraints
808  ) / nonZeroChiralConstraints;
809  }
810  }
811 
815  template<class Visitor>
817  const VectorType& positions,
818  FloatType& error,
819  Eigen::Ref<VectorType> gradient,
820  Visitor&& visitor
821  ) const {
822  assert(positions.size() == gradient.size());
823  constexpr FloatType reductionFactor = 1.0 / 10;
824 
825  for(const DihedralConstraint& constraint : dihedralConstraints) {
826  const ThreeDimensionalVector alpha = getAveragePosition3D(positions, constraint.sites[0]);
827  const ThreeDimensionalVector beta = getAveragePosition3D(positions, constraint.sites[1]);
828  const ThreeDimensionalVector gamma = getAveragePosition3D(positions, constraint.sites[2]);
829  const ThreeDimensionalVector delta = getAveragePosition3D(positions, constraint.sites[3]);
830 
831  const ThreeDimensionalVector f = alpha - beta;
832  const ThreeDimensionalVector g = beta - gamma;
833  const ThreeDimensionalVector h = delta - gamma;
834 
835  ThreeDimensionalVector a = f.cross(g);
836  ThreeDimensionalVector b = h.cross(g);
837 
838  const FloatType constraintSumHalved = (static_cast<FloatType>(constraint.lower) + static_cast<FloatType>(constraint.upper)) / 2;
839  constexpr FloatType pi {M_PI};
840 
841  // Calculate the dihedral angle
842  FloatType phi = std::atan2(
843  a.cross(b).dot(-g.normalized()),
844  a.dot(b)
845  );
846 
847  if(phi < constraintSumHalved - pi) {
848  phi += 2 * pi;
849  } else if(phi > constraintSumHalved + pi) {
850  phi -= 2 * pi;
851  }
852 
853  const FloatType w_phi = phi - constraintSumHalved;
854 
855  FloatType h_phi = std::fabs(w_phi) - (static_cast<FloatType>(constraint.upper) - static_cast<FloatType>(constraint.lower)) / 2;
856 
857  /* "Apply the max function": If h <= 0, then the max function yields zero,
858  * so we can skip this constraint
859  */
860  if(h_phi <= 0) {
861  visitor.dihedralTerm(constraint.sites, 0.0);
862  continue;
863  }
864 
865  // Error contribution
866  const FloatType errorContribution = h_phi * h_phi * reductionFactor;
867  error += errorContribution;
868  visitor.dihedralTerm(constraint.sites, errorContribution);
869 
870  // Multiply with sgn (w)
871  h_phi *= static_cast<int>(0 < w_phi) - static_cast<int>(w_phi < 0);
872 
873  // Multiply in 2 and the reduction factor
874  h_phi *= 2 * reductionFactor;
875 
876  // Precompute some reused expressions
877  const FloatType gLength = g.norm();
878  if(gLength == 0) {
879  throw std::runtime_error("Encountered zero-length g vector in dihedral errors");
880  }
881 
882  const FloatType aLengthSq = a.squaredNorm();
883  if(aLengthSq == 0) {
884  throw std::runtime_error("Encountered zero-length a vector in dihedral gradient contributions");
885  }
886  a /= aLengthSq;
887  const FloatType bLengthSq = b.squaredNorm();
888  if(bLengthSq == 0) {
889  throw std::runtime_error("Encountered zero-length b vector in dihedral gradient contributions");
890  }
891  b /= bLengthSq;
892  const FloatType fDotG = f.dot(g);
893  const FloatType gDotH = g.dot(h);
894 
895  const ThreeDimensionalVector iContribution = (
896  -(h_phi / constraint.sites[0].size()) * gLength * a
897  );
898 
899  const ThreeDimensionalVector jContribution = (
900  (h_phi / constraint.sites[1].size()) * (
901  (gLength + fDotG / gLength) * a
902  - (gDotH / gLength) * b
903  )
904  );
905 
906  const ThreeDimensionalVector kContribution = (
907  (h_phi / constraint.sites[2].size()) * (
908  (gDotH / gLength - gLength) * b
909  - (fDotG / gLength) * a
910  )
911  );
912 
913  const ThreeDimensionalVector lContribution = (
914  (h_phi / constraint.sites[3].size()) * gLength * b
915  );
916 
917  if(
918  !iContribution.array().isFinite().all()
919  || !jContribution.array().isFinite().all()
920  || !kContribution.array().isFinite().all()
921  || !lContribution.array().isFinite().all()
922  ) {
923  throw std::runtime_error("Encountered non-finite dihedral gradient contributions");
924  }
925 
926  /* Distribute contributions among constituting indices */
927  for(const AtomIndex alphaConstitutingIndex : constraint.sites[0]) {
928  gradient.template segment<3>(dimensionality * alphaConstitutingIndex) += iContribution;
929  }
930 
931  for(const AtomIndex betaConstitutingIndex: constraint.sites[1]) {
932  gradient.template segment<3>(dimensionality * betaConstitutingIndex) += jContribution;
933  }
934 
935  for(const AtomIndex gammaConstitutingIndex : constraint.sites[2]) {
936  gradient.template segment<3>(dimensionality * gammaConstitutingIndex) += kContribution;
937  }
938 
939  for(const AtomIndex deltaConstitutingIndex : constraint.sites[3]) {
940  gradient.template segment<3>(dimensionality * deltaConstitutingIndex) += lContribution;
941  }
942  }
943  }
944 
949  template<class Visitor = DefaultTermVisitor>
951  const VectorType& positions,
952  FloatType& error,
953  Eigen::Ref<VectorType> gradient,
954  Visitor&& visitor = {}
955  ) const {
956  if(dimensionality != 4) {
957  return;
958  }
959 
960  // Collect all fourth dimension values, square and sum, add to error
961  const unsigned N = positions.size() / dimensionality;
963  for(unsigned i = 0; i < N; ++i) {
964  const FloatType fourthDimensionalValue = positions(4 * i + 3);
965  const FloatType value = fourthDimensionalValue * fourthDimensionalValue;
966  error += value;
967  visitor.fourthDimensionTerm(i, value);
968  gradient(4 * i + 3) += 2 * fourthDimensionalValue;
969  }
970  } else {
971  for(unsigned i = 0; i < N; ++i) {
972  visitor.fourthDimensionTerm(i, 0.0);
973  }
974  }
975  }
977 };
978 
983 template<typename RefinementType>
985  // Trick to make the compiler tell me the template arguments to RefinementType
986  template<
987  template<unsigned, typename, bool> class BaseClass,
988  unsigned dimensionality,
989  typename FloatingPointType,
990  bool SIMD
991  > static auto templateArgumentHelper(BaseClass<dimensionality, FloatingPointType, SIMD> /* a */)
992  -> std::tuple<
993  std::integral_constant<unsigned, dimensionality>,
994  FloatingPointType,
995  std::integral_constant<bool, SIMD>
996  >
997  {
998  return {};
999  }
1000 
1001  using TemplateArgumentsTuple = decltype(templateArgumentHelper(std::declval<RefinementType>()));
1002  using DimensionalityConstant = std::tuple_element_t<0, TemplateArgumentsTuple>;
1003  using FloatingPointType = std::tuple_element_t<1, TemplateArgumentsTuple>;
1004  using SimdConstant = std::tuple_element_t<2, TemplateArgumentsTuple>;
1005 };
1006 
1007 } // namespace DistanceGeometry
1008 } // namespace Molassembler
1009 } // namespace Scine
1010 
1011 #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:700
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
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:35
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
void distanceContributionsImpl(const VectorType &positions, FloatType &error, Eigen::Ref< VectorType > gradient, Visitor &&) const
SIMD implementation of distance contributions.
Definition: EigenRefinement.h:554
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:487
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:43
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:984
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:816
void fourthDimensionContributionsImpl(const VectorType &positions, FloatType &error, Eigen::Ref< VectorType > gradient, Visitor &&visitor={}) const
Adds fourth dimension error and gradient contributions.
Definition: EigenRefinement.h:950
Class storing atom-pairwise distance bounds.
double upper
Upper bound on dihedral angle.
Definition: DistanceGeometry.h:70