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