Example M7: Displacement vectors with reference configuration
This Python modifier computes each particle’s displacement vector with respect to a
reference configuration of the system, which is loaded by the modifier from a separate input file. Thus, it
replicates some of the functionality provided by the built-in CalculateDisplacementsModifier
of OVITO.
The modifier is based on the advanced programming interface, i.e., it is implemented in the form of a
Python class inheriting from ModifierInterface.
See also
from ovito.data import DataCollection
from ovito.pipeline import ModifierInterface, FileSource
from ovito.traits import OvitoObjectTrait
from ovito.vis import VectorVis
from traits.api import Int, Bool
class CalculateDisplacementsWithReference(ModifierInterface):
# Give the modifier a second input slot for reading the reference config from a separate file:
reference = OvitoObjectTrait(FileSource)
# The trajectory frame from the reference file to use as (static) reference configuration (default 0).
reference_frame = Int(default_value=0, label='Reference trajectory frame')
# This flag controls whether the modifier tries to detect when particles have crossed a periodic boundary
# of the simulation cell. The computed displacement vectors will be corrected accordingly.
minimum_image_convention = Bool(default_value=True, label='Use minimum image convention')
# A VectorVis visual element managed by this modifier, which will be assigned to the 'Displacement' output property to visualize the vectors.
vector_vis = OvitoObjectTrait(VectorVis, alignment=VectorVis.Alignment.Head, flat_shading=False, title='Displacements')
# Tell the pipeline system to keep two trajectory frames in memory: the current input frame and the reference configuration.
def input_caching_hints(self, frame: int, **kwargs):
return {
'upstream': frame,
'reference': self.reference_frame
}
# The actual function called by the pipeline system to let the modifier do its thing.
def modify(self, data: DataCollection, *, input_slots: dict[str, ModifierInterface.InputSlot], **kwargs):
# Request the reference configuration.
ref_data = input_slots['reference'].compute(self.reference_frame)
# Get current particle positions and reference positions, making sure the ordering of the two arrays
# is the same even if the storage order of particles changes with time.
current_positions = data.particles.positions
reference_positions = ref_data.particles.positions[ref_data.particles.remap_indices(data.particles)]
# Compute particle displacement vectors. Use SimulationCell.delta_vector() method to
# correctly handle particles that have crossed a periodic boundary.
if self.minimum_image_convention and data.cell:
displacements = data.cell.delta_vector(reference_positions, current_positions)
else:
displacements = current_positions - reference_positions
# Output the computed displacement vectors as a new particle property.
# Assign our visual element to the property to render the displacement vectors as arrows.
data.particles_.create_property("Displacement", data=displacements).vis = self.vector_vis