Example M7: Displacement vectors with reference configuration
This Python modifier computes each particle’s displacement vector with respect to an explicit
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
.
from ovito.data import DataCollection
from ovito.pipeline import ModifierInterface, FileSource
from ovito.traits import OvitoObject
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 = OvitoObject(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 = OvitoObject(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
See also
For the sake of completeness, we also provide a version of the modifier that does not load an explicit reference configuration from a separate file. Instead, the following version of the modifier obtains the reference particle positions from the current upstream pipeline by evaluating it at a given animation time:
from ovito.data import DataCollection
from ovito.pipeline import ModifierInterface
from ovito.traits import OvitoObject
from ovito.vis import VectorVis
from traits.api import Int, Bool
class CalculateDisplacements(ModifierInterface):
# The trajectory frame to use as 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 = OvitoObject(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 frame.
def input_caching_hints(self, frame: int, **kwargs):
return [frame, 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 from the upstream pipeline.
ref_data = input_slots['upstream'].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