""" Chemistry detection utilities for identifying chemical equations and contexts. This module provides functions to detect chemical reactions, conditions, and other chemistry-specific patterns in mathematical equations. """ from typing import Optional, Dict from lxml import etree as ET # OMML namespace constant OMML_NS = "http://schemas.openxmlformats.org/officeDocument/2006/math" def contains_chinese_characters(text: str) -> bool: """Check if text contains Chinese characters. Args: text: Text to check Returns: True if text contains at least one Chinese character Example: >>> contains_chinese_characters('加热') True >>> contains_chinese_characters('heat') False """ if not text: return False for char in text: if '\u4e00' <= char <= '\u9fff': # Chinese character range return True return False def is_typical_reaction_condition_text(text: str) -> bool: """Check if text looks like a typical chemical reaction condition. Args: text: Text to check Returns: True if text contains typical chemical reaction condition terms Example: >>> is_typical_reaction_condition_text('加热') True >>> is_typical_reaction_condition_text('heat') True >>> is_typical_reaction_condition_text('abc') False """ if not text: return False # Common chemical reaction conditions typical_conditions = { # Chinese conditions "电解", "加热", "催化剂", "高温", "高压", "光照", "加压", "蒸馏", "熔融", "溶解", "酸化", "碱化", "氧化", "还原", "分解", "合成", "中和", "干燥", "过滤", "沉淀", "结晶", "升华", "煅烧", # English conditions "heat", "catalyst", "pressure", "light", "oxidation", "reduction", "electrolysis", "distillation", "crystallization", "precipitation", "filtration", "drying", # Symbols and abbreviations "Δ", "hv", "H+", "OH-", "cat", "pt", "ni", "pd" } text_lower = text.lower().strip() return any(condition.lower() in text_lower for condition in typical_conditions) def is_chemical_reaction_arrow(eqArr_node: ET._Element) -> Optional[Dict[str, str]]: """Detect if an eqArr represents a chemical reaction arrow with conditions. Args: eqArr_node: OMML eqArr element Returns: Dictionary with chemical reaction info if detected, None otherwise. Structure: { 'above': str, # Text above the arrow (e.g., '电解') 'below': str, # Text below the arrow (e.g., '加热', can be empty) 'type': str # 'chemical_reaction' } Example: >>> # Assuming eqArr_node is an OMML element with reaction conditions >>> result = is_chemical_reaction_arrow(eqArr_node) >>> result['above'] '电解' """ # Find all equation array elements e_elements = eqArr_node.findall(f"{{{OMML_NS}}}e") # Must have exactly 2 elements for chemical reaction conditions if len(e_elements) != 2: return None # Extract text from first element (above the arrow) above_text = "" first_elem = e_elements[0] # Extract text from all t elements in the first element, avoiding duplication # Use a set to track unique text content text_parts = set() for t_elem in first_elem.findall(f".//{{{OMML_NS}}}t"): if t_elem.text and t_elem.text.strip(): text_parts.add(t_elem.text.strip()) # Join unique text parts above_text = "".join(sorted(text_parts)) # Extract text from second element (below the arrow) below_text = "" second_elem = e_elements[1] # Extract text from all t elements in the second element, avoiding duplication text_parts_below = set() for t_elem in second_elem.findall(f".//{{{OMML_NS}}}t"): if t_elem.text and t_elem.text.strip(): text_parts_below.add(t_elem.text.strip()) # Join unique text parts below_text = "".join(sorted(text_parts_below)) # Validate that this looks like a chemical reaction pattern if above_text.strip(): # Check if contains chemical condition patterns (Chinese text or typical reaction words) if (contains_chinese_characters(above_text) or is_typical_reaction_condition_text(above_text) or contains_chinese_characters(below_text) or is_typical_reaction_condition_text(below_text)): return { 'above': above_text.strip(), 'below': below_text.strip(), 'type': 'chemical_reaction' } return None def detect_chemistry_context(equation_text: str) -> bool: """Detect if equation suggests chemistry context - Conservative approach. Args: equation_text: The full equation text to analyze Returns: True if chemistry context is detected Example: >>> detect_chemistry_context('H2O + pH = 7') True >>> detect_chemistry_context('sin x + cos x') False """ if not equation_text: return False # Check if we're in a set theory context - if so, don't treat letters as chemical elements if has_set_theory_symbols(equation_text): return False # Check if we're in a mathematical context (trigonometry, calculus, etc.) if has_mathematical_functions(equation_text): return False # Conservative chemistry detection: require multiple indicators, not just chemical elements chemistry_indicators = ['pH', 'pOH', 'mol', 'atm', 'bar', 'Torr', 'eq', 'aq', 'sol', '↑', '↓', '→', '⇋', '⇌', 'xlongequal', 'rightarrow'] # Only return True if we have actual chemistry indicators in the equation text return any(indicator in equation_text for indicator in chemistry_indicators) def has_mathematical_functions(equation_text: str) -> bool: """Check if the equation contains mathematical functions. Args: equation_text: The equation text to analyze Returns: True if mathematical functions are detected Example: >>> has_mathematical_functions('sin x + cos x') True >>> has_mathematical_functions('H2O') False """ if not equation_text: return False # Common mathematical functions that indicate math context, not chemistry math_functions = ['sin', 'cos', 'tan', 'log', 'ln', 'exp', 'sqrt', 'lim', 'max', 'min', 'arcsin', 'arccos', 'arctan', 'sinh', 'cosh', 'tanh'] return any(func in equation_text for func in math_functions) def has_set_theory_symbols(equation_text: str) -> bool: """Check if the equation contains set theory symbols. Args: equation_text: The equation text to analyze Returns: True if set theory symbols are detected Example: >>> has_set_theory_symbols('A ∩ B ⊂ C') True >>> has_set_theory_symbols('H2O') False """ if not equation_text: return False set_symbols = ['⊂', '⊃', '⊆', '⊇', '⊄', '⊅', '⊈', '⊉', '⊊', '⊋', '∈', '∉', '∋', '∌', '∪', '∩', '∖', '∁'] return any(symbol in equation_text for symbol in set_symbols) def detect_physics_context(equation_text: str) -> bool: """Detect if equation suggests physics context. Args: equation_text: The equation text to analyze Returns: True if physics context is detected Example: >>> detect_physics_context('F = ma, where m is in kg') True >>> detect_physics_context('x + y = z') False """ if not equation_text: return False # Check if we're in a mathematical context (trigonometry, calculus, etc.) if has_mathematical_functions(equation_text): return False # Common physics constants and units - look for these in the equation context physics_indicators = [ 'kg', 'm', 's', 'A', 'K', 'mol', 'cd', # SI base units 'Hz', 'N', 'Pa', 'J', 'W', 'C', 'V', 'F', 'Ω', 'S', 'Wb', 'T', 'H', # Derived units 'eV', 'cal', 'Bar', 'L', # Common non-SI # Note: removed single-letter constants like 'c', 'h', 'k', 'G', 'R', 'NA' # These are too common in math contexts and cause false positives ] # Only return True if we have actual physics indicators in the equation text return any(indicator in equation_text for indicator in physics_indicators) def is_physics_constant(content: str) -> bool: """Check if content is a known physics constant. Args: content: String to check Returns: True if content represents a known physics constant Example: >>> is_physics_constant('c') True >>> is_physics_constant('x') False """ constants = { 'c', # speed of light 'h', # Planck constant 'hbar', # reduced Planck constant 'k', # Boltzmann constant 'kB', # Boltzmann constant 'G', # gravitational constant 'R', # gas constant 'NA', # Avogadro number 'e', # elementary charge 'me', # electron mass 'mp', # proton mass 'mn', # neutron mass 'mu', # atomic mass unit 'epsilon0', # vacuum permittivity 'mu0', # vacuum permeability 'alpha', # fine structure constant 'sigma', # Stefan-Boltzmann constant } return content in constants