ovito.data

This module contains various types of data objects, which are produced and processed within OVITO’s data pipeline system. It also provides the DataCollection container class for such data objects and some additional utility classes to compute neighbor lists to iterate over the bonds of particles.

Data container:

Data objects:

Auxiliary data classes:

Utility classes:

class ovito.data.DataCollection

A DataCollection is a container that holds together multiple data objects, each representing a different facet of a dataset. Data collections are the main entities that are generated and processed in OVITO’s data pipeline system. DataCollection instances are typically returned by the Pipeline.compute() and the FileSource.compute() methods and contain the results of a data pipeline.

Within a data collection, you will typically find a bunch of data objects, which collectively form the dataset, for example:

All these types derive from the common DataObject base class. A DataCollection comprises two main parts:

  1. The objects list, which can hold an arbitrary number of data objects of the types listed above.
  2. The attributes dictionary, which stores auxialliary data in the form of simple key-value pairs.

Data object access

The find() and find_all() methods allow you to look up data objects in the objects list of a data collection by type. For example, to retrieve the SimulationCell from a data collection:

data = pipeline.compute()
cell = data.find(SimulationCell)

The find() method yields None if there is no instance of the given type in the collection. Alternatively, you can use the expect() method, which will instead raise an exception in case the requested object type is not present:

cell = data.expect(SimulationCell)

It is possible to programmatically add or remove data objects from the data collection by manipulating its objects list. For instance, to populate a new data collection with a SimulationCell object we can write:

cell = SimulationCell()
mydata = DataCollection()
mydata.objects.append(cell)

There are certain conventions regarding the numbers and types of data objects that may be present in a data collection. For example, there should never be more than one SimulationCell instance in a data collection. In contrast, there may be an arbitrary number of ParticleProperty instances in a data collection, but they all must have unique names and the same array length. Furthermore, there must always be one ParticleProperty named Position in a data collection, or no ParticleProperty at all. When manipulating the objects list of a data collection directly, it is your responsibility to make sure that these conventions are followed.

Particle and bond access

To simplify the work with particles and bonds, which are represented by a bunch of ParticleProperty or BondProperty instances, respectively, the DataCollection class provides two special accessor fields. The particles field represents a dictionary-like view of all the ParticleProperty data objects that are contained in a data collection. It thus works like a dynamic filter for the objects list and permits name-based access to individual particle properties:

positions = data.particles['Position']
assert(positions in data.objects)

Similarly, the bonds field is a dictionary-like view of all the BondProperty instances in a data collection. If you are adding or removing particle or bond properties in a data collection, you should always do so through these accessor fields instead of manipulating the objects list directly. This will ensure that certain invariants are always maintained, e.g. the uniqueness of property names and the consistent size of all property arrays.

Attribute access

In addition to data objects, which represent complex forms of data, a data collection can store an arbitrary number of attributes, which are simple key-value pairs. The attributes field of the data collection behaves like a Python dictionary and allows you to read, manipulate or newly insert attributes, which are typically numeric values or string values.

Data ownership

One data object may be part of several DataCollection instances at a time, i.e. it may be shared by several data collections. OVITO’ pipeline system uses shallow data copies for performance reasons and to implement efficient data caching. Modifiers typically manipulate only certain data objects in a collection. For example, the ColorCodingModifier will selectively modify the values of the Color particle property but won’t touch any of the other data objects present in the input data collection. The unmodified data objects will simply be passed through to the output data collection without creating a new copy of the data values. As a consequence of this design, both the input data collection and the output collection of the pipeline may refer to the same data objects. In such a situation, no data collection owns the data objects exclusively anymore.

Thus, in general it is not safe to manipulate the contents of a data object in a data collection, because that could lead to unwanted side effects or corruption of data maintained by the pipeline system. For example, modifying the particle positions in a data collection that was returned by a system function is forbidden (or rather discouraged):

data = pipeline.compute()
positions = data.particles['Position']
with positions:                # NEVER DO THIS! You may accidentally be manipulating 
    positions[...] += (0,0,2)  # shared data that is owned by the pipeline system!

Before manipulating the contents of a data object in any way, it is crucial to ensure that no second data collection is referring to the same object. The copy_if_needed() method helps you ensure that a data object is exclusive owned by a certain data collection:

# First, pass the data object from the collection to the copy_if_needed() method.
# A data copy is made if and only if the collection is not yet the exclusive owner.
positions = data.copy_if_needed( data.particles['Position'] )

# Now it's guaranteed that the data object is not shared by any other collection 
# and it has become safe to modify its contents:
with positions:
    positions[...] += (0,0,2) # Displacing all particles along z-direction.

copy_if_needed() first checks whether the given object is currently shared by more than one data collection. If yes, a deep copy of the object is made and the original object in the data collection is replaced with the copy. Now we can be confident that the copied data object is exclusively owned by the data collection and it’s safe to modify it without risking side effects.

attributes

A dictionary of key-value pairs that represent global tokens of information which are not associated with any specific data object in the data collection.

An attribute is a value of type int, float, or str with a unique identifier name such as "Timestep" or "ConstructSurfaceMesh.surface_area". The attribute name serves as keys for the attributes dictionary of the data collection. Attributes are dynamically generated by modifiers in a data pipeline or by a data source as explained in the following.

Attributes loaded from input files

The Timestep attribute is loaded from LAMMPS dump files and other simulation file formats that store the simulation timestep. Such input attributes can be retrieved from the .attributes dictionary of a pipeline’s FileSource:

>>> pipeline = import_file('snapshot_140000.dump')
>>> pipeline.source.attributes['Timestep']
140000

Other attributes read from an input file are, for example, the key-value pairs found in the header line of extended XYZ files.

Dynamically computed attributes

Analysis modifiers like the CommonNeighborAnalysisModifier or the ClusterAnalysisModifier output scalar computation results as attributes. The reference documentation of each modifier type lists the attributes it produces.

For example, the number of clusters identified by the ClusterAnalysisModifier can be queried as follows:

pipeline.modifiers.append(ClusterAnalysisModifier(cutoff = 3.1))
data = pipeline.compute()
nclusters = data.attributes["ClusterAnalysis.cluster_count"]

Exporting attributes to a text file

