|
1 | 1 | """Explicit additive/replacement feature collection for selected parents.""" |
2 | 2 |
|
3 | 3 | from .assembly_parts import AssemblyParts, SubstituentItem |
| 4 | +from .locants import parse_locant |
4 | 5 | from .molecule import Molecule |
5 | 6 | from .name_operations import HydroOperation |
6 | 7 | from .namer_config import INDICATED_H_RETAINED_NAMES |
@@ -28,7 +29,16 @@ def add_indicated_hydrogens(mol: Molecule, parts: AssemblyParts, numbered_path: |
28 | 29 | if metadata is not None and default_indicated_h and atom.is_carbon and locant not in default_indicated_h: |
29 | 30 | continue |
30 | 31 | if oxo_derivative: |
31 | | - if atom.explicit_h_count + atom.total_h_count <= 0: |
| 32 | + # A ring nitrogen at a saturated position is a hydrogenated position |
| 33 | + # of the parent hydride whether it holds the hydrogen or a |
| 34 | + # substituent, so theophylline is |
| 35 | + # 1,3-dimethyl-3,7-dihydro-1H-purine-2,6-dione: N1 and N3 count even |
| 36 | + # though both are methylated. |
| 37 | + ring_bond_order = sum( |
| 38 | + bond.order for n in mol.get_neighbors(idx) if n in numbered_path and (bond := mol.get_bond(idx, n)) |
| 39 | + ) |
| 40 | + substituted_ring_nitrogen = atom.symbol == "N" and ring_bond_order == 2 |
| 41 | + if atom.explicit_h_count + atom.total_h_count <= 0 and not substituted_ring_nitrogen: |
32 | 42 | continue |
33 | 43 | # Hydrogen introduced next to =O is implied by ``-one``/``-dione`` |
34 | 44 | # and must not become a new indicated-H locant. Carbon is allowed |
@@ -77,6 +87,28 @@ def add_indicated_hydrogens(mol: Molecule, parts: AssemblyParts, numbered_path: |
77 | 87 | ) |
78 | 88 | return |
79 | 89 |
|
| 90 | + # A mancude parent hydride supports a fixed number of indicated hydrogens. |
| 91 | + # Saturated positions beyond that are *added* hydrogen and take a hydro |
| 92 | + # prefix, so xanthine is 3,7-dihydro-1H-purine-2,6-dione rather than a run |
| 93 | + # of indicated-H citations. The lowest locants stay indicated. |
| 94 | + # A hydro prefix saturates whole double bonds, so it can only absorb an even |
| 95 | + # surplus. An odd one stays as indicated hydrogen (1H,9H-purin-6-one), |
| 96 | + # which is still a readable name. |
| 97 | + supported = metadata.indicated_hydrogen_count if metadata is not None else len(candidates) |
| 98 | + if metadata is not None and len(candidates) > supported and (len(candidates) - supported) % 2 == 0: |
| 99 | + candidates.sort(key=lambda candidate: parse_locant(candidate[0])) |
| 100 | + surplus = candidates[supported:] |
| 101 | + candidates = candidates[:supported] |
| 102 | + parts.hydro_operations.append( |
| 103 | + HydroOperation( |
| 104 | + key="additive_hydrogen", |
| 105 | + reason="Saturation beyond the parent's indicated hydrogen is added hydrogen.", |
| 106 | + locants=tuple(locant for locant, _ in surplus), |
| 107 | + atom_ids=tuple(atom_idx for _, atom_idx in surplus), |
| 108 | + operation_kind="additive_hydrogen", |
| 109 | + ) |
| 110 | + ) |
| 111 | + |
80 | 112 | for locant, atom_idx in candidates: |
81 | 113 | parts.indicated_hydrogens.append(locant) |
82 | 114 | parts.hydro_operations.append( |
|
0 commit comments