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
molbuilder/cli/demos.py
ADDED
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
"""Demo functions for each Molecule Builder module.
|
|
2
|
+
|
|
3
|
+
Each ``demo_*`` function exercises one logical area of the package and
|
|
4
|
+
prints a self-contained text report (plus optional interactive
|
|
5
|
+
visualisation for the two matplotlib-based demos).
|
|
6
|
+
|
|
7
|
+
Migrated from legacy/main.py.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# ===================================================================
|
|
12
|
+
# Module demos
|
|
13
|
+
# ===================================================================
|
|
14
|
+
|
|
15
|
+
def demo_bohr_model():
|
|
16
|
+
"""Bohr atomic model: orbital radii, energies, spectral transitions."""
|
|
17
|
+
from molbuilder.atomic.bohr import BohrAtom, from_symbol
|
|
18
|
+
|
|
19
|
+
print("=" * 60)
|
|
20
|
+
print(" BOHR ATOMIC MODEL")
|
|
21
|
+
print("=" * 60)
|
|
22
|
+
print()
|
|
23
|
+
|
|
24
|
+
# Hydrogen
|
|
25
|
+
H = from_symbol("H")
|
|
26
|
+
print(H.summary())
|
|
27
|
+
print()
|
|
28
|
+
|
|
29
|
+
# Balmer series (visible transitions)
|
|
30
|
+
print(" Hydrogen Balmer Series (visible spectrum):")
|
|
31
|
+
print(f" {'Transition':<12} {'Wavelength':>12} {'Energy':>12}")
|
|
32
|
+
print(f" {'-' * 40}")
|
|
33
|
+
for ni in range(3, 7):
|
|
34
|
+
wl = H.transition_wavelength_nm(ni, 2)
|
|
35
|
+
e = H.transition_energy(ni, 2)
|
|
36
|
+
print(f" n={ni} -> n=2 {wl:>10.1f} nm {e:>10.3f} eV")
|
|
37
|
+
print()
|
|
38
|
+
|
|
39
|
+
# Multi-electron atoms
|
|
40
|
+
for sym in ["C", "Na", "Fe"]:
|
|
41
|
+
atom = from_symbol(sym)
|
|
42
|
+
print(f" {atom.name} (Z={atom.atomic_number}):")
|
|
43
|
+
print(f" Ground state energy: {atom.energy_level(1):.3f} eV")
|
|
44
|
+
print(f" Ionisation energy: {atom.ionization_energy():.3f} eV")
|
|
45
|
+
print(f" 1st orbital radius: {atom.orbital_radius_pm(1):.1f} pm")
|
|
46
|
+
print()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def demo_quantum_model():
|
|
50
|
+
"""Quantum mechanical atom: electron configurations, Slater's rules."""
|
|
51
|
+
from molbuilder.atomic.quantum_atom import QuantumAtom, from_symbol, aufbau_order
|
|
52
|
+
|
|
53
|
+
print("=" * 60)
|
|
54
|
+
print(" QUANTUM MECHANICAL ATOM")
|
|
55
|
+
print("=" * 60)
|
|
56
|
+
print()
|
|
57
|
+
|
|
58
|
+
# Aufbau order (first 10 subshells)
|
|
59
|
+
order = aufbau_order()
|
|
60
|
+
labels = {0: "s", 1: "p", 2: "d", 3: "f"}
|
|
61
|
+
print(" Aufbau filling order:")
|
|
62
|
+
print(" ", " -> ".join(
|
|
63
|
+
f"{n}{labels[l]}" for n, l in order[:10]))
|
|
64
|
+
print()
|
|
65
|
+
|
|
66
|
+
# Example atoms including Aufbau exceptions
|
|
67
|
+
atoms = [
|
|
68
|
+
("H", "simplest atom"),
|
|
69
|
+
("C", "basis of organic chemistry"),
|
|
70
|
+
("Fe", "transition metal"),
|
|
71
|
+
("Cr", "Aufbau exception: half-filled 3d"),
|
|
72
|
+
("Cu", "Aufbau exception: filled 3d"),
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
print(f" {'Symbol':<6} {'Config':<28} {'Spin mult':>10}")
|
|
76
|
+
print(f" {'-' * 50}")
|
|
77
|
+
for sym, note in atoms:
|
|
78
|
+
atom = from_symbol(sym)
|
|
79
|
+
config = atom.electron_configuration_string()
|
|
80
|
+
mult = atom.spin_multiplicity()
|
|
81
|
+
print(f" {sym:<6} {config:<28} {mult:>10}")
|
|
82
|
+
print()
|
|
83
|
+
|
|
84
|
+
# Detailed view for carbon
|
|
85
|
+
C = from_symbol("C")
|
|
86
|
+
print(C.summary())
|
|
87
|
+
print()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def demo_element_data():
|
|
91
|
+
"""Element property data: electronegativity, radii, CPK colours."""
|
|
92
|
+
from molbuilder.core.element_properties import (
|
|
93
|
+
electronegativity, covalent_radius_pm, cpk_color,
|
|
94
|
+
estimated_bond_length_pm, period, can_expand_octet,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
print("=" * 60)
|
|
98
|
+
print(" ELEMENT DATA")
|
|
99
|
+
print("=" * 60)
|
|
100
|
+
print()
|
|
101
|
+
|
|
102
|
+
elements = ["H", "C", "N", "O", "F", "P", "S", "Cl", "Br", "I", "Xe"]
|
|
103
|
+
print(f" {'Sym':<4} {'EN':>5} {'Radius':>8} {'Period':>7} {'Expand':>7} {'Color'}")
|
|
104
|
+
print(f" {'-' * 48}")
|
|
105
|
+
for sym in elements:
|
|
106
|
+
en = electronegativity(sym)
|
|
107
|
+
r = covalent_radius_pm(sym)
|
|
108
|
+
p = period(sym)
|
|
109
|
+
exp = "yes" if can_expand_octet(sym) else "no"
|
|
110
|
+
col = cpk_color(sym)
|
|
111
|
+
print(f" {sym:<4} {en:>5.2f} {r:>6.0f} pm {p:>6} {exp:>7} {col}")
|
|
112
|
+
print()
|
|
113
|
+
|
|
114
|
+
# Bond lengths
|
|
115
|
+
print(" Estimated bond lengths:")
|
|
116
|
+
bonds = [("C", "H", 1), ("C", "C", 1), ("C", "C", 2),
|
|
117
|
+
("C", "O", 2), ("N", "N", 3)]
|
|
118
|
+
for a, b, order in bonds:
|
|
119
|
+
sym = {1: "-", 2: "=", 3: "#"}[order]
|
|
120
|
+
bl = estimated_bond_length_pm(a, b, order)
|
|
121
|
+
print(f" {a}{sym}{b}: {bl:.0f} pm")
|
|
122
|
+
print()
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def demo_lewis_structure():
|
|
126
|
+
"""Lewis structures: bonding pairs, lone pairs, octets."""
|
|
127
|
+
from molbuilder.bonding.lewis import LewisStructure
|
|
128
|
+
|
|
129
|
+
print("=" * 60)
|
|
130
|
+
print(" LEWIS STRUCTURES")
|
|
131
|
+
print("=" * 60)
|
|
132
|
+
print()
|
|
133
|
+
|
|
134
|
+
molecules = [
|
|
135
|
+
("H2O", 0, "bent, 2 lone pairs"),
|
|
136
|
+
("CO2", 0, "linear, double bonds"),
|
|
137
|
+
("NH3", 0, "trigonal pyramidal"),
|
|
138
|
+
("CH4", 0, "tetrahedral"),
|
|
139
|
+
("BF3", 0, "incomplete octet"),
|
|
140
|
+
("SF6", 0, "expanded octet"),
|
|
141
|
+
("PCl5", 0, "expanded octet"),
|
|
142
|
+
("NH4", 1, "ammonium ion"),
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
for formula, charge, note in molecules:
|
|
146
|
+
lewis = LewisStructure(formula, charge)
|
|
147
|
+
charge_str = f" (charge {charge:+d})" if charge else ""
|
|
148
|
+
print(f" {formula}{charge_str} -- {note}")
|
|
149
|
+
print(f" Valence e-: {lewis.total_valence_electrons}")
|
|
150
|
+
print(f" Central atom: {lewis.central_symbol}")
|
|
151
|
+
print(f" Bonding pairs: {lewis.bonding_pairs_on_central()}")
|
|
152
|
+
print(f" Lone pairs: {lewis.lone_pairs_on_central()}")
|
|
153
|
+
print(f" Steric number: {lewis.steric_number()}")
|
|
154
|
+
|
|
155
|
+
bond_strs = []
|
|
156
|
+
for bond in lewis.bonds:
|
|
157
|
+
sa = lewis.atoms[bond.atom_a]
|
|
158
|
+
sb = lewis.atoms[bond.atom_b]
|
|
159
|
+
sym = {1: "-", 2: "=", 3: "#"}.get(bond.order, "?")
|
|
160
|
+
bond_strs.append(f"{sa}{sym}{sb}")
|
|
161
|
+
print(f" Bonds: {', '.join(bond_strs)}")
|
|
162
|
+
print()
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def demo_vsepr_model():
|
|
166
|
+
"""VSEPR molecular geometry: AXnEm classification, 3D coordinates."""
|
|
167
|
+
from molbuilder.bonding.vsepr import VSEPRMolecule
|
|
168
|
+
|
|
169
|
+
print("=" * 60)
|
|
170
|
+
print(" VSEPR MOLECULAR GEOMETRY")
|
|
171
|
+
print("=" * 60)
|
|
172
|
+
print()
|
|
173
|
+
|
|
174
|
+
molecules = [
|
|
175
|
+
("CO2", 0), # linear
|
|
176
|
+
("BF3", 0), # trigonal planar
|
|
177
|
+
("CH4", 0), # tetrahedral
|
|
178
|
+
("NH3", 0), # trigonal pyramidal
|
|
179
|
+
("H2O", 0), # bent
|
|
180
|
+
("PCl5", 0), # trigonal bipyramidal
|
|
181
|
+
("SF4", 0), # seesaw
|
|
182
|
+
("SF6", 0), # octahedral
|
|
183
|
+
("XeF4", 0), # square planar
|
|
184
|
+
("ClF3", 0), # T-shaped
|
|
185
|
+
("IF5", 0), # square pyramidal
|
|
186
|
+
]
|
|
187
|
+
|
|
188
|
+
print(f" {'Formula':<8} {'AXE':>6} {'Electron Geom':<24} {'Molecular Geom':<24}")
|
|
189
|
+
print(f" {'-' * 64}")
|
|
190
|
+
for formula, charge in molecules:
|
|
191
|
+
mol = VSEPRMolecule(formula, charge)
|
|
192
|
+
axe = mol.axe
|
|
193
|
+
print(f" {formula:<8} {axe.axe_notation:>6} "
|
|
194
|
+
f"{axe.electron_geometry:<24} {axe.molecular_geometry:<24}")
|
|
195
|
+
print()
|
|
196
|
+
|
|
197
|
+
# Detailed view for water
|
|
198
|
+
water = VSEPRMolecule("H2O")
|
|
199
|
+
print(water.summary())
|
|
200
|
+
print()
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def demo_covalent_bonds():
|
|
204
|
+
"""Covalent bond analysis: polarity, orbital composition, BDE."""
|
|
205
|
+
from molbuilder.bonding.covalent import (
|
|
206
|
+
single_bond, double_bond, triple_bond,
|
|
207
|
+
MolecularBondAnalysis,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
print("=" * 60)
|
|
211
|
+
print(" COVALENT BONDS")
|
|
212
|
+
print("=" * 60)
|
|
213
|
+
print()
|
|
214
|
+
|
|
215
|
+
# Individual bonds
|
|
216
|
+
bonds = [
|
|
217
|
+
single_bond("H", "H"),
|
|
218
|
+
single_bond("H", "Cl"),
|
|
219
|
+
double_bond("C", "O"),
|
|
220
|
+
triple_bond("N", "N"),
|
|
221
|
+
single_bond("C", "C"),
|
|
222
|
+
]
|
|
223
|
+
|
|
224
|
+
print(f" {'Bond':<8} {'Order':>5} {'dEN':>6} {'Polarity':<20}"
|
|
225
|
+
f" {'BDE (kJ)':>9} {'Length':>8}")
|
|
226
|
+
print(f" {'-' * 62}")
|
|
227
|
+
for b in bonds:
|
|
228
|
+
sym = b.order_symbol
|
|
229
|
+
label = f"{b.symbol_a}{sym}{b.symbol_b}"
|
|
230
|
+
bde = f"{b.dissociation_energy_kj:.0f}" if b.dissociation_energy_kj else "---"
|
|
231
|
+
bl = f"{b.bond_length_pm:.0f} pm"
|
|
232
|
+
print(f" {label:<8} {b.bond_order:>5} {b.delta_en:>6.2f}"
|
|
233
|
+
f" {b.polarity.name:<20} {bde:>9} {bl:>8}")
|
|
234
|
+
print()
|
|
235
|
+
|
|
236
|
+
# Sigma/pi composition
|
|
237
|
+
print(" Orbital composition:")
|
|
238
|
+
for b in bonds:
|
|
239
|
+
label = f"{b.symbol_a}{b.order_symbol}{b.symbol_b}"
|
|
240
|
+
parts = ", ".join(f"{oc.orbital_type.name}" for oc in b.orbital_contributions)
|
|
241
|
+
print(f" {label}: {parts}")
|
|
242
|
+
print()
|
|
243
|
+
|
|
244
|
+
# Molecular polarity
|
|
245
|
+
print(" Molecular polarity analysis:")
|
|
246
|
+
formulas = ["H2", "HCl", "CO2", "H2O", "CH4", "NH3", "BF3", "CCl4"]
|
|
247
|
+
print(f" {'Formula':<10} {'Polarity':<20} {'sigma':>5} {'pi':>5}")
|
|
248
|
+
print(f" {'-' * 44}")
|
|
249
|
+
for f in formulas:
|
|
250
|
+
ma = MolecularBondAnalysis(f)
|
|
251
|
+
print(f" {f:<10} {ma.molecular_polarity:<20}"
|
|
252
|
+
f" {ma.total_sigma_bonds:>5} {ma.total_pi_bonds:>5}")
|
|
253
|
+
print()
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def demo_molecular_conformations():
|
|
257
|
+
"""Molecular conformations: ethane, butane, cyclohexane, stereochemistry."""
|
|
258
|
+
from molbuilder.molecule.builders import (
|
|
259
|
+
build_ethane, build_butane, build_cyclohexane, build_2_butene,
|
|
260
|
+
build_chiral_molecule,
|
|
261
|
+
)
|
|
262
|
+
from molbuilder.molecule.conformations import classify_conformation, scan_torsion
|
|
263
|
+
from molbuilder.molecule.graph import RingConformation
|
|
264
|
+
|
|
265
|
+
print("=" * 60)
|
|
266
|
+
print(" MOLECULAR CONFORMATIONS & STEREOCHEMISTRY")
|
|
267
|
+
print("=" * 60)
|
|
268
|
+
print()
|
|
269
|
+
|
|
270
|
+
# Ethane conformations
|
|
271
|
+
print(" --- Ethane: Staggered vs Eclipsed ---")
|
|
272
|
+
staggered = build_ethane(60.0)
|
|
273
|
+
eclipsed = build_ethane(0.0)
|
|
274
|
+
e_s = staggered.torsional_energy(0, 1).total_kj_per_mol
|
|
275
|
+
e_e = eclipsed.torsional_energy(0, 1).total_kj_per_mol
|
|
276
|
+
print(f" Staggered energy: {e_s:.2f} kJ/mol")
|
|
277
|
+
print(f" Eclipsed energy: {e_e:.2f} kJ/mol")
|
|
278
|
+
print(f" Barrier: {e_e - e_s:.2f} kJ/mol")
|
|
279
|
+
print()
|
|
280
|
+
|
|
281
|
+
# Newman projections
|
|
282
|
+
print(" Newman projection (staggered):")
|
|
283
|
+
np_s = staggered.newman_projection(0, 1)
|
|
284
|
+
for idx, sym, ang in np_s.front_substituents:
|
|
285
|
+
print(f" Front: {sym}[{idx}] at {ang:.1f} deg")
|
|
286
|
+
for idx, sym, ang in np_s.back_substituents:
|
|
287
|
+
print(f" Back: {sym}[{idx}] at {ang:.1f} deg")
|
|
288
|
+
print()
|
|
289
|
+
|
|
290
|
+
# Butane conformations
|
|
291
|
+
print(" --- Butane conformations ---")
|
|
292
|
+
print(f" {'Dihedral':>8} {'Energy':>12} {'Type'}")
|
|
293
|
+
print(f" {'-' * 36}")
|
|
294
|
+
for dih in [180, 60, 0, 120]:
|
|
295
|
+
mol = build_butane(dih)
|
|
296
|
+
energy = mol.torsional_energy(1, 2).total_kj_per_mol
|
|
297
|
+
conf = classify_conformation(dih)
|
|
298
|
+
print(f" {dih:>6} deg {energy:>8.2f} kJ/mol {conf.name}")
|
|
299
|
+
print()
|
|
300
|
+
|
|
301
|
+
# Butane torsion scan (condensed)
|
|
302
|
+
print(" --- Butane torsion scan (every 30 deg) ---")
|
|
303
|
+
butane = build_butane(180.0)
|
|
304
|
+
scan = scan_torsion(butane, 1, 2, 0, 3, steps=12)
|
|
305
|
+
print(f" {'Angle':>7} {'kJ/mol':>8} Bar")
|
|
306
|
+
for angle, energy in scan:
|
|
307
|
+
bar = "#" * max(0, int(energy / 2))
|
|
308
|
+
print(f" {angle:>7.0f} {energy:>8.2f} {bar}")
|
|
309
|
+
print()
|
|
310
|
+
|
|
311
|
+
# Cyclohexane
|
|
312
|
+
print(" --- Cyclohexane ---")
|
|
313
|
+
chair = build_cyclohexane(RingConformation.CHAIR)
|
|
314
|
+
boat = build_cyclohexane(RingConformation.BOAT)
|
|
315
|
+
dists_chair = [chair.distance(i, (i+1) % 6) for i in range(6)]
|
|
316
|
+
dists_boat = [boat.distance(i, (i+1) % 6) for i in range(6)]
|
|
317
|
+
print(f" Chair C-C: {min(dists_chair):.3f} - {max(dists_chair):.3f} A")
|
|
318
|
+
print(f" Boat C-C: {min(dists_boat):.3f} - {max(dists_boat):.3f} A")
|
|
319
|
+
print()
|
|
320
|
+
|
|
321
|
+
# E/Z isomerism
|
|
322
|
+
print(" --- E/Z Isomerism (2-butene) ---")
|
|
323
|
+
cis = build_2_butene(is_cis=True)
|
|
324
|
+
trans = build_2_butene(is_cis=False)
|
|
325
|
+
print(f" cis-2-butene: {cis.assign_ez(1, 2).name}")
|
|
326
|
+
print(f" trans-2-butene: {trans.assign_ez(1, 2).name}")
|
|
327
|
+
print()
|
|
328
|
+
|
|
329
|
+
# R/S chirality
|
|
330
|
+
print(" --- R/S Chirality (CHFClBr) ---")
|
|
331
|
+
chiral = build_chiral_molecule(["H", "F", "Cl", "Br"])
|
|
332
|
+
print(f" Configuration: {chiral.assign_rs(0).name}")
|
|
333
|
+
print()
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def demo_amino_acids():
|
|
337
|
+
"""Amino acids: all 20, functional groups, peptide bonds, chirality."""
|
|
338
|
+
from molbuilder.molecule.amino_acids import (
|
|
339
|
+
build_amino_acid, form_peptide_bond, build_peptide,
|
|
340
|
+
set_secondary_structure, add_hydroxyl, add_amino,
|
|
341
|
+
add_carboxyl, add_thiol, add_phenyl_ring,
|
|
342
|
+
AminoAcidType, SecondaryStructure, AMINO_ACID_DATA,
|
|
343
|
+
)
|
|
344
|
+
from molbuilder.molecule.graph import Molecule, Hybridization
|
|
345
|
+
from molbuilder.core.bond_data import SP3_ANGLE
|
|
346
|
+
|
|
347
|
+
print("=" * 60)
|
|
348
|
+
print(" AMINO ACIDS & FUNCTIONAL GROUPS")
|
|
349
|
+
print("=" * 60)
|
|
350
|
+
print()
|
|
351
|
+
|
|
352
|
+
# Functional groups
|
|
353
|
+
print(" --- Functional Group Builders ---")
|
|
354
|
+
demo_mol = Molecule("demo chain")
|
|
355
|
+
c0 = demo_mol.add_atom("C", [0.0, 0.0, 0.0], Hybridization.SP3)
|
|
356
|
+
c1 = demo_mol.add_atom_bonded("C", c0, hybridization=Hybridization.SP3)
|
|
357
|
+
c2 = demo_mol.add_atom_bonded("C", c1, angle_ref=c0,
|
|
358
|
+
bond_angle_deg=SP3_ANGLE,
|
|
359
|
+
hybridization=Hybridization.SP3)
|
|
360
|
+
|
|
361
|
+
oh = add_hydroxyl(demo_mol, c0)
|
|
362
|
+
nh2 = add_amino(demo_mol, c1, angle_ref=c0, dihedral_deg=60.0)
|
|
363
|
+
sh = add_thiol(demo_mol, c2, angle_ref=c1, dihedral_deg=60.0)
|
|
364
|
+
|
|
365
|
+
print(f" Carbon chain with -OH, -NH2, -SH attached")
|
|
366
|
+
print(f" Total atoms: {len(demo_mol.atoms)}, bonds: {len(demo_mol.bonds)}")
|
|
367
|
+
print()
|
|
368
|
+
|
|
369
|
+
# All 20 amino acids
|
|
370
|
+
print(" --- All 20 Standard Amino Acids ---")
|
|
371
|
+
print(f" {'Name':<16} {'1-letter':>8} {'3-letter':>8} {'Atoms':>6} {'Bonds':>6}")
|
|
372
|
+
print(f" {'-' * 50}")
|
|
373
|
+
|
|
374
|
+
all_aa = {}
|
|
375
|
+
for aa_type in AminoAcidType:
|
|
376
|
+
data = AMINO_ACID_DATA[aa_type]
|
|
377
|
+
mol = build_amino_acid(aa_type)
|
|
378
|
+
all_aa[aa_type] = mol
|
|
379
|
+
print(f" {data['name']:<16} {data['code1']:>8} {data['code3']:>8}"
|
|
380
|
+
f" {len(mol.atoms):>6} {len(mol.bonds):>6}")
|
|
381
|
+
print()
|
|
382
|
+
|
|
383
|
+
# Chirality check
|
|
384
|
+
print(" --- L-Chirality Verification ---")
|
|
385
|
+
print(f" {'Name':<16} {'CIP config':>10}")
|
|
386
|
+
print(f" {'-' * 28}")
|
|
387
|
+
for aa_type, mol in all_aa.items():
|
|
388
|
+
bb = mol.backbone
|
|
389
|
+
data = AMINO_ACID_DATA[aa_type]
|
|
390
|
+
if aa_type == AminoAcidType.GLY:
|
|
391
|
+
config = "achiral"
|
|
392
|
+
else:
|
|
393
|
+
config = mol.assign_rs(bb.CA).name
|
|
394
|
+
print(f" {data['name']:<16} {config:>10}")
|
|
395
|
+
print()
|
|
396
|
+
|
|
397
|
+
# Dipeptide
|
|
398
|
+
print(" --- Dipeptide: Ala-Gly ---")
|
|
399
|
+
ala = build_amino_acid(AminoAcidType.ALA)
|
|
400
|
+
gly = build_amino_acid(AminoAcidType.GLY)
|
|
401
|
+
dipeptide = form_peptide_bond(ala, gly)
|
|
402
|
+
print(f" Atoms: {len(dipeptide.atoms)}, Bonds: {len(dipeptide.bonds)}")
|
|
403
|
+
if hasattr(dipeptide, 'residues') and len(dipeptide.residues) >= 2:
|
|
404
|
+
c_i = dipeptide.residues[0].C
|
|
405
|
+
n_i = dipeptide.residues[1].N
|
|
406
|
+
print(f" Peptide bond: {dipeptide.distance(c_i, n_i):.3f} A")
|
|
407
|
+
ca1 = dipeptide.residues[0].CA
|
|
408
|
+
ca2 = dipeptide.residues[1].CA
|
|
409
|
+
omega = dipeptide.dihedral_angle(ca1, c_i, n_i, ca2)
|
|
410
|
+
print(f" Omega angle: {omega:.1f} deg (trans)")
|
|
411
|
+
print()
|
|
412
|
+
|
|
413
|
+
# Tripeptide with secondary structure
|
|
414
|
+
print(" --- Tripeptide: Ala-Ala-Ala (alpha helix) ---")
|
|
415
|
+
tripeptide = build_peptide(
|
|
416
|
+
[AminoAcidType.ALA, AminoAcidType.ALA, AminoAcidType.ALA])
|
|
417
|
+
if hasattr(tripeptide, 'residues'):
|
|
418
|
+
set_secondary_structure(tripeptide, tripeptide.residues,
|
|
419
|
+
SecondaryStructure.ALPHA_HELIX)
|
|
420
|
+
print(f" Atoms: {len(tripeptide.atoms)}, Bonds: {len(tripeptide.bonds)}")
|
|
421
|
+
print(f" Residues: {len(getattr(tripeptide, 'residues', []))}")
|
|
422
|
+
print(f" Phi/psi: alpha helix (-57, -47)")
|
|
423
|
+
print()
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def demo_visualization():
|
|
427
|
+
"""3D molecule visualization (requires matplotlib)."""
|
|
428
|
+
try:
|
|
429
|
+
import matplotlib
|
|
430
|
+
except ImportError:
|
|
431
|
+
print(" matplotlib is not installed. Skipping visualization.")
|
|
432
|
+
return
|
|
433
|
+
|
|
434
|
+
from molbuilder.bonding.vsepr import VSEPRMolecule
|
|
435
|
+
from molbuilder.visualization.molecule_viz import visualize_molecule, visualize_gallery
|
|
436
|
+
|
|
437
|
+
print("=" * 60)
|
|
438
|
+
print(" 3D MOLECULE VISUALIZATION")
|
|
439
|
+
print("=" * 60)
|
|
440
|
+
print()
|
|
441
|
+
|
|
442
|
+
choice = input(" Show: [1] Single molecule [2] Gallery [3] Skip: ").strip()
|
|
443
|
+
|
|
444
|
+
if choice == "1":
|
|
445
|
+
formula = input(" Enter formula (e.g. H2O, CH4, SF6): ").strip()
|
|
446
|
+
if not formula:
|
|
447
|
+
formula = "H2O"
|
|
448
|
+
try:
|
|
449
|
+
mol = VSEPRMolecule(formula)
|
|
450
|
+
print(f" Rendering {formula}...")
|
|
451
|
+
visualize_molecule(mol)
|
|
452
|
+
except Exception as e:
|
|
453
|
+
print(f" Error: {e}")
|
|
454
|
+
|
|
455
|
+
elif choice == "2":
|
|
456
|
+
formulas = ["H2O", "NH3", "CH4", "CO2", "BF3",
|
|
457
|
+
"SF6", "PCl5", "XeF4", "ClF3"]
|
|
458
|
+
mols = []
|
|
459
|
+
for f in formulas:
|
|
460
|
+
try:
|
|
461
|
+
mols.append(VSEPRMolecule(f))
|
|
462
|
+
except Exception as e:
|
|
463
|
+
print(f" Skipping {f}: {e}")
|
|
464
|
+
if mols:
|
|
465
|
+
print(f" Rendering gallery of {len(mols)} molecules...")
|
|
466
|
+
visualize_gallery(mols)
|
|
467
|
+
else:
|
|
468
|
+
print(" Skipped.")
|
|
469
|
+
print()
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def demo_quantum_visualization():
|
|
473
|
+
"""Quantum orbital visualization (requires matplotlib)."""
|
|
474
|
+
try:
|
|
475
|
+
import matplotlib
|
|
476
|
+
except ImportError:
|
|
477
|
+
print(" matplotlib is not installed. Skipping visualization.")
|
|
478
|
+
return
|
|
479
|
+
|
|
480
|
+
from molbuilder.atomic.quantum_atom import from_symbol as q_from_symbol
|
|
481
|
+
from molbuilder.visualization.quantum_viz import (
|
|
482
|
+
plot_orbital_3d,
|
|
483
|
+
plot_radial_probability,
|
|
484
|
+
plot_energy_levels,
|
|
485
|
+
visualize_atom,
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
print("=" * 60)
|
|
489
|
+
print(" QUANTUM ORBITAL VISUALIZATION")
|
|
490
|
+
print("=" * 60)
|
|
491
|
+
print()
|
|
492
|
+
|
|
493
|
+
choice = input(" Show: [1] Hydrogen 1s [2] Carbon 2p "
|
|
494
|
+
"[3] Full atom overview [4] Skip: ").strip()
|
|
495
|
+
|
|
496
|
+
if choice == "1":
|
|
497
|
+
print(" Rendering hydrogen 1s orbital...")
|
|
498
|
+
plot_orbital_3d(1, 0, 0, Z=1)
|
|
499
|
+
|
|
500
|
+
elif choice == "2":
|
|
501
|
+
print(" Rendering carbon 2p orbital...")
|
|
502
|
+
plot_orbital_3d(2, 1, 0, Z=6)
|
|
503
|
+
|
|
504
|
+
elif choice == "3":
|
|
505
|
+
element = input(" Enter element symbol (e.g. C, Fe, Na): ").strip()
|
|
506
|
+
if not element:
|
|
507
|
+
element = "C"
|
|
508
|
+
try:
|
|
509
|
+
atom = q_from_symbol(element)
|
|
510
|
+
print(f" Rendering full overview for {element}...")
|
|
511
|
+
visualize_atom(atom)
|
|
512
|
+
except Exception as e:
|
|
513
|
+
print(f" Error: {e}")
|
|
514
|
+
else:
|
|
515
|
+
print(" Skipped.")
|
|
516
|
+
print()
|
molbuilder/cli/menu.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Interactive menu system for Molecule Builder.
|
|
2
|
+
|
|
3
|
+
Provides a CLI with numbered options for each demo module,
|
|
4
|
+
an 'all' mode, and command-line argument support.
|
|
5
|
+
|
|
6
|
+
Migrated from legacy/main.py.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
from molbuilder.cli.demos import (
|
|
12
|
+
demo_bohr_model,
|
|
13
|
+
demo_quantum_model,
|
|
14
|
+
demo_element_data,
|
|
15
|
+
demo_lewis_structure,
|
|
16
|
+
demo_vsepr_model,
|
|
17
|
+
demo_covalent_bonds,
|
|
18
|
+
demo_molecular_conformations,
|
|
19
|
+
demo_amino_acids,
|
|
20
|
+
demo_visualization,
|
|
21
|
+
demo_quantum_visualization,
|
|
22
|
+
)
|
|
23
|
+
from molbuilder.cli.wizard import wizard_main
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# ===================================================================
|
|
27
|
+
# Menu
|
|
28
|
+
# ===================================================================
|
|
29
|
+
|
|
30
|
+
DEMOS = [
|
|
31
|
+
("Bohr Atomic Model", demo_bohr_model),
|
|
32
|
+
("Quantum Mechanical Atom", demo_quantum_model),
|
|
33
|
+
("Element Data", demo_element_data),
|
|
34
|
+
("Lewis Structures", demo_lewis_structure),
|
|
35
|
+
("VSEPR Molecular Geometry", demo_vsepr_model),
|
|
36
|
+
("Covalent Bonds", demo_covalent_bonds),
|
|
37
|
+
("Molecular Conformations", demo_molecular_conformations),
|
|
38
|
+
("Amino Acids & Functional Groups", demo_amino_acids),
|
|
39
|
+
("3D Molecule Visualization", demo_visualization),
|
|
40
|
+
("Quantum Orbital Visualization", demo_quantum_visualization),
|
|
41
|
+
("Molecule Builder Wizard", wizard_main),
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def print_banner():
|
|
46
|
+
print()
|
|
47
|
+
print("=" * 60)
|
|
48
|
+
print(" MOLECULE BUILDER -- Alpha 1.0")
|
|
49
|
+
print(" Taylor C. Powell")
|
|
50
|
+
print("=" * 60)
|
|
51
|
+
print()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def print_menu():
|
|
55
|
+
print(" Available modules:")
|
|
56
|
+
print()
|
|
57
|
+
for i, (name, _) in enumerate(DEMOS, 1):
|
|
58
|
+
print(f" [{i:>2}] {name}")
|
|
59
|
+
print()
|
|
60
|
+
print(f" [ a] Run all text demos (1-8)")
|
|
61
|
+
print(f" [ q] Quit")
|
|
62
|
+
print()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def run_all_text():
|
|
66
|
+
"""Run all non-visualization demos in sequence."""
|
|
67
|
+
for name, func in DEMOS[:8]:
|
|
68
|
+
try:
|
|
69
|
+
func()
|
|
70
|
+
except Exception as e:
|
|
71
|
+
print(f" ERROR in {name}: {e}")
|
|
72
|
+
print()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def main():
|
|
76
|
+
print_banner()
|
|
77
|
+
|
|
78
|
+
if len(sys.argv) > 1:
|
|
79
|
+
arg = sys.argv[1].lower()
|
|
80
|
+
if arg == "all":
|
|
81
|
+
run_all_text()
|
|
82
|
+
return
|
|
83
|
+
if arg == "q":
|
|
84
|
+
return
|
|
85
|
+
try:
|
|
86
|
+
idx = int(arg) - 1
|
|
87
|
+
if 0 <= idx < len(DEMOS):
|
|
88
|
+
DEMOS[idx][1]()
|
|
89
|
+
return
|
|
90
|
+
except ValueError:
|
|
91
|
+
pass
|
|
92
|
+
print(f" Unknown argument: {sys.argv[1]}")
|
|
93
|
+
print(f" Usage: python main.py [1-{len(DEMOS)} | all | q]")
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
# Interactive menu loop
|
|
97
|
+
while True:
|
|
98
|
+
print_menu()
|
|
99
|
+
choice = input(" Select option: ").strip().lower()
|
|
100
|
+
|
|
101
|
+
if choice == "q":
|
|
102
|
+
print(" Goodbye!")
|
|
103
|
+
break
|
|
104
|
+
|
|
105
|
+
if choice == "a":
|
|
106
|
+
run_all_text()
|
|
107
|
+
continue
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
idx = int(choice) - 1
|
|
111
|
+
if 0 <= idx < len(DEMOS):
|
|
112
|
+
print()
|
|
113
|
+
try:
|
|
114
|
+
DEMOS[idx][1]()
|
|
115
|
+
except Exception as e:
|
|
116
|
+
print(f" ERROR: {e}")
|
|
117
|
+
import traceback
|
|
118
|
+
traceback.print_exc()
|
|
119
|
+
print()
|
|
120
|
+
else:
|
|
121
|
+
print(f" Please enter 1-{len(DEMOS)}, 'a', or 'q'.")
|
|
122
|
+
except ValueError:
|
|
123
|
+
print(f" Please enter 1-{len(DEMOS)}, 'a', or 'q'.")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
if __name__ == "__main__":
|
|
127
|
+
main()
|