molbuilder 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- molbuilder/__init__.py +8 -0
- molbuilder/__main__.py +6 -0
- molbuilder/atomic/__init__.py +4 -0
- molbuilder/atomic/bohr.py +235 -0
- molbuilder/atomic/quantum_atom.py +334 -0
- molbuilder/atomic/quantum_numbers.py +196 -0
- molbuilder/atomic/wavefunctions.py +297 -0
- molbuilder/bonding/__init__.py +4 -0
- molbuilder/bonding/covalent.py +442 -0
- molbuilder/bonding/lewis.py +347 -0
- molbuilder/bonding/vsepr.py +433 -0
- molbuilder/cli/__init__.py +1 -0
- molbuilder/cli/demos.py +516 -0
- molbuilder/cli/menu.py +127 -0
- molbuilder/cli/wizard.py +831 -0
- molbuilder/core/__init__.py +6 -0
- molbuilder/core/bond_data.py +170 -0
- molbuilder/core/constants.py +51 -0
- molbuilder/core/element_properties.py +183 -0
- molbuilder/core/elements.py +181 -0
- molbuilder/core/geometry.py +232 -0
- molbuilder/gui/__init__.py +2 -0
- molbuilder/gui/app.py +286 -0
- molbuilder/gui/canvas3d.py +115 -0
- molbuilder/gui/dialogs.py +117 -0
- molbuilder/gui/event_handler.py +118 -0
- molbuilder/gui/sidebar.py +105 -0
- molbuilder/gui/toolbar.py +71 -0
- molbuilder/io/__init__.py +1 -0
- molbuilder/io/json_io.py +146 -0
- molbuilder/io/mol_sdf.py +169 -0
- molbuilder/io/pdb.py +184 -0
- molbuilder/io/smiles_io.py +47 -0
- molbuilder/io/xyz.py +103 -0
- molbuilder/molecule/__init__.py +2 -0
- molbuilder/molecule/amino_acids.py +919 -0
- molbuilder/molecule/builders.py +257 -0
- molbuilder/molecule/conformations.py +70 -0
- molbuilder/molecule/functional_groups.py +484 -0
- molbuilder/molecule/graph.py +712 -0
- molbuilder/molecule/peptides.py +13 -0
- molbuilder/molecule/stereochemistry.py +6 -0
- molbuilder/process/__init__.py +3 -0
- molbuilder/process/conditions.py +260 -0
- molbuilder/process/costing.py +316 -0
- molbuilder/process/purification.py +285 -0
- molbuilder/process/reactor.py +297 -0
- molbuilder/process/safety.py +476 -0
- molbuilder/process/scale_up.py +427 -0
- molbuilder/process/solvent_systems.py +204 -0
- molbuilder/reactions/__init__.py +3 -0
- molbuilder/reactions/functional_group_detect.py +728 -0
- molbuilder/reactions/knowledge_base.py +1716 -0
- molbuilder/reactions/reaction_types.py +102 -0
- molbuilder/reactions/reagent_data.py +1248 -0
- molbuilder/reactions/retrosynthesis.py +1430 -0
- molbuilder/reactions/synthesis_route.py +377 -0
- molbuilder/reports/__init__.py +158 -0
- molbuilder/reports/cost_report.py +206 -0
- molbuilder/reports/molecule_report.py +279 -0
- molbuilder/reports/safety_report.py +296 -0
- molbuilder/reports/synthesis_report.py +283 -0
- molbuilder/reports/text_formatter.py +170 -0
- molbuilder/smiles/__init__.py +4 -0
- molbuilder/smiles/parser.py +487 -0
- molbuilder/smiles/tokenizer.py +291 -0
- molbuilder/smiles/writer.py +375 -0
- molbuilder/visualization/__init__.py +1 -0
- molbuilder/visualization/bohr_viz.py +166 -0
- molbuilder/visualization/molecule_viz.py +368 -0
- molbuilder/visualization/quantum_viz.py +434 -0
- molbuilder/visualization/theme.py +12 -0
- molbuilder-1.0.0.dist-info/METADATA +360 -0
- molbuilder-1.0.0.dist-info/RECORD +78 -0
- molbuilder-1.0.0.dist-info/WHEEL +5 -0
- molbuilder-1.0.0.dist-info/entry_points.txt +2 -0
- molbuilder-1.0.0.dist-info/licenses/LICENSE +21 -0
- molbuilder-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"""Core data and utilities for molecular modeling."""
|
|
2
|
+
from molbuilder.core.constants import *
|
|
3
|
+
from molbuilder.core.elements import ELEMENTS, SYMBOL_TO_Z, from_symbol, from_name
|
|
4
|
+
from molbuilder.core.element_properties import electronegativity, covalent_radius_pm
|
|
5
|
+
from molbuilder.core.geometry import normalize, rotation_matrix, place_atom_zmatrix
|
|
6
|
+
from molbuilder.core.bond_data import bond_length, SP3_ANGLE, SP2_ANGLE, SP_ANGLE
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Bond reference data: standard bond lengths, dissociation energies,
|
|
3
|
+
torsion barrier parameters.
|
|
4
|
+
|
|
5
|
+
Consolidated from molecular_conformations.py (STANDARD_BOND_LENGTHS,
|
|
6
|
+
TORSION_BARRIERS) and covalent_bonds.py (BDE_TABLE).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from molbuilder.core.element_properties import estimated_bond_length_angstrom
|
|
10
|
+
|
|
11
|
+
# ===================================================================
|
|
12
|
+
# Standard bond lengths in Angstroms (experimental averages)
|
|
13
|
+
# ===================================================================
|
|
14
|
+
# Keys: (symbol_a, symbol_b, order) with symbols in alphabetical order.
|
|
15
|
+
|
|
16
|
+
STANDARD_BOND_LENGTHS: dict[tuple[str, str, int], float] = {
|
|
17
|
+
("C", "C", 1): 1.54,
|
|
18
|
+
("C", "C", 2): 1.34,
|
|
19
|
+
("C", "C", 3): 1.20,
|
|
20
|
+
("C", "H", 1): 1.09,
|
|
21
|
+
("C", "O", 1): 1.43,
|
|
22
|
+
("C", "O", 2): 1.23,
|
|
23
|
+
("C", "N", 1): 1.47,
|
|
24
|
+
("C", "N", 2): 1.29,
|
|
25
|
+
("C", "N", 3): 1.16,
|
|
26
|
+
("C", "F", 1): 1.35,
|
|
27
|
+
("C", "Cl", 1): 1.77,
|
|
28
|
+
("Br", "C", 1): 1.94,
|
|
29
|
+
("C", "I", 1): 2.14,
|
|
30
|
+
("C", "S", 1): 1.82,
|
|
31
|
+
("H", "O", 1): 0.96,
|
|
32
|
+
("H", "N", 1): 1.01,
|
|
33
|
+
("H", "S", 1): 1.34,
|
|
34
|
+
("F", "F", 1): 1.42,
|
|
35
|
+
("Cl", "Cl", 1): 1.99,
|
|
36
|
+
("Br", "Br", 1): 2.28,
|
|
37
|
+
("H", "H", 1): 0.74,
|
|
38
|
+
("S", "S", 1): 2.05,
|
|
39
|
+
("N", "O", 1): 1.36,
|
|
40
|
+
("N", "O", 2): 1.21,
|
|
41
|
+
("O", "P", 1): 1.63,
|
|
42
|
+
("O", "P", 2): 1.48,
|
|
43
|
+
("H", "P", 1): 1.44,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# Standard bond angles in degrees
|
|
47
|
+
SP3_ANGLE = 109.47 # tetrahedral
|
|
48
|
+
SP2_ANGLE = 120.0 # trigonal planar
|
|
49
|
+
SP_ANGLE = 180.0 # linear
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# ===================================================================
|
|
53
|
+
# Bond dissociation energies (kJ/mol)
|
|
54
|
+
# ===================================================================
|
|
55
|
+
# Sources: CRC Handbook, Darwent (NSRDS-NBS 31), Kerr & Stocker.
|
|
56
|
+
# Keys: (symbol_a, symbol_b, bond_order) alphabetical order.
|
|
57
|
+
|
|
58
|
+
BDE_TABLE: dict[tuple[str, str, int], float] = {
|
|
59
|
+
# Single bonds
|
|
60
|
+
("H", "H", 1): 436,
|
|
61
|
+
("C", "H", 1): 413,
|
|
62
|
+
("C", "C", 1): 348,
|
|
63
|
+
("C", "N", 1): 293,
|
|
64
|
+
("C", "O", 1): 358,
|
|
65
|
+
("C", "F", 1): 485,
|
|
66
|
+
("C", "Cl", 1): 328,
|
|
67
|
+
("C", "Br", 1): 276,
|
|
68
|
+
("C", "I", 1): 240,
|
|
69
|
+
("C", "S", 1): 272,
|
|
70
|
+
("N", "H", 1): 391,
|
|
71
|
+
("N", "N", 1): 163,
|
|
72
|
+
("N", "O", 1): 201,
|
|
73
|
+
("O", "H", 1): 463,
|
|
74
|
+
("O", "O", 1): 146,
|
|
75
|
+
("F", "F", 1): 155,
|
|
76
|
+
("F", "H", 1): 567,
|
|
77
|
+
("Cl", "Cl", 1): 242,
|
|
78
|
+
("Cl", "H", 1): 431,
|
|
79
|
+
("Br", "Br", 1): 193,
|
|
80
|
+
("Br", "H", 1): 366,
|
|
81
|
+
("H", "I", 1): 297,
|
|
82
|
+
("I", "I", 1): 151,
|
|
83
|
+
("H", "S", 1): 363,
|
|
84
|
+
("S", "S", 1): 266,
|
|
85
|
+
("P", "H", 1): 322,
|
|
86
|
+
("O", "Si", 1): 452,
|
|
87
|
+
("H", "Si", 1): 318,
|
|
88
|
+
# Double bonds
|
|
89
|
+
("C", "C", 2): 614,
|
|
90
|
+
("C", "N", 2): 615,
|
|
91
|
+
("C", "O", 2): 799,
|
|
92
|
+
("N", "N", 2): 418,
|
|
93
|
+
("O", "O", 2): 498,
|
|
94
|
+
("N", "O", 2): 607,
|
|
95
|
+
("C", "S", 2): 577,
|
|
96
|
+
("S", "O", 2): 522,
|
|
97
|
+
# Additional single bonds (P2-DATA-2)
|
|
98
|
+
("C", "P", 1): 264, # Luo 2007
|
|
99
|
+
("C", "Si", 1): 318, # CRC Handbook
|
|
100
|
+
("N", "S", 1): 272, # Benson estimates
|
|
101
|
+
("Cl", "N", 1): 200, # chloramine / N-Cl
|
|
102
|
+
("F", "O", 1): 190, # OF2
|
|
103
|
+
("O", "S", 1): 265, # dimethyl sulfoxide (single)
|
|
104
|
+
("F", "S", 1): 327, # SF6 type
|
|
105
|
+
("N", "P", 1): 230, # P-N amine
|
|
106
|
+
("F", "Si", 1): 565, # SiF4
|
|
107
|
+
# Triple bonds
|
|
108
|
+
("C", "C", 3): 839,
|
|
109
|
+
("C", "N", 3): 891,
|
|
110
|
+
("N", "N", 3): 941,
|
|
111
|
+
("C", "O", 3): 1072,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# ===================================================================
|
|
116
|
+
# Torsion barrier parameters (kJ/mol) -- OPLS-AA
|
|
117
|
+
# ===================================================================
|
|
118
|
+
|
|
119
|
+
TORSION_BARRIERS: dict[str, dict[str, float]] = {
|
|
120
|
+
# sp3-sp3 torsions (alkanes)
|
|
121
|
+
"H_sp3_sp3_H": {"V1": 0.0, "V2": 0.0, "V3": 1.39},
|
|
122
|
+
"C_sp3_sp3_C": {"V1": 2.73, "V2": -0.53, "V3": 0.84},
|
|
123
|
+
"C_sp3_sp3_H": {"V1": 0.0, "V2": 0.0, "V3": 0.76},
|
|
124
|
+
"H_sp3_sp3_C": {"V1": 0.0, "V2": 0.0, "V3": 0.76},
|
|
125
|
+
# sp2-sp3 torsions (P2-DATA-3, OPLS-AA Jorgensen et al. JACS 1996)
|
|
126
|
+
"C_sp2_sp3_C": {"V1": 0.0, "V2": 0.0, "V3": 0.50},
|
|
127
|
+
"C_sp2_sp3_H": {"V1": 0.0, "V2": 0.0, "V3": 0.50},
|
|
128
|
+
"H_sp2_sp3_C": {"V1": 0.0, "V2": 0.0, "V3": 0.50},
|
|
129
|
+
"H_sp2_sp3_H": {"V1": 0.0, "V2": 0.0, "V3": 0.50},
|
|
130
|
+
"H_sp3_sp2_C": {"V1": 0.0, "V2": 0.0, "V3": 0.50},
|
|
131
|
+
"C_sp3_sp2_C": {"V1": 0.0, "V2": 0.0, "V3": 0.50},
|
|
132
|
+
"C_sp3_sp2_H": {"V1": 0.0, "V2": 0.0, "V3": 0.50},
|
|
133
|
+
# sp2-sp2 torsions (conjugated systems)
|
|
134
|
+
"C_sp2_sp2_C": {"V1": 0.0, "V2": 12.0, "V3": 0.0},
|
|
135
|
+
"C_sp2_sp2_H": {"V1": 0.0, "V2": 12.0, "V3": 0.0},
|
|
136
|
+
"H_sp2_sp2_C": {"V1": 0.0, "V2": 12.0, "V3": 0.0},
|
|
137
|
+
"H_sp2_sp2_H": {"V1": 0.0, "V2": 12.0, "V3": 0.0},
|
|
138
|
+
"default": {"V1": 0.0, "V2": 0.0, "V3": 1.00},
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# ===================================================================
|
|
143
|
+
# Electronegativity thresholds for bond polarity
|
|
144
|
+
# ===================================================================
|
|
145
|
+
|
|
146
|
+
NONPOLAR_THRESHOLD = 0.4
|
|
147
|
+
POLAR_COVALENT_MAX = 1.7
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# ===================================================================
|
|
151
|
+
# Lookup functions
|
|
152
|
+
# ===================================================================
|
|
153
|
+
|
|
154
|
+
def bond_length(sym_a: str, sym_b: str, order: int = 1) -> float:
|
|
155
|
+
"""Look up standard bond length in Angstroms.
|
|
156
|
+
|
|
157
|
+
Checks STANDARD_BOND_LENGTHS first, falls back to
|
|
158
|
+
element_properties covalent-radii estimate.
|
|
159
|
+
"""
|
|
160
|
+
a, b = sorted([sym_a, sym_b])
|
|
161
|
+
key = (a, b, order)
|
|
162
|
+
if key in STANDARD_BOND_LENGTHS:
|
|
163
|
+
return STANDARD_BOND_LENGTHS[key]
|
|
164
|
+
return estimated_bond_length_angstrom(sym_a, sym_b, order)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def bde_lookup(sym_a: str, sym_b: str, order: int = 1) -> float | None:
|
|
168
|
+
"""Look up mean bond dissociation energy in kJ/mol, or None."""
|
|
169
|
+
a, b = sorted([sym_a, sym_b])
|
|
170
|
+
return BDE_TABLE.get((a, b, order))
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Physical constants (SI units) used throughout molbuilder.
|
|
3
|
+
|
|
4
|
+
Consolidated from bohr_model.py, quantum_wavefunctions.py, and
|
|
5
|
+
atomic_particle_physical_laws.py.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import math
|
|
9
|
+
|
|
10
|
+
# ---------------------------------------------------------------------------
|
|
11
|
+
# Fundamental constants
|
|
12
|
+
# ---------------------------------------------------------------------------
|
|
13
|
+
BOHR_RADIUS_M = 5.29177210903e-11 # metres
|
|
14
|
+
BOHR_RADIUS_PM = 52.9177210903 # picometres
|
|
15
|
+
PLANCK_CONSTANT = 6.62607015e-34 # J s
|
|
16
|
+
HBAR = PLANCK_CONSTANT / (2 * math.pi) # reduced Planck constant (J s)
|
|
17
|
+
SPEED_OF_LIGHT = 2.99792458e8 # m/s
|
|
18
|
+
ELECTRON_CHARGE = 1.602176634e-19 # C
|
|
19
|
+
ELECTRON_MASS = 9.1093837015e-31 # kg
|
|
20
|
+
COULOMB_CONSTANT = 8.9875517873681764e9 # N m^2 / C^2
|
|
21
|
+
VACUUM_PERMITTIVITY = 8.8541878128e-12 # F/m
|
|
22
|
+
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
# Unit conversions
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
EV_TO_JOULES = 1.602176634e-19
|
|
27
|
+
RYDBERG_ENERGY_EV = 13.605693122994 # eV
|
|
28
|
+
DEBYE_PER_E_ANGSTROM = 4.8032 # 1 e*A = 4.8032 D
|
|
29
|
+
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
# Fine structure constant
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
FINE_STRUCTURE_ALPHA = (
|
|
34
|
+
ELECTRON_CHARGE**2
|
|
35
|
+
/ (4 * math.pi * VACUUM_PERMITTIVITY * HBAR * SPEED_OF_LIGHT)
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
# Shell capacity
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
MAX_ELECTRONS_PER_SHELL = [2 * n**2 for n in range(1, 8)] # shells 1-7
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
# Electrostatics
|
|
46
|
+
# ---------------------------------------------------------------------------
|
|
47
|
+
def coulombs_law(q1: float, q2: float, r: float) -> float:
|
|
48
|
+
"""Coulomb force between two charges q1, q2 (Coulombs) separated by r (metres)."""
|
|
49
|
+
if r <= 0:
|
|
50
|
+
raise ValueError("Distance must be positive")
|
|
51
|
+
return COULOMB_CONSTANT * (abs(q1) * abs(q2)) / r**2
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Chemical properties of elements: electronegativity, covalent radii,
|
|
3
|
+
CPK colours, octet targets, period/group information.
|
|
4
|
+
|
|
5
|
+
Migrated from element_data.py.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import warnings
|
|
9
|
+
|
|
10
|
+
from molbuilder.core.elements import SYMBOL_TO_Z
|
|
11
|
+
|
|
12
|
+
# ---------------------------------------------------------------------------
|
|
13
|
+
# Pauling electronegativity (symbol -> float or None)
|
|
14
|
+
# ---------------------------------------------------------------------------
|
|
15
|
+
PAULING_ELECTRONEGATIVITY: dict[str, float | None] = {
|
|
16
|
+
"H": 2.20, "He": None,
|
|
17
|
+
"Li": 0.98, "Be": 1.57, "B": 2.04, "C": 2.55, "N": 3.04,
|
|
18
|
+
"O": 3.44, "F": 3.98, "Ne": None,
|
|
19
|
+
"Na": 0.93, "Mg": 1.31, "Al": 1.61, "Si": 1.90, "P": 2.19,
|
|
20
|
+
"S": 2.58, "Cl": 3.16, "Ar": None,
|
|
21
|
+
"K": 0.82, "Ca": 1.00, "Sc": 1.36, "Ti": 1.54, "V": 1.63,
|
|
22
|
+
"Cr": 1.66, "Mn": 1.55, "Fe": 1.83, "Co": 1.88, "Ni": 1.91,
|
|
23
|
+
"Cu": 1.90, "Zn": 1.65, "Ga": 1.81, "Ge": 2.01, "As": 2.18,
|
|
24
|
+
"Se": 2.55, "Br": 2.96, "Kr": 3.00,
|
|
25
|
+
"Rb": 0.82, "Sr": 0.95, "Y": 1.22, "Zr": 1.33, "Nb": 1.60,
|
|
26
|
+
"Mo": 2.16, "Tc": 1.90, "Ru": 2.20, "Rh": 2.28, "Pd": 2.20,
|
|
27
|
+
"Ag": 1.93, "Cd": 1.69, "In": 1.78, "Sn": 1.96, "Sb": 2.05,
|
|
28
|
+
"Te": 2.10, "I": 2.66, "Xe": 2.60,
|
|
29
|
+
"Cs": 0.79, "Ba": 0.89, "La": 1.10, "Ce": 1.12, "Pr": 1.13,
|
|
30
|
+
"Nd": 1.14, "Pm": 1.13, "Sm": 1.17, "Eu": 1.20, "Gd": 1.20,
|
|
31
|
+
"Tb": 1.10, "Dy": 1.22, "Ho": 1.23, "Er": 1.24, "Tm": 1.25,
|
|
32
|
+
"Yb": 1.10, "Lu": 1.27, "Hf": 1.30, "Ta": 1.50, "W": 2.36,
|
|
33
|
+
"Re": 1.90, "Os": 2.20, "Ir": 2.20, "Pt": 2.28, "Au": 2.54,
|
|
34
|
+
"Hg": 2.00, "Tl": 1.62, "Pb": 1.87, "Bi": 2.02, "Po": 2.00,
|
|
35
|
+
"At": 2.20, "Rn": 2.20,
|
|
36
|
+
"Fr": 0.70, "Ra": 0.90, "Ac": 1.10, "Th": 1.30, "Pa": 1.50,
|
|
37
|
+
"U": 1.38, "Np": 1.36, "Pu": 1.28, "Am": 1.30, "Cm": 1.30,
|
|
38
|
+
"Bk": 1.30, "Cf": 1.30, "Es": 1.30, "Fm": 1.30, "Md": 1.30,
|
|
39
|
+
"No": 1.30, "Lr": 1.30,
|
|
40
|
+
"Rf": None, "Db": None, "Sg": None, "Bh": None, "Hs": None,
|
|
41
|
+
"Mt": None, "Ds": None, "Rg": None, "Cn": None, "Nh": None,
|
|
42
|
+
"Fl": None, "Mc": None, "Lv": None, "Ts": None, "Og": None,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
# Single-bond covalent radii in picometres (Cordero et al. 2008)
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
COVALENT_RADII_PM: dict[str, float] = {
|
|
49
|
+
"H": 31, "He": 28,
|
|
50
|
+
"Li": 128, "Be": 96, "B": 84, "C": 76, "N": 71,
|
|
51
|
+
"O": 66, "F": 57, "Ne": 58,
|
|
52
|
+
"Na": 166, "Mg": 141, "Al": 121, "Si": 111, "P": 107,
|
|
53
|
+
"S": 105, "Cl": 102, "Ar": 106,
|
|
54
|
+
"K": 203, "Ca": 176, "Sc": 170, "Ti": 160, "V": 153,
|
|
55
|
+
"Cr": 139, "Mn": 139, "Fe": 132, "Co": 126, "Ni": 124,
|
|
56
|
+
"Cu": 132, "Zn": 122, "Ga": 122, "Ge": 120, "As": 119,
|
|
57
|
+
"Se": 120, "Br": 120, "Kr": 116,
|
|
58
|
+
"Rb": 220, "Sr": 195, "Y": 190, "Zr": 175, "Nb": 164,
|
|
59
|
+
"Mo": 154, "Tc": 147, "Ru": 146, "Rh": 142, "Pd": 139,
|
|
60
|
+
"Ag": 145, "Cd": 144, "In": 142, "Sn": 139, "Sb": 139,
|
|
61
|
+
"Te": 138, "I": 139, "Xe": 140,
|
|
62
|
+
"Cs": 244, "Ba": 215, "La": 207, "Ce": 204, "Pr": 203,
|
|
63
|
+
"Nd": 201, "Pm": 199, "Sm": 198, "Eu": 198, "Gd": 196,
|
|
64
|
+
"Tb": 194, "Dy": 192, "Ho": 192, "Er": 189, "Tm": 190,
|
|
65
|
+
"Yb": 187, "Lu": 187, "Hf": 175, "Ta": 170, "W": 162,
|
|
66
|
+
"Re": 151, "Os": 144, "Ir": 141, "Pt": 136, "Au": 136,
|
|
67
|
+
"Hg": 132, "Tl": 145, "Pb": 146, "Bi": 148, "Po": 140,
|
|
68
|
+
"At": 150, "Rn": 150,
|
|
69
|
+
"Fr": 260, "Ra": 221, "Ac": 215, "Th": 206, "Pa": 200,
|
|
70
|
+
"U": 196, "Np": 190, "Pu": 187, "Am": 180, "Cm": 169,
|
|
71
|
+
# Elements 97-118 (Pyykko & Atsumi 2009, estimated for superheavy)
|
|
72
|
+
"Bk": 168, "Cf": 168, "Es": 165, "Fm": 167, "Md": 173,
|
|
73
|
+
"No": 176, "Lr": 161, "Rf": 157, "Db": 149, "Sg": 143,
|
|
74
|
+
"Bh": 141, "Hs": 134, "Mt": 129, "Ds": 128, "Rg": 121,
|
|
75
|
+
"Cn": 122, "Nh": 136, "Fl": 143, "Mc": 162, "Lv": 175,
|
|
76
|
+
"Ts": 165, "Og": 157,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# ---------------------------------------------------------------------------
|
|
80
|
+
# CPK colours (hex strings)
|
|
81
|
+
# ---------------------------------------------------------------------------
|
|
82
|
+
CPK_COLORS: dict[str, str] = {
|
|
83
|
+
"H": "#FFFFFF", "He": "#D9FFFF",
|
|
84
|
+
"Li": "#CC80FF", "Be": "#C2FF00", "B": "#FFB5B5", "C": "#909090",
|
|
85
|
+
"N": "#3050F8", "O": "#FF0D0D", "F": "#90E050", "Ne": "#B3E3F5",
|
|
86
|
+
"Na": "#AB5CF2", "Mg": "#8AFF00", "Al": "#BFA6A6", "Si": "#F0C8A0",
|
|
87
|
+
"P": "#FF8000", "S": "#FFFF30", "Cl": "#1FF01F", "Ar": "#80D1E3",
|
|
88
|
+
"K": "#8F40D4", "Ca": "#3DFF00", "Sc": "#E6E6E6", "Ti": "#BFC2C7",
|
|
89
|
+
"V": "#A6A6AB", "Cr": "#8A99C7", "Mn": "#9C7AC7", "Fe": "#E06633",
|
|
90
|
+
"Co": "#F090A0", "Ni": "#50D050", "Cu": "#C88033", "Zn": "#7D80B0",
|
|
91
|
+
"Ga": "#C28F8F", "Ge": "#668F8F", "As": "#BD80E3", "Se": "#FFA100",
|
|
92
|
+
"Br": "#A62929", "Kr": "#5CB8D1",
|
|
93
|
+
"Rb": "#702EB0", "Sr": "#00FF00", "Y": "#94FFFF", "Zr": "#94E0E0",
|
|
94
|
+
"Nb": "#73C2C9", "Mo": "#54B5B5", "Tc": "#3B9E9E", "Ru": "#248F8F",
|
|
95
|
+
"Rh": "#0A7D8C", "Pd": "#006985", "Ag": "#C0C0C0", "Cd": "#FFD98F",
|
|
96
|
+
"In": "#A67573", "Sn": "#668080", "Sb": "#9E63B5", "Te": "#D47A00",
|
|
97
|
+
"I": "#940094", "Xe": "#429EB0",
|
|
98
|
+
"default": "#FF69B4",
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
# ---------------------------------------------------------------------------
|
|
102
|
+
# Target electron counts (Lewis structure octet rules)
|
|
103
|
+
# ---------------------------------------------------------------------------
|
|
104
|
+
TARGET_ELECTRONS: dict[str, int] = {
|
|
105
|
+
"H": 2, "He": 2, "Li": 2, "Be": 4, "B": 6,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# ===================================================================
|
|
110
|
+
# Helper functions
|
|
111
|
+
# ===================================================================
|
|
112
|
+
|
|
113
|
+
def electronegativity(symbol: str) -> float:
|
|
114
|
+
"""Pauling electronegativity. Returns 0.0 for elements without a value."""
|
|
115
|
+
val = PAULING_ELECTRONEGATIVITY.get(symbol)
|
|
116
|
+
return 0.0 if val is None else val
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def covalent_radius_pm(symbol: str) -> float:
|
|
120
|
+
"""Single-bond covalent radius in picometres. Fallback: 150 pm."""
|
|
121
|
+
val = COVALENT_RADII_PM.get(symbol)
|
|
122
|
+
if val is None:
|
|
123
|
+
warnings.warn(
|
|
124
|
+
f"No covalent radius data for '{symbol}'; using 150 pm fallback",
|
|
125
|
+
stacklevel=2,
|
|
126
|
+
)
|
|
127
|
+
return 150.0
|
|
128
|
+
return val
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def estimated_bond_length_pm(symbol_a: str, symbol_b: str,
|
|
132
|
+
bond_order: int = 1) -> float:
|
|
133
|
+
"""Estimate bond length in picometres from covalent radii.
|
|
134
|
+
|
|
135
|
+
Single bond: r(A) + r(B)
|
|
136
|
+
Double bond: ~0.87 * single
|
|
137
|
+
Triple bond: ~0.78 * single
|
|
138
|
+
"""
|
|
139
|
+
single = covalent_radius_pm(symbol_a) + covalent_radius_pm(symbol_b)
|
|
140
|
+
if bond_order == 2:
|
|
141
|
+
return single * 0.87
|
|
142
|
+
elif bond_order == 3:
|
|
143
|
+
return single * 0.78
|
|
144
|
+
return single
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def estimated_bond_length_angstrom(symbol_a: str, symbol_b: str,
|
|
148
|
+
bond_order: int = 1) -> float:
|
|
149
|
+
"""Estimate bond length in Angstroms (pm / 100)."""
|
|
150
|
+
return estimated_bond_length_pm(symbol_a, symbol_b, bond_order) / 100.0
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def cpk_color(symbol: str) -> str:
|
|
154
|
+
"""CPK hex colour for an element, with fallback."""
|
|
155
|
+
return CPK_COLORS.get(symbol, CPK_COLORS["default"])
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def target_electrons(symbol: str) -> int:
|
|
159
|
+
"""Target electron count for octet/duet rule."""
|
|
160
|
+
return TARGET_ELECTRONS.get(symbol, 8)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def period(symbol: str) -> int:
|
|
164
|
+
"""Period (row) of an element in the periodic table."""
|
|
165
|
+
z = SYMBOL_TO_Z.get(symbol, 0)
|
|
166
|
+
if z <= 2:
|
|
167
|
+
return 1
|
|
168
|
+
if z <= 10:
|
|
169
|
+
return 2
|
|
170
|
+
if z <= 18:
|
|
171
|
+
return 3
|
|
172
|
+
if z <= 36:
|
|
173
|
+
return 4
|
|
174
|
+
if z <= 54:
|
|
175
|
+
return 5
|
|
176
|
+
if z <= 86:
|
|
177
|
+
return 6
|
|
178
|
+
return 7
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def can_expand_octet(symbol: str) -> bool:
|
|
182
|
+
"""Whether an element can have an expanded octet (period 3+)."""
|
|
183
|
+
return period(symbol) >= 3
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Periodic table element data: symbols, names, atomic weights.
|
|
3
|
+
|
|
4
|
+
Migrated from bohr_model.py -- the canonical element registry for molbuilder.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# ---------------------------------------------------------------------------
|
|
8
|
+
# Element data (atomic_number -> (symbol, name, standard_atomic_weight))
|
|
9
|
+
# ---------------------------------------------------------------------------
|
|
10
|
+
ELEMENTS: dict[int, tuple[str, str, float]] = {
|
|
11
|
+
1: ("H", "Hydrogen", 1.008),
|
|
12
|
+
2: ("He", "Helium", 4.003),
|
|
13
|
+
3: ("Li", "Lithium", 6.941),
|
|
14
|
+
4: ("Be", "Beryllium", 9.012),
|
|
15
|
+
5: ("B", "Boron", 10.811),
|
|
16
|
+
6: ("C", "Carbon", 12.011),
|
|
17
|
+
7: ("N", "Nitrogen", 14.007),
|
|
18
|
+
8: ("O", "Oxygen", 15.999),
|
|
19
|
+
9: ("F", "Fluorine", 18.998),
|
|
20
|
+
10: ("Ne", "Neon", 20.180),
|
|
21
|
+
11: ("Na", "Sodium", 22.990),
|
|
22
|
+
12: ("Mg", "Magnesium", 24.305),
|
|
23
|
+
13: ("Al", "Aluminium", 26.982),
|
|
24
|
+
14: ("Si", "Silicon", 28.086),
|
|
25
|
+
15: ("P", "Phosphorus", 30.974),
|
|
26
|
+
16: ("S", "Sulfur", 32.065),
|
|
27
|
+
17: ("Cl", "Chlorine", 35.453),
|
|
28
|
+
18: ("Ar", "Argon", 39.948),
|
|
29
|
+
19: ("K", "Potassium", 39.098),
|
|
30
|
+
20: ("Ca", "Calcium", 40.078),
|
|
31
|
+
21: ("Sc", "Scandium", 44.956),
|
|
32
|
+
22: ("Ti", "Titanium", 47.867),
|
|
33
|
+
23: ("V", "Vanadium", 50.942),
|
|
34
|
+
24: ("Cr", "Chromium", 51.996),
|
|
35
|
+
25: ("Mn", "Manganese", 54.938),
|
|
36
|
+
26: ("Fe", "Iron", 55.845),
|
|
37
|
+
27: ("Co", "Cobalt", 58.933),
|
|
38
|
+
28: ("Ni", "Nickel", 58.693),
|
|
39
|
+
29: ("Cu", "Copper", 63.546),
|
|
40
|
+
30: ("Zn", "Zinc", 65.380),
|
|
41
|
+
31: ("Ga", "Gallium", 69.723),
|
|
42
|
+
32: ("Ge", "Germanium", 72.630),
|
|
43
|
+
33: ("As", "Arsenic", 74.922),
|
|
44
|
+
34: ("Se", "Selenium", 78.971),
|
|
45
|
+
35: ("Br", "Bromine", 79.904),
|
|
46
|
+
36: ("Kr", "Krypton", 83.798),
|
|
47
|
+
37: ("Rb", "Rubidium", 85.468),
|
|
48
|
+
38: ("Sr", "Strontium", 87.620),
|
|
49
|
+
39: ("Y", "Yttrium", 88.906),
|
|
50
|
+
40: ("Zr", "Zirconium", 91.224),
|
|
51
|
+
41: ("Nb", "Niobium", 92.906),
|
|
52
|
+
42: ("Mo", "Molybdenum", 95.950),
|
|
53
|
+
43: ("Tc", "Technetium", 98.000),
|
|
54
|
+
44: ("Ru", "Ruthenium", 101.070),
|
|
55
|
+
45: ("Rh", "Rhodium", 102.906),
|
|
56
|
+
46: ("Pd", "Palladium", 106.420),
|
|
57
|
+
47: ("Ag", "Silver", 107.868),
|
|
58
|
+
48: ("Cd", "Cadmium", 112.414),
|
|
59
|
+
49: ("In", "Indium", 114.818),
|
|
60
|
+
50: ("Sn", "Tin", 118.710),
|
|
61
|
+
51: ("Sb", "Antimony", 121.760),
|
|
62
|
+
52: ("Te", "Tellurium", 127.600),
|
|
63
|
+
53: ("I", "Iodine", 126.904),
|
|
64
|
+
54: ("Xe", "Xenon", 131.293),
|
|
65
|
+
55: ("Cs", "Caesium", 132.905),
|
|
66
|
+
56: ("Ba", "Barium", 137.327),
|
|
67
|
+
57: ("La", "Lanthanum", 138.905),
|
|
68
|
+
58: ("Ce", "Cerium", 140.116),
|
|
69
|
+
59: ("Pr", "Praseodymium",140.908),
|
|
70
|
+
60: ("Nd", "Neodymium", 144.242),
|
|
71
|
+
61: ("Pm", "Promethium", 145.000),
|
|
72
|
+
62: ("Sm", "Samarium", 150.360),
|
|
73
|
+
63: ("Eu", "Europium", 151.964),
|
|
74
|
+
64: ("Gd", "Gadolinium", 157.250),
|
|
75
|
+
65: ("Tb", "Terbium", 158.925),
|
|
76
|
+
66: ("Dy", "Dysprosium", 162.500),
|
|
77
|
+
67: ("Ho", "Holmium", 164.930),
|
|
78
|
+
68: ("Er", "Erbium", 167.259),
|
|
79
|
+
69: ("Tm", "Thulium", 168.934),
|
|
80
|
+
70: ("Yb", "Ytterbium", 173.045),
|
|
81
|
+
71: ("Lu", "Lutetium", 174.967),
|
|
82
|
+
72: ("Hf", "Hafnium", 178.490),
|
|
83
|
+
73: ("Ta", "Tantalum", 180.948),
|
|
84
|
+
74: ("W", "Tungsten", 183.840),
|
|
85
|
+
75: ("Re", "Rhenium", 186.207),
|
|
86
|
+
76: ("Os", "Osmium", 190.230),
|
|
87
|
+
77: ("Ir", "Iridium", 192.217),
|
|
88
|
+
78: ("Pt", "Platinum", 195.084),
|
|
89
|
+
79: ("Au", "Gold", 196.967),
|
|
90
|
+
80: ("Hg", "Mercury", 200.592),
|
|
91
|
+
81: ("Tl", "Thallium", 204.383),
|
|
92
|
+
82: ("Pb", "Lead", 207.200),
|
|
93
|
+
83: ("Bi", "Bismuth", 208.980),
|
|
94
|
+
84: ("Po", "Polonium", 209.000),
|
|
95
|
+
85: ("At", "Astatine", 210.000),
|
|
96
|
+
86: ("Rn", "Radon", 222.000),
|
|
97
|
+
87: ("Fr", "Francium", 223.000),
|
|
98
|
+
88: ("Ra", "Radium", 226.000),
|
|
99
|
+
89: ("Ac", "Actinium", 227.000),
|
|
100
|
+
90: ("Th", "Thorium", 232.038),
|
|
101
|
+
91: ("Pa", "Protactinium",231.036),
|
|
102
|
+
92: ("U", "Uranium", 238.029),
|
|
103
|
+
93: ("Np", "Neptunium", 237.000),
|
|
104
|
+
94: ("Pu", "Plutonium", 244.000),
|
|
105
|
+
95: ("Am", "Americium", 243.000),
|
|
106
|
+
96: ("Cm", "Curium", 247.000),
|
|
107
|
+
97: ("Bk", "Berkelium", 247.000),
|
|
108
|
+
98: ("Cf", "Californium", 251.000),
|
|
109
|
+
99: ("Es", "Einsteinium", 252.000),
|
|
110
|
+
100:("Fm", "Fermium", 257.000),
|
|
111
|
+
101:("Md", "Mendelevium", 258.000),
|
|
112
|
+
102:("No", "Nobelium", 259.000),
|
|
113
|
+
103:("Lr", "Lawrencium", 266.000),
|
|
114
|
+
104:("Rf", "Rutherfordium",267.000),
|
|
115
|
+
105:("Db", "Dubnium", 268.000),
|
|
116
|
+
106:("Sg", "Seaborgium", 269.000),
|
|
117
|
+
107:("Bh", "Bohrium", 270.000),
|
|
118
|
+
108:("Hs", "Hassium", 277.000),
|
|
119
|
+
109:("Mt", "Meitnerium", 278.000),
|
|
120
|
+
110:("Ds", "Darmstadtium",281.000),
|
|
121
|
+
111:("Rg", "Roentgenium", 282.000),
|
|
122
|
+
112:("Cn", "Copernicium", 285.000),
|
|
123
|
+
113:("Nh", "Nihonium", 286.000),
|
|
124
|
+
114:("Fl", "Flerovium", 289.000),
|
|
125
|
+
115:("Mc", "Moscovium", 290.000),
|
|
126
|
+
116:("Lv", "Livermorium", 293.000),
|
|
127
|
+
117:("Ts", "Tennessine", 294.000),
|
|
128
|
+
118:("Og", "Oganesson", 294.000),
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# Reverse lookup: symbol -> atomic number
|
|
132
|
+
SYMBOL_TO_Z: dict[str, int] = {v[0]: k for k, v in ELEMENTS.items()}
|
|
133
|
+
|
|
134
|
+
# Reverse lookup: lowercase name -> atomic number (O(1) name lookup)
|
|
135
|
+
_NAME_TO_Z: dict[str, int] = {v[1].lower(): k for k, v in ELEMENTS.items()}
|
|
136
|
+
|
|
137
|
+
# Noble gas core atomic numbers (for shorthand notation)
|
|
138
|
+
NOBLE_GASES: dict[int, str] = {
|
|
139
|
+
2: "He", 10: "Ne", 18: "Ar",
|
|
140
|
+
36: "Kr", 54: "Xe", 86: "Rn",
|
|
141
|
+
118: "Og",
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# ---------------------------------------------------------------------------
|
|
146
|
+
# Convenience lookups
|
|
147
|
+
# ---------------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
def from_symbol(symbol: str) -> tuple[int, str, str, float]:
|
|
150
|
+
"""Look up element by symbol. Returns (Z, symbol, name, weight)."""
|
|
151
|
+
sym = symbol.strip()
|
|
152
|
+
if len(sym) > 1:
|
|
153
|
+
sym = sym[0].upper() + sym[1:].lower()
|
|
154
|
+
else:
|
|
155
|
+
sym = sym.upper()
|
|
156
|
+
z = SYMBOL_TO_Z.get(sym)
|
|
157
|
+
if z is None:
|
|
158
|
+
raise ValueError(f"Unknown element symbol: {symbol}")
|
|
159
|
+
s, name, weight = ELEMENTS[z]
|
|
160
|
+
return z, s, name, weight
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def from_name(name: str) -> tuple[int, str, str, float]:
|
|
164
|
+
"""Look up element by name. Returns (Z, symbol, name, weight).
|
|
165
|
+
|
|
166
|
+
Uses a pre-built reverse mapping for O(1) lookup instead of linear scan.
|
|
167
|
+
"""
|
|
168
|
+
name_lower = name.strip().lower()
|
|
169
|
+
z = _NAME_TO_Z.get(name_lower)
|
|
170
|
+
if z is None:
|
|
171
|
+
raise ValueError(f"Unknown element name: {name}")
|
|
172
|
+
sym, elem_name, weight = ELEMENTS[z]
|
|
173
|
+
return z, sym, elem_name, weight
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def atomic_weight(symbol: str) -> float:
|
|
177
|
+
"""Standard atomic weight for an element symbol."""
|
|
178
|
+
z = SYMBOL_TO_Z.get(symbol)
|
|
179
|
+
if z is None:
|
|
180
|
+
raise ValueError(f"Unknown element: {symbol}")
|
|
181
|
+
return ELEMENTS[z][2]
|