From 56945a56aabe0d97aa33e9bc87539d1170161fe7 Mon Sep 17 00:00:00 2001
From: sjplimp <sjplimp@f3b2605a-c512-4ea7-a41b-209d697bcdaa>
Date: Tue, 6 Sep 2016 21:55:39 +0000
Subject: [PATCH] git-svn-id: svn://svn.icms.temple.edu/lammps-ro/trunk@15547
 f3b2605a-c512-4ea7-a41b-209d697bcdaa

---
 src/compute_omega_chunk.cpp | 127 +++++++++++++++++++++++++-----------
 src/group.cpp               |  73 +++++++++++----------
 2 files changed, 129 insertions(+), 71 deletions(-)

diff --git a/src/compute_omega_chunk.cpp b/src/compute_omega_chunk.cpp
index ca4af7931f..f2a8394964 100644
--- a/src/compute_omega_chunk.cpp
+++ b/src/compute_omega_chunk.cpp
@@ -18,11 +18,14 @@
 #include "modify.h"
 #include "compute_chunk_atom.h"
 #include "domain.h"
+#include "math_extra.h"
 #include "memory.h"
 #include "error.h"
 
 using namespace LAMMPS_NS;
 
+#define EPSILON 1.0e-6
+
 /* ---------------------------------------------------------------------- */
 
 ComputeOmegaChunk::ComputeOmegaChunk(LAMMPS *lmp, int narg, char **arg) :
@@ -192,49 +195,97 @@ void ComputeOmegaChunk::compute_array()
   MPI_Allreduce(&angmom[0][0],&angmomall[0][0],3*nchunk,
                 MPI_DOUBLE,MPI_SUM,world);
 
-  // compute omega for each chunk from L = Iw, inverting I to solve for w
+  // compute omega for each chunk
 
