Geometric analysis

The Core concepts page covers the basics of working with coordination polyhedra: bond distances, angles, volume, edge graphs, symmetry measures, and neighbour sharing. This guide covers the remaining geometric analysis tools available on CoordinationPolyhedron.

All examples assume you have a Configuration with polyhedra already constructed:

poly = config.polyhedra[0]

Vertex species composition

In mixed-anion systems (e.g. TiOF2, where Ti is coordinated by both O and F), it is useful to know which species occupy the vertex sites.

The vertex_labels property returns the species label for each vertex:

poly.vertex_labels
# ['F', 'F', 'O', 'O', 'O', 'O']

To get a count of vertices grouped by species, use vertex_count:

poly.vertex_count
# Counter({'O': 4, 'F': 2})

To pair each vertex distance with its species label, use vertex_distances_and_labels():

poly.vertex_distances_and_labels()
# ((1.98, 'F'), (2.01, 'F'), (1.95, 'O'), (1.96, 'O'), (1.97, 'O'), (1.97, 'O'))

This accepts an optional reference argument ('central_atom' or 'centroid') to choose the reference point for the distance calculation.

Distances and vectors

The vertex_distances() method returns an array of distances from either the central atom or the centroid to each vertex:

poly.vertex_distances()                       # from central atom (default)
poly.vertex_distances(reference='centroid')    # from centroid

The existing coordination_distances() method is a convenience wrapper that always measures from the central atom and returns a list rather than a numpy array.

The vertex_vectors() method returns the vectors from a reference point to each vertex:

poly.vertex_vectors()                         # from centroid (default)
poly.vertex_vectors(reference='central_atom') # from central atom

These vectors are the basis for most other geometric calculations (angles, orientations, symmetry measures).

The centroid() method returns the centroid of the vertex positions, correctly accounting for periodic boundary conditions:

poly.centroid()  # np.array([x, y, z])

Off-centre displacement

In ferroelectric and related materials, the displacement of the central atom away from the geometric centre of its coordination shell is physically significant.

The centroid_to_central_atom_vector() method returns the 3D displacement vector:

poly.centroid_to_central_atom_vector()  # np.array([dx, dy, dz])

The scalar magnitude is available as the off_centre_displacement property:

poly.off_centre_displacement  # 0.042

Radial distortion

The radial_distortion_parameter() method quantifies how much the vertex distances deviate from their mean value. By default it computes the normalised mean squared deviation (MSD):

poly.radial_distortion_parameter()

The method argument selects between MSD and mean absolute deviation (MAD):

poly.radial_distortion_parameter(method='MAD')

Other options control the reference point and normalisation:

# Measure from the central atom instead of the centroid
poly.radial_distortion_parameter(reference='central_atom')

# Un-normalised (absolute deviations, not divided by the mean distance)
poly.radial_distortion_parameter(normalize=False)

Angular analysis

The vertex_angles() method calculates angles between specific pairs of vertices, identified by their global atom indices:

pairs = ((3, 7), (3, 12))
poly.vertex_angles(pairs)  # np.array([90.1, 88.5])

This complements the existing angles() method, which returns all pairwise vertex–centroid–vertex angles.

The vertex_vector_orientations() method returns the angular orientation of each vertex vector as (theta, phi) pairs. Theta is the angle from [0, 0, 1] (0–180 degrees) and phi is the angle from [1, 0, 0] (-180 to +180 degrees):

poly.vertex_vector_orientations()  # from centroid (default)
# [(45.0, 90.0), (135.0, -90.0), ...]

# In radians
poly.vertex_vector_orientations(units='radians')

# Also return the distance for each vertex
poly.vertex_vector_orientations(return_distance=True)
# [(45.0, 90.0, 1.98), (135.0, -90.0, 2.01), ...]

Vector projections

The vertex_vector_projections() method projects each vertex vector onto one or more input vectors. This is useful for measuring the component of each bond along a specific crystallographic direction:

import numpy as np

# Project onto the c-axis
c_axis = np.array([0.0, 0.0, 1.0])
poly.vertex_vector_projections(c_axis)
# np.array of shape (n_vertices, 1)

# Project onto multiple directions at once
axes = np.array([[1.0, 0.0, 0.0],
                 [0.0, 1.0, 0.0],
                 [0.0, 0.0, 1.0]])
poly.vertex_vector_projections(axes)
# np.array of shape (n_vertices, 3)

Topology: faces, edges, and convex hull

The faces() method returns the faces of the polyhedron as tuples of vertex atom indices:

poly.faces()
# ((3, 7, 12), (3, 7, 16), (3, 12, 19), ...)

The edge_vertex_indices() method returns all edges as sorted pairs:

poly.edge_vertex_indices()
# ((3, 7), (3, 12), (3, 16), (7, 12), ...)

This is an alternative representation of the same connectivity as the edge_graph property (documented in Core concepts), which uses an adjacency list format instead.

For custom analysis, the underlying convex_hull() method returns a scipy ConvexHull object:

hull = poly.convex_hull()

Identity and utility

Labels and indices:

Each polyhedron has a label (defaults to the central atom species) and a method to change it:

poly.label              # 'Ti'
poly.set_label('Ti_1')

The index property returns the global atom index of the central atom, and vertex_indices returns the global indices of the vertex atoms:

poly.index           # 0
poly.vertex_indices  # [3, 7, 12, 16, 19, 23]

The Cartesian coordinates of the vertex atoms are available as an Nx3 array:

poly.vertex_coords  # np.array of shape (6, 3)

Periodic boundary conditions:

The minimum_image_vertex_coordinates() method returns vertex coordinates where each vertex is the closest periodic image to the central atom:

poly.minimum_image_vertex_coordinates()  # np.array of shape (6, 3)

Shared vertices:

The intersection() method returns the atom indices shared with another polyhedron:

poly_a = config.polyhedra[0]
poly_b = config.polyhedra[1]
poly_a.intersection(poly_b)  # (12, 16)

This is the underlying operation used by the neighbour analysis methods (see Neighbour analysis).

Comparing polyhedra:

Three methods provide different levels of equality testing:

# Same vertex atom indices?
poly_a.equal_vertices(poly_b)

# Same edge connectivity graph?
poly_a.equal_edge_graph(poly_b)

# Same central atom AND same vertices?
poly_a.equal_members(poly_b)

The default == operator uses equal_edge_graph.

Index lookups:

To retrieve a subset of vertex atoms by their global indices:

poly.vertices_by_indices([12, 16])  # [Atom, Atom]

To convert a global atom index to the internal (0-based) vertex index:

poly.vertex_internal_index_from_global_index(12)  # 2

Creating polyhedra directly:

The from_sites() class method creates a polyhedron directly from pymatgen Site objects, without going through a Configuration:

from polyhedral_analysis.coordination_polyhedron import CoordinationPolyhedron

poly = CoordinationPolyhedron.from_sites(
    central_site=central_site,
    vertex_sites=vertex_sites,
    label='Ti',  # optional
)