The ovito.io.export_file() function supports writing attribute values to a text file, possibly as functions of time:

export_file(pipeline, "data.txt", "txt", 
    columns = ["Timestep", "ClusterAnalysis.cluster_count"], 
    multiple_frames = True)

User-defined attributes

The PythonScriptModifier allows you to generate your own attributes that are dynamically computed (typically on the basis of some other input information):

pipeline.modifiers.append(CommonNeighborAnalysisModifier())
            
def compute_fcc_fraction(frame, input, output):
    n_fcc = input.attributes['CommonNeighborAnalysis.counts.FCC']
    output.attributes['fcc_fraction'] = n_fcc / input.particles.count


pipeline.modifiers.append(PythonScriptModifier(function = compute_fcc_fraction))
print(pipeline.compute().attributes['fcc_fraction'])

The CommonNeighborAnalysisModifier used in the example above generates the attribute CommonNeighborAnalysis.counts.FCC to report the number of atoms that form an FCC lattice. To compute the fraction of FCC atoms from that, we need to divide by the total number of atoms in the system. To this end, we insert a PythonScriptModifier into the pipeline behind the CommonNeighborAnalysisModifier. Our custom modifier function generates a new attribute named fcc_fraction. Finally, the value of the user-defined attribute can be queried from the pipeline or exported to a text file using the export_file() function as described above.

bonds

Returns a BondsView, which allows to access all BondProperty objects stored in this data collection by name. Furthermore, it provides convenience functions for adding new bond properties to the collection.

copy_if_needed(obj, deepcopy=False)

Makes a copy of a data object from this data collection if the object is not exclusively owned by the data collection but shared with other collections. After the method returns, the data object is exclusively owned by the collection and it becomes safe to modify the object without causing unwanted side effects.

Typically, this method is used in custom modifier functions (see PythonScriptModifier) that participate in OVITO’s data pipeline system. A modifier function receives an input collection of data objects from the system. However, modifying these input objects in place is not allowed, because they are owned by the pipeline and modifying them would lead do unexpected side effects. This is where this method comes into play: It makes a copy of a given data object and replaces the original in the data collection with the copy. The caller can now safely modify this copy in place, because no other data collection can possibly be referring to it.

The copy_if_needed() method first checks if obj, which must be a data object from this data collection, is shared with some other data collection. If yes, it creates an exact copy of obj and replaces the original in this data collection with the copy. Otherwise it leaves the object as is, because it is already exclusively owned by this data collection.

Parameters:obj (DataObject) – The object from this data collection to be copied if needed.
Returns:An exact copy of obj if it was shared with some other data collection. Otherwise the original object is returned.
expect(object_type)

Looks up the first data object in this collection of the given class type. Raises a KeyError if there is no instance matching the type. Use find() instead to test if the data collection contains the given type of data object.

Parameters:object_type – The DataObject subclass specifying the type of object to find.
Returns:The first instance of the given class or its subclasses from the objects list.
find(object_type)

Looks up the first data object from this collection of the given class type.

Parameters:object_type – The DataObject subclass that should be looked up.
Returns:The first instance of the given class or its subclasses from the objects list; or None if there is no instance.

Method implementation:

def find(self, object_type):
    for o in self.objects:
        if isinstance(o, object_type): return o
    return None
find_all(object_type)

Looks up all data objects from this collection of the given class type.

Parameters:object_type – The DataObject subclass that should be looked up.
Returns:A Python list containing all instances of the given class or its subclasses from the objects list.

Method implementation:

def find_all(self, object_type):
    return [o for o in self.objects if isinstance(o, object_type)]
objects

The list of data objects that make up the data collection. Data objects are instances of DataObject-derived classes, for example ParticleProperty, Bonds or SimulationCell.

You can add or remove objects from the objects list to insert them or remove them from the DataCollection. However, it is your responsibility to ensure that the data objects are all in a consistent state. For example, all ParticleProperty objects in a data collection must have the same lengths at all times, because the length implicitly specifies the number of particles. The order in which data objects are stored in the data collection does not matter.

Note that the DataCollection class also provides convenience views of the data objects contained in the objects list: For example, the particles dictionary lists all ParticleProperty instances in the data collection by name and the bonds does the same for all BondProperty instances. Since these dictionaries are views, they always reflect the current contents of the master objects list.

particles

Returns a ParticlesView object representing the particles, which provides name-based access to the ParticleProperty instances stored in this DataCollection. Furthermore, the view object provides convenience functions for creating new particle properties.

class ovito.data.DataObject

Abstract base class for all data objects. A DataObject represents a data fragment processed and produced by a data pipeline. See the ovito.data module for a list of the different types of data objects in OVITO. Typically, a data object is contained in a DataCollection together with other data objects, forming a data set. Furthermore, data objects may be shared by several data collections.

Certain data objects are associated with a DataVis object, which is responsible for generating the visual representation of the data and rendering it in the viewports. The vis field provides access to the attached visual element, which can be configured as needed to change the visual appearance of the data. The different visual element types of OVITO are all documented in the ovito.vis module.

vis

The DataVis element associated with this data object, which is responsible for rendering the data visually. If this field contains None, the data is non-visual and doesn’t appear in rendered images or the viewports.

class ovito.data.SimulationCell
Base class:ovito.data.DataObject

Stores the geometric shape and the boundary conditions of the simulation cell. A SimulationCell data object is typically part of a DataCollection and can be retrieved through its expect() method:

from ovito.io import import_file
from ovito.data import SimulationCell

pipeline = import_file("input/simulation.dump")
cell = pipeline.compute().expect(SimulationCell)

# Print cell matrix to the console. [...] is for casting to Numpy. 
print(cell[...])

The simulation cell geometry is stored as a 3x4 matrix (with column-major ordering). The first three columns of the matrix represent the three cell vectors and the last column is the position of the cell’s origin. For two-dimensional datasets, the is2D flag ist set. In this case the third cell vector and the z-coordinate of the cell origin are ignored by OVITO.

import numpy.linalg

# Compute simulation cell volume by taking the determinant of the
# left 3x3 submatrix of the cell matrix:
vol = abs(numpy.linalg.det(cell[0:3,0:3]))

# The SimulationCell.volume property computes the same value.
assert numpy.isclose(cell.volume, vol)

The SimulationCell object behaves like a standard Numpy array of shape (3,4). Data access is read-only, however. If you want to manipulate the cell vectors, you have to use a with compound statement as follows:

# Make cell twice as large along the Y direction by scaling the second cell vector: 
with cell:
    cell[:,1] *= 2.0

A SimulationCell instance are always associated with a corresponding SimulationCellVis, which controls the visual appearance of the simulation box. It can be accessed through the vis attribute inherited from DataObject.

# Change display color of simulation cell to red:
cell.vis.rendering_color = (1.0, 0.0, 0.0)
is2D

Specifies whether the system is two-dimensional (true) or three-dimensional (false). For two-dimensional systems the PBC flag in the third direction (z) and the third cell vector are ignored.

Default:false
pbc

A tuple of three boolean values, which specify periodic boundary flags of the simulation cell along each cell vector.

volume

Computes the volume of the three-dimensional simulation cell. The volume is the absolute value of the determinant of the 3x3 submatrix formed by the three cell vectors.

volume2D

Computes the area of the two-dimensional simulation cell (see is2D).

class ovito.data.Property
Base class:ovito.data.DataObject

Stores the values for an array of elements (e.g. particle or bonds).

In OVITO’s data model, an arbitrary number of properties can be associated with data elements such as particle or bonds, each property being represented by a Property object. A Property is basically an array of values whose length matches the number of data elements.

Property is the common base class for the ParticleProperty and BondProperty specializations.

Data access

A Property object behaves almost like a Numpy array. For example, you can access the property value for the i-th data element using indexing:

property = data.particles['Velocity']
print('Velocity vector of first particle:', property[0])
print('Z-velocity of second particle:', property[1,2])
for v in property: print(v)

Element indices start at zero. Properties can be either vectorial (e.g. velocity vectors are stored as an N x 3 array) or scalar (1-d array of length N). Length of the first array dimension is in both cases equal to the number of data elements (number of particles in the example above). Array elements can either be of data type float or int.

If necessary, you can cast a Property to a standard Numpy array:

velocities = numpy.asarray(property)

No data is copied during the conversion; the Numpy array will refer to the same memory as the Property. By default, the memory of a Property is write-protected. Thus, trying to modify property values will raise an error:

property[0] = (0, 0, -4) # "ValueError: assignment destination is read-only"

A direct modification is prevented by the system, because OVITO’s data pipeline uses shallow data copies and needs to know when data objects are being modified. Only then results that depend on the changing data can be automatically recalculated. We need to explicitly announce a modification by using Python’s with statement:

with property:
    property[0] = (0, 0, -4)

Within the with compound statement, the array is temporarily made writable, allowing us to alter the per-particle data stored in the Property object.

components

The number of vector components if this is a vector property; or 1 if this is a scalar property.

name

The name of the property.

class ovito.data.SurfaceMesh
Base class:ovito.data.DataObject

This data object type stores a triangle mesh describing a surface or, more precisely, a two-dimensional manifold that is closed and orientable. Typically, surface meshes are produced by modifiers such as the ConstructSurfaceModifier, CreateIsosurfaceModifier or CoordinationPolyhedraModifier.

Periodic domains

What is special about surface meshes is that they may be embedded in a periodic domain, i.e. a simulation cell with periodic boundary conditions. That means triangles of a surface mesh can connect vertices on opposite sides of a simulation box and wrap around correctly. OVITO takes care of computing the intersections of the periodic surface with the box boundaries and automatically produces a non-periodic representation of the triangle mesh when it comes to visualizing the surface.

The domain the surface mesh is embedded in is represented by a SimulationCell object, which is attached to the SurfaceMesh instance. You can access it through the domain attribute.

Visual representation

The visual appearance of the surface mesh in rendered images is controlled by its attached SurfaceMeshVis element, which is accessible through the vis base class attribute.

Interior and exterior region

As surface meshes are closed orientable manifolds, one can define an interior and an exterior region of space that are separated by the manifold. For example, if the surface mesh is constructed by the ConstructSurfaceModifier from a set of particles, then the region enclosed by the surface is the “solid” region and the outside region is the one containing no particles.

It can be that there is no interior region and the exterior region is infinite and fills all space. In this case the surface mesh is degenerate and comprises no triangles. The opposite extreme is also possible in periodic domains: The interior region extends over the entire domain and there is no outside region. Again, the surface mesh will consist of zero triangles in this case. To discriminate between the two situations, the SurfaceMesh class provides the all_interior flag, which is set when the interior region fills the entire periodic domain.

The locate_point() method can be used to test whether some point in space belongs to the interior or the exterior region.

File export

A surface mesh can be written to a file in the form of a conventional triangle mesh. For this, a non-periodic version is produced by truncating triangles at the domain boundaries and generating “cap polygons” to fill the holes that occur at the intersection of the interior region with the domain boundaries. To export the mesh, use the ovito.io.export_file() function and select vtk/trimesh as output format:

from ovito.io import import_file, export_file
from ovito.data import SurfaceMesh
from ovito.modifiers import ConstructSurfaceModifier

# Load a particle set and construct the surface mesh:
pipeline = import_file("input/simulation.dump")
pipeline.modifiers.append(ConstructSurfaceModifier(radius = 2.8))
mesh = pipeline.compute().expect(SurfaceMesh)

# Export the mesh to a VTK file for visualization with ParaView.
export_file(mesh, 'output/surface_mesh.vtk', 'vtk/trimesh')

Cutting planes

An arbitrary number of cutting planes can be attached to a SurfaceMesh, which allow to cut away parts of the mesh for visualization purposes. This is sometimes useful, if you want to open a hole in a closed surface to allow a look inside. The SurfaceMesh maintains a list of cutting planes, which are accessible through the get_cutting_planes() and set_cutting_planes() methods. Note that the cuts are non-destructive and dynamically computed only on the transient version of the mesh produced for visualization and data export purposes. The SliceModifier, which can act on a SurfaceMesh, performs the slice by simply adding a new entry to the SurfaceMesh’s list of cutting planes.

Mesh data access

The methods get_vertices(), get_faces() and get_face_adjacency() methods provide access to the internal data of the surface mesh.

