Source code for synkit.Rule.Compose.valence_constrain
import importlib.util
import importlib.resources
from typing import List, Tuple
from synkit.IO.debug import setup_logging
from synkit.IO.data_io import load_database
if importlib.util.find_spec("mod"):
from mod import BondType
else:
BondType = None
print("Optional 'mod' package not found")
logger = setup_logging()
[docs]
class ValenceConstrain:
def __init__(self):
"""Initialize the ValenceConstrain class by setting up bond type orders
and loading the maximum valence data.
Parameters:
- None
Returns:
- None
"""
self.btToOrder = {
BondType.Single: 1,
BondType.Double: 2,
BondType.Triple: 3,
BondType.Aromatic: 0,
}
maxValence_path = importlib.resources.files("synkit.Rule.Compose").joinpath(
"MaxValence.json.gz"
)
self.maxValence = load_database(maxValence_path)[0]
[docs]
def valence(self, vertex) -> int:
"""Calculate the valence of a vertex based on its incident edges.
Parameters:
- vertex (Vertex): The vertex for which to calculate the valence.
Returns:
- int: The total valence of the vertex.
"""
return sum(self.btToOrder[edge.bondType] for edge in vertex.incidentEdges)
[docs]
def check_rule(self, rule, verbose: bool = False, log_error: bool = False) -> bool:
"""Check if the rule is chemically valid according to valence rules.
Parameters:
- rule (Rule): The rule to check for chemical validity.
- verbose (bool): If true, logs additional information about the rule
checking process.
- log_error (bool): If true, logs additional information about the valence
checking issue.
Returns:
- bool: True if the rule is chemically valid, False otherwise.
"""
try:
for vertex_pair in rule.vertices:
left_valence = self.valence(vertex_pair.left)
right_valence = self.valence(vertex_pair.right)
left_label = vertex_pair.left.stringLabel
right_label = vertex_pair.right.stringLabel
if left_valence != right_valence:
raise ValueError(
f"Valence mismatch: left {left_valence} vs right {right_valence}"
)
if left_valence > self.maxValence.get(
left_label, 0
) or right_valence > self.maxValence.get(right_label, 0):
if verbose:
logger.info(
f"Bad Rule for vertex {left_label} --->"
+ "Exceeds max chemical valence"
)
return False
return True
except Exception as e:
if log_error:
logger.error(f"Error checking rule {rule}: {e}")
return False
[docs]
def split(self, rules: List) -> Tuple[List, List]:
"""Split rules into 'good' and 'bad' based on their chemical validity.
Parameters:
- rules (List[Rule]): A list of rules to be checked and split.
Returns:
- Tuple[List[Rule], List[Rule]]: A tuple containing two lists, one for
'good' rules and another for 'bad' rules.
"""
good, bad = [], []
for rule in rules:
if self.check_rule(rule):
good.append(rule)
else:
bad.append(rule)
return good, bad