Geometry utilities¶
Fragment alignment, translation, and bond-perception helpers:
cage_isomer_builder.utils.geometry.
random_rotation_matrix ¶
Return a uniformly random 3×3 rotation matrix.
Uses Gram-Schmidt orthonormalisation of two independent random vectors to construct a right-handed orthonormal basis, which is equivalent to sampling uniformly from SO(3).
Returns:
| Name | Type | Description |
|---|---|---|
R |
numpy.ndarray of shape (3, 3)
|
A random proper rotation matrix (det = +1). |
Source code in cage_isomer_builder/utils/geometry.py
find_third_point ¶
Extrapolate a third point P3 beyond P1, away from P2:
R-P2-P1----P3. Used to construct a second reference direction
for aligning a fragment onto a single bonding site.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
p1
|
(ndarray, shape(3))
|
|
required |
p2
|
(ndarray, shape(3))
|
|
required |
bond_length
|
float
|
Distance of P3 from P1. |
required |
Returns:
| Type | Description |
|---|---|
(ndarray, shape(3))
|
Coordinates of P3. |
Source code in cage_isomer_builder/utils/geometry.py
kabsch ¶
Kabsch algorithm: the optimal rotation matrix aligning one point set onto another.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
coordinates1
|
ndarray
|
Coordinates of the first point set. |
required |
coordinates2
|
ndarray
|
Coordinates of the second point set. |
required |
Returns:
| Type | Description |
|---|---|
(ndarray, shape(3, 3))
|
Rotation matrix that best aligns coordinates1 onto coordinates2. |
Source code in cage_isomer_builder/utils/geometry.py
find_connected_atoms ¶
Indices of every atom bonded to atom_index (e.g. the real atom
a dummy "X" marker is attached to), via a per-atom covalent-radius
cutoff.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
atoms
|
Atoms
|
|
required |
atom_index
|
int
|
Index of the atom whose bonded neighbours are wanted. |
required |
Returns:
| Type | Description |
|---|---|
np.ndarray of int
|
Indices of atoms bonded to |
Source code in cage_isomer_builder/utils/geometry.py
bonded_pairs ¶
All bonded atom-index pairs (i, j), i < j, via distance-based perception.
Uses ase.neighborlist.natural_cutoffs (mult times each atom's
covalent radius) with skin=0.0. The default skin=0.3 is meant as
an MD rebuild buffer, but NeighborList.get_neighbors() also counts
anything inside cutoff_i + cutoff_j + skin as bonded - for a typical
aromatic ring (meta C...C ~2.42 Angstrom) that silently adds every
meta/para ring pair as a spurious "bond" alongside the real ones,
corrupting the whole molecular graph (extra angle/torsion terms, wrong
coordination number fed into UFF typing). Leaving skin at 0 avoids that.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
atoms
|
Atoms
|
|
required |
mult
|
float
|
Covalent-radius multiplier passed to |
1.2
|
exclude
|
int
|
Atom index to skip entirely (e.g. a dummy/anchor atom whose own bonds the caller tracks separately). |
None
|
Returns:
| Type | Description |
|---|---|
list of (int, int)
|
Each bonded pair once, with the smaller index first. |
Source code in cage_isomer_builder/utils/geometry.py
align_fragment_orientation ¶
align_fragment_orientation(host_system, sub_fragment, bond_length, host_marker='X', fragment_marker='X', host_index=None, host_neigh_index=None)
Rotate sub_fragment in place so its own dummy-marker bond axis
aligns with a bonding site's axis in host_system.
A second reference point (find_third_point) is constructed along
the host's own bond axis so the alignment has two point pairs to work
with; resolve_fragment_twist should be applied afterward to fix
the remaining rotational freedom around that axis.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
host_system
|
Atoms
|
Host structure containing the bonding site. |
required |
sub_fragment
|
Atoms
|
Fragment to align, containing one dummy marker atom. Rotated in place. |
required |
bond_length
|
float
|
Distance of the constructed reference point from the site atom. |
required |
host_marker
|
str
|
Element symbol marking the bonding site in the host (use 'At'
for a host prepared via |
'X'
|
fragment_marker
|
str
|
Element symbol marking the bonding atom in |
'X'
|
host_index
|
int
|
Use this atom index as the host bonding site instead of searching
for the first atom matching |
None
|
host_neigh_index
|
int
|
Use this atom index as the site's real bonded neighbour instead of
searching for it with |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
sub_fragment |
Atoms
|
The aligned fragment (same object, rotated in place). |
sub_x |
int
|
Index of the dummy atom in |
host_x |
int
|
Index of the site atom in |
sub_neigh |
int
|
Index of the fragment atom bonded to its own dummy atom. |
Source code in cage_isomer_builder/utils/geometry.py
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 | |
resolve_fragment_twist ¶
resolve_fragment_twist(host_system, sub_fragment, host_x, sub_neigh, tolerance=0.75, n_angles=24, host_neigh_index=None)
Rotate an already-translated fragment around its own bond axis to avoid clashing with the host.
align_fragment_orientation only fixes the bond-axis direction (it aligns two points that are collinear with that axis - see find_third_point), so rotation of the fragment around that axis is left arbitrary. For a single-atom fragment that is harmless, but for anything with more atoms (e.g. NH2) an arbitrary twist can point a fragment atom straight into a nearby host atom. This samples n_angles evenly-spaced twists around the bond axis and keeps whichever one maximises the closest fragment-host approach (covalent-radius scaled), i.e. the least (ideally zero) clash.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
host_system
|
Atoms
|
Host structure to avoid clashing with (its own dummy/site atom at
|
required |
sub_fragment
|
Atoms
|
Fragment already translated so |
required |
host_x
|
int
|
Index of the site atom in |
required |
sub_neigh
|
int
|
Index of the fragment atom bonded to its own dummy atom - the pivot the rest of the fragment rotates around. |
required |
tolerance
|
float
|
Fraction of the summed covalent radii used as the clash threshold (default 0.75). |
0.75
|
n_angles
|
int
|
Number of evenly-spaced trial angles in [0, 360) (default 24, i.e. every 15 degrees). |
24
|
host_neigh_index
|
int
|
Use this specific atom index as host_x's real bonded neighbour instead of searching for it with find_connected_atoms - see align_fragment_orientation's own host_neigh_index for why (host_x is typically "At", whose unusually large covalent radius makes find_connected_atoms prone to picking up the wrong nearby atom in a compact structure). |
None
|
Returns:
| Type | Description |
|---|---|
Atoms
|
|
Source code in cage_isomer_builder/utils/geometry.py
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 | |
translate_fragment ¶
Translate an aligned fragment so its bonded atom coincides with the host's site atom, forming the new bond.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
host_system
|
Atoms
|
Host structure containing the bonding site. |
required |
sub_fragment
|
Atoms
|
Aligned fragment to translate. |
required |
sub_neigh
|
int
|
Index of the fragment atom bonded to its own dummy atom. |
required |
host_x
|
int
|
Index of the site atom in |
required |
Returns:
| Name | Type | Description |
|---|---|---|
sub_fragment |
Atoms
|
The translated fragment (same object, moved in place). |
Source code in cage_isomer_builder/utils/geometry.py
reflect_positions ¶
Function to reflect all atomic positions in an ase_atom (molecule/material) through a plane defined by its normal vector using the Householder reflection formula: H = I - 2 * n̂ * n̂ᵀ
This translates to:
new_position -> position - 2 * (position . n̂) * n̂
where n̂ is the unit normal vector of the reflection plane.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ase_atom
|
Atoms
|
The structure whose atomic positions will be reflected. Positions are modified in place. |
required |
reflection_normal
|
numpy.ndarray of shape (3,)
|
Normal vector defining the reflection plane passing through the origin. Does not need to be a unit vector — it is normalised internally. Examples: [0, 0, 1] -> reflect through the XY plane [1, 0, 0] -> reflect through the YZ plane [0, 1, 0] -> reflect through the XZ plane |
required |
Returns:
| Name | Type | Description |
|---|---|---|
new_ase_atom |
Atoms
|
The structure with reflected atomic positions. |
Source code in cage_isomer_builder/utils/geometry.py
project_permutation ¶
A function to find the permutation mapping between an original set of functional group anchor positions and a transformed (rotated/reflected) set, via a globally-optimal one-to-one match (the Hungarian algorithm) rather than nearest-neighbour-per-point.
Matching each transformed point to its independently-nearest
original point (the previous approach) is not guaranteed to be a
bijection: two different transformed points can both be closest to
the same original point, silently producing a "permutation" that
isn't actually one - a real, confirmed failure mode once anchors
are packed closely enough (e.g. 8 aromatic positions per linker on
a biphenyl-type core, vs. the usual 4 on a plain benzene ring).
scipy.optimize.linear_sum_assignment finds the assignment
minimising total distance subject to being one-to-one, which is
always a valid bijection; max_distance then catches the
genuinely-different failure mode where even that best assignment
doesn't actually line up (the input doesn't have the symmetry this
transformation assumes).
E.g: original: [0, 1, 2, 3] transformed: [2, 0, 3, 1]
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
original_fg_anchors
|
Atoms
|
The original set of functional group anchor atoms (At atoms), before any symmetry transformation is applied. |
required |
transformed_fg_anchors
|
Atoms
|
The transformed set of functional group anchor atoms (At atoms), after applying a rotation or reflection symmetry operation. Must have the same number of atoms as original_fg_anchors. |
required |
max_distance
|
float
|
Maximum acceptable distance (Angstrom) between a transformed anchor and its matched original anchor. Loose enough for minor numerical noise, tight enough to catch a genuine symmetry mismatch rather than silently returning a wrong permutation. |
1.0
|
Returns:
| Name | Type | Description |
|---|---|---|
permutation |
list of int
|
A list of length len(original_fg_anchors) where permutation[i] is the index in original_fg_anchors that corresponds to position i in transformed_fg_anchors. e.g. permutation[0] = 2 means the first transformed anchor maps to the third original anchor. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the best possible one-to-one match still leaves some pair
further apart than |
Source code in cage_isomer_builder/utils/geometry.py
409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 | |