all_interior

Boolean flag indicating that the SurfaceMesh is degenerate and the interior region extends over the entire domain.

domain

The SimulationCell describing the (possibly periodic) domain in which this surface mesh is embedded in.

get_cutting_planes()

Returns a N x 4 array containing the definitions of the N cutting planes attached to this SurfaceMesh.

Each plane is defined by its unit normal vector and a signed displacement magnitude, which determines the plane’s distance from the coordinate origin along the normal, giving four numbers per plane in total. Those parts of the surface mesh which are on the positive side of the plane (in the direction the normal vector) are cut away.

Note that the returned Numpy array is a copy of the internal data stored by the SurfaceMesh.

get_face_adjacency()

Returns a M x 3 array listing the indices of the three faces that are adjacent to each of the M triangle faces in the mesh. This information can be used to traverse the neighbors of triangle faces. Every triangle face has exactly three neighbors, because surface meshes are closed manifolds.

get_faces()

Returns a M x 3 array with the vertex indices of the M triangles in the mesh. Note that the returned Numpy array is a copy of the internal data stored by the SurfaceMesh. Also keep in mind that a triangle face can cross domain boundaries if PBCs are used.

get_vertices()

Returns a N x 3 array with the xyz coordinates of the N vertices in the mesh. Note that the returned Numpy array is a copy of the internal data stored by the SurfaceMesh.

locate_point(pos, eps=1e-6)

Determines whether a spatial location is inside the region enclosed by the surface, outside of it, or exactly on the surface itself.

Parameters:
  • pos – The (x,y,z) coordinates of the test point
  • eps – Numerical precision threshold for point-on-surface test
Returns:

-1 if pos is inside the region enclosed by the surface, +1 if outside, 0 if exactly on the surface

set_cutting_planes(planes)

Sets the cutting planes to be applied to this SurfaceMesh. The array planes must follow the same format as the one returned by get_cutting_planes().

class ovito.data.DislocationNetwork
Base class:ovito.data.DataObject

This data object types stores the network of dislocation lines extracted by a DislocationAnalysisModifier.

Instances of this class are associated with a DislocationVis that controls the visual appearance of the dislocation lines. It can be accessed through the vis attribute of the DataObject base class.

Example:

from ovito.io import import_file, export_file
from ovito.modifiers import DislocationAnalysisModifier
from ovito.data import DislocationNetwork

pipeline = import_file("input/simulation.dump")

# Extract dislocation lines from a crystal with diamond structure:
modifier = DislocationAnalysisModifier()
modifier.input_crystal_structure = DislocationAnalysisModifier.Lattice.CubicDiamond
pipeline.modifiers.append(modifier)
data = pipeline.compute()

total_line_length = data.attributes['DislocationAnalysis.total_line_length']
cell_volume = data.attributes['DislocationAnalysis.cell_volume']
print("Dislocation density: %f" % (total_line_length / cell_volume))

# Print list of dislocation lines:
network = data.expect(DislocationNetwork)
print("Found %i dislocation segments" % len(network.segments))
for segment in network.segments:
    print("Segment %i: length=%f, Burgers vector=%s" % (segment.id, segment.length, segment.true_burgers_vector))
    print(segment.points)

# Export dislocation lines to a CA file:
export_file(pipeline, "output/dislocations.ca", "ca")

# Or export dislocations to a ParaView file:
export_file(pipeline, "output/dislocations.vtk", "vtk/disloc")

File export

A dislocation network can be written to a data file in the form of polylines using the ovito.io.export_file() function (select the vtk/disloc output format). During export, a non-periodic version is produced by clipping dislocation lines at the domain boundaries.

segments

The list of dislocation segments in this dislocation network. This list-like object is read-only and contains DislocationSegment objects.

class ovito.data.DislocationSegment

A single dislocation line from a DislocationNetwork.

The list of dislocation segments is returned by the DislocationNetwork.segments attribute.

cluster_id

The numeric identifier of the crystal cluster of atoms containing this dislocation segment.

The true Burgers vector of the segment is expressed in the local coordinate system of this crystal cluster.

id

The unique identifier of this dislocation segment.

is_infinite_line

This property indicates whether this segment is an infinite line passing through a periodic simulation box boundary. A segment is considered infinite if it is a closed loop and its start and end points do not coincide.

See also the is_loop property.

is_loop

This property indicates whether this segment forms a closed dislocation loop. Note that an infinite dislocation line passing through a periodic boundary is also considered a loop.

See also the is_infinite_line property.

length

Returns the length of this dislocation segment.

points

The list of space points that define the shape of this dislocation segment. This is a N x 3 Numpy array, where N is the number of points along the segment. For closed loops, the first and the last point coincide.

spatial_burgers_vector

The Burgers vector of the segment, expressed in the global coordinate system of the simulation. This vector is calculated by transforming the true Burgers vector from the local lattice coordinate system to the global simulation coordinate system using the average orientation matrix of the crystal cluster the dislocation segment is embedded in.

true_burgers_vector

The Burgers vector of the segment, expressed in the local coordinate system of the crystal. Also known as the True Burgers vector.

class ovito.data.ParticleProperty
Base class:ovito.data.Property

Stores an array of per-particle values. This class derives from Property, which provides the base functionality shared by all property types in OVITO.

In OVITO’s data model, an arbitrary number of properties can be associated with the particles, each property being represented by a separate ParticleProperty object. A ParticleProperty is basically an array of values whose length matches the number of particles.

The set of properties currently associated with all particles is exposed by the DataCollection.particles view, which allows accessing them by name and adding new properties.

Standard properties

OVITO differentiates between standard properties and user-defined properties. The former have a special meaning to OVITO, a prescribed name and data layout. Certain standard properties control the visual representation of particles. Typical examples are the Position property, the Color property and the Radius property. User-defined properties, on the other hand, may have arbitrary names (as long as they do not collide with one of the standard names) and the property values have no special meaning to OVITO, only to you, the user. Whether a ParticleProperty is a standard or a user-defined property is indicated by the value of its type attribute.

Creating particle properties

New properties can be created and assigned to particles with the ParticlesView.create_property() factory method. User-defined modifier functions, for example, use this to output their computation results.

Typed particle properties

