Example M7: Displacement vectors with reference configuration — OVITO Python Reference 3.16.0 documentation

# 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

- Input data caching

- User parameters

- Defining additional input slots

- `Particles.remap_indices()`

- `SimulationCell.delta_vector()`

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