-  double ione[3][3],inverse[3][3];
+  double determinant,invdeterminant;
+  double idiag[3],ex[3],ey[3],ez[3],cross[3];
+  double ione[3][3],inverse[3][3],evectors[3][3];
+  double *iall,*mall;
 
   for (m = 0; m < nchunk; m++) {
-    ione[0][0] = inertiaall[m][0];
-    ione[1][1] = inertiaall[m][1];
-    ione[2][2] = inertiaall[m][2];
-    ione[0][1] = inertiaall[m][3];
-    ione[1][2] = inertiaall[m][4];
-    ione[0][2] = inertiaall[m][5];
-    ione[1][0] = ione[0][1];
-    ione[2][1] = ione[1][2];
-    ione[2][0] = ione[0][2];
-
-    inverse[0][0] = ione[1][1]*ione[2][2] - ione[1][2]*ione[2][1];
-    inverse[0][1] = -(ione[0][1]*ione[2][2] - ione[0][2]*ione[2][1]);
-    inverse[0][2] = ione[0][1]*ione[1][2] - ione[0][2]*ione[1][1];
-
-    inverse[1][0] = -(ione[1][0]*ione[2][2] - ione[1][2]*ione[2][0]);
-    inverse[1][1] = ione[0][0]*ione[2][2] - ione[0][2]*ione[2][0];
-    inverse[1][2] = -(ione[0][0]*ione[1][2] - ione[0][2]*ione[1][0]);
-
-    inverse[2][0] = ione[1][0]*ione[2][1] - ione[1][1]*ione[2][0];
-    inverse[2][1] = -(ione[0][0]*ione[2][1] - ione[0][1]*ione[2][0]);
-    inverse[2][2] = ione[0][0]*ione[1][1] - ione[0][1]*ione[1][0];
-
-    double determinant = ione[0][0]*ione[1][1]*ione[2][2] +
-      ione[0][1]*ione[1][2]*ione[2][0] + ione[0][2]*ione[1][0]*ione[2][1] -
-      ione[0][0]*ione[1][2]*ione[2][1] - ione[0][1]*ione[1][0]*ione[2][2] -
-      ione[2][0]*ione[1][1]*ione[0][2];
-
-    if (determinant > 0.0)
+
+    // determinant = triple product of rows of inertia matrix
+
+    iall = &inertiaall[m][0];
+    determinant = iall[0] * (iall[1]*iall[2] - iall[4]*iall[4]) + 
+      iall[3] * (iall[4]*iall[5] - iall[3]*iall[2]) + 
+      iall[5] * (iall[3]*iall[4] - iall[1]*iall[5]);
+
+    ione[0][0] = iall[0];
+    ione[1][1] = iall[1];
+    ione[2][2] = iall[2];
+    ione[0][1] = ione[1][0] = iall[3];
+    ione[1][2] = ione[2][1] = iall[4];
+    ione[0][2] = ione[2][0] = iall[5];
+
+    // non-singular I matrix
+    // use L = Iw, inverting I to solve for w
+
+    if (determinant > EPSILON) {
+      inverse[0][0] = ione[1][1]*ione[2][2] - ione[1][2]*ione[2][1];
+      inverse[0][1] = -(ione[0][1]*ione[2][2] - ione[0][2]*ione[2][1]);
+      inverse[0][2] = ione[0][1]*ione[1][2] - ione[0][2]*ione[1][1];
+
+      inverse[1][0] = -(ione[1][0]*ione[2][2] - ione[1][2]*ione[2][0]);
+      inverse[1][1] = ione[0][0]*ione[2][2] - ione[0][2]*ione[2][0];
+      inverse[1][2] = -(ione[0][0]*ione[1][2] - ione[0][2]*ione[1][0]);
+
+      inverse[2][0] = ione[1][0]*ione[2][1] - ione[1][1]*ione[2][0];
+      inverse[2][1] = -(ione[0][0]*ione[2][1] - ione[0][1]*ione[2][0]);
+      inverse[2][2] = ione[0][0]*ione[1][1] - ione[0][1]*ione[1][0];
+
+      invdeterminant = 1.0/determinant;
       for (i = 0; i < 3; i++)
         for (j = 0; j < 3; j++)
-          inverse[i][j] /= determinant;
-
-    omega[m][0] = inverse[0][0]*angmom[m][0] + inverse[0][1]*angmom[m][1] +
-      inverse[0][2]*angmom[m][2];
-    omega[m][1] = inverse[1][0]*angmom[m][0] + inverse[1][1]*angmom[m][1] +
-      inverse[1][2]*angmom[m][2];
-    omega[m][2] = inverse[2][0]*angmom[m][0] + inverse[2][1]*angmom[m][1] +
-      inverse[2][2]*angmom[i][2];
+          inverse[i][j] *= invdeterminant;
+      
+      mall = &angmomall[m][0];
+      omega[m][0] = inverse[0][0]*mall[0] + inverse[0][1]*mall[1] +
+        inverse[0][2]*mall[2];
+      omega[m][1] = inverse[1][0]*mall[0] + inverse[1][1]*mall[1] +
+        inverse[1][2]*mall[2];
+      omega[m][2] = inverse[2][0]*mall[0] + inverse[2][1]*mall[1] +
+        inverse[2][2]*mall[2];
+
+    // handle each (nearly) singular I matrix
+    // due to 2-atom chunk or linear molecule
+    // use jacobi() and angmom_to_omega() to calculate valid omega
+
+    } else {
+      int ierror = MathExtra::jacobi(ione,idiag,evectors);
+      if (ierror) error->all(FLERR,
+                             "Insufficient Jacobi rotations for omega/chunk");
+
+      ex[0] = evectors[0][0];
+      ex[1] = evectors[1][0];
+      ex[2] = evectors[2][0];
+      ey[0] = evectors[0][1];
+      ey[1] = evectors[1][1];
+      ey[2] = evectors[2][1];
+      ez[0] = evectors[0][2];
+      ez[1] = evectors[1][2];
+      ez[2] = evectors[2][2];
+
+      // enforce 3 evectors as a right-handed coordinate system
+      // flip 3rd vector if needed
+      
+      MathExtra::cross3(ex,ey,cross);
+      if (MathExtra::dot3(cross,ez) < 0.0) MathExtra::negate3(ez);
+
+      // if any principal moment < scaled EPSILON, set to 0.0
+      
+      double max;
+      max = MAX(idiag[0],idiag[1]);
+      max = MAX(max,idiag[2]);
+      
+      if (idiag[0] < EPSILON*max) idiag[0] = 0.0;
+      if (idiag[1] < EPSILON*max) idiag[1] = 0.0;
+      if (idiag[2] < EPSILON*max) idiag[2] = 0.0;
+
+      // calculate omega using diagonalized inertia matrix
+
+      MathExtra::angmom_to_omega(&angmomall[m][0],ex,ey,ez,idiag,&omega[m][0]);
+    }
   }
 }
 
diff --git a/src/group.cpp b/src/group.cpp
index 5b572b2070..da0e94fc11 100644
--- a/src/group.cpp
+++ b/src/group.cpp
@@ -29,6 +29,7 @@
 #include "input.h"
 #include "variable.h"
 #include "dump.h"
+#include "math_extra.h"
 #include "memory.h"
 #include "error.h"
 