The standard property 'Particle Type' stores the types of particles encoded as integer values, e.g.:

>>> data = node.compute()
>>> tprop = data.particles['Particle Type']
>>> print(tprop[...])
[2 1 3 ..., 2 1 2]

Here, each number in the property array refers to a defined particle type (e.g. 1=Cu, 2=Ni, 3=Fe, etc.). The defined particle types, each one represented by an instance of the ParticleType auxiliary class, are stored in the types array of the ParticleProperty object. Each type has a unique id, a human-readable name and other attributes like color and radius that control the visual appearance of particles belonging to the type:

>>> for type in tprop.types:
...     print(type.id, type.name, type.color, type.radius)
... 
1 Cu (0.188 0.313 0.972) 0.74
2 Ni (0.564 0.564 0.564) 0.77
3 Fe (1 0.050 0.050) 0.74

IDs of types typically start at 1 and form a consecutive sequence as in the example above. Note, however, that the types list may store the ParticleType objects in an arbitrary order. Thus, in general, it is not valid to directly use a type ID as an index into the types array. Instead, the type_by_id() method should be used to look up the ParticleType:

>>> for i,t in enumerate(tprop): # (loop over the type ID of each particle)
...     print('Atom', i, 'is of type', tprop.type_by_id(t).name)
...
Atom 0 is of type Ni
Atom 1 is of type Cu
Atom 2 is of type Fe
Atom 3 is of type Cu

Similarly, a type_by_name() method exists that looks up a ParticleType by name. For example, to count the number of Fe atoms in a system:

>>> Fe_type_id = tprop.type_by_name('Fe').id   # Determine ID of the 'Fe' type
>>> numpy.count_nonzero(tprop == Fe_type_id)   # Count particles having that type ID
957

Note that OVITO supports multiple type classifications. For example, in addition to the 'Particle Type' standard particle property, which stores the chemical types of atoms (e.g. C, H, Fe, …), the 'Structure Type' property may hold the structural types computed for atoms (e.g. FCC, BCC, …) maintaining its own list of known structure types in the types array.

type

The type of the particle property. One of the following constants:

Type constant Property name Data type Component names
ParticleProperty.Type.User (a user-defined property with a non-standard name) int/float  
ParticleProperty.Type.ParticleType Particle Type int  
ParticleProperty.Type.Position Position float X, Y, Z
ParticleProperty.Type.Selection Selection int  
ParticleProperty.Type.Color Color float R, G, B
ParticleProperty.Type.Displacement Displacement float X, Y, Z
ParticleProperty.Type.DisplacementMagnitude Displacement Magnitude float  
ParticleProperty.Type.PotentialEnergy Potential Energy float  
ParticleProperty.Type.KineticEnergy Kinetic Energy float  
ParticleProperty.Type.TotalEnergy Total Energy float  
ParticleProperty.Type.Velocity Velocity float X, Y, Z
ParticleProperty.Type.Radius Radius float  
ParticleProperty.Type.Cluster Cluster int  
ParticleProperty.Type.Coordination Coordination int  
ParticleProperty.Type.StructureType Structure Type int  
ParticleProperty.Type.Identifier Particle Identifier int  
ParticleProperty.Type.StressTensor Stress Tensor float XX, YY, ZZ, XY, XZ, YZ
ParticleProperty.Type.StrainTensor Strain Tensor float XX, YY, ZZ, XY, XZ, YZ
ParticleProperty.Type.DeformationGradient Deformation Gradient float XX, YX, ZX, XY, YY, ZY, XZ, YZ, ZZ
ParticleProperty.Type.Orientation Orientation float X, Y, Z, W
ParticleProperty.Type.Force Force float X, Y, Z
ParticleProperty.Type.Mass Mass float  
ParticleProperty.Type.Charge Charge float  
ParticleProperty.Type.PeriodicImage Periodic Image int X, Y, Z
ParticleProperty.Type.Transparency Transparency float  
ParticleProperty.Type.DipoleOrientation Dipole Orientation float X, Y, Z
ParticleProperty.Type.DipoleMagnitude Dipole Magnitude float  
ParticleProperty.Type.AngularVelocity Angular Velocity float X, Y, Z
ParticleProperty.Type.AngularMomentum Angular Momentum float X, Y, Z
ParticleProperty.Type.Torque Torque float X, Y, Z
ParticleProperty.Type.Spin Spin float  
ParticleProperty.Type.CentroSymmetry Centrosymmetry float  
ParticleProperty.Type.VelocityMagnitude Velocity Magnitude float  
ParticleProperty.Type.Molecule Molecule Identifier int  
ParticleProperty.Type.AsphericalShape Aspherical Shape float X, Y, Z
ParticleProperty.Type.VectorColor Vector Color float R, G, B
ParticleProperty.Type.ElasticStrainTensor Elastic Strain float XX, YY, ZZ, XY, XZ, YZ
ParticleProperty.Type.ElasticDeformationGradient Elastic Deformation Gradient float XX, YX, ZX, XY, YY, ZY, XZ, YZ, ZZ
ParticleProperty.Type.Rotation Rotation float X, Y, Z, W
ParticleProperty.Type.StretchTensor Stretch Tensor float XX, YY, ZZ, XY, XZ, YZ
ParticleProperty.Type.MoleculeType Molecule Type int  
type_by_id(id)

Looks up the ParticleType with the given numeric ID in the types list. Raises a KeyError if the ID does not exist.

type_by_name(name)

Looks up the ParticleType with the given name in the types list. If multiple types exists with the same name, the first type is returned. Raises a KeyError if there is no type with such a name.

types

A (mutable) list of ParticleType instances.

Note that the particle types may be stored in arbitrary order in this list. Thus, it is not valid to use a numeric type ID as an index into this list.

class ovito.data.ParticleType

Represents a particle type or atom type. ParticleType instancea are typically part of a typed ParticleProperty, but this class is also used in other contexts, for example to define the list of structural types identified by the PolyhedralTemplateMatchingModifier.

color

The display color to use for particles of this type.

enabled

This flag only has a meaning in the context of structure analysis and identification. Modifiers such as the PolyhedralTemplateMatchingModifier or the CommonNeighborAnalysisModifier manage a list of structural types that they can identify (e.g. FCC, BCC, etc.). The identification of individual structure types can be turned on or off by setting their enabled flag.

