Skip to content

Commit db3375f

Browse files
committed
feat(coordination_sphere): add prioritization logic for coordinating atoms in validation for boron
1 parent cbaf875 commit db3375f

3 files changed

Lines changed: 110 additions & 49 deletions

File tree

cell2mol/charge/protonation_enumerator.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -754,8 +754,9 @@ def _handle_non_haptic_group(
754754
in_cycles = [c for c in cycles if idx in c]
755755

756756
if len(in_cycles) == 1:
757-
if numN == 2:
758-
print(
757+
if numN == 2 and len(in_cycles[0]) == 5:
758+
logger.debug(
759+
"Ligand formula: %s, Atom site label: %s, Adjacency labels: %s, Type: %s",
759760
ligand.formula,
760761
a.atom_site_label,
761762
adj_labels,

cell2mol/connectivity.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -758,9 +758,9 @@ def apply_graph_to_blocklist(
758758
"""Split a list of atoms into blocks of connected atoms."""
759759

760760
new_blocklist = []
761-
# logger.debug("Applying graph analysis to blocklist: %s", blocklist)
761+
logger.debug("Applying graph analysis to blocklist: %s", blocklist)
762762
for b in blocklist:
763-
# logger.debug("block=%s", b)
763+
logger.debug("block=%s", b)
764764

765765
gr_labels = extract_from_list(b, conn_labels, dimension=1)
766766
gr_coord = extract_from_list(b, conn_coord, dimension=1)
@@ -796,7 +796,7 @@ def apply_graph_to_blocklist(
796796
continue
797797

798798
cycle = cycle_basis[0]
799-
# logger.debug("Found single cycle in block %s: %s", b, cycle)
799+
logger.debug("Found single cycle in block %s: %s", b, cycle)
800800

801801
# Full cycle covers all atoms
802802
if len(cycle) == len(G.nodes):
@@ -807,10 +807,10 @@ def apply_graph_to_blocklist(
807807
cycle_block = sorted([b[idx] for idx in cycle])
808808
new_blocklist.append(cycle_block)
809809

810-
# logger.debug("Cycle block indices=%s", cycle_block)
810+
logger.debug("Cycle block indices=%s", cycle_block)
811811

812-
remaining = [n for n in G.nodes if n not in cycle]
813-
# logger.debug("Remaining nodes in block=%s", remaining)
812+
remaining = [b[n] for n in G.nodes if n not in cycle]
813+
logger.debug("Remaining nodes in block=%s", remaining)
814814

815815
rem_labels = extract_from_list(remaining, conn_labels, dimension=1)
816816
rem_coord = extract_from_list(remaining, conn_coord, dimension=1)
@@ -839,12 +839,12 @@ def apply_graph_to_blocklist(
839839
for comp in nx.connected_components(G_rem):
840840
remaining_block = [remaining[idx] for idx in comp]
841841
new_blocklist.append(remaining_block)
842-
# logger.debug(
843-
# "Remaining connected block=%s",
844-
# remaining_block,
845-
# )
842+
logger.debug(
843+
"Remaining connected block=%s",
844+
remaining_block,
845+
)
846846

847-
# logger.debug("Final new_blocklist=%s", new_blocklist)
847+
logger.debug("Final new_blocklist=%s", new_blocklist)
848848

849849
return new_blocklist
850850

cell2mol/coordination_sphere.py

Lines changed: 96 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,95 @@ def unique_atoms(atoms):
588588
return unique
589589

590590

591+
def prioritize_coordinating_atoms_to_validate(
592+
gr_atoms_dict, sorted_gr_atoms, metal, ligand
593+
):
594+
priority_atoms = []
595+
596+
group_formula = labels2formula(gr_atoms_dict["labels"])
597+
598+
def add_priority_atom(atom):
599+
if atom not in priority_atoms:
600+
priority_atoms.append(atom)
601+
602+
if "Si" in gr_atoms_dict["labels"]:
603+
si_idx = gr_atoms_dict["labels"].index("Si")
604+
si_margin = gr_atoms_dict["margins"][si_idx]
605+
606+
# Si priority: If Si is present and has a large margin, prioritize it for validation
607+
if si_margin > 0.25:
608+
add_priority_atom(sorted_gr_atoms[si_idx])
609+
610+
if "B" in gr_atoms_dict["labels"]:
611+
is_bh4_like = ligand.formula == "H4-B" or (
612+
gr_atoms_dict["labels"].count("B") >= 1
613+
and gr_atoms_dict["labels"].count("H") >= 2
614+
)
615+
616+
logger.debug(
617+
"Evaluating BH4-like priority: ligand formula %s, group formula %s, is_bh4_like=%s",
618+
ligand.formula,
619+
group_formula,
620+
is_bh4_like,
621+
)
622+
623+
gr_atom_site_set = set(gr_atoms_dict["atom_site_labels"])
624+
excluded_neighbor_labels = {}
625+
626+
for atom in sorted_gr_atoms:
627+
if atom.label != "B":
628+
continue
629+
630+
if is_bh4_like:
631+
add_priority_atom(atom)
632+
continue
633+
634+
adj_atoms = [
635+
ligand.get_parent("molecule").atoms[adj] for adj in atom.adjacency
636+
]
637+
638+
local_neighbors = [
639+
adj
640+
for adj in adj_atoms
641+
if adj.atom_site_label in gr_atom_site_set
642+
and adj.label not in excluded_neighbor_labels
643+
]
644+
645+
local_atoms = [atom] + local_neighbors
646+
n_adj_in_gr_atoms = len(local_neighbors)
647+
648+
atom_margin = (
649+
np.linalg.norm(metal.coord - atom.coord) - metal.radii - atom.radii
650+
)
651+
652+
max_local_margin = max(
653+
np.linalg.norm(metal.coord - local_atom.coord)
654+
- metal.radii
655+
- local_atom.radii
656+
for local_atom in local_atoms
657+
)
658+
659+
is_farthest_atom = np.isclose(atom_margin, max_local_margin)
660+
661+
logger.debug(
662+
"B atom %s: n_valid_neighbors=%d, local_neighbor_labels=%s, "
663+
"atom_margin=%.3f, max_local_margin=%.3f, is_farthest_atom=%s",
664+
atom.atom_site_label,
665+
n_adj_in_gr_atoms,
666+
[adj.label for adj in local_neighbors],
667+
atom_margin,
668+
max_local_margin,
669+
is_farthest_atom,
670+
)
671+
672+
# Prioritize B if at least two neighboring atoms are also coordinated
673+
if n_adj_in_gr_atoms >= 2:
674+
add_priority_atom(atom)
675+
elif n_adj_in_gr_atoms == 1 and group_formula == "H-B":
676+
add_priority_atom(atom)
677+
return priority_atoms
678+
679+
591680
def validate_coordinated_atoms(gr_atoms, metal, ligand, haptic, removed_ligand_indices):
592681
"""
593682
Checks if atoms in gr_atoms are truly connected to the metal.
@@ -651,7 +740,6 @@ def validate_coordinated_atoms(gr_atoms, metal, ligand, haptic, removed_ligand_i
651740
gr_atoms_dict["atom_site_labels"] = None
652741

653742
# 2. Detailed Debug Logging
654-
group_formula = labels2formula(gr_atoms_dict["labels"])
655743
logger.debug(
656744
"Sorted coordinated atoms (farthest first): %s", gr_atoms_dict["labels"]
657745
)
@@ -661,11 +749,11 @@ def validate_coordinated_atoms(gr_atoms, metal, ligand, haptic, removed_ligand_i
661749
logger.debug("Sorted coordinated atom distances: %s", gr_atoms_dict["distances"])
662750
logger.debug("Sorted coordinated atom margins: %s", gr_atoms_dict["margins"])
663751

664-
if haptic:
665-
logger.debug(
666-
"Haptic coordination detected. Skipping validation. %s", group_formula
667-
)
668-
return gr_atoms, False
752+
# if haptic:
753+
# logger.debug(
754+
# "Haptic coordination detected. Skipping validation. %s", group_formula
755+
# )
756+
# return gr_atoms, False
669757

670758
# if "H" not in gr_atoms_labels:
671759
# outlier_atoms = find_atom_outlier(sorted_gr_atoms, metal, haptic)
@@ -675,37 +763,9 @@ def validate_coordinated_atoms(gr_atoms, metal, ligand, haptic, removed_ligand_i
675763
# logger.debug(
676764
# "Hydrogen atoms detected in coordination sphere. Prioritizing their validation."
677765
# )
678-
679-
priority_atoms = []
680-
681-
def add_priority_atom(atom):
682-
if atom not in priority_atoms:
683-
priority_atoms.append(atom)
684-
685-
# Si priority: If Si is present and has a large margin, prioritize it for validation
686-
if "Si" in gr_atoms_dict["labels"]:
687-
si_idx = gr_atoms_dict["labels"].index("Si")
688-
si_margin = gr_atoms_dict["margins"][si_idx]
689-
690-
if si_margin > 0.25:
691-
add_priority_atom(sorted_gr_atoms[si_idx])
692-
693-
# BH4-like priority
694-
is_bh4_like = ligand.formula == "H4-B" or (
695-
gr_atoms_dict["labels"].count("B") >= 1
696-
and gr_atoms_dict["labels"].count("H") >= 2
697-
)
698-
logger.debug(
699-
"Evaluating BH4-like priority: ligand formula %s, group formula %s, %s",
700-
ligand.formula,
701-
group_formula,
702-
is_bh4_like,
766+
priority_atoms = prioritize_coordinating_atoms_to_validate(
767+
gr_atoms_dict, sorted_gr_atoms, metal, ligand
703768
)
704-
if is_bh4_like:
705-
for atom in sorted_gr_atoms:
706-
if atom.label == "B":
707-
add_priority_atom(atom)
708-
709769
if priority_atoms:
710770
logger.debug(
711771
"Priority atoms identified for validation: %s %s",

0 commit comments

Comments
 (0)