-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Utility function to generate arrows and ellipsoids from physical data
This allows to visualize vectorial and tensorial properties for each atom.
- Loading branch information
Showing
8 changed files
with
257 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
import numpy as np | ||
|
||
|
||
def center_shape(shape): | ||
""" | ||
Takes a dictionary that describes a custom shape, and centers it, subtracting the | ||
mean of the vertex positions. Returns a shallow copy, with a new list for the | ||
vertices. | ||
:param shape: A dictionary describing a custom shape | ||
""" | ||
|
||
if shape["kind"] != "custom": | ||
raise ValueError("Only `custom` shapes can be centered") | ||
|
||
new_shape = {k: shape[k] for k in shape} | ||
points = np.array(shape["vertices"]) | ||
points -= points.mean(axis=0) | ||
new_shape["vertices"] = points.tolist() | ||
|
||
return new_shape | ||
|
||
|
||
def _oriented_circle(radius, vec, n_points=20): | ||
""" | ||
Generates a circle in 3D centered at the origin ands oriented according to the | ||
given vector | ||
""" | ||
# makes sure the normal is unit | ||
nvec = vec / np.linalg.norm(vec) | ||
|
||
# Generate an arbitrary vector not collinear with n | ||
if nvec[0] or nvec[1]: | ||
vx = np.array([0, 0, 1]) | ||
else: | ||
vx = np.array([0, 1, 0]) | ||
|
||
# generate orthogonal vectors in the plane defined by nvec | ||
u = vx - np.dot(vx, nvec) * nvec | ||
u = u / np.linalg.norm(u) | ||
v = np.cross(nvec, u) | ||
|
||
# generate n_points in the plane defined by nvec, centered at vec | ||
angles = np.linspace(0, 2 * np.pi, n_points, endpoint=False) | ||
circle_points = radius * np.outer(np.cos(angles), u) + np.outer(np.sin(angles), v) | ||
|
||
return circle_points | ||
|
||
|
||
def arrow_from_vector( | ||
vec, scale=1.0, radius=0.1, head_radius_scale=1.75, head_length_scale=4, n_points=16 | ||
): | ||
""" | ||
Draws an arrow from the origin to the specified 3D position. Returns a custom shape | ||
in the form required by the chemiscope input. | ||
:param scale: conversion from the units of the vector to the units of the atomic | ||
positions (usually Å) | ||
:param radius: radius of the stem of the arrow (same units as the atomic positions, | ||
typically Å) | ||
:param head_radius_scale: radius of the arrow tip, relative to the stem radius | ||
:param head_length_scale: length of the arrow tip, relative to the stem radius | ||
:param n_points: resolution of the discretization of the shape | ||
""" | ||
|
||
tip = np.asarray(vec, dtype=float) * scale | ||
head_tip = tip + tip / np.linalg.norm(tip) * radius * head_length_scale | ||
circle = _oriented_circle(1.0, tip, n_points) | ||
base_circle = radius * circle | ||
stem_circle = radius * circle + tip | ||
head_circle = radius * head_radius_scale * circle + tip | ||
|
||
arrow = dict( | ||
kind="custom", | ||
vertices=[head_tip.tolist()] | ||
+ head_circle.tolist() | ||
+ stem_circle.tolist() | ||
+ base_circle.tolist() | ||
+ [[0, 0, 0]], | ||
simplices=( | ||
[[0, i, i + 1] for i in range(1, n_points)] | ||
+ [[0, n_points, 1]] | ||
+ [[i, i + 1, i + 1 + n_points] for i in range(1, n_points)] | ||
+ [[n_points, 1, n_points + 1]] | ||
+ [[i + n_points, i, i + 1 + n_points] for i in range(1, n_points)] | ||
+ [[2 * n_points, n_points, n_points + 1]] | ||
+ [[i, i + 1, i + 1 + n_points] for i in range(1 + n_points, 2 * n_points)] | ||
+ [[2 * n_points, 1 + n_points, 2 * n_points + 1]] | ||
+ [ | ||
[i + n_points, i, i + 1 + n_points] | ||
for i in range(1 + n_points, 2 * n_points) | ||
] | ||
+ [[3 * n_points, 2 * n_points, 2 * n_points + 1]] | ||
+ [ | ||
[3 * n_points + 1, i, i + 1] | ||
for i in range(1 + 2 * n_points, 3 * n_points) | ||
] | ||
+ [[3 * n_points + 1, 3 * n_points, 2 * n_points + 1]] | ||
), | ||
) | ||
return arrow | ||
|
||
|
||
def ellipsoid_from_tensor(tensor, scale=1.0, force_positive=False): | ||
""" | ||
Returns an ellipsoid (semiaxes + quaternion) representation of a positive definite | ||
tensor (e.g. a polarizability), in the form required by the chemiscope input. | ||
:param tensor: a positive-definite tensor (3x3 or a 6-array [xx,yy,zz,xy,xz,yz]) | ||
:param scale: conversion from the units of the tensor to the units of the atomic | ||
positions (usually Å) | ||
:param force_positive: takes the absolute value of eigenvalues, to handle | ||
non-positive tensors | ||
""" | ||
|
||
tensor = np.asarray(tensor) * (scale**2) | ||
if len(tensor.flatten()) == 9: | ||
matrix = tensor.reshape((3, 3)) | ||
elif len(tensor) == 6: | ||
matrix = np.array( | ||
[ | ||
[tensor[0], tensor[3], tensor[4]], | ||
[tensor[3], tensor[1], tensor[5]], | ||
[tensor[4], tensor[5], tensor[2]], | ||
] | ||
) | ||
# Compute the eigenvalues and eigenvectors | ||
eigenvalues, eigenvectors = np.linalg.eigh(matrix) | ||
|
||
if force_positive: | ||
eigenvalues = np.abs(eigenvalues) | ||
|
||
# The lengths of the principal axes are the square roots of the eigenvalues | ||
old_settings = np.seterr(invalid="raise") | ||
try: | ||
ax = np.sqrt(eigenvalues[0]) | ||
ay = np.sqrt(eigenvalues[1]) | ||
az = np.sqrt(eigenvalues[2]) | ||
except FloatingPointError as e: | ||
raise ValueError( | ||
f"Non-positive definite tensor found with eigenvalues {eigenvalues}.\n" | ||
"If this is acceptable, set `force_positive=True` to take the " | ||
"absolute values.", | ||
) from e | ||
np.seterr(**old_settings) | ||
|
||
# makes sure the rotation is proper | ||
if np.linalg.det(eigenvectors) < 0: | ||
eigenvectors[:, 0] *= -1 | ||
|
||
# Form the rotation matrix from eigenvectors | ||
rotation = eigenvectors.T | ||
|
||
# converts to quaternion | ||
try: | ||
from scipy.spatial.transform import Rotation | ||
except ImportError as e: | ||
raise RuntimeError( | ||
"scipy is required to construct ellipsoids from tensors" | ||
) from e | ||
quaternion = Rotation.from_matrix(rotation).as_quat() | ||
|
||
return dict( | ||
kind="ellipsoid", | ||
semiaxes=[ax, ay, az], | ||
orientation=list(quaternion), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters