Particle properties

OVITO stores particle properties such as the position, mass, color, etc. in separate data arrays. A particle system is therefore nothing else than as a loose collection of ParticleProperty instances, and the number of particles is implicitly defined by the length of these data arrays (which is the same for all properties). All defined particle properties are stored in a DataCollection instance, which is a generic container for data objects (ParticleProperty is a subclass of DataObject).

A DataCollection can hold an arbitrary number of particle properties and other data objects. At the very least you will find one ParticleProperty instance in a data collection, namely the Position property, which is essential to constitute a particle system. Furthermore, the number of particles is returned by the DataCollection.number_of_particles attribute, which is a shortcut to querying the length of the data array of the Position particle property.

To find out which particle properties are defined, you can query the DataCollection.particle_properties dictionary view for its keys:

>>> data_collection = node.output
>>> list(data_collection.particle_properties.keys())
['Particle Identifier', 'Particle Type', 'Position', 'Color']

Accordingly, individual particle properties can be accessed through these dictionary keys:

>>> data_collection.particle_properties['Particle Identifier']
<ParticleProperty at 0x7fe16c8bc7b0>

In addition to particle properties, a data collection can contain other data objects such as a SimulationCell or a Bonds object. These are accessible through the dictionary interface of the DataCollection itself, which lists all stored data objects (including the particle properties):

>>> list(data_collection.keys())
['Simulation cell', 'Bonds', 'Particle Identifier', 'Particle Type', 'Position', 'Color']

>>> data_collection['Simulation cell']
<SimulationCell at 0x7fd54ba34c40>

A ObjectNode has two DataCollections: one caching the original input data of the modification pipeline, which was read from the external file, and another one caching the output of the pipeline after the modifiers have been applied. For example:

>>> node.source
DataCollection(['Simulation cell', 'Position'])

>>> node.compute()
>>> node.output
DataCollection(['Simulation cell', 'Position', 'Color', 'Structure Type', 'Bonds'])

Here, some modifiers in the pipeline have added two additional particle properties and created a set of bonds, which are stored in a Bonds data object in the output data collection.

The dictionary interface of the DataCollection class allows to access data objects via their name keys. As a simplification, it is also possible to access standard particle properties, the simulation cell, and bonds, as object attributes, e.g.:

>>> node.output.particle_properties.position
<ParticleProperty at 0x7fe16c8bc7b0>

>>> node.output.particle_properties.structure_type
<ParticleProperty at 0x7ff46263cff0>

>>> node.output.cell
<SimulationCell at 0x7fd54ba34c40>

>>> node.output.bonds
<Bonds at 0x7ffe88613a60>

To access standard particle properties in this way, the Python attribute name can be derived from the particle property name by replacing all letters with their lower-case variants and white-spaces with underscores (e.g. particle_properties['Structure Type'] becomes particle_properties.structure_type). The names of all standard particle properties are listed here.

The per-particle data stored in a ParticleProperty can be accessed through its array attribute, which returns a NumPy array:

>>> coordinates = node.output.particle_properties.position.array
>>> print(coordinates)
[[ 73.24230194  -5.77583981  -0.87618297]
 [-49.00170135 -35.47610092 -27.92519951]
 [-50.36349869 -39.02569962 -25.61310005]
 ...,
 [ 42.71210098  59.44919968  38.6432991 ]
 [ 42.9917984   63.53770065  36.33330154]
 [ 44.17670059  61.49860001  37.5401001 ]]

>>> len(coordinates)      # This is equal to the number of particles
112754

Note

The array attribute of a particle property allows you to directly access the per-particle data as a NumPy array. The array is one-dimensional for scalar particle properties and two-dimensional for vectorial properties. The data in the array is marked as read-only, because OVITO requires that the data does not change without the program knowing it. If you want to alter the values of a particle property directly (e.g. because there is no modifier to achieve the same effect), then have a look at the marray attribute of the ParticleProperty class, which provides write access to the internal data.

Particle type property

Most particle properties are instances of the ParticleProperty class. However, there exist specializations. For instance, the ParticleTypeProperty class is a subclass of ParticleProperty and supplements the per-particle type info with a list of defined particle types, each having a name, a display color, and a display radius:

>>> node = import_file('example.poscar')

>>> ptp = node.source.particle_properties.particle_type   # Access the 'Particle Type' property
>>> ptp
<ParticleTypeProperty at 0x7fe0a2c355d0>

>>> ptp.array     # This contains the per-particle data, one integer per particle
[1 1 2 ..., 1 2 1]

>>> for ptype in ptp.type_list:
...     print(ptype.id, ptype.name, ptype.color)
1 Cu (1.0 0.4 0.4)
2 Zr (0.0 1.0 0.4)

The type_list attribute lists the defined ParticleTypes. In the example above we were looping over this list to print the numeric ID, human-readable name, and color of each atom type.

Bonds and bond properties

Bonds are stored in a Bonds object, which is basically a data array containing two integers per bond: The (zero-based) index of the particle the bond originates from and the index of the particle it is pointing to. In fact, OVITO uses two half-bonds to represent every full bond between two particles; one half-bond from particle A to B, and an opposite half-bond pointing from B to A. The Bonds class stores all half-bonds in a big list with arbitrary order, which can be accessed through the array attribute:

>>> node.output.bonds.array
[[   0    1]
 [   1    0]
 [   1    2]
 ...,
 [2998 2997]
 [2998 2999]
 [2999 2998]]

In addition, bonds can have a number of properties, analogous to particle properties. Bond properties are stored separately as instances of the BondProperty class, which can be accessed via the bond_properties dictionary view of the DataCollection:

>>> list(node.output.bond_properties.keys())
['Bond Type', 'Color']

>>> btype_prop = node.output.bond_properties.bond_type
>>> btype_prop
<BondTypeProperty at 0x7fe16c8bc7b0>

The BondTypeProperty class is a specialization of the BondProperty base class.

The length of a BondProperty data array is always equal to the number of half-bonds:

>>> len(node.output.bonds.array)
6830
>>> len(node.output.bond_properties.bond_type.array)
6830
>>> node.output.number_of_bonds
6830