David McKay has written such a method, sent to the CCP-NC list.
# Function to return the dihedral angle defined by four atoms
import numpy as np
import math
def my_dihedral(atoms, atom1, atom2, atom3, atom4, degrees=False):
# Use least_mirror for positions rather than atom.position to allow for a closer image
bond1 = np.subtract(atoms.least_mirror(atom2.position, atom1.position)[1], atom1.position)
bond2 = np.subtract(atoms.least_mirror(atom3.position, atom2.position)[1], atom2.position)
bond3 = np.subtract(atoms.least_mirror(atom4.position, atom3.position)[1], atom3.position)
# Make unit vectors
bond1 = bond1 / np.linalg.norm(bond1)
bond2 = bond2 / np.linalg.norm(bond2)
bond3 = bond3 / np.linalg.norm(bond3)
norm1 = np.cross(bond1, bond2)
norm2 = np.cross(bond2, bond3)
# Get m, where m, norm1 and bond2 form an orthogonal frame
m = np.cross(norm1, bond2)
# x = cos(theta) where theta is the angle between norm1 and norm2
x = np.dot(norm1, norm2)
# y = cos(phi) where phi is the angle between m and norm2
y = np.dot(m, norm2)
# atan2(y, x) gives the theta over a range of 2pi with correct sign
# therefore preferable over acos(x)
if degrees:
return np.rad2deg(math.atan2(y, x))
else:
return math.atan2(y, x)