|
6 | 6 |
|
7 | 7 | from .assembly_parts import NameAtomBinding, NameTokenBinding |
8 | 8 | from .assembly_prefixes import substituent_sort_key |
| 9 | +from .chains import get_cyclic_atoms |
9 | 10 | from .charge_pair_roles import charge_pair_roles |
10 | 11 | from .formatting import format_counted_prefixes, format_multiplier, oxy_prefix_from_branch, strip_outer_parentheses |
11 | 12 | from .molecule import Molecule |
@@ -118,6 +119,8 @@ def structural_replacement_parent_result( |
118 | 119 | ("phosphane_borane_zwitterion", lambda: phosphane_borane_zwitterion_result(mol, component_atoms, branch_namer)), |
119 | 120 | ("sulfonium_ylide", lambda: sulfonium_ylide_result(mol, component_atoms, branch_namer)), |
120 | 121 | ("hydroxyurea_parent", lambda: hydroxyurea_parent_result(mol, component_atoms, branch_namer)), |
| 122 | + ("sulfamic_acid", lambda: sulfamic_acid_result(mol, component_atoms, branch_namer)), |
| 123 | + ("azinic_acid", lambda: azinic_acid_result(mol, component_atoms, branch_namer)), |
121 | 124 | ("oxoacid_ester", lambda: oxoacid_ester_result(mol, component_atoms, branch_namer)), |
122 | 125 | ("oxoacid_parent", lambda: oxoacid_parent_result(mol, component_atoms)), |
123 | 126 | ("organophosphinic_acid", lambda: organophosphinic_acid_result(mol, component_atoms)), |
@@ -1137,6 +1140,175 @@ def hydroxyurea_parent_result( |
1137 | 1140 | return _component_name_result(mol, component_atoms, name, "hydroxyurea_parent", bindings=tuple(bindings)) |
1138 | 1141 |
|
1139 | 1142 |
|
| 1143 | +def sulfamic_acid_result( |
| 1144 | + mol: Molecule, |
| 1145 | + component_atoms: set[int], |
| 1146 | + branch_namer: RecursiveSubgraphNamer | None = None, |
| 1147 | +) -> SpecialComponentName | None: |
| 1148 | + """Name sulfamic acid and its N-substituted derivatives. |
| 1149 | +
|
| 1150 | + A sulfonic sulfur bonded to nitrogen rather than carbon has no carbon |
| 1151 | + skeleton to hang ``…sulfonic acid`` on, so the acid used to be demoted to a |
| 1152 | + ``hydroxysulfonyl`` prefix and some weaker group became the suffix. |
| 1153 | + ``H2N-SO3H`` is the retained functional parent, and its nitrogen carries the |
| 1154 | + substituents. |
| 1155 | + """ |
| 1156 | + |
| 1157 | + # A ring nitrogen keeps its ring as the parent -- 1H-imidazole-1-sulfonic |
| 1158 | + # acid is not a sulfamic acid whose ring has been dissolved into prefixes. |
| 1159 | + cyclic_atoms = get_cyclic_atoms(mol) |
| 1160 | + centers = [] |
| 1161 | + for idx in component_atoms: |
| 1162 | + atom = mol.atoms[idx] |
| 1163 | + if atom.symbol != "S" or atom.charge != 0: |
| 1164 | + continue |
| 1165 | + neighbors = [n for n in mol.get_neighbors(idx) if n in component_atoms] |
| 1166 | + oxygens = [n for n in neighbors if mol.atoms[n].symbol == "O"] |
| 1167 | + nitrogens = [n for n in neighbors if mol.atoms[n].symbol == "N"] |
| 1168 | + if len(neighbors) != 4 or len(oxygens) != 3 or len(nitrogens) != 1: |
| 1169 | + continue |
| 1170 | + if nitrogens[0] in cyclic_atoms: |
| 1171 | + continue |
| 1172 | + double_o = [o for o in oxygens if (bond := mol.get_bond(idx, o)) is not None and bond.order == 2] |
| 1173 | + single_o = [o for o in oxygens if o not in double_o] |
| 1174 | + if len(double_o) != 2 or len(single_o) != 1: |
| 1175 | + continue |
| 1176 | + hydroxyl = single_o[0] |
| 1177 | + if mol.atoms[hydroxyl].charge != 0 or mol.degree(hydroxyl) != 1: |
| 1178 | + continue |
| 1179 | + centers.append((idx, nitrogens[0], oxygens)) |
| 1180 | + if len(centers) != 1: |
| 1181 | + return None |
| 1182 | + sulfur, nitrogen, oxygens = centers[0] |
| 1183 | + if mol.atoms[nitrogen].charge != 0: |
| 1184 | + return None |
| 1185 | + |
| 1186 | + core_atoms = {sulfur, nitrogen, *oxygens} |
| 1187 | + ligands: list[tuple[set[int], str]] = [] |
| 1188 | + for root in mol.get_neighbors(nitrogen): |
| 1189 | + if root in core_atoms or root not in component_atoms or mol.atoms[root].symbol == "H": |
| 1190 | + continue |
| 1191 | + if branch_namer is None: |
| 1192 | + return None |
| 1193 | + name = branch_namer(mol, root, (set(mol.atoms) - component_atoms) | core_atoms, upstream_atom=nitrogen) |
| 1194 | + if isinstance(name, tuple): |
| 1195 | + name = name[0] |
| 1196 | + if not name: |
| 1197 | + return None |
| 1198 | + ligand_atoms = _component_atoms_until_blocked(mol, component_atoms, root, core_atoms) |
| 1199 | + if not ligand_atoms: |
| 1200 | + return None |
| 1201 | + ligands.append((set(ligand_atoms), strip_outer_parentheses(name))) |
| 1202 | + |
| 1203 | + represented = set(core_atoms) |
| 1204 | + for ligand_atoms, _name in ligands: |
| 1205 | + represented.update(ligand_atoms) |
| 1206 | + if represented != component_atoms: |
| 1207 | + return None |
| 1208 | + |
| 1209 | + prefix = format_counted_prefixes([name for _atoms, name in ligands]) if ligands else "" |
| 1210 | + name = f"{prefix}sulfamic acid" |
| 1211 | + bindings = [ |
| 1212 | + NameAtomBinding( |
| 1213 | + stage="shortcut", |
| 1214 | + role="sulfamic_acid_core", |
| 1215 | + term="sulfamic acid", |
| 1216 | + atom_ids=set(core_atoms), |
| 1217 | + bond_ids=_bond_ids_within_atoms(mol, set(core_atoms)), |
| 1218 | + ) |
| 1219 | + ] |
| 1220 | + bindings.extend( |
| 1221 | + NameAtomBinding( |
| 1222 | + stage="shortcut", |
| 1223 | + role="sulfamic_acid_n_ligand", |
| 1224 | + term=ligand_name, |
| 1225 | + atom_ids=set(ligand_atoms), |
| 1226 | + bond_ids=_bond_ids_within_atoms(mol, set(ligand_atoms) | {nitrogen}), |
| 1227 | + locants=("N",), |
| 1228 | + ) |
| 1229 | + for ligand_atoms, ligand_name in ligands |
| 1230 | + ) |
| 1231 | + return _component_name_result(mol, component_atoms, name, "sulfamic_acid", bindings=tuple(bindings)) |
| 1232 | + |
| 1233 | + |
| 1234 | +def azinic_acid_result( |
| 1235 | + mol: Molecule, |
| 1236 | + component_atoms: set[int], |
| 1237 | + branch_namer: RecursiveSubgraphNamer | None = None, |
| 1238 | +) -> SpecialComponentName | None: |
| 1239 | + """Name azinic acid derivatives, ``HN(=O)OH`` substituted at nitrogen. |
| 1240 | +
|
| 1241 | + The nitrogen carries an oxido and a hydroxy oxygen, which distinguishes it |
| 1242 | + from a nitro group -- the two differ by a hydrogen. Its remaining bond is |
| 1243 | + either single, giving ``N-phenylazinic acid``, or double, giving the |
| 1244 | + ylidene form. |
| 1245 | + """ |
| 1246 | + |
| 1247 | + centers = [] |
| 1248 | + for idx in component_atoms: |
| 1249 | + atom = mol.atoms[idx] |
| 1250 | + if atom.symbol != "N" or atom.charge != 1: |
| 1251 | + continue |
| 1252 | + neighbors = [n for n in mol.get_neighbors(idx) if n in component_atoms] |
| 1253 | + oxygens = [n for n in neighbors if mol.atoms[n].symbol == "O"] |
| 1254 | + others = [n for n in neighbors if n not in oxygens] |
| 1255 | + if len(oxygens) != 2 or len(others) != 1: |
| 1256 | + continue |
| 1257 | + oxido = [o for o in oxygens if mol.atoms[o].charge == -1 and mol.degree(o) == 1] |
| 1258 | + hydroxy = [ |
| 1259 | + o |
| 1260 | + for o in oxygens |
| 1261 | + if mol.atoms[o].charge == 0 and mol.degree(o) == 1 and mol.atoms[o].total_h_count > 0 |
| 1262 | + ] |
| 1263 | + if len(oxido) != 1 or len(hydroxy) != 1: |
| 1264 | + continue |
| 1265 | + centers.append((idx, others[0], oxygens)) |
| 1266 | + if len(centers) != 1: |
| 1267 | + return None |
| 1268 | + nitrogen, ligand_root, oxygens = centers[0] |
| 1269 | + bond = mol.get_bond(nitrogen, ligand_root) |
| 1270 | + if bond is None or bond.order not in {1, 2}: |
| 1271 | + return None |
| 1272 | + if branch_namer is None: |
| 1273 | + return None |
| 1274 | + |
| 1275 | + core_atoms = {nitrogen, *oxygens} |
| 1276 | + ligand_name = branch_namer( |
| 1277 | + mol, ligand_root, (set(mol.atoms) - component_atoms) | core_atoms, upstream_atom=nitrogen |
| 1278 | + ) |
| 1279 | + if isinstance(ligand_name, tuple): |
| 1280 | + ligand_name = ligand_name[0] |
| 1281 | + if not ligand_name: |
| 1282 | + return None |
| 1283 | + ligand_atoms = _component_atoms_until_blocked(mol, component_atoms, ligand_root, core_atoms) |
| 1284 | + if not ligand_atoms or set(ligand_atoms) | core_atoms != component_atoms: |
| 1285 | + return None |
| 1286 | + |
| 1287 | + ligand_name = strip_outer_parentheses(ligand_name) |
| 1288 | + if bond.order == 2: |
| 1289 | + name = f"{format_multiplier(ligand_name, 1, safe_enclose=True)}azinic acid" |
| 1290 | + else: |
| 1291 | + name = f"N-{format_multiplier(ligand_name, 1)}azinic acid" |
| 1292 | + bindings = ( |
| 1293 | + NameAtomBinding( |
| 1294 | + stage="shortcut", |
| 1295 | + role="azinic_acid_core", |
| 1296 | + term="azinic acid", |
| 1297 | + atom_ids=set(core_atoms), |
| 1298 | + bond_ids=_bond_ids_within_atoms(mol, set(core_atoms)), |
| 1299 | + ), |
| 1300 | + NameAtomBinding( |
| 1301 | + stage="shortcut", |
| 1302 | + role="azinic_acid_ligand", |
| 1303 | + term=ligand_name, |
| 1304 | + atom_ids=set(ligand_atoms), |
| 1305 | + bond_ids=_bond_ids_within_atoms(mol, set(ligand_atoms) | {nitrogen}), |
| 1306 | + locants=("N",), |
| 1307 | + ), |
| 1308 | + ) |
| 1309 | + return _component_name_result(mol, component_atoms, name, "azinic_acid", bindings=bindings) |
| 1310 | + |
| 1311 | + |
1140 | 1312 | def _urea_n_ligand_names( |
1141 | 1313 | mol: Molecule, |
1142 | 1314 | component_atoms: set[int], |
|
0 commit comments