Source code for chemistrykit.structure.systems.ring_strain

r"""Baeyer's angle-strain theory of carbocyclic rings.

Adolf von Baeyer argued in 1885 that a carbon atom's four bonds prefer the
tetrahedral angle of van't Hoff and Le Bel, :math:`\theta_T =
\arccos(-1/3) \approx 109.47^\circ`, and that a ring compound whose ring
is *planar* must bend its C-C-C angles to the polygon's interior angle,
:math:`180^\circ(n-2)/n`. Baeyer shared the distortion between the two
bonds at each carbon, so his angle strain per bond is

.. math::

    \delta(n) = \tfrac{1}{2}\left[\theta_T - \frac{180^\circ\,(n-2)}{n}\right]

(A. Baeyer, *Ber. Dtsch. Chem. Ges.* 18, 2269 (1885); see Eliel &
Wilen, *Stereochemistry of Organic Compounds* (Wiley, 1994), Ch. 11).
It predicts cyclopentane to be nearly strain-free and cyclopropane to be
badly strained, both correct. It also predicts growing strain for rings
of six or more, which is wrong: those rings pucker out of the plane
(Sachse, 1890) and can keep tetrahedral angles, as :func:`chair_cyclohexane_coordinates`
shows for the chair form of cyclohexane.
"""

from __future__ import annotations

import numpy as np

__all__ = ["TETRAHEDRAL_ANGLE", "planar_ring_angle", "baeyer_angle_strain", "chair_cyclohexane_coordinates"]

#: The ideal tetrahedral angle :math:`\arccos(-1/3)`, in degrees.
TETRAHEDRAL_ANGLE = float(np.degrees(np.arccos(-1.0 / 3.0)))


[docs] def planar_ring_angle(ring_size: int) -> float: """Interior angle of a regular planar polygon with `ring_size` vertices, in degrees. Parameters ---------- ring_size : int Number of ring atoms, at least 3. Returns ------- float Examples -------- >>> planar_ring_angle(3), planar_ring_angle(6) (60.0, 120.0) """ if ring_size < 3: raise ValueError("ring_size must be at least 3") return 180.0 * (ring_size - 2) / ring_size
[docs] def baeyer_angle_strain(ring_size: int) -> float: r"""Baeyer's angle strain per bond for a planar ring, in degrees. .. math:: \delta(n) = \tfrac{1}{2}\left[109.47^\circ - \frac{180^\circ\,(n-2)}{n}\right] Parameters ---------- ring_size : int Number of ring atoms, at least 3. Returns ------- float Positive when the ring angle is squeezed below tetrahedral, negative when a planar ring would force it wider. Examples -------- Baeyer's own values: 24 deg 44 min for cyclopropane, 9 deg 44 min for cyclobutane and only 0 deg 44 min for cyclopentane: >>> [round(baeyer_angle_strain(n), 2) for n in (3, 4, 5, 6)] [24.74, 9.74, 0.74, -5.26] """ return 0.5 * (TETRAHEDRAL_ANGLE - planar_ring_angle(ring_size))
[docs] def chair_cyclohexane_coordinates(bond_length: float = 1.54) -> np.ndarray: r"""Carbon coordinates of an ideal chair cyclohexane with exactly tetrahedral C-C-C angles. The six carbons sit alternately at heights :math:`\pm z` on a circle of radius :math:`\rho`, 60 degrees apart. Requiring bond length `d` and bond angle :math:`\theta_T` fixes :math:`\rho^2 = 2d^2(1-\cos\theta_T)/3` (the 1-3 distance is :math:`\rho\sqrt3`) and then :math:`4z^2 = d^2 - \rho^2`. Parameters ---------- bond_length : float, default 1.54 C-C bond length, in angstrom. Returns ------- ndarray, shape (6, 3) Ring atoms in order around the ring. Examples -------- Every C-C-C angle of the puckered chair is tetrahedral, so Baeyer's predicted strain for a six-membered ring vanishes once the ring is not forced to be planar: >>> from chemistrykit.structure.core.base_system import angle_between >>> x = chair_cyclohexane_coordinates() >>> round(angle_between(x[0] - x[1], x[2] - x[1]), 4) 109.4712 """ d = float(bond_length) rho = d * np.sqrt(2.0 * (1.0 - np.cos(np.radians(TETRAHEDRAL_ANGLE))) / 3.0) z = 0.5 * np.sqrt(d**2 - rho**2) phi = np.radians(60.0 * np.arange(6)) heights = z * np.array([1, -1, 1, -1, 1, -1], dtype=np.float64) return np.column_stack([rho * np.cos(phi), rho * np.sin(phi), heights])