id

The identifier of the particle type.

name

The display name of this particle type.

radius

The display radius to use for particles of this type.

class ovito.data.BondProperty
Base class:ovito.data.Property

Stores an array of per-bond values. This class derives from Property, which provides the base functionality shared by all property types in OVITO.

In OVITO’s data model, an arbitrary set of properties can be associated with bonds, each property being represented by a BondProperty object. A BondProperty is basically an array of values whose length matches the numer of bonds in the data collection (see BondsView.count).

BondProperty objects have the same fields and behave the same way as ParticleProperty objects. Both property classes derives from the common Property base class. Please see its documentation on how to access per-bond values.

The set of properties currently associated with the bonds is exposed by the DataCollection.bonds view, which allows accessing them by name and adding new properties.

Note that the topological definition of bonds, i.e. the connectivity between particles, is stored in the BondProperty named Topology.

type

The type of the bond property (user-defined or one of the standard types). One of the following constants:

Type constant Property name Data type
BondProperty.Type.User (a user-defined property with a non-standard name) int/float
BondProperty.Type.BondType Bond Type int
BondProperty.Type.Selection Selection int
BondProperty.Type.Color Color float (3x)
BondProperty.Type.Length Length float
BondProperty.Type.Topology Topology int (2x)
BondProperty.Type.PeriodicImage Periodic Image int (3x)
type_by_id(id)

Returns the BondType with the given numeric ID from the types list. Raises a KeyError if the ID does not exist.

type_by_name(name)

Returns the BondType with the given name from the types list. If multiple types exists with the same name, the first type is returned. Raises a KeyError if there is no type with such a name.

types

A (mutable) list of BondType instances.

Note that the bond types may be stored in arbitrary order in this type list.

class ovito.data.BondType

Represents a bond type. A BondType instance is always owned by a BondTypeProperty.

color

The display color to use for bonds of this type.

id

The identifier of the bond type.

name

The display name of this bond type.

class ovito.data.BondsEnumerator

Utility class that permits efficient iteration over the bonds connected to specific particles.

The constructor takes a DataCollection object as input. From the unordered list of bonds in the data collection, the BondsEnumerator will build a lookup table for quick enumeration of bonds of particular particles.

All bonds connected to a given particle can be subsequently visited using the bonds_of_particle() method.

Warning: Do not modify the underlying bonds list in the data collection while the BondsEnumerator is in use. Adding or deleting bonds would render the internal lookup table of the BondsEnumerator invalid.

Usage example

from ovito.io import import_file
from ovito.data import BondsEnumerator
from ovito.modifiers import ComputeBondLengthsModifier

# Load a dataset containing atoms and bonds.
pipeline = import_file('input/bonds.data.gz', atom_style='bond')

# For demonstration purposes, let a modifier calculate the length of each bond.
pipeline.modifiers.append(ComputeBondLengthsModifier())

# Obtain pipeline results.
data = pipeline.compute()
positions = data.particles['Position']  # array with atomic positions
bond_topology = data.bonds['Topology']  # array with bond topology
bond_lengths = data.bonds['Length']     # array with bond lengths

# Create bonds enumerator object.
bonds_enum = BondsEnumerator(data)

# Loop over atoms.
for particle_index in range(data.particles.count):
    # Loop over bonds of current atom.
    for bond_index in bonds_enum.bonds_of_particle(particle_index):
        # Obtain the indices of the two particles connected by the bond:
        a = bond_topology[bond_index, 0]
        b = bond_topology[bond_index, 1]
        
        # Bond orientations can be arbitrary (a->b or b->a):
        assert(a == particle_index or b == particle_index)
        
        # Obtain the length of the bond from the 'Length' bond property:
        length = bond_lengths[bond_index]

        print("Bond from atom %i to atom %i has length %f" % (a, b, length))
bonds_of_particle()

Returns an iterator that yields the indices of the bonds connected to the given particle. The indices can be used to index into the BondProperty arrays.

class ovito.data.CutoffNeighborFinder(cutoff, data_collection)

A utility class that computes particle neighbor lists.

This class allows to iterate over the neighbors of a given particle within a specified cutoff distance. You can use it to build neighbors lists or perform computations that require neighbor information.

The constructor takes a positive cutoff radius and a DataCollection containing the input particle positions and the cell geometry (including periodic boundary flags).

Once the CutoffNeighborFinder has been constructed, you can call its find() method to iterate over the neighbors of a particle, for example:

from ovito.io import import_file
from ovito.data import CutoffNeighborFinder

# Load input simulation file.
pipeline = import_file("input/simulation.dump")
data = pipeline.compute()

# Initialize neighbor finder object:
cutoff = 3.5
finder = CutoffNeighborFinder(cutoff, data)

# Prefetch the property array containing the particle type information:
ptypes = data.particles['Particle Type']

# Loop over all particles:
for index in range(data.particles.count):
    print("Neighbors of particle %i:" % index)

    # Iterate over the neighbors of the current particle:
    for neigh in finder.find(index):
        print(neigh.index, neigh.distance, neigh.delta, neigh.pbc_shift)

        # The index can be used to access properties of the current neighbor, e.g.
        type_of_neighbor = ptypes[neigh.index]

Note: In case you rather want to determine the N nearest neighbors of a particle, use the NearestNeighborFinder class instead.

find(index)

Returns an iterator over all neighbors of the given particle.

Parameters:index (int) – The index of the central particle whose neighbors should be iterated. Particle indices start at 0.
Returns:A Python iterator that visits all neighbors of the central particle within the cutoff distance. For each neighbor the iterator returns an object with the following attributes:
  • index: The global index of the current neighbor particle (starting at 0).
  • distance: The distance of the current neighbor from the central particle.
  • distance_squared: The squared neighbor distance.
  • delta: The three-dimensional vector connecting the central particle with the current neighbor (taking into account periodicity).
  • pbc_shift: The periodic shift vector, which specifies how often each periodic boundary of the simulation cell is crossed when going from the central particle to the current neighbor.

The index value returned by the iterator can be used to look up properties of the neighbor particle as demonstrated in the example above.