@@ -37,6 +38,7 @@
 using namespace LAMMPS_NS;
 
 #define MAX_GROUP 32
+#define EPSILON 1.0e-6
 
 enum{TYPE,MOLECULE,ID};
 enum{LT,LE,GT,GE,EQ,NEQ,BETWEEN};
@@ -1662,42 +1664,47 @@ void Group::inertia(int igroup, double *cm, double itensor[3][3], int iregion)
 }
 
 /* ----------------------------------------------------------------------
-   compute angular velocity omega from L = Iw, inverting I to solve for w
+   compute angular velocity omega from L and I
    really not a group/region operation, but L,I were computed for a group/region
+   diagonalize I instead of inverting it, to allow for a singular matrix
 ------------------------------------------------------------------------- */
 
 void Group::omega(double *angmom, double inertia[3][3], double *w)
 {
-  double inverse[3][3];
-
-  inverse[0][0] = inertia[1][1]*inertia[2][2] - inertia[1][2]*inertia[2][1];
-  inverse[0][1] = -(inertia[0][1]*inertia[2][2] - inertia[0][2]*inertia[2][1]);
-  inverse[0][2] = inertia[0][1]*inertia[1][2] - inertia[0][2]*inertia[1][1];
-
-  inverse[1][0] = -(inertia[1][0]*inertia[2][2] - inertia[1][2]*inertia[2][0]);
-  inverse[1][1] = inertia[0][0]*inertia[2][2] - inertia[0][2]*inertia[2][0];
-  inverse[1][2] = -(inertia[0][0]*inertia[1][2] - inertia[0][2]*inertia[1][0]);
-
-  inverse[2][0] = inertia[1][0]*inertia[2][1] - inertia[1][1]*inertia[2][0];
-  inverse[2][1] = -(inertia[0][0]*inertia[2][1] - inertia[0][1]*inertia[2][0]);
-  inverse[2][2] = inertia[0][0]*inertia[1][1] - inertia[0][1]*inertia[1][0];
-
-  double determinant = inertia[0][0]*inertia[1][1]*inertia[2][2] +
-    inertia[0][1]*inertia[1][2]*inertia[2][0] +
-    inertia[0][2]*inertia[1][0]*inertia[2][1] -
-    inertia[0][0]*inertia[1][2]*inertia[2][1] -
-    inertia[0][1]*inertia[1][0]*inertia[2][2] -
-    inertia[2][0]*inertia[1][1]*inertia[0][2];
-
-  if (determinant > 0.0)
-    for (int i = 0; i < 3; i++)
-      for (int j = 0; j < 3; j++)
-        inverse[i][j] /= determinant;
-
-  w[0] = inverse[0][0]*angmom[0] + inverse[0][1]*angmom[1] +
-    inverse[0][2]*angmom[2];
-  w[1] = inverse[1][0]*angmom[0] + inverse[1][1]*angmom[1] +
-    inverse[1][2]*angmom[2];
-  w[2] = inverse[2][0]*angmom[0] + inverse[2][1]*angmom[1] +
-    inverse[2][2]*angmom[2];
+  double idiag[3],ex[3],ey[3],ez[3],cross[3];
+  double evectors[3][3];
+  
+  int ierror = MathExtra::jacobi(inertia,idiag,evectors);
+  if (ierror) error->all(FLERR,
+                         "Insufficient Jacobi rotations for group::omega");
+
+  ex[0] = evectors[0][0];
+  ex[1] = evectors[1][0];
+  ex[2] = evectors[2][0];
+  ey[0] = evectors[0][1];
+  ey[1] = evectors[1][1];
+  ey[2] = evectors[2][1];
+  ez[0] = evectors[0][2];
+  ez[1] = evectors[1][2];
+  ez[2] = evectors[2][2];
+  
+  // enforce 3 evectors as a right-handed coordinate system
+  // flip 3rd vector if needed
+  
+  MathExtra::cross3(ex,ey,cross);
+  if (MathExtra::dot3(cross,ez) < 0.0) MathExtra::negate3(ez);
+  
+  // if any principal moment < scaled EPSILON, set to 0.0
+  
+  double max;
+  max = MAX(idiag[0],idiag[1]);
+  max = MAX(max,idiag[2]);
+  
+  if (idiag[0] < EPSILON*max) idiag[0] = 0.0;
+  if (idiag[1] < EPSILON*max) idiag[1] = 0.0;
+  if (idiag[2] < EPSILON*max) idiag[2] = 0.0;
+  
+  // calculate omega using diagonalized inertia matrix
+  
+  MathExtra::angmom_to_omega(angmom,ex,ey,ez,idiag,w);
 }
-- 
GitLab