"""
Geometric units for elemental molecular geometry
"""
import numpy as np
import buildamol.structural.base as base
[docs]
class Geometry:
"""
The base class for geometries
"""
max_points = -1
size = -1
angle = -1
[docs]
def make_and_apply(
self, atoms_to_make_from: list, atoms_to_apply_to: list, **kwargs
):
"""
Automatically make and apply the geometry to a list of atom objects
Parameters
----------
atoms_to_make_from : list
The list of atoms to make the geometry from.
Note that there must not be more atoms than the geometry can generate coordinates for.
Note that in each case the central atom must be the first atom in the list.
Other atoms (planar or axial, where applicable) must come after. Be sure to pass
a 'direction' argument to specify the inference direction if the points are ambiguous.
atoms_to_apply_to : list
The list of atoms to apply the geometry to.
Note that there must not be more atoms than the geometry can generate coordinates for.
Note that in each case the central atom must be the first atom in the list.
Other atoms (planar or axial, where applicable) must come after.
**kwargs
Additional keyword arguments to pass to the coordinate generation function.
"""
coords = self.make_coords(*atoms_to_make_from, **kwargs)
if len(atoms_to_apply_to) > self.size:
raise ValueError(
f"Too many atoms for this geometry. {self.__class__.__name__} generates {self.size} coordinates but the input has {len(atoms_to_apply_to)} atoms."
)
for i, atom in enumerate(atoms_to_apply_to):
atom.coord = coords[i]
[docs]
def apply(self, atoms: list, bonds: list = None, **kwargs):
"""
Automatically apply coordinate generation to a list of atom objects
Parameters
----------
atoms : list
The list of atoms to apply the geometry to.
Note that there must not be more atoms than the geometry can generate coordinates for.
Note that in each case the central atom must be the first atom in the list.
Other atoms (planar or axial, where applicable) must come after. Be sure to pass
a 'direction' argument to specify the inference direction if the points are ambiguous.
bonds : list
A list of tuples of the same atoms as in 'atoms' that are bonded. This can be used to adjust the bond lengths after the geometry is applied.
**kwargs
Additional keyword arguments to pass to the coordinate generation function.
Returns
-------
list
The list of atoms with the new coordinates applied.
"""
if hasattr(atoms[0], "get_coord"):
coord_getter = lambda x: x.get_coord()
elif hasattr(atoms[0], "coord"):
coord_getter = lambda x: x.coord
else:
raise ValueError(
"Invalid input for 'apply'"
+ str(atoms)
+ ". If you have coordinates rather than a molecule or atoms use 'make_coords' instead."
)
old = [coord_getter(i) for i in atoms]
if len(old) > self.size:
raise ValueError(
f"Too many atoms for this geometry. {self.__class__.__name__} generates {self.size} coordinates but the input has {len(old)} atoms."
)
new = self.make_coords(*old[: self.max_points], **kwargs)
if hasattr(atoms[0], "set_coord"):
coord_setter = lambda x, y: x.set_coord(y)
else:
coord_setter = lambda x, y: setattr(x, "coord", y)
if bonds is not None:
old_lengths = {
b: np.linalg.norm(coord_getter(a) - coord_getter(b)) for a, b in bonds
}
for i, atom in enumerate(atoms):
coord_setter(atom, new[i])
if bonds is not None:
for bond in bonds:
base.adjust_distance(*bond, new_length=old_lengths[bond])
return atoms
[docs]
def fill_hydrogens(self, *atoms, make_bonds: bool = True, **kwargs):
"""
Fill the geometry with hydrogen atoms where empty coordinates are found
Parameters
----------
atoms : list
The list of atoms to apply the geometry to.
Note that there must not be more atoms than the geometry can generate coordinates for.
Note that in each case the central atom must be the first atom in the list.
Other atoms (planar or axial, where applicable) must come after. Be sure to pass a 'direction' argument to specify the inference direction if the points are ambiguous.
make_bonds : bool
If True, bond objects will also be returned connecting the central atom to the other atoms (provided or newly made).
**kwargs
Additional keyword arguments to pass to the coordinate generation function.
Returns
-------
atoms : list
The list of atoms with the new coordinates applied and hydrogens added where necessary.
bonds : list
A list of Bonds of the same atoms as in 'atoms' that are bonded. Or None if 'make_bonds' is False.
"""
if len(atoms) > self.max_points:
_atoms = atoms[: self.max_points]
else:
_atoms = atoms
coords = self.make_coords(*(a.coord for a in _atoms), **kwargs)
# # set the coordinates of the atoms
# for i, atom in enumerate(atoms):
# if atom.coord is None:
# atom.coord = coords[i]
# now fill the rest with hydrogens
import buildamol.base_classes as core
atoms = list(atoms)
for coord in coords[len(atoms) :]:
atom = core.Atom.new("H", coord=coord)
atoms.append(atom)
if make_bonds:
bonds = [core.Bond(atoms[0], a) for a in atoms[1:]]
return atoms, bonds
else:
return atoms, None
[docs]
def make_coords(*coords, **kwargs):
"""
Make the coordinates of the geometry
"""
raise NotImplementedError
[docs]
class Tetrahedral(Geometry):
"""
Tetrahedral geometry for Sp3 hybridized atoms
(or atoms with 4 substituents)
"""
max_points = 3
size = 5
angle = np.radians(120)
dihedral = np.radians(109.5)
def __init__(self, bond_length=1.2):
self.bond_length = bond_length
[docs]
def make_coords(self, *coords, length: float = None, **kwargs):
"""
Make the coordinates of a tetrahedron
Parameters
----------
coords : array-like
The coordinates of the atoms that define the tetrahedron
The first atom is the center of the tetrahedron and the other 4 atoms follow.
float : float
The bond length to use for the tetrahedron.
By default the bond length is either the default value or the distance between center and first other point (unless specified using this argument).
Returns
-------
array-like
The coordinates of the tetrahedron with center at the 0th index and the other 4 atoms following.
Provided points always precede the ones that were generated.
"""
if len(coords) == 1:
return self.make_coords_from_one(coords[0], length=length)
elif len(coords) == 2:
return self.make_coords_from_two(*coords, length=length)
elif len(coords) == 3:
return self.make_coords_from_three(*coords, length=length)
# elif len(coords) == 4:
# return self.make_coords_from_four(*coords, length=length)
else:
raise ValueError("Invalid number of atoms")
[docs]
def make_coords_from_one(self, center, length: float = None):
"""
Get the coordinates of a tetrahedron
Parameters
----------
center : Atom or array-like
The center of the tetrahedron
float : float
The bond length to use for the tetrahedron.
If not provided the default bond length is used.
Returns
-------
array-like
The coordinates of the tetrahedron with center at the 0th index and the other 4 atoms following.
"""
length = length or self.bond_length
center = getattr(center, "coord", center)
other = center + base.x_axis * length
return self.make_coords_from_two(center, other, length=length)
[docs]
def make_coords_from_two(self, center, other, length: float = None):
"""
Get the coordinates of a tetrahedron
Parameters
----------
center : Atom or array-like
The center of the tetrahedron
other : Atom or array-like
The other atom to define one axis of the tetrahedron
float : float
The bond length to use for the tetrahedron.
If not provided the distance between center and other is used.
Returns
-------
array-like
The coordinates of the tetrahedron with center at the 0th index and other the 1st index, and remaing 3 atoms following.
Provided points always precede the ones that were generated. E.g. other will be at index 1.
"""
center = getattr(center, "coord", center)
other = getattr(other, "coord", other)
if length is None:
length = np.linalg.norm(center - other)
if length == 0:
raise ValueError("The two atoms are at the same position")
axis = center - other
axis /= np.linalg.norm(axis)
a = np.abs(axis)
if a[0] < 1e-6 and a[1] < 1e-6:
axis2 = base.x_axis
elif a[0] < 1e-6 and a[2] < 1e-6:
axis2 = base.z_axis
else:
axis2 = base.y_axis
coords = np.zeros((5, 3))
coords[0] = center
coords[1] = other
coords[2] = (
base.rotate_coords(-axis * length, self.dihedral, np.cross(axis, axis2))
+ center
)
_new_axis = center - coords[2]
_new_axis /= np.linalg.norm(_new_axis)
coords[3] = base.rotate_coords(-_new_axis * length, self.angle, axis) + center
coords[4] = base.rotate_coords(-_new_axis * length, -self.angle, axis) + center
return coords
[docs]
def make_coords_from_three(self, center, other1, other2, length: float = None):
"""
Get the coordinates of a tetrahedron
Parameters
----------
center : Atom or array-like
The center of the tetrahedron
other1 : Atom or array-like
The first atom to define one axis of the tetrahedron
other2 : Atom or array-like
The second atom to define one axis of the tetrahedron
float : float
The bond length to use for the tetrahedron.
If not provided the distance between center and other1 is used.
Returns
-------
array-like
The coordinates of the tetrahedron with center at the 0th index and the other 4 atoms following.
Provided points always precede the ones that were generated. E.g. other1 will be at index 1 and other2 at index 2.
"""
center = getattr(center, "coord", center)
other1 = getattr(other1, "coord", other1)
other2 = getattr(other2, "coord", other2)
length1 = np.linalg.norm(center - other1)
length2 = np.linalg.norm(center - other2)
if length1 == 0 or length2 == 0:
raise ValueError("The two atoms are at the same position")
if length is None:
length = length1
axis1 = center - other1
axis1 /= np.linalg.norm(axis1)
axis2 = center - other2
axis2 /= np.linalg.norm(axis2)
coords = np.zeros((5, 3))
coords[0] = center
coords[1] = other1
coords[2] = other2
coords[3] = base.rotate_coords(-axis2 * length, self.angle, axis1) + center
coords[4] = base.rotate_coords(-axis2 * length, 2 * self.angle, axis1) + center
return coords
# def make_coords_from_four(
# self, center, other1, other2, other3, length: float = None
# ):
# """
# Get the coordinates of a tetrahedron
# Parameters
# ----------
# center : Atom or array-like
# The center of the tetrahedron
# other1 : Atom or array-like
# The first atom to define one axis of the tetrahedron
# other2 : Atom or array-like
# The second atom to define one axis of the tetrahedron
# other3 : Atom or array-like
# The third atom to define one axis of the tetrahedron
# float : float
# The bond length to use for the tetrahedron.
# If not provided the distance between center and other1 is used.
# Returns
# -------
# array-like
# The coordinates of the tetrahedron with center at the 0th index and the other 4 atoms following.
# """
# center = getattr(center, "coord", center)
# other1 = getattr(other1, "coord", other1)
# other2 = getattr(other2, "coord", other2)
# other3 = getattr(other3, "coord", other3)
# length1 = np.linalg.norm(center - other1)
# length2 = np.linalg.norm(center - other2)
# length3 = np.linalg.norm(center - other3)
# if length1 == 0 or length2 == 0 or length3 == 0:
# raise ValueError("The two atoms are at the same position")
# if length is None:
# length = length1
# axis1 = center - other1
# axis1 /= np.linalg.norm(axis1)
# # axis2 = center - other2
# # axis2 /= np.linalg.norm(axis2)
# axis3 = center - other3
# axis3 /= np.linalg.norm(axis3)
# coords = np.zeros((5, 3))
# coords[0] = center
# coords[1] = other1
# coords[2] = other2
# coords[3] = other3
# coords[4] = base.rotate_coords(-axis3 * length, self.angle, axis1) + center
# return coords
[docs]
class TrigonalPlanar(Geometry):
"""
Planar geometry for Sp2 hybridized atoms
(or atoms with 3 substituents)
"""
max_points = 3
size = 4
angle = np.radians(120)
def __init__(self, bond_length=1.2):
self.bond_length = bond_length
[docs]
def make_coords(self, *coords, length: float = None, **kwargs):
"""
Make the coordinates of a planar triangle
Parameters
----------
coords : array-like
The coordinates of the atoms that define the planar triangle
The first atom is the center of the planar triangle and the other 3 atoms follow.
float : float
The bond length to use for the planar triangle .
By default the bond length is either the default value or the distance between center and first other point (unless specified using this argument).
Returns
-------
array-like
The coordinates of the planar triangle with center at the 0th index and the other 3 atoms following.
Provided points always precede the ones that were generated.
"""
if len(coords) == 1:
return self.make_coords_from_one(coords[0], length=length)
elif len(coords) == 2:
return self.make_coords_from_two(*coords, length=length)
elif len(coords) == 3:
return self.make_coords_from_three(*coords, length=length)
else:
raise ValueError("Invalid number of atoms")
[docs]
def make_coords_from_one(self, center, length: float = None):
"""
Get the coordinates of a planar triangle
Parameters
----------
center : Atom or array-like
The center of the planar triangle
float : float
The bond length to use for the planar triangle .
If not provided the default bond length is used.
Returns
-------
array-like
The coordinates of the planar triangle with center at the 0th index and the other 3 atoms following.
"""
length = length or self.bond_length
center = getattr(center, "coord", center)
other = center + base.x_axis * length
return self.make_coords_from_two(center, other, length=length)
[docs]
def make_coords_from_two(self, center, other, length: float = None):
"""
Get the coordinates of a planar triangle
Parameters
----------
center : Atom or array-like
The center of the planar triangle
other : Atom or array-like
The other atom to define one axis of the planar triangle
float : float
The bond length to use for the planar triangle .
If not provided the distance between center and other is used.
Returns
-------
array-like
The coordinates of the planar triangle with center at the 0th index and other the 1st index, and remaing 2 atoms following.
Provided points always precede the ones that were generated. E.g. other will be at index 1.
"""
center = getattr(center, "coord", center)
other = getattr(other, "coord", other)
if length is None:
length = np.linalg.norm(center - other)
if length == 0:
raise ValueError("The two atoms are at the same position")
axis = center - other
axis /= np.linalg.norm(axis)
coords = np.zeros((4, 3))
coords[0] = center
coords[1] = other
coords[2] = (
base.rotate_coords(-axis * length, self.angle, np.cross(axis, base.z_axis))
+ center
)
coords[3] = (
base.rotate_coords(
-axis * length, 2 * self.angle, np.cross(axis, base.z_axis)
)
+ center
)
return coords
[docs]
def make_coords_from_three(self, center, other1, other2, length: float = None):
"""
Get the coordinates of a planar triangle
Parameters
----------
center : Atom or array-like
The center of the planar triangle
other1 : Atom or array-like
The first atom to define one axis of the planar triangle
other2 : Atom or array-like
The second atom to define one axis of the planar triangle
float : float
The bond length to use for the planar triangle .
If not provided the distance between center and other1 is used.
Returns
-------
array-like
The coordinates of the planar triangle with center at the 0th index and the other 3 atoms following.
Provided points always precede the ones that were generated. E.g. other1 will be at index 1 and other2 at index 2.
"""
center = getattr(center, "coord", center)
other1 = getattr(other1, "coord", other1)
other2 = getattr(other2, "coord", other2)
length1 = np.linalg.norm(center - other1)
length2 = np.linalg.norm(center - other2)
if length1 == 0 or length2 == 0:
raise ValueError("The two atoms are at the same position")
if length is None:
length = length1
axis1 = center - other1
axis1 /= np.linalg.norm(axis1)
axis2 = center - other2
axis2 /= np.linalg.norm(axis2)
coords = np.zeros((4, 3))
coords[0] = center
coords[1] = other1
coords[2] = other2
coords[3] = base.rotate_coords(-axis2 * length, np.pi, axis1) + center
return coords
[docs]
class SquarePlanar(Geometry):
"""
Square Planar geometry (or partial octahedral without axial atoms)
(has 4 substituents)
"""
max_points = 3
size = 5
angle = np.pi / 2
def __init__(self, bond_length=1.2):
self.bond_length = bond_length
[docs]
def make_coords(self, *coords, length: float = None, **kwargs):
"""
Make the coordinates of a square planar
Parameters
----------
coords : array-like
The coordinates of the atoms that define the square planar
The first atom is the center of the square planar and two more atoms may follow.
float : float
The bond length to use for the square planar.
By default the bond length is either the default value or the distance between center and first other point (unless specified using this argument).
Returns
-------
array-like
The coordinates of the square planar with center at the 0th index and the other 4 atoms following.
Provided points always precede the ones that were generated.
"""
if len(coords) == 1:
return self.make_coords_from_one(coords[0], length=length)
elif len(coords) == 2:
return self.make_coords_from_two(*coords, length=length)
elif len(coords) == 3:
return self.make_coords_from_three(*coords, length=length)
[docs]
def make_coords_from_one(self, center, length: float = None):
"""
Get the coordinates of a square planar
Parameters
----------
center : Atom or array-like
The center of the square planar
float : float
The bond length to use for the square planar.
If not provided the default bond length is used.
Returns
-------
array-like
The coordinates of the square planar with center at the 0th index and the other 5 atoms following, where first come the planar ones, then the axial ones.
"""
length = length or self.bond_length
center = getattr(center, "coord", center)
other = center + base.x_axis * length
return self.make_coords_from_two(center, other, length=length)
[docs]
def make_coords_from_two(self, center, other, length: float = None):
"""
Make the coordinates of a square planar given the central node and another node
Parameters
----------
center : Atom or array-like
The center of the square planar
other : Atom or array-like
The planar node
float : float
The bond length to use for the square planar.
If not provided the distance between center and other is used.
Returns
-------
array-like
The coordinates of the square planar with center at the 0th index and the other 4 atoms following.
Provided points always precede the ones that were generated. E.g. other will be at the 1st index.
"""
center = getattr(center, "coord", center)
other = getattr(other, "coord", other)
if length is None:
length = np.linalg.norm(center - other)
if length == 0:
raise ValueError("The two atoms are at the same position")
axis = center - other
axis /= np.linalg.norm(axis)
perpendicular_axis = np.cross(axis, base.z_axis)
coords = np.zeros((5, 3))
coords[0] = center
coords[1] = other
coords[2] = (
base.rotate_coords(-axis * length, self.angle, perpendicular_axis) + center
)
coords[3] = (
base.rotate_coords(-axis * length, 2 * self.angle, perpendicular_axis)
+ center
)
coords[4] = (
base.rotate_coords(-axis * length, 3 * self.angle, perpendicular_axis)
+ center
)
return coords
[docs]
def make_coords_from_three(self, center, other1, other2, length: float = None):
"""
Make the coordinates of a square planar given the two other nodes
Parameters
----------
other1 : Atom or array-like
The first planar node
other2 : Atom or array-like
The second planar node
float : float
The bond length to use for the square planar.
If not provided the distance between other1 and other2 is used.
Returns
-------
array-like
The coordinates of the square planar with center at the 0th index and the other 4 atoms following.
Provided points always precede the ones that were generated. E.g. other1 and other2 will be at the 1st and 2nd index.
"""
center = getattr(center, "coord", center)
other1 = getattr(other1, "coord", other1)
other2 = getattr(other2, "coord", other2)
length1 = np.linalg.norm(center - other1)
length2 = np.linalg.norm(center - other2)
if length1 == 0 or length2 == 0:
raise ValueError("The two atoms are at the same position")
if length is None:
length = length1
axis1 = center - other1
axis1 /= np.linalg.norm(axis1)
axis2 = center - other2
axis2 /= np.linalg.norm(axis2)
if np.allclose(np.abs(axis1), np.abs(axis2)):
a = np.abs(axis1)
if a[0] < 1e-6 and a[1] < 1e-6:
axis2 = base.y_axis
elif a[0] < 1e-6 and a[2] < 1e-6:
axis2 = base.z_axis
else:
axis2 = base.x_axis
perpendicular_axis = np.cross(axis1, axis2)
coords = np.zeros((5, 3))
coords[0] = center
coords[1] = other1
coords[2] = other2
coords[3] = (
base.rotate_coords(-axis1 * length, 2 * self.angle, perpendicular_axis)
+ center
)
coords[4] = (
base.rotate_coords(-axis1 * length, 3 * self.angle, perpendicular_axis)
+ center
)
return coords
[docs]
class Linear(Geometry):
"""
Linear geometry for Sp1 hybridized atoms
(or atoms with 2 substituents)
"""
max_points = 2
size = 3
angle = np.pi
def __init__(self, bond_length=1.2):
self.bond_length = bond_length
[docs]
def make_coords(self, *coords, length: float = None, **kwargs):
"""
Make the coordinates of a line
Parameters
----------
coords : array-like
The coordinates of the atoms that define the line
The first atom is the center of the line and the other 2 atoms follow.
float : float
The bond length to use for the line.
By default the bond length is either the default value or the distance between center and first other point (unless specified using this argument).
Returns
-------
array-like
The coordinates of the line with center at the 0th index and the other 2 atoms following.
Provided points always precede the ones that were generated.
"""
if len(coords) == 1:
return self.make_coords_from_one(coords[0], length=length)
elif len(coords) == 2:
return self.make_coords_from_two(*coords, length=length)
else:
raise ValueError("Invalid number of atoms")
[docs]
def make_coords_from_one(self, center, length: float = None):
"""
Get the coordinates of a line
Parameters
----------
center : Atom or array-like
The center of the line
float : float
The bond length to use for the line.
If not provided the default bond length is used.
Returns
-------
array-like
The coordinates of the line with center at the 0th index and the other 2 atoms following.
"""
length = length or self.bond_length
center = getattr(center, "coord", center)
other = center + base.x_axis * length
return self.make_coords_from_two(center, other, length=length)
[docs]
def make_coords_from_two(self, center, other, length: float = None):
"""
Get the coordinates of a line
Parameters
----------
center : Atom or array-like
The center of the line
other : Atom or array-like
The other atom to define one axis of the line
float : float
The bond length to use for the line.
If not provided the distance between center and other is used.
Returns
-------
array-like
The coordinates of the line with center at the 0th index and other the 1st index, and
the remaining atom following. Provided points always precede the ones that were generated.
E.g. other will be at index 1.
"""
center = getattr(center, "coord", center)
other = getattr(other, "coord", other)
if length is None:
length = np.linalg.norm(center - other)
if length == 0:
raise ValueError("The two atoms are at the same position")
axis = center - other
axis /= np.linalg.norm(axis)
coords = np.zeros((3, 3))
coords[0] = center
coords[1] = other
coords[2] = center + axis * length
return coords
[docs]
class TrigonalBipyramidal(Geometry):
"""
Trigonal Bipyramidal geometry for Sp3d hybridized atoms
(or atoms with 5 substituents)
"""
max_points = 3
size = 6
angle = np.radians(120)
def __init__(self, bond_length=1.2):
self.bond_length = bond_length
[docs]
def make_coords(self, *coords, length: float = None, direction: str = None):
"""
Make the coordinates of a trigonal bipyramidal
Parameters
----------
coords : array-like
The coordinates of the atoms that define the trigonal bipyramidal
The first atom is the center of the trigonal bipyramidal and two more atoms may follow.
float : float
The bond length to use for the trigonal bipyramidal.
By default the bond length is either the default value or the distance between center and first other point (unless specified using this argument).
direction : str
If the points are ambiguous with respect to the axial and planar atoms, the direction can be specified.
This can be either "axial", "planar", or "mixed".
Returns
-------
array-like
The coordinates of the trigonal bipyramidal with center at the 0th index
and the other 5 atoms following, where first come the planar ones, then the axial ones.
Points that were provided always precede the ones that were generated.
"""
if len(coords) == 1:
return self.make_coords_from_one(coords[0], length=length)
elif len(coords) == 2:
if direction == "axial":
return self.make_coords_from_two_axial(*coords, length=length)
elif direction == "planar":
return self.make_coords_from_two_planar(*coords, length=length)
else:
raise ValueError(
"No direction specified or invalid direction for two points"
)
elif len(coords) == 3:
if direction is None:
direction = _infer_point_relations(coords, self.angle)
if direction == "axial":
return self.make_coords_from_two_axial(*coords[:2], length=length)
elif direction == "planar":
return self.make_coords_from_three_planar(*coords, length=length)
elif direction == "mixed":
return self.make_coords_from_three_mixed(*coords, length=length)
else:
raise ValueError("Invalid direction for three points")
elif len(coords) > 3:
raise ValueError(
"Too many atoms for this geometry, provide at most 3 atoms"
)
[docs]
def make_coords_from_one(self, center, length: float = None):
"""
Get the coordinates of a trigonal bipyramidal
Parameters
----------
center : Atom or array-like
The center of the trigonal bipyramidal
float : float
The bond length to use for the trigonal bipyramidal.
If not provided the default bond length is used.
Returns
-------
array-like
The coordinates of the trigonal bipyramidal with center at the 0th index and the other 5 atoms following, where first come the planar ones, then the axial ones.
"""
length = length or self.bond_length
center = getattr(center, "coord", center)
other = center + base.x_axis * length
return self.make_coords_from_two_planar(center, other, length=length)
[docs]
def make_coords_from_two_axial(self, center, other, length: float = None):
"""
Make the coordinates of a trigonal bipyramidal given the central node and an axial node
(node that is in the linear axis with the central node)
Parameters
----------
center : Atom or array-like
The center of the trigonal bipyramidal
other : Atom or array-like
The axial node
float : float
The bond length to use for the trigonal bipyramidal.
If not provided the distance between center and other is used.
Returns
-------
array-like
The coordinates of the trigonal bipyramidal with center at the 0th index and the other 5 atoms following, where first come the planar ones, then the axial ones.
Provided points always precede the ones that were generated. E.g. other will be at index 1.
"""
center = getattr(center, "coord", center)
other = getattr(other, "coord", other)
if length is None:
length = np.linalg.norm(center - other)
if length == 0:
raise ValueError("The two atoms are at the same position")
axis = center - other
axis /= np.linalg.norm(axis)
perpendicular_axis = np.cross(axis, base.z_axis)
coords = np.zeros((6, 3))
coords[0] = center
coords[1] = other
coords[2] = (
base.rotate_coords(-perpendicular_axis * length, self.angle, axis) + center
)
coords[3] = (
base.rotate_coords(-perpendicular_axis * length, 2 * self.angle, axis)
+ center
)
coords[4] = (
base.rotate_coords(-perpendicular_axis * length, 3 * self.angle, axis)
+ center
)
coords[5] = center + axis * length
return coords
[docs]
def make_coords_from_two_planar(self, center, other, length: float = None):
"""
Make the coordinates of a trigonal bipyramidal given the central node and a planar node
(node that is in the plane with the central node)
Parameters
----------
center : Atom or array-like
The center of the trigonal bipyramidal
other : Atom or array-like
The planar node
float : float
The bond length to use for the trigonal bipyramidal.
If not provided the distance between center and other is used.
Returns
-------
array-like
The coordinates of the trigonal bipyramidal with center at the 0th index and the other 5 atoms following, where first come the planar ones, then the axial ones.
Provided points always precede the ones that were generated. E.g. other will be at index 1.
"""
center = getattr(center, "coord", center)
other = getattr(other, "coord", other)
if length is None:
length = np.linalg.norm(center - other)
if length == 0:
raise ValueError("The two atoms are at the same position")
axis = center - other
axis /= np.linalg.norm(axis)
a = np.abs(axis)
if a[0] < 1e-6 and a[1] < 1e-6:
axis2 = base.y_axis
elif a[0] < 1e-6 and a[2] < 1e-6:
axis2 = base.z_axis
else:
axis2 = base.x_axis
perpendicular_axis = np.cross(axis2, base.z_axis)
coords = np.zeros((6, 3))
coords[0] = center
coords[1] = other
coords[2] = (
base.rotate_coords(-axis * length, self.angle, perpendicular_axis) + center
)
coords[3] = (
base.rotate_coords(-axis * length, 2 * self.angle, perpendicular_axis)
+ center
)
coords[4] = center + perpendicular_axis * length
coords[5] = center - perpendicular_axis * length
return coords
# def make_coords_from_three_axial(
# self,
# center,
# other1,
# other2,
# length: float = None,
# ):
# """
# Make the coordinates of a trigonal bipyramidal given the axial nodes
# Parameters
# ----------
# other1 : Atom or array-like
# The first axial node
# other2 : Atom or array-like
# The second axial node
# float : float
# The bond length to use for the trigonal bipyramidal.
# If not provided the distance between other1 and other2 is used.
# Returns
# -------
# array-like
# The coordinates of the trigonal bipyramidal with center at the 0th index and the other 5 atoms following, where first come the planar ones, then the axial ones.
# """
# center = getattr(center, "coord", center)
# other1 = getattr(other1, "coord", other1)
# other2 = getattr(other2, "coord", other2)
# length1 = np.linalg.norm(center - other1)
# length2 = np.linalg.norm(center - other2)
# if length1 == 0 or length2 == 0:
# raise ValueError("The two atoms are at the same position")
# if length is None:
# length = length1
# axis1 = center - other1
# axis1 /= np.linalg.norm(axis1)
# axis2 = center - other2
# axis2 /= np.linalg.norm(axis2)
# if np.allclose(np.abs(axis1), np.abs(axis2)):
# a = np.abs(axis1)
# if a[0] < 1e-6 and a[1] < 1e-6:
# axis2 = base.y_axis
# elif a[0] < 1e-6 and a[2] < 1e-6:
# axis2 = base.z_axis
# else:
# axis2 = base.x_axis
# perpendicular_axis = np.cross(axis1, axis2)
# coords = np.zeros((6, 3))
# coords[0] = center
# coords[1] = (
# base.rotate_coords(-perpendicular_axis * length, self.angle, axis1) + center
# )
# coords[2] = (
# base.rotate_coords(-perpendicular_axis * length, 2 * self.angle, axis1)
# + center
# )
# coords[3] = (
# base.rotate_coords(-perpendicular_axis * length, 3 * self.angle, axis1)
# + center
# )
# coords[4] = other1
# coords[5] = other2
# return coords
[docs]
def make_coords_from_three_planar(
self, center, other1, other2, length: float = None
):
"""
Make the coordinates of a trigonal bipyramidal given the planar nodes
Parameters
----------
other1 : Atom or array-like
The first planar node
other2 : Atom or array-like
The second planar node
float : float
The bond length to use for the trigonal bipyramidal.
If not provided the distance between other1 and other2 is used.
Returns
-------
array-like
The coordinates of the trigonal bipyramidal with center at the 0th index and the other 5 atoms following, where first come the planar ones, then the axial ones.
Provided points always precede the ones that were generated. E.g. other1 will be at index 1 and other2 at index 2.
"""
center = getattr(center, "coord", center)
other1 = getattr(other1, "coord", other1)
other2 = getattr(other2, "coord", other2)
length1 = np.linalg.norm(center - other1)
length2 = np.linalg.norm(center - other2)
if length1 == 0 or length2 == 0:
raise ValueError("The two atoms are at the same position")
if length is None:
length = length1
axis1 = center - other1
axis1 /= np.linalg.norm(axis1)
axis2 = center - other2
axis2 /= np.linalg.norm(axis2)
if np.allclose(np.abs(axis1), np.abs(axis2)):
a = np.abs(axis1)
if a[0] < 1e-6 and a[1] < 1e-6:
axis2 = base.y_axis
elif a[0] < 1e-6 and a[2] < 1e-6:
axis2 = base.z_axis
else:
axis2 = base.x_axis
perpendicular_axis = np.cross(axis1, axis2)
coords = np.zeros((6, 3))
coords[0] = center
coords[1] = other1
coords[2] = other2
coords[3] = (
base.rotate_coords(-axis1 * length, 2 * self.angle, perpendicular_axis)
+ center
)
coords[4] = center - perpendicular_axis * length
coords[5] = center + perpendicular_axis * length
return coords
[docs]
def make_coords_from_three_mixed(
self, center, other1, other2, length: float = None
):
"""
Make the coordinates of a trigonal bipyramidal given mixed nodes
Parameters
----------
center : Atom or array-like
The center of the trigonal bipyramidal
other1 : Atom or array-like
The first node (planar)
other2 : Atom or array-like
The second node (axial)
float : float
The bond length to use for the trigonal bipyramidal.
If not provided the distance between other1 and other2 is used.
Returns
-------
array-like
The coordinates of the trigonal bipyramidal with center at the 0th index and the other 5 atoms following, where first come the planar ones, then the axial ones.
Provided points always precede the ones that were generated. E.g. other1 will be at index 1 and other2 at index 2.
"""
center = getattr(center, "coord", center)
other1 = getattr(other1, "coord", other1)
other2 = getattr(other2, "coord", other2)
length1 = np.linalg.norm(center - other2)
length2 = np.linalg.norm(center - other1)
if length1 == 0 or length2 == 0:
raise ValueError("The two atoms are at the same position")
if length is None:
length = length1
axis1 = center - other2
axis1 /= np.linalg.norm(axis1)
axis2 = center - other1
axis2 /= np.linalg.norm(axis2)
coords = np.zeros((6, 3))
coords[0] = center
coords[1] = other1
coords[2] = other2
coords[3] = base.rotate_coords(-axis2 * length, self.angle, axis1) + center
coords[4] = base.rotate_coords(-axis2 * length, 2 * self.angle, axis1) + center
coords[5] = center + axis1 * length
return coords
def _infer_point_relations(points, planar_angle, mixed_angle=np.pi / 2):
"""
Infer the directionality relation between points
Parameters
----------
points : array-like
The coordinates of the points
planar_angle : float
The angle that defines the planar relation
mixed_angle : float
The angle that defines the planar-axial mixed relation
Returns
-------
str
The directionality relation between the points
"""
if len(points) < 3:
raise ValueError("At least 3 points are required")
if len(points) == 3:
if hasattr(points[0], "coord"):
points = [p.coord for p in points]
a = points[0] - points[1]
b = points[0] - points[2]
theta = np.arccos(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))
if abs(theta - mixed_angle) < 1e-3:
return "mixed"
elif abs(theta - planar_angle) < 1e-3:
return "planar"
elif abs(theta - np.pi) < 1e-3:
return "axial"
else:
raise ValueError(
"Cannot infer point relationships. Specify 'direction' manually."
)
raise ValueError("Invalid number of points")
[docs]
class Octahedral(Geometry):
"""
Octahedral geometry for Sp3d2 hybridized atoms
(or atoms with 6 substituents)
"""
max_points = 3
size = 7
angle = np.pi / 2
def __init__(self, bond_length=1.2):
self.bond_length = bond_length
[docs]
def make_coords(self, *coords, length: float = None, direction: str = None):
"""
Make the coordinates of a trigonal bipyramidal
Parameters
----------
coords : array-like
The coordinates of the atoms that define the trigonal bipyramidal
The first atom is the center of the trigonal bipyramidal and two more atoms may follow.
float : float
The bond length to use for the trigonal bipyramidal.
By default the bond length is either the default value or the distance between center and first other point (unless specified using this argument).
direction : str
If the points are ambiguous with respect to the axial and planar atoms, the direction can be specified.
This can be either "axial", "planar", or "mixed".
Returns
-------
array-like
The coordinates of the trigonal bipyramidal with center at the 0th index
and the other 6 atoms following, where first come the planar ones, then the axial ones.
Points that were provided always precede the ones that were generated.
"""
if len(coords) == 1:
return self.make_coords_from_one(coords[0], length=length)
elif len(coords) == 2:
if direction == "axial":
return self.make_coords_from_two_axial(*coords, length=length)
elif direction == "planar":
return self.make_coords_from_two_planar(*coords, length=length)
else:
raise ValueError(
"No direction specified or invalid direction for two points"
)
elif len(coords) == 3:
if direction is None:
direction = _infer_point_relations(coords, self.angle)
if direction == "axial":
return self.make_coords_from_two_axial(*coords[:2], length=length)
elif direction == "planar":
return self.make_coords_from_three_planar(*coords, length=length)
elif direction == "mixed":
return self.make_coords_from_three_mixed(*coords, length=length)
else:
raise ValueError("Invalid direction for three points")
elif len(coords) > 3:
raise ValueError(
"Too many atoms for this geometry, provide at most 3 atoms"
)
[docs]
def make_coords_from_one(self, center, length: float = None):
"""
Get the coordinates of a trigonal bipyramidal
Parameters
----------
center : Atom or array-like
The center of the trigonal bipyramidal
float : float
The bond length to use for the trigonal bipyramidal.
If not provided the default bond length is used.
Returns
-------
array-like
The coordinates of the trigonal bipyramidal with center at the 0th index and the other 5 atoms following, where first come the planar ones, then the axial ones.
"""
length = length or self.bond_length
center = getattr(center, "coord", center)
other = center + base.x_axis * length
return self.make_coords_from_two_planar(center, other, length=length)
[docs]
def make_coords_from_two_axial(self, center, other, length: float = None):
"""
Make the coordinates of a trigonal bipyramidal given the central node and an axial node
(node that is in the linear axis with the central node)
Parameters
----------
center : Atom or array-like
The center of the trigonal bipyramidal
other : Atom or array-like
The axial node
float : float
The bond length to use for the trigonal bipyramidal.
If not provided the distance between center and other is used.
Returns
-------
array-like
The coordinates of the trigonal bipyramidal with center at the 0th index and the other 6 atoms following, where first come the planar ones, then the axial ones.
Provided points always precede the ones that were generated. E.g. center and other will be at the 0th and 1st index.
"""
center = getattr(center, "coord", center)
other = getattr(other, "coord", other)
if length is None:
length = np.linalg.norm(center - other)
if length == 0:
raise ValueError("The two atoms are at the same position")
axis = center - other
axis /= np.linalg.norm(axis)
perpendicular_axis = np.cross(axis, base.z_axis)
coords = np.zeros((7, 3))
coords[0] = center
coords[1] = other
coords[2] = (
base.rotate_coords(-perpendicular_axis * length, self.angle, axis) + center
)
coords[3] = (
base.rotate_coords(-perpendicular_axis * length, 2 * self.angle, axis)
+ center
)
coords[4] = (
base.rotate_coords(-perpendicular_axis * length, 3 * self.angle, axis)
+ center
)
coords[5] = (
base.rotate_coords(-perpendicular_axis * length, 4 * self.angle, axis)
+ center
)
coords[6] = center + axis * length
return coords
[docs]
def make_coords_from_two_planar(self, center, other, length: float = None):
"""
Make the coordinates of a trigonal bipyramidal given the central node and a planar node
(node that is in the plane with the central node)
Parameters
----------
center : Atom or array-like
The center of the trigonal bipyramidal
other : Atom or array-like
The planar node
float : float
The bond length to use for the trigonal bipyramidal.
If not provided the distance between center and other is used.
Returns
-------
array-like
The coordinates of the trigonal bipyramidal with center at the 0th index and the other 5 atoms following, where first come the planar ones, then the axial ones.
Provided points always precede the ones that were generated. E.g. other will be at the 1st index.
"""
center = getattr(center, "coord", center)
other = getattr(other, "coord", other)
if length is None:
length = np.linalg.norm(center - other)
if length == 0:
raise ValueError("The two atoms are at the same position")
axis = center - other
axis /= np.linalg.norm(axis)
perpendicular_axis = np.cross(axis, base.z_axis)
coords = np.zeros((7, 3))
coords[0] = center
coords[1] = other
coords[2] = (
base.rotate_coords(-axis * length, self.angle, perpendicular_axis) + center
)
coords[3] = (
base.rotate_coords(-axis * length, 2 * self.angle, perpendicular_axis)
+ center
)
coords[4] = (
base.rotate_coords(-axis * length, 3 * self.angle, perpendicular_axis)
+ center
)
coords[5] = center + perpendicular_axis * length
coords[6] = center - perpendicular_axis * length
return coords
# def make_coords_from_three_axial(
# self,
# center,
# other1,
# other2,
# length: float = None,
# ):
# """
# Make the coordinates of a trigonal bipyramidal given the axial nodes
# Parameters
# ----------
# other1 : Atom or array-like
# The first axial node
# other2 : Atom or array-like
# The second axial node
# float : float
# The bond length to use for the trigonal bipyramidal.
# If not provided the distance between other1 and other2 is used.
# Returns
# -------
# array-like
# The coordinates of the trigonal bipyramidal with center at the 0th index and the other 5 atoms following, where first come the planar ones, then the axial ones.
# """
# center = getattr(center, "coord", center)
# other1 = getattr(other1, "coord", other1)
# other2 = getattr(other2, "coord", other2)
# length1 = np.linalg.norm(center - other1)
# length2 = np.linalg.norm(center - other2)
# if length1 == 0 or length2 == 0:
# raise ValueError("The two atoms are at the same position")
# if length is None:
# length = length1
# axis1 = center - other1
# axis1 /= np.linalg.norm(axis1)
# axis2 = center - other2
# axis2 /= np.linalg.norm(axis2)
# if np.allclose(np.abs(axis1), np.abs(axis2)):
# a = np.abs(axis1)
# if a[0] < 1e-6 and a[1] < 1e-6:
# axis2 = base.y_axis
# elif a[0] < 1e-6 and a[2] < 1e-6:
# axis2 = base.z_axis
# else:
# axis2 = base.x_axis
# perpendicular_axis = np.cross(axis1, axis2)
# coords = np.zeros((7, 3))
# coords[0] = center
# coords[1] = (
# base.rotate_coords(-perpendicular_axis * length, self.angle, axis1) + center
# )
# coords[2] = (
# base.rotate_coords(-perpendicular_axis * length, 2 * self.angle, axis1)
# + center
# )
# coords[3] = (
# base.rotate_coords(-perpendicular_axis * length, 3 * self.angle, axis1)
# + center
# )
# coords[4] = (
# base.rotate_coords(-perpendicular_axis * length, 4 * self.angle, axis1)
# + center
# )
# coords[5] = other1
# coords[6] = other2
# return coords
[docs]
def make_coords_from_three_planar(
self, center, other1, other2, length: float = None
):
"""
Make the coordinates of a trigonal bipyramidal given the planar nodes
Parameters
----------
other1 : Atom or array-like
The first planar node
other2 : Atom or array-like
The second planar node
float : float
The bond length to use for the trigonal bipyramidal.
If not provided the distance between other1 and other2 is used.
Returns
-------
array-like
The coordinates of the trigonal bipyramidal with center at the 0th index and the other 5 atoms following, where first come the planar ones, then the axial ones.
Provided points always precede the ones that were generated. E.g. other1 and other2 will be at the 1st and 2nd index.
"""
center = getattr(center, "coord", center)
other1 = getattr(other1, "coord", other1)
other2 = getattr(other2, "coord", other2)
length1 = np.linalg.norm(center - other1)
length2 = np.linalg.norm(center - other2)
if length1 == 0 or length2 == 0:
raise ValueError("The two atoms are at the same position")
if length is None:
length = length1
axis1 = center - other1
axis1 /= np.linalg.norm(axis1)
axis2 = center - other2
axis2 /= np.linalg.norm(axis2)
if np.allclose(np.abs(axis1), np.abs(axis2)):
a = np.abs(axis1)
if a[0] < 1e-6 and a[1] < 1e-6:
axis2 = base.y_axis
elif a[0] < 1e-6 and a[2] < 1e-6:
axis2 = base.z_axis
else:
axis2 = base.x_axis
perpendicular_axis = np.cross(axis1, axis2)
coords = np.zeros((7, 3))
coords[0] = center
coords[1] = other1
coords[2] = other2
coords[3] = (
base.rotate_coords(-axis1 * length, 2 * self.angle, perpendicular_axis)
+ center
)
coords[4] = (
base.rotate_coords(-axis1 * length, 3 * self.angle, perpendicular_axis)
+ center
)
coords[5] = center - perpendicular_axis * length
coords[6] = center + perpendicular_axis * length
return coords
[docs]
def make_coords_from_three_mixed(
self, center, other1, other2, length: float = None
):
"""
Make the coordinates of a trigonal bipyramidal given mixed nodes
Parameters
----------
center : Atom or array-like
The center of the trigonal bipyramidal
other1 : Atom or array-like
The first node (planar)
other2 : Atom or array-like
The second node (axial)
float : float
The bond length to use for the trigonal bipyramidal.
If not provided the distance between other1 and other2 is used.
Returns
-------
array-like
The coordinates of the trigonal bipyramidal with center at the 0th index and the other 5 atoms following, where first come the planar ones, then the axial ones.
Provided points always precede the ones that were generated. E.g. other1 will be at index 1 and other2 will be at index 2.
"""
center = getattr(center, "coord", center)
other1 = getattr(other1, "coord", other1)
other2 = getattr(other2, "coord", other2)
length1 = np.linalg.norm(center - other2)
length2 = np.linalg.norm(center - other1)
if length1 == 0 or length2 == 0:
raise ValueError("The two atoms are at the same position")
if length is None:
length = length1
axis1 = center - other2
axis1 /= np.linalg.norm(axis1)
axis2 = center - other1
axis2 /= np.linalg.norm(axis2)
coords = np.zeros((7, 3))
coords[0] = center
coords[1] = other1
coords[2] = other2
coords[3] = base.rotate_coords(-axis2 * length, self.angle, axis1) + center
coords[4] = base.rotate_coords(-axis2 * length, 2 * self.angle, axis1) + center
coords[5] = base.rotate_coords(-axis2 * length, 3 * self.angle, axis1) + center
coords[6] = center + axis1 * length
return coords
tetrahedral = Tetrahedral()
"""
The default tetrahedral geometry
"""
trigonal_planar = TrigonalPlanar()
"""
The default trigonal planar geometry
"""
square_planar = SquarePlanar()
"""
The default square planar geometry
"""
linear = Linear()
"""
The default linear geometry
"""
trigonal_bipyramidal = TrigonalBipyramidal()
"""
The default trigonal bipyramidal geometry
"""
octahedral = Octahedral()
"""
The default octahedral geometry
"""
if __name__ == "__main__":
import buildamol as bam
base.origin = np.array([0, 0, 0], dtype=np.float64)
bam.load_small_molecules()
# mol = bam.get_compound("CH4")
# mol.rename_atom("C", "A").rename_atom("HC1", "B").rename_atom(
# "HC2", "C"
# ).rename_atom("HC3", "D").rename_atom("HC4", "E")
# ics = infer.compute_internal_coordinates(mol.bonds)
# c = mol.get_atom("A").coord
# d = base.norm_vector(*mol.bonds[0])
# # coords = compute_tetrahedron_coords(c, d)
# tet = Sp3()
# coords = tet.make_coords_from_two(mol.get_atom("A").coord, mol.get_atom("B").coord)
# v = mol.draw()
# v.draw_points(coords, colors=["orange"] * 5)
# v.draw_points(
# tet.make_coords_from_one(mol.get_atom("A").coord),
# colors=["blue"] * 5,
# )
# out3 = tet.make_coords_from_three(
# mol.get_atom("A").coord, mol.get_atom("B").coord, mol.get_atom("C").coord
# )
# v.draw_points(
# out3,
# colors=["green"] * 5,
# )
# for i in range(1, 5):
# v.draw_vector(
# str(i),
# out3[0],
# out3[i],
# color="limegreen",
# )
# v.show()
# mol = bam.get_compound("formaldehyde")[0]
# mol.rename_atom("C1", "A").rename_atom("O1", "B").rename_atom(
# "H11", "C"
# ).rename_atom("H12", "D")
# sp2 = Sp2()
# coords = sp2.make_coords_from_two(mol.get_atom("A").coord, mol.get_atom("B").coord)
# v = mol.draw()
# v.draw_points(coords, colors=["orange"] * 4)
# coords = sp2.make_coords_from_one(mol.get_atom("A").coord)
# v.draw_points(
# coords,
# colors=["blue"] * 4,
# )
# coords = sp2.make_coords_from_three(
# mol.get_atom("A").coord,
# mol.get_atom("B").coord,
# mol.get_atom("C").coord,
# length=1.8,
# )
# v.draw_points(
# coords,
# colors=["green"] * 4,
# )
# v.show()
# mol = bam.get_compound("C2H2")[0].autolabel()
# mol.rename_atom("C1", "A").rename_atom("C2", "B").rename_atom(
# "H1", "C"
# ).rename_atom("H2", "D")
# sp1 = Linear()
# coords = sp1.make_coords_from_two(mol.get_atom("A").coord, mol.get_atom("B").coord)
# v = mol.draw()
# v.draw_points(coords, colors=["orange"] * 3)
# coords = sp1.make_coords_from_one(mol.get_atom("A").coord)
# v.draw_points(
# coords,
# colors=["blue"] * 3,
# )
# v.show()
bipl = TrigonalBipyramidal()
v = bam.MoleculeViewer3D()
# coords = bipl.make_coords_from_one(np.array([0, 0, 0], dtype=np.float64))
# v.draw_points(coords, colors=["orange"] * 6)
# coords = bipl.make_coords_from_two_axial(
# np.array([0, 0, 0], dtype=np.float64), np.array([0, 1, 0], dtype=np.float64)
# )
# v.draw_points(coords, colors=["blue"] * 6)
# coords = bipl.make_coords_from_two_planar(
# np.array([0, 0, 0], dtype=np.float64), np.array([0, 1, 0], dtype=np.float64)
# )
# v.draw_points(coords, colors=["green"] * 6)
# coords = bipl.make_coords_from_three_axial(
# np.array([0, 0, 0], dtype=np.float64),
# np.array([0, 1, 0], dtype=np.float64),
# np.array([0, -1, 0], dtype=np.float64),
# )
# v.draw_points(coords, colors=["red"] * 6)
# p2 = base.rotate_coords(
# np.array([0, 1, 0], dtype=np.float64), np.radians(120), np.array([1, 0, 0])
# )
# coords = bipl.make_coords_from_three_planar(
# np.array([0, 0, 0], dtype=np.float64), np.array([0, 1, 0], dtype=np.float64), p2
# )
# v.draw_points(coords, colors=["purple"] * 6)
# coords = bipl.make_coords_from_three_mixed(coords[0], coords[5], coords[1])
# v.draw_points(coords, colors=["orange"] * 6)
octa = TrigonalBipyramidal()
coords = octa.make_coords(
np.array([0, 0, 0], dtype=np.float64),
# np.array([0.5, 0, 0], dtype=np.float64),
# np.array([1, 0, 0], dtype=np.float64),
# direction="axial",
)
v.draw_points(coords, colors=["orange"] * 7)
coords = octa.make_coords(
np.array([0, 0, 0], dtype=np.float64),
np.array([0.5, 0, 0], dtype=np.float64),
# np.array([1, 0, 0], dtype=np.float64),
direction="planar",
)
v.draw_points(coords, colors=["blue"] * 7)
coords = octa.make_coords(
np.array([0, 0, 0], dtype=np.float64),
np.array([1, 0, 0], dtype=np.float64),
np.array([0, 1, 0], dtype=np.float64),
direction="mixed",
)
v.draw_points(coords, colors=["green"] * 7)
v.show()
pass