Note that all periodic images of particles within the cutoff radius are visited. Thus, the same particle index may appear multiple times in the neighbor list of a central particle. In fact, the central particle may be among its own neighbors in a sufficiently small periodic simulation cell. However, the computed vector (delta) and PBC shift (pbc_shift) taken together will be unique for each visited image of a neighboring particle.

class ovito.data.NearestNeighborFinder(N, data_collection)

A utility class that finds the N nearest neighbors of a particle or around a spatial location.

The constructor takes the (maximum) number of requested nearest neighbors, N, and a DataCollection containing the input particles and the cell geometry (including periodic boundary flags). N must be a positive integer not greater than 30 (which is the built-in maximum supported by this class).

Once the NearestNeighborFinder has been constructed, you can call its find() method to iterate over the sorted list of nearest neighbors of a specific particle, for example:

from ovito.io import import_file
from ovito.data import NearestNeighborFinder

# Load input simulation file.
pipeline = import_file("input/simulation.dump")
data = pipeline.compute()

# Initialize neighbor finder object.
# Visit the 12 nearest neighbors of each particle.
N = 12
finder = NearestNeighborFinder(N, data)

# Prefetch the property array containing the particle type information:
ptypes = data.particles['Particle Type']

# Loop over all input particles:
for index in range(data.particles.count):
    print("Nearest neighbors of particle %i:" % index)
    # Iterate over the neighbors of the current particle, starting with the closest:
    for neigh in finder.find(index):
        print(neigh.index, neigh.distance, neigh.delta)
        # The index can be used to access properties of the current neighbor, e.g.
        type_of_neighbor = ptypes[neigh.index]

Furthermore, the class offers the find_at() method, which lets you determine the N nearest particles around an arbitrary spatial location:

# Find particles closest to some spatial point (x,y,z):
coords = (0, 0, 0)
for neigh in finder.find_at(coords):
    print(neigh.index, neigh.distance, neigh.delta)

Note: In case you rather want to find all neighbor particles within a certain cutoff range of a particle, use the CutoffNeighborFinder class instead.

find(index)

Returns an iterator that visits the N nearest neighbors of the given particle in order of ascending distance.

Parameters:index (int) – The index of the central particle whose neighbors should be iterated. Particle indices start at 0.
Returns:A Python iterator that visits the N nearest neighbors of the central particle in order of ascending distance. For each visited neighbor the iterator returns an object with the following attributes:
  • index: The global index of the current neighbor particle.
  • distance: The distance of the current neighbor from the central particle.
  • distance_squared: The squared neighbor distance.
  • delta: The three-dimensional vector connecting the central particle with the current neighbor (correctly taking into account periodic boundary conditions).

The global index returned by the iterator can be used to look up properties of the neighbor as demonstrated in the first example code above.

Note that several periodic images of the same particle may be visited. Thus, the same particle index may appear multiple times in the neighbor list of a central particle. In fact, the central particle may be among its own neighbors in a sufficiently small periodic simulation cell. However, the computed neighbor vector (delta) will be unique for each visited image of a neighboring particle.

The number of neighbors actually visited may be smaller than the requested number, N, if the system contains too few particles and has no periodic boundary conditions.

find_at(coords)

Returns an iterator that visits the N nearest particles around a spatial point given by coords in order of ascending distance. Unlike the find() method, which queries the nearest neighbors of a physical particle, the find_at() method allows searching for neareby particles at arbitrary locations in space.

Parameters:coords – A (x,y,z) coordinate triplet specifying the spatial location where the N nearest particles should be queried.
Returns:A Python iterator that visits the N nearest neighbors in order of ascending distance. For each visited particle the iterator returns an object with the following attributes:
  • index: The index of the current particle (starting at 0).
  • distance: The distance of the current neighbor from the query location.
  • distance_squared: The squared distance to the query location.
  • delta: The three-dimensional vector from the query point to the current particle (correctly taking into account periodic boundary conditions).

If there exists a particle that is exactly located at the query location given by coords, then it will be returned by this function. This is in contrast to the find() function, which does not visit the central particle itself.

The number of neighbors actually visited may be smaller than the requested number, N, if the system contains too few particles and has no periodic boundary conditions.

class ovito.data.ParticlesView(data_collection)

A dictionary view of all ParticleProperty objects in a DataCollection. An instance of this class is returned by DataCollection.particles.

It implements the collections.abc.Mapping interface. That means it can be used like a standard read-only Python dict object to access the particle properties by name, e.g.:

data = pipeline.compute()

positions = data.particles['Position']
has_selection = 'Selection' in data.particles
name_list = data.particles.keys()

New particle properties can be added with the create_property() method.

count

This read-only attribute returns the number of particles in the DataCollection.

create_property(name, dtype=None, components=None, data=None)

Adds a new particle property to the data collection and optionally initializes it with the per-particle data provided by the data parameter. The method returns the new ParticleProperty instance.

The method allows to create standard as well as user-defined particle properties. To create a standard particle property, one of the standard property names must be provided as name argument:

colors = numpy.random.random_sample(size = (data.particles.count, 3))
data.particles.create_property('Color', data=colors)

The length of the provided data array must match the number of particles, which is given by the count attribute. You can also set the values of the property after its construction:

prop = data.particles.create_property('Color')
with prop:
    prop[...] = numpy.random.random_sample(size = prop.shape)

To create a user-defined particle property, use a non-standard property name:

values = numpy.arange(0, data.particles.count, dtype=int)
data.particles.create_property('myint', data=values)

In this case the data type and the number of vector components of the new property are inferred from the provided data Numpy array. Providing a one-dimensional array creates a scalar property while a two-dimensional array creates a vectorial property. Alternatively, the dtype and components parameters can be specified explicitly if initialization of the property values should happen after property creation:

prop = data.particles.create_property('myvector', dtype=float, components=3)
with prop:
    prop[...] = numpy.random.random_sample(size = prop.shape)

If the property to be created already exists in the data collection, it is replaced with a new one. The existing per-particle data from the old property is however retained if data is None.

Note: If the data collection contains no particles yet, that is, even the Position property is not present in the data collection yet, then the Position standard property can still be created from scratch as a first particle property by the create_property() method. The data array has to be provided in this case to specify the number of particles to create:

# An empty data collection to begin with:
data = DataCollection()

# Create 10 particles with random xyz coordinates:
positions = numpy.random.random_sample(size = (10,3))
data.particles.create_property('Position', data=positions)

After the initial Positions property has been created, the number of particles is now specified and any subsequently added properties must have the exact same length.

Parameters:
  • name – Either a standard property type constant or a name string.
  • data – An optional data array with per-particle values for initializing the new property. The size of the array must match the particle count in the data collection and the shape must be consistent with the number of components of the property.
  • dtype – The element data type when creating a user-defined property. Must be either int or float.
  • components (int) – The number of vector components when creating a user-defined property.
Returns:

The newly created ParticleProperty

class ovito.data.BondsView(data_collection)

This class provides a dictionary view of all BondProperty objects in a DataCollection. An instance is returned by the bonds attribute of the data collection:

data = pipeline.compute()
print("Number of bonds:", data.bonds.count)

The count attribute of the BondsView class reports the number of bonds.

Bond properties

Bonds can possess an arbitrary set of bond properties, just like particles possess particle properties. The values of each bond property are stored in a separate BondProperty data object. The BondsView class operates like a Python dictionary and provides access to all BondProperty objects based on their unique name:

print("Bond property names:")
print(data.bonds.keys())
if 'Lengths' in data.bonds:
    lengths_prop = data.bonds['Length']
    assert(len(lengths_prop) == data.bonds.count)

New bond properties can be added using the create_property() method. Removal of a property is possible by deleting it from the DataCollection.objects list.

Bond topology

If bonds exist in a data collection, then the Topology bond property is always present. It has the special role of defining the connectivity between particles in the form of a N x 2 array of indices into the particles list. In other words, each bond is defined by a pair of particle indices.

topology = data.bonds['Topology']
for a,b in topology:
    print("Bond from particle %i to particle %i" % (a,b))

Bonds are stored in no particular order. If you need to enumerate all bonds connected to a certain particle, you can use the BondsEnumerator utility class for that.

Bond display settings

The Topology bond property has a BondsVis element attached to it, which controls the visual appearance of the bonds in rendered images. It can be accessed through the vis attribute:

data.bonds['Topology'].vis.enabled = True
data.bonds['Topology'].vis.shading = BondsVis.Shading.Flat
data.bonds['Topology'].vis.width = 0.3

Computing bond vectors

Since each bond is defined by two indices into the particles array, we can use this to determine the corresponding spatial bond vectors. They can be computed from the positions of the particles:

topology = data.bonds['Topology']
positions = data.particles['Position']
bond_vectors = positions[topology[:,1]] - positions[topology[:,0]]

Here, the first and the second column of the bonds topology array are used to index into the particle positions array. The subtraction of the two indexed arrays yields the list of bond vectors. Each vector in this list points from the first particle to the second particle of the corresponding bond.

Finally, we might have to correct for the effect of periodic boundary conditions when a bond connects two particles on opposite sides of the box. OVITO keeps track of such cases by means of the the special Periodic Image bond property. It stores a shift vector for each bond, specifying the directions in which the bond crosses periodic boundaries. We can use this information to correct the bond vectors computed above. This is done by adding the product of the cell matrix and the shift vectors from the Periodic Image bond property:

cell = data.expect(SimulationCell)
bond_vectors += numpy.dot(cell[:,:3], data.bonds['Periodic Image'].T).T

The shift vectors array is transposed here to facilitate the transformation of the entire array of vectors with a single 3x3 cell matrix. To summarize: In the two code snippets above we have performed the following calculation for every bond (a, b) in parallel:

v = x(b) - x(a) + dot(H, pbc)

where H is the cell matrix and pbc is the bond’s PBC shift vector of the form (nx, ny, nz).

count

This read-only attribute returns the number of bonds in the DataCollection. It always matches the lengths of all BondProperty arrays in the data collection.

create_property(name, dtype=None, components=None, data=None)

Adds a new bond property to the data collection and optionally initializes it with the per-bond data provided by the data parameter. The method returns the new BondProperty instance.

The method can create standard and user-defined bond properties. To create a standard bond property, one of the standard property names must be provided as name argument:

colors = numpy.random.random_sample(size = (data.bonds.count, 3))
data.bonds.create_property('Color', data=colors)

The size of the provided data array must match the number of bonds in the data collection given by the count attribute. You can also set the property values after construction:

prop = data.bonds.create_property('Color')
with prop:
    prop[...] = numpy.random.random_sample(size = prop.shape)

To create a user-defined bond property, use a non-standard property name:

values = numpy.arange(0, data.bonds.count, dtype=int)
data.bonds.create_property('myint', data=values)

In this case the data type and the number of vector components of the new property are inferred from the provided data Numpy array. Providing a one-dimensional array creates a scalar property while a two-dimensional array creates a vectorial property. Alternatively, the dtype and components parameters can be specified explicitly if initialization of the property values should happen after property creation:

prop = data.bonds.create_property('myvector', dtype=float, components=3)
with prop:
    prop[...] = numpy.random.random_sample(size = prop.shape)

If the property to be created already exists in the data collection, it is replaced with a new one. The existing per-bond data from the old property is however retained if data is None.

Note: If the data collection contains no bonds yet, that is, even the Topology property is not present in the data collection yet, then the Topology property can still be created from scratch as a first bond property by the create_property() method. The data array has to be provided in this case to specify the number of bonds to create. After the initial Topology property has been created, the number of bonds is now specified and any subsequently added properties must have the exact same length.

Parameters:
  • name – Either a standard property type constant or a name string.
  • data – An optional data array with per-bond values for initializing the new property. The size of the array must match the bond count and the shape must be consistent with the number of components of the property.
  • dtype – The element data type when creating a user-defined property. Must be either int or float.
  • components (int) – The number of vector components when creating a user-defined property.
Returns:

The newly created BondProperty

class ovito.data.TrajectoryLines
Base class:ovito.data.DataObject

This data object type stores the traced trajectory lines of a group of particles. It is typically generated by a GenerateTrajectoryLinesModifier.

Each TrajectoryLines object is associated with a TrajectoryVis element, which controls the visual appearance of the trajectory lines in rendered images. It is accessible through the vis base class attribute.