""" Chemistry equation detection utilities. This module provides functions to detect whether an equation is chemistry-related based on strong indicators like multi-char elements, charges, and reaction arrows. """ from lxml import etree as ET from typing import Optional, Tuple from ..data.chemistry_data import ( MULTI_CHAR_ELEMENTS, ALL_CHEMICAL_ELEMENTS, CHEMICAL_GROUPS, CHINESE_CHEMICAL_TERMS, ) def detect_chemistry_equation(root: ET.Element) -> bool: """ Detect if equation is chemistry-related using STRONG indicators only. DISABLED: Subject detection is obsolete. We rely on mathvariant="normal" only. Chemistry formulas are properly formatted when MathML includes mathvariant="normal". Args: root: The root MathML element () Returns: False - always (chemistry detection disabled) """ # DISABLED: Always return False - no heuristic chemistry detection return False # OLD CODE (kept for reference): # Strong indicators: # 1. Multi-char chemical elements in single tag (Cu, Al, Fe, Mn, etc.) # 2. Chemical groups (OH, SO, NO, CO, NH) # 3. Element + numeric subscript (H₂O pattern) # 4. Electric charges in superscript (^{2+}, ^{2-}) # 5. Chemical arrows (↑, ↓) # 6. Chinese chemical terms (高温, 电解, etc.) # # for node in root.iter(): # # Remove namespace if present # tag = node.tag.split('}')[-1] if '}' in node.tag else node.tag # text = (node.text or '').strip() # # # Strong indicator 1: Multi-char element in single # if tag == 'mi' and text in MULTI_CHAR_ELEMENTS: # return True # # # Strong indicator 2: Chemical group # if tag == 'mtext' and text in CHEMICAL_GROUPS: # return True # # # Strong indicator 3: Element + numeric subscript (H₂O, SO₄ pattern) # if tag == 'msub' and len(node) >= 2: # base_node = node[0] # sub_node = node[1] # base_text = (base_node.text or '').strip() # sub_text = (sub_node.text or '').strip() # # # Element with numeric subscript # if base_text in ALL_CHEMICAL_ELEMENTS and sub_text.isdigit(): # return True # # # Strong indicator 4: Electric charge superscript (Cu^{2+}, Mn^{2-}) # if tag == 'msup' and len(node) >= 2: # sup_node = node[1] # sup_text = (sup_node.text or '').strip() # # # Check for charge notation (contains + or -) # # Pattern: "2+", "+", "2-", "-", "3+", etc. # if sup_text and ('+' in sup_text or '-' in sup_text): # # Additional check: ensure it's likely a charge, not math # # Charges are typically: digit* followed by +/- # # Math would be expressions like "x+1", "y-2" # # Simple heuristic: if it's short and has +/- at end, it's likely a charge # if len(sup_text) <= 3 and (sup_text.endswith('+') or sup_text.endswith('-')): # return True # # Also check for format: "2 +" or "2 -" (with space) # parts = sup_text.split() # if len(parts) == 2 and parts[0].isdigit() and parts[1] in ('+', '-'): # return True # # # Strong indicator 5: Chemical arrows (precipitate, gas) # if tag == 'mo' and text in ('↑', '↓'): # return True # # # Strong indicator 6: Chinese chemical terms # if tag in ('mtext', 'mi') and text in CHINESE_CHEMICAL_TERMS: # return True # # return False def calculate_chemistry_probability(root: ET.Element, subject_mode: Optional[str] = None) -> float: """ Calculate chemistry probability score based on multiple indicators. Scoring weights: - Ion charge present (^{2+}, ^{3-}): +1.0 - Multi-char element in single : +0.8 - Nearby multi-char element: +0.8 - Reaction arrow (→, ⇌, ⇄): +0.6 - Chemical subscripts (H_2, O_2): +0.4 - Chinese chemical term: +0.3 - User set --subject chemistry: +0.5 Thresholds: - Chemistry mode: ≥0.8 (merge fragmented elements) - Auto-mode: ≥1.5 (only VERY strong indicators) Args: root: The root MathML element () subject_mode: Optional subject mode ('chemistry', 'physics', etc.) Returns: Probability score (0.0-3.0+) Examples: >>> # M n^{2+} with MnO nearby: 1.0 + 0.8 + 0.8 = 2.6 (strong) >>> calculate_chemistry_probability(math_root) # 2.6 >>> # Just "M n" with no context: 0.0 (weak) >>> calculate_chemistry_probability(math_root) # 0.0 >>> # Chemistry mode boost: M n → 0.5 (mode) + context >>> calculate_chemistry_probability(math_root, 'chemistry') # 0.5+ """ # DISABLED: Always return 0.0 - no chemistry detection return 0.0 # OLD CODE (kept for reference): # score = 0.0 # # Mode boost: user explicitly set --subject chemistry # if subject_mode == 'chemistry': # score += 0.5 # # # Counters for various indicators # has_ion_charge = False # multi_char_element_count = 0 # has_reaction_arrow = False # chemical_subscript_count = 0 # has_chinese_term = False # # for node in root.iter(): # # Remove namespace if present # tag = node.tag.split('}')[-1] if '}' in node.tag else node.tag # text = (node.text or '').strip() # # # Indicator 1: Multi-char element in single (+0.8 each, max 2) # if tag == 'mi' and text in MULTI_CHAR_ELEMENTS: # multi_char_element_count += 1 # # # Indicator 2: Chemical group (+0.8) # if tag == 'mtext' and text in CHEMICAL_GROUPS: # multi_char_element_count += 1 # # # Indicator 3: Element + numeric subscript (H₂O pattern) (+0.4 each, max 3) # if tag == 'msub' and len(node) >= 2: # base_node = node[0] # sub_node = node[1] # base_text = (base_node.text or '').strip() # sub_text = (sub_node.text or '').strip() # # if base_text in ALL_CHEMICAL_ELEMENTS and sub_text.isdigit(): # chemical_subscript_count += 1 # # # Indicator 4: Ion charge superscript (+1.0, strongest indicator) # if tag == 'msup' and len(node) >= 2: # sup_node = node[1] # sup_text = (sup_node.text or '').strip() # # # Check for charge notation # if sup_text and ('+' in sup_text or '-' in sup_text): # if len(sup_text) <= 3 and (sup_text.endswith('+') or sup_text.endswith('-')): # has_ion_charge = True # # Also check for format: "2 +" or "2 -" # parts = sup_text.split() # if len(parts) == 2 and parts[0].isdigit() and parts[1] in ('+', '-'): # has_ion_charge = True # # # Indicator 5: Reaction arrows (+0.6) # if tag == 'mo': # # Chemical arrows (precipitate, gas) # if text in ('↑', '↓'): # has_reaction_arrow = True # # Reaction arrows (→, ⇌, ⇄, etc.) # if text in ('→', '←', '⇌', '⇄', '⟶', '⟵', '⇀', '↽'): # has_reaction_arrow = True # # # Indicator 6: Chinese chemical terms (+0.3) # if tag in ('mtext', 'mi') and text in CHINESE_CHEMICAL_TERMS: # has_chinese_term = True # # # Calculate total score # if has_ion_charge: # score += 1.0 # # # Cap multi-char elements at 2 (prevent over-scoring) # score += min(multi_char_element_count, 2) * 0.8 # # if has_reaction_arrow: # score += 0.6 # # # Cap chemical subscripts at 3 # score += min(chemical_subscript_count, 3) * 0.4 # # if has_chinese_term: # score += 0.3 # # return score def detect_chemistry_equation_with_probability(root: ET.Element) -> Tuple[bool, float]: """ Detect chemistry equation and return both boolean and probability score. DISABLED: Subject detection is obsolete. Args: root: The root MathML element () Returns: Tuple of (False, 0.0) - always (chemistry detection disabled) """ # DISABLED: Always return (False, 0.0) return False, 0.0 # OLD CODE (kept for reference): # probability = calculate_chemistry_probability(root) # is_chemistry = probability >= 1.0 # Strong indicator threshold # return is_chemistry, probability def is_chemistry_variable(text: str, in_chemistry_context: bool) -> bool: """ Determine if text is a chemistry variable (should stay italic). DISABLED: Chemistry detection is obsolete. Args: text: The identifier text in_chemistry_context: Whether we're in a chemistry equation (ignored) Returns: False - always (not used anymore) """ # DISABLED: Always return False - chemistry detection disabled return False # OLD CODE (kept for reference): # from ..data.chemistry_data import CHEMISTRY_VARIABLES # # In chemistry context, check if it's a known variable # if in_chemistry_context: # return text in CHEMISTRY_VARIABLES # # Not in chemistry context, not a chemistry variable # return False