55// Created by Philip Turner on 10/21/23.
66//
77
8+ import OpenMM
9+
810extension MM4ForceField {
911 /// Create random thermal velocities, while conserving the total (bulk)
1012 /// momentum of each rigid body.
@@ -45,6 +47,157 @@ extension MM4ForceField {
4547 // Notes about angular momentum:
4648 // https://www.physicsforums.com/threads/how-can-angular-velocity-be-independent-of-the-choice-of-origin.986098/#:~:text=Both%20the%20angular%20momentum%20and,the%20angular%20velocity%20does%20not.
4749
50+ // Using reordered indices.
51+ let descriptor = MM4StateDescriptor ( )
52+ descriptor. positions = true
53+ descriptor. velocities = true
54+ let originalState = state ( descriptor: descriptor)
55+ let positions = originalState. positions!
56+ let bulkVelocities = originalState. velocities!
57+
58+ // Using reordered indices.
59+ latestContext. context. setVelocitiesToTemperature ( temperature)
60+ descriptor. positions = false
61+ let thermalState = state ( descriptor: descriptor)
62+ let thermalVelocities = thermalState. velocities!
63+
64+ // Using reordered indices.
65+ var anchorStatuses = [ Bool] ( repeating: false , count: system. atomCount)
66+ for anchor in _anchors {
67+ anchorStatuses [ Int ( anchor) ] = true
68+ }
69+
70+ var activeRigidBodies : [ Int ]
71+ if let rigidBodies {
72+ activeRigidBodies = rigidBodies
73+ } else {
74+ activeRigidBodies = [ ]
75+ for rigidBodyID in system. parameters. rigidBodies. indices {
76+ activeRigidBodies. append ( rigidBodyID)
77+ }
78+ }
79+
80+ // Set the system's velocities to these at the end of the function. Note,
81+ // these are in reordered indices, while the 'velocities' property uses
82+ // original indices. Do not set these using the 'velocities' property.
83+ var newVelocities : [ SIMD3 < Float > ] = bulkVelocities
84+
85+ // Don't forget to always map atom IDs to reordered IDs!
86+ for rigidBodyID in activeRigidBodies {
87+ // Last anchor ID is reordered.
88+ let range = system. parameters. rigidBodies [ rigidBodyID]
89+ var anchorCount = 0
90+ var lastAnchorID : Int32 = - 1
91+ for originalID in range {
92+ let atomID = system. reorderedIndices [ Int ( originalID) ]
93+ if anchorStatuses [ Int ( atomID) ] {
94+ anchorCount += 1
95+ lastAnchorID = atomID
96+ }
97+ }
98+
99+ let atoms = system. parameters. atoms
100+ var totalMass : Double = . zero
101+ var centerOfMass : SIMD3 < Float >
102+ if anchorCount == 1 {
103+ // Reading with a reordered index.
104+ centerOfMass = positions [ Int ( lastAnchorID) ]
105+ } else {
106+ var positionSum : SIMD3 < Double > = . zero
107+ for originalID in range {
108+ let atomID = system. reorderedIndices [ Int ( originalID) ]
109+ let mass = Double ( atoms. masses [ Int ( originalID) ] )
110+ let position = positions [ Int ( atomID) ]
111+ totalMass += mass
112+ positionSum += mass * SIMD3 < Double > ( position)
113+ }
114+ centerOfMass = SIMD3 < Float > ( positionSum / totalMass)
115+ }
116+
117+ // Find the original/thermalized linear/angular velocities, while
118+ // accumulating the moment of inertia.
119+ var bulkMomentum : SIMD3 < Double > = . zero
120+ var thermalMomentum : SIMD3 < Double > = . zero
121+ var bulkAngularMomentum : SIMD3 < Double > = . zero
122+ var thermalAngularMomentum : SIMD3 < Double > = . zero
123+ var rotationalInertia : RotationalInertia = . init( )
124+ for originalID in range {
125+ let atomID = system. reorderedIndices [ Int ( originalID) ]
126+ let mass = Double ( atoms. masses [ Int ( originalID) ] )
127+ let position = positions [ Int ( atomID) ]
128+ let bulkVelocity = bulkVelocities [ Int ( atomID) ]
129+ let thermalVelocity = thermalVelocities [ Int ( atomID) ]
130+ bulkMomentum += mass * SIMD3( bulkVelocity)
131+ thermalMomentum += mass * SIMD3( thermalVelocity)
132+
133+ // From Wikipedia:
134+ // https://en.wikipedia.org/wiki/Rigid_body_dynamics#Linear_and_angular_momentum
135+ //
136+ // L = m * (R - R_cm) cross d/dt (R - R_cm)
137+ // assume R_cm is stationary
138+ // L = m * (R - R_cm) cross v
139+ let relativePosition = position - centerOfMass
140+ let bulkAngularVelocity = cross ( relativePosition, bulkVelocity)
141+ let thermalAngularVelocity = cross ( relativePosition, thermalVelocity)
142+ bulkAngularMomentum += mass * SIMD3( bulkAngularVelocity)
143+ thermalAngularMomentum += mass * SIMD3( thermalAngularVelocity)
144+ rotationalInertia. append ( mass: mass, relativePosition: relativePosition)
145+ }
146+
147+ // If the system has >1 anchor points, suppress the angular velocity.
148+ if anchorCount > 1 {
149+ bulkAngularMomentum = . zero
150+ }
151+
152+ // Matrix:
153+ // L = I * w
154+ // (I^{-1}) L = w
155+ // w = angular velocity
156+ //
157+ // Resulting vector:
158+ // w_x: angular velocity around x-axis (YZ plane)
159+ // w_y: angular velocity around y-axis (ZX plane)
160+ // w_z: angular velocity around z-axis (XY plane)
161+ //
162+ // Convert into a linear velocity for each particle:
163+ // v = w cross r
164+ let inverse = rotationalInertia. inverse
165+ func project( momentum: SIMD3 < Double > ) -> SIMD3 < Float > {
166+ var output : SIMD3 < Double > = . zero
167+ output += momentum. x * inverse. 0
168+ output += momentum. y * inverse. 1
169+ output += momentum. z * inverse. 2
170+ return SIMD3 < Float > ( output)
171+ }
172+
173+ let bulkVelocity = bulkMomentum / totalMass
174+ let thermalVelocity = thermalMomentum / totalMass
175+ let velocityCorrection = - SIMD3 < Float > ( thermalVelocity - bulkVelocity)
176+
177+ let bulkAngularVelocity = project ( momentum: bulkAngularMomentum)
178+ let thermalAngularVelocity = project ( momentum: thermalAngularMomentum)
179+ let angularVelocityCorrection = - SIMD3 < Float > (
180+ thermalAngularVelocity - bulkAngularVelocity)
181+
182+ // Apply the correction to rotational and angular velocity.
183+ for originalID in range {
184+ let atomID = system. reorderedIndices [ Int ( originalID) ]
185+ let position = positions [ Int ( atomID) ]
186+ var velocity = thermalVelocities [ Int ( atomID) ]
187+ velocity += velocityCorrection
188+
189+ let relativePosition = position - centerOfMass
190+ velocity += cross ( angularVelocityCorrection, relativePosition)
191+ newVelocities [ Int ( atomID) ] = velocity
192+ }
193+ }
194+
195+ // Set the system's velocities to the new ones, reverting changes to rigid
196+ // bodies that shouldn't be thermalized.
197+ let array = OpenMM_Vec3Array ( size: system. atomCount)
198+ for (index, velocity) in newVelocities. enumerated ( ) {
199+ array [ index] = SIMD3 < Double > ( velocity)
200+ }
48201 }
49202}
50203
@@ -66,15 +219,74 @@ func cross<T: BinaryFloatingPoint & SIMDScalar>(
66219///
67220/// Source: [Stack Overflow](https://stackoverflow.com/a/18504573)
68221struct RotationalInertia {
69- /// The position to compute rotation around.
70- var origin : SIMD3 < Float >
71-
72222 /// The accumulator for the rigid body's moment of inertia.
73223 var columns : ( SIMD3 < Double > , SIMD3 < Double > , SIMD3 < Double > )
74224
75225 /// Initialize a moment of inertia with zero mass.
76- init ( origin: SIMD3 < Float > ) {
77- self . origin = origin
226+ init ( ) {
78227 self . columns = ( . zero, . zero, . zero)
79228 }
229+
230+ /// Add an atom to the accumulator.
231+ mutating func append( mass: Double , relativePosition: SIMD3 < Float > ) {
232+ // From Wikipedia:
233+ // https://en.wikipedia.org/wiki/Rigid_body_dynamics#Mass_properties
234+ //
235+ // I_R = m * (I (S^T S) - S S^T)
236+ // where S is the column vector R - R_cm
237+ let STS = ( relativePosition * relativePosition) . sum ( )
238+ var column0 = SIMD3 ( STS, 0 , 0 )
239+ var column1 = SIMD3 ( 0 , STS, 0 )
240+ var column2 = SIMD3 ( 0 , 0 , STS)
241+ column0 -= relativePosition. x * relativePosition
242+ column1 -= relativePosition. y * relativePosition
243+ column2 -= relativePosition. z * relativePosition
244+
245+ // Convert to FP64 before adding to the accumulator. The matrix is
246+ // symmetric, so it doesn't matter whether you mix up the rows and columns.
247+ columns. 0 += mass * SIMD3 < Double > ( column0)
248+ columns. 1 += mass * SIMD3 < Double > ( column1)
249+ columns. 2 += mass * SIMD3 < Double > ( column2)
250+ }
251+
252+ // The matrix is symmetric, but not exactly orthonormal. Inversion is not a
253+ // simple transpose operation.
254+ var inverse : ( SIMD3 < Double > , SIMD3 < Double > , SIMD3 < Double > ) {
255+ // Source: https://stackoverflow.com/a/18504573
256+ //
257+ // double det = m(0, 0) * (m(1, 1) * m(2, 2) - m(2, 1) * m(1, 2)) -
258+ // m(0, 1) * (m(1, 0) * m(2, 2) - m(1, 2) * m(2, 0)) +
259+ // m(0, 2) * (m(1, 0) * m(2, 1) - m(1, 1) * m(2, 0));
260+ let determinant =
261+ columns. 0 [ 0 ] * ( columns. 1 [ 1 ] * columns. 2 [ 2 ] - columns. 2 [ 1 ] * columns. 1 [ 2 ] ) -
262+ columns. 0 [ 1 ] * ( columns. 1 [ 0 ] * columns. 2 [ 2 ] - columns. 1 [ 2 ] * columns. 2 [ 0 ] ) +
263+ columns. 0 [ 2 ] * ( columns. 1 [ 0 ] * columns. 2 [ 1 ] - columns. 1 [ 1 ] * columns. 2 [ 0 ] )
264+ let invdet = 1 / determinant
265+
266+ // minv(0, 0) = (m(1, 1) * m(2, 2) - m(2, 1) * m(1, 2)) * invdet;
267+ // minv(0, 1) = (m(0, 2) * m(2, 1) - m(0, 1) * m(2, 2)) * invdet;
268+ // minv(0, 2) = (m(0, 1) * m(1, 2) - m(0, 2) * m(1, 1)) * invdet;
269+ let result00 = ( columns. 1 [ 1 ] * columns. 2 [ 2 ] - columns. 2 [ 1 ] * columns. 1 [ 2 ] ) * invdet
270+ let result01 = ( columns. 0 [ 2 ] * columns. 2 [ 1 ] - columns. 0 [ 1 ] * columns. 2 [ 2 ] ) * invdet
271+ let result02 = ( columns. 0 [ 1 ] * columns. 1 [ 2 ] - columns. 0 [ 2 ] * columns. 1 [ 1 ] ) * invdet
272+
273+ // minv(1, 0) = (m(1, 2) * m(2, 0) - m(1, 0) * m(2, 2)) * invdet;
274+ // minv(1, 1) = (m(0, 0) * m(2, 2) - m(0, 2) * m(2, 0)) * invdet;
275+ // minv(1, 2) = (m(1, 0) * m(0, 2) - m(0, 0) * m(1, 2)) * invdet;
276+ let result10 = ( columns. 1 [ 2 ] * columns. 2 [ 0 ] - columns. 1 [ 0 ] * columns. 2 [ 2 ] ) * invdet
277+ let result11 = ( columns. 0 [ 0 ] * columns. 2 [ 2 ] - columns. 0 [ 2 ] * columns. 2 [ 0 ] ) * invdet
278+ let result12 = ( columns. 1 [ 0 ] * columns. 0 [ 2 ] - columns. 0 [ 0 ] * columns. 1 [ 2 ] ) * invdet
279+
280+ // minv(2, 0) = (m(1, 0) * m(2, 1) - m(2, 0) * m(1, 1)) * invdet;
281+ // minv(2, 1) = (m(2, 0) * m(0, 1) - m(0, 0) * m(2, 1)) * invdet;
282+ // minv(2, 2) = (m(0, 0) * m(1, 1) - m(1, 0) * m(0, 1)) * invdet;
283+ let result20 = ( columns. 1 [ 0 ] * columns. 2 [ 1 ] - columns. 2 [ 0 ] * columns. 1 [ 1 ] ) * invdet
284+ let result21 = ( columns. 2 [ 0 ] * columns. 0 [ 1 ] - columns. 0 [ 0 ] * columns. 2 [ 1 ] ) * invdet
285+ let result22 = ( columns. 0 [ 0 ] * columns. 1 [ 1 ] - columns. 1 [ 0 ] * columns. 0 [ 1 ] ) * invdet
286+
287+ let column0 = SIMD3 ( result00, result10, result20)
288+ let column1 = SIMD3 ( result01, result11, result21)
289+ let column2 = SIMD3 ( result02, result12, result22)
290+ return ( column0, column1, column2)
291+ }
80292}
0 commit comments