1 from .Vector3DReal
import Vector3DReal
7 class ParticleCollection:
9 Represents the state of a collection of particles.
14 Constructs an empty particle collection.
26 Sets the particle positions and velocities.
28 The given arguments will be copied into an instance-internal store.
31 Throws if the arguments do not have the types specified in their
34 Throws if the `positions` and `velocities` lists do not have
38 A list of `Vector3DReal` instances, describing the particle
41 A list of `Vector3DReal` instances, describing the particle
45 if not isinstance(positions, list):
47 if not isinstance(velocities, list):
52 if not isinstance(x, Vector3DReal):
55 if not isinstance(x, Vector3DReal):
59 if len(velocities) != 0:
60 if len(positions) != len(velocities):
61 raise ValueError(
"Arrays of different length given")
73 Sets a uniform mass for all particles, including currently stored ones
74 and ones that may be added or altered later.
77 Throws if `mass` is neither `int` nor `float`.
79 Throws if `mass` is smaller than `0`.
82 The particle mass, which must be a non-negative `int` or
86 if not isinstance(mass, (int, float)):
97 Returns whether this collection contains any particles.
105 Returns the number of particles in this collection.
113 Returns a reference to the internal list of position vectors.
116 It is assumed that the returned reference will not be manipulated!
124 Returns the position of the particle with the given `index`, as an
125 instance of `Vector3DReal`.
128 Throws if `index` is negative or `index >= getParticleCount()`.
130 Throws if `index` is not an `int`.
133 The index of the particle to query, in the range of
134 `[0, getParticleCount() - 1]`.
137 if not isinstance(index, int):
148 Returns a reference to the internal list of velocity vectors.
151 It is assumed that the returned reference will not be manipulated!
159 Returns the velocity of the particle with the given `index`, as an
160 instance of `Vector3DReal`
163 Throws if `index` is negative or `index >= getParticleCount()`.
165 Throws if `index` is not an `int`.
168 The index of the particle to query, in the range of
169 `[0, getParticleCount() - 1]`.
172 if not isinstance(index, int):
183 Returns the mass of the particle with the given `index` as a `float`.
186 Throws if `index` is negative or `index >= getParticleCount()`.
188 Throws if no mass has been specified for the given particle.
190 Throws if `index` is not an `int`.
193 The index of the particle to query, in the range of
194 `[0, getParticleCount() - 1]`.
197 if not isinstance(index, int):
211 Returns the momentum of the particle with the given `index`, as an
212 instance of `Vector3DReal`
215 Throws if `index` is negative or `index >= getParticleCount()`.
217 Throws if no mass has been specified for the given particle.
219 Throws if `index` is not an `int`.
222 The index of the particle to query, in the range of
223 `[0, getParticleCount() - 1]`.
231 Returns the center of mass of the particles, as an instance of
235 Throws if `isEmpty()`.
237 Throws if the mass has not been specified for all particles.
246 for index, position
in enumerate(self.
positions):
247 currentMass = self.
getMass(index)
249 cumulative += position * currentMass
251 centerOfMass = cumulative / mass
257 Returns the unweighted average of all particle positions, as an instance
261 Throws if `isEmpty()`.
270 cumulative += position
277 Returns the velocity of the center of mass, as an instance of
281 Throws if `isEmpty()`.
283 Throws if the mass has not been specified for all particles.
292 for index, velocity
in enumerate(self.
velocities):
293 currentMass = self.
getMass(index)
295 cumulative += velocity * currentMass
297 centerOfMassVelocity = cumulative / mass
298 return centerOfMassVelocity
303 Rotates all position and velocity vectors around the given `axis` for
307 Throws if one of the arguments is of the wrong type.
309 Throws if `axis` is not a unit-length vector.
312 The axis to rotate around, which must have unit length.
314 The angle to rotate, in radians, as an instance of `float`.
317 if not isinstance(axis, Vector3DReal):
320 if not isinstance(angle, float):
323 if not axis.getLengthSquared() == 1.0:
328 position.rotateAroundNormalizedAxis(axis, angle)
331 velocity.rotateAroundNormalizedAxis(axis, angle)
338 Transforms the positions and velocities into the center of mass frame.
344 for index, _
in enumerate(self.
positions):
348 self.
velocities[index] -= centerOfMassVelocity
355 Returns the gyration tensor \f$ S \f$.
357 The returned object is a \f$ 3 \times 3 \f$ symmetric matrix of type
359 Given \f$ N \f$ particles with positions \f$ \vec{r}^{(i)} \f$ (in any
360 Cartesian coordinate frame), the \f$ \left( m, n \right) \f$-component
361 of the gyration tensor is defined by
368 \left( \vec{r}^{(i)}_m - \vec{r}^{(j)}_m \right)
369 \left( \vec{r}^{(i)}_n - \vec{r}^{(j)}_n \right)
373 Throws if there are no particles in this instance.
383 S = numpy.zeros((3, 3))
387 for m
in range(0, 3):
388 for n
in range(m, 3):
389 S[m][n] += (r1[m] - r2[m]) * (r1[n] - r2[n])
392 for m
in range(0, 3):
393 for n
in range(m, 3):
404 def getMomentOfInertiaTensor(self):
406 if centerOfMass.getLengthSquared() > 1e-20:
408 "The center of mass has to be at the origin! " +
409 "Instead, it squared distance to the origin is: " +
410 str(centerOfMass.getLengthSquared()))
419 for index, position
in enumerate(self.
positions):
425 Ixx += mass * (y * y + z * z)
428 Iyy += mass * (x * x + z * z)
430 Izz += mass * (x * x + y * y)
439 def getTotalAngularMomentumVector(self):
442 for index, position
in enumerate(self.
positions):
447 def getRotationFrequencyVector(self):
450 L = numpy.array([L.getX(), L.getY(), L.getZ()])
451 omega = numpy.linalg.inv(I).dot(L)
457 Returns the eigenvalues \f$ \lambda_x^2 \f$, \f$ \lambda_y^2 \f$, and
458 \f$ \lambda_z^2 \f$ of the gyration tensor (as returned by
459 `getGyrationTensor`). The eigenvalues are arranged such that
460 \f$ \lambda_x^2 \le \lambda_y^2 \le \lambda_y^z \f$.
462 The eigenvalues are real and of type `numpy.float64`, and returned as
463 the elements of a list.
467 eigenvalues, _ = numpy.linalg.eig(S)
469 x, y, z = numpy.sort(eigenvalues)
471 if not (numpy.isreal(x)
and numpy.isreal(y)
and numpy.isreal(z)):
472 raise ValueError(
"Unexpected")
479 Returns the eigenvalues and eigenvectors of the gyration tensor (as
480 returned by `getGyrationTensor`).
482 The returned object is a list, with each element being a list of first
483 the eigenvalue, and second the associated eigenvector. The tuples are
484 sorted by the value of the eigenvalue, smallest first.
486 The eigenvalues are real and of type `numpy.float64`, and the
487 eigenvectors are of type `numpy.ndarray` with three entries of type
492 eigenvalues, eigenvectors = numpy.linalg.eig(S)
494 for eigenvalue
in eigenvalues:
495 if not numpy.isreal(eigenvalue):
496 raise ValueError(
"Unexpected")
498 indices = eigenvalues.argsort()
501 for index
in indices:
502 ret.append([eigenvalues[index], eigenvectors[:, index]])
509 Returns the sum of the eigenvalues of the gyration tensor, as returned
510 by `getGyrationTensorPrincipalMoments`
519 Returns the radius of gyration \f$ R_g \f$, i.e. the square root of the
520 result of `getRadiusOfGyrationSquared`.
527 Returns the asphericity \f$ b \f$, i.e.
529 \lambda_z^2 - \frac{1}{2} \left( \lambda_x^2 + \lambda_y^2 \right)
531 where \f$ \lambda_z^2 \f$ is the largest eigenvalue of the gyration
532 tensor, and \f$ \lambda_x^2 \f$ and \f$ \lambda_y^2 \f$ are the two
533 smallest eigenvalues.
538 return z - 0.5 * (x + y)
542 Returns the acylindricity \f$ c \f$, i.e.
544 \lambda_y^2 - \lambda_x^2
546 where \f$ \lambda_x^2 \f$ is the smallest eigenvalue of the gyration
547 tensor, \f$ \lambda_z^2 \f$ is the largest eigenvalue, and
548 \f$ \lambda_y^2 \f$ is the one in between.
557 Returns the relative shape anisotropy \f$ \kappa^2 \f$, i.e.
559 \frac{ b^2 + \frac{3}{4} c^2 }{ R_g^4 }
561 where \f$ b \f$ is the asphericity, \f$ c \f$ is the acylindricity, and
562 \f$ R_g \f$ is the radius of gyration.
569 return (b * b + 0.75 * c * c) / (R_g_squared * R_g_squared)
573 Returns the angle between the given `axis` and the eigenvector of the
574 gyration tensor that corresponds to the largest eigenvalue.
586 Returns the angle between the given `axis` and the eigenvectors of the
587 gyration tensor. The returned value is a list, sorted by increasing
588 eigenvalues, of pairs of eigenvalue and corresponding angle.
597 for eigenpair
in eigenpairs:
598 eigenvalue = eigenpair[0]
599 eigenvector = eigenpair[1]
603 ret.append([eigenvalue, angle])
609 def _getAngleBetweenLines(self, v1, v2):
611 Returns the angle between two lines, characterized by the given two
615 dot = numpy.dot(v1, v2)
619 len1Squared = numpy.dot(v1, v1)
620 len2Squared = numpy.dot(v2, v2)
622 return numpy.arccos(dot / numpy.sqrt(len1Squared * len2Squared))
627 The equality operator.
629 Returns whether this instance stores the same particles (i.e. their
630 count, positions, velocities, and masses) as the given `rhs` instance.
632 Having set a different uniform mass in two instances makes them not
633 equal, even if there are no particles.
635 @param[in] rhs The right-hand-side instance to compare to.
637 @return Returns whether the two instances are equal, or `NotImplemented`
638 if the two instances are not of the same type.
641 if not isinstance(rhs, self.__class__):
642 return NotImplemented
644 if len(self.
positions) != len(rhs.positions):
650 for index, position
in enumerate(self.
positions):
651 if position != rhs.positions[index]:
653 if self.
velocities[index] != rhs.velocities[index]:
661 The inequality operator.
663 @param[in] rhs The right-hand-side instance to compare to.
665 @return Returns `NotImplemented` if the two instances are not of the
666 same type, and `not self == rhs` otherwise.
669 if not isinstance(rhs, self.__class__):
670 return NotImplemented
672 return not self == rhs