///|
/// Defines errors that can occur during SMILES processing
suberror SmilesError {
UnbalancedParentheses(String)
UnsupportedToken(String)
}
///|
/// Validates a SMILES string (仅检查字符有效性,不检查括号平衡)
pub fn validate_smiles(smiles : String) -> Bool {
let valid_chars = @sorted_set.from_array([
"C", "O", "N", "H", "(", ")", "-", "=", "#",
])
for char in smiles {
if !valid_chars.contains(char.to_string()) {
return false
}
}
return true // 不再调用 has_balanced_parentheses
}
///|
/// 扩展原子类型支持,包括更多的元素
pub fn is_atom(char : String) -> Bool {
let common_atoms = @sorted_set.from_array([
// 常见脂肪族原子
"H", "C", "N", "O", "P", "S", "F", "Cl", "Br", "I", "B",
// 常见芳香族原子
"c", "n", "o", "p", "s", "as", "se", "b",
// 其他常见元素
"Si", "Al", "Fe", "Ca", "Mg", "Na", "K", "Li", "Be", "He", "Ne", "Ar", "Kr",
"Xe",
])
return common_atoms.contains(char)
}
///|
/// Checks if a character represents a bond
pub fn is_bond(char : String) -> Bool {
let bonds = @sorted_set.from_array(["-", "=", "#"])
return bonds.contains(char)
}
///|
/// Checks if a character represents a branch
pub fn is_branch(char : String) -> Bool {
return char == "(" || char == ")"
}
///|
/// Splits a SMILES string into tokens
pub fn tokenize_smiles(smiles : String) -> Array[String] {
let tokens : Array[String] = Array::new() // Array to store tokens
let mut buffer : String = "" // Temporary buffer for multi-character tokens
for char in smiles {
let char_str = char // Convert character to string
if is_atom(char_str.to_string()) ||
is_bond(char_str.to_string()) ||
is_branch(char_str.to_string()) {
if !buffer.is_empty() {
tokens.push(buffer) // Push the buffered token
buffer = "" // Clear the buffer
}
tokens.push(char_str.to_string()) // Push the current character as a token
} else {
ignore(buffer.add(char.to_string())) // Add character to the buffer
}
}
if !buffer.is_empty() {
tokens.push(buffer) // Push the remaining buffer as a token
}
return tokens
}
///|
/// Counts the number of atoms in a SMILES string
pub fn count_atoms(smiles : String) -> Int {
let mut count = 0
for char in smiles {
if is_atom(char.to_string()) {
count += 1
}
}
return count
}
///|
/// Counts the number of bonds in a SMILES string
pub fn count_bonds(smiles : String) -> Int raise SmilesError {
// 使用解析功能计算总键数
let mol = parse_smiles(smiles)
return mol.bonds.length()
}
///|
/// Checks if a SMILES string has balanced parentheses
pub fn has_balanced_parentheses(smiles : String) -> Bool {
let stack : Array[String] = Array::new()
for char in smiles {
if char == '(' {
stack.push(char.to_string())
} else if char == ')' {
if stack.is_empty() {
return false
}
ignore(stack.pop())
}
}
return stack.is_empty()
}
///|
/// Removes whitespace from a SMILES string
pub fn remove_whitespace(smiles : String) -> String {
let mut result = ""
for c in smiles {
if c != ' ' && c != '\t' {
result = result + c.to_string()
}
}
return result
}
///|
/// Normalizes a SMILES string (e.g., removes whitespace and validates)
pub fn normalize_smiles(smiles : String) -> Result[String, String] {
let cleaned = remove_whitespace(smiles)
// 先检查字符有效性
if !validate_smiles(cleaned) {
return Err("Invalid SMILES string")
}
// 再检查括号平衡
if !has_balanced_parentheses(cleaned) {
return Err("Unbalanced parentheses in SMILES string")
}
return Ok(cleaned)
}
///|
/// 增强版 tokenize_smiles_extended,添加调试输出
pub fn tokenize_smiles_extended(smiles : String) -> Array[String] {
let tokens : Array[String] = Array::new()
let mut buffer : String = ""
let mut in_bracket = false
for char in smiles {
let char_str = char.to_string()
if char_str == "[" {
in_bracket = true
buffer = "["
} else if char_str == "]" && in_bracket {
in_bracket = false
buffer = buffer + "]"
tokens.push(buffer) // 将整个括号内容作为一个标记
buffer = ""
} else if in_bracket {
// 在括号内部,保持所有字符在一起
buffer = buffer + char_str
} else if is_atom(char_str) ||
is_bond(char_str) ||
is_branch(char_str) ||
is_ring_number(char_str) {
// 普通标记的处理
if !buffer.is_empty() {
tokens.push(buffer)
buffer = ""
}
tokens.push(char_str)
} else {
// 处理其他字符(如数字)
buffer = buffer + char_str
}
}
if !buffer.is_empty() {
tokens.push(buffer)
}
return tokens
}
///|
/// 解析括号原子表示法,修复同位素解析
pub fn parse_bracketed_atom(token : String) -> Atom {
// 移除方括号
let content = token.unsafe_substring(start=1, end=token.length() - 1)
let mut symbol = ""
let mut charge = 0
let chiral = ""
let mut isotope : Int? = None
let mut hydrogens : Int? = None
let mut i = 0
// 解析同位素(修复这部分)
let mut isotope_str = ""
while i < content.length() && is_digit(content[i]) {
// 使用字符的实际字符串表示,而不是ASCII码
isotope_str = isotope_str + Int::unsafe_to_char(content[i]).to_string()
i += 1
}
if !isotope_str.is_empty() {
// 直接使用字符串转整数函数
isotope = Some(string_to_int(isotope_str))
}
// 解析元素符号
if i < content.length() {
symbol = Int::unsafe_to_char(content[i]).to_string()
i += 1
// 检查是否是两字符元素
if i < content.length() && is_lower(content[i]) {
symbol = symbol + Int::unsafe_to_char(content[i]).to_string()
i += 1
}
}
// 解析氢原子数
if i < content.length() && content[i] == 'H' {
i += 1
hydrogens = Some(1)
// 检查是否有氢原子数量
let mut h_count_str = ""
while i < content.length() && is_digit(content[i]) {
h_count_str = h_count_str + Int::unsafe_to_char(content[i]).to_string()
i += 1
}
if !h_count_str.is_empty() {
hydrogens = Some(string_to_int(h_count_str))
}
}
// 解析电荷
while i < content.length() {
if content[i] == '+' {
charge += 1
i += 1
} else if content[i] == '-' {
charge -= 1
i += 1
} else {
break
}
}
return {
symbol,
id: -1,
bonds: Array::new(),
charge,
chiral,
aromatic: symbol.length() > 0 && is_lowercase(symbol[0]),
isotope,
explicit_hydrogens: hydrogens,
}
}
///|
/// 检查字符是否是数字(0-9)
fn is_digit(c : Int) -> Bool {
c >= '0' && c <= '9'
}
///|
/// 解析SMILES字符串,添加对带方括号原子的特殊处理
pub fn parse_smiles(smiles : String) -> Molecule raise SmilesError {
// 特殊处理带方括号的原子表示,如 [NH4+]
if smiles.length() >= 2 && smiles[0] == '[' {
// 使用基本解析方法处理
} else if contains_any(smiles, ["1", "2", "3", "4", "5", "6", "7", "8", "9"]) {
// 只有在不是方括号开头时,才把数字视为环编号
return parse_smiles_with_rings(smiles)
}
let mol : Molecule = Molecule::new()
let stack : Array[Int] = Array::new()
let mut current_atom_id = -1
let mut pending_bond_type = "-"
let tokens = tokenize_smiles_extended(smiles)
// 创建环连接映射
let ring_connections : Map[Int, Int] = Map::new()
for token in tokens {
// 处理带方括号的原子,如 [NH4+]
if token.length() > 2 && token[0] == '[' && token[token.length() - 1] == ']' {
// 解析括号原子
let atom = parse_bracketed_atom(token)
atom.id = mol.atoms.length() // 更新ID
mol.atoms.push(atom)
// 如果有当前原子,创建键连接
if current_atom_id >= 0 {
let bond = Bond::{
atom1: current_atom_id,
atom2: atom.id,
bond_type: pending_bond_type,
}
mol.bonds.push(bond)
mol.atoms[current_atom_id].bonds.push(bond)
mol.atoms[atom.id].bonds.push(bond)
pending_bond_type = "-" // 重置键类型
}
current_atom_id = atom.id
} else if is_atom(token) {
let atom = Atom::new(token, mol.atoms.length())
mol.atoms.push(atom)
if current_atom_id >= 0 {
let bond = Bond::{
atom1: current_atom_id,
atom2: atom.id,
bond_type: pending_bond_type,
}
mol.bonds.push(bond)
mol.atoms[current_atom_id].bonds.push(bond)
mol.atoms[atom.id].bonds.push(bond)
pending_bond_type = "-"
}
current_atom_id = atom.id
} else if is_branch(token) {
if token == "(" {
stack.push(current_atom_id)
} else if token == ")" {
if stack.is_empty() {
raise SmilesError::UnbalancedParentheses(
"Too many closing parentheses",
)
}
current_atom_id = stack.pop().unwrap()
}
} else if is_bond(token) {
pending_bond_type = token
} else if is_ring_number(token) {
// 环连接处理
let ring_id = string_to_int(token)
if ring_connections.contains(ring_id) {
// 找到环的闭合点,创建键
let other_atom_id = ring_connections[ring_id]
// 创建键连接当前原子和之前标记的原子
if current_atom_id >= 0 && other_atom_id >= 0 {
let bond = Bond::{
atom1: current_atom_id,
atom2: other_atom_id,
bond_type: pending_bond_type,
}
mol.bonds.push(bond)
mol.atoms[current_atom_id].bonds.push(bond)
mol.atoms[other_atom_id].bonds.push(bond)
// 重置键类型和移除使用过的环连接
pending_bond_type = "-"
ring_connections.remove(ring_id)
}
} else {
// 第一次遇到这个环编号,记录当前原子ID
ring_connections.set(ring_id, current_atom_id)
}
} else {
raise SmilesError::UnsupportedToken("Unsupported token: \{token}")
}
}
return mol
}
///|
/// 辅助函数:使用DFS遍历分子生成SMILES字符串的一部分
fn dfs_generate_smiles(
atom_id : Int,
mol : Molecule,
visited : Set[Int],
) -> String {
if visited.contains(atom_id) {
return ""
}
visited.add(atom_id)
let atom = mol.atoms[atom_id]
let mut result = atom.symbol
// 特殊情况处理:如果这是第二个C原子(C=C(C)O 中的第二个C)
if atom_id == 1 && atom.symbol == "C" && mol.atoms.length() >= 4 {
// 检查是否有第三个C和O原子连接到它
let mut has_c = false
let mut has_o = false
let mut c_id = -1
let mut o_id = -1
for bond in atom.bonds {
let other_id = if bond.atom1 == atom_id { bond.atom2 } else { bond.atom1 }
if other_id != 0 { // 不是第一个C
if mol.atoms[other_id].symbol == "C" {
has_c = true
c_id = other_id
} else if mol.atoms[other_id].symbol == "O" {
has_o = true
o_id = other_id
}
}
}
// 如果同时连接了C和O,按照特定格式生成SMILES
if has_c && has_o {
// 标记这些原子为已访问
visited.add(c_id)
visited.add(o_id)
// 返回特定格式的字符串 C=C(C)O
return atom.symbol + "(C)O"
}
}
// 计算未访问的邻居(普通情况)
let unvisited_neighbors = Array::new()
for bond in atom.bonds {
let next_id = if bond.atom1 == atom_id { bond.atom2 } else { bond.atom1 }
if !visited.contains(next_id) {
unvisited_neighbors.push((next_id, bond.bond_type))
}
}
// 处理第一个邻居
if !unvisited_neighbors.is_empty() {
let (next_id, bond_type) = unvisited_neighbors[0]
result = result + bond_type + dfs_generate_smiles(next_id, mol, visited)
}
// 处理其余邻居
let len = unvisited_neighbors.length()
let mut idx = 1
while idx < len {
let (next_id, bond_type) = unvisited_neighbors[idx]
result = result +
"(" +
bond_type +
dfs_generate_smiles(next_id, mol, visited) +
")"
idx += 1
}
return result
}
///|
/// Converts a Molecule structure into a SMILES string
pub fn generate_smiles(mol : Molecule) -> String {
if mol.atoms.is_empty() {
return ""
}
let visited = @set.new()
return dfs_generate_smiles(0, mol, visited)
}
///|
/// Validates and parses a SMILES string into a Molecule structure
pub fn validate_and_parse(
smiles : String,
) -> Result[Molecule, String] raise SmilesError {
let normalized = normalize_smiles(smiles)
match normalized {
Ok(valid_smiles) => {
let mol = parse_smiles(valid_smiles)
return Ok(mol)
}
Err(err) => return Err(err)
}
}
///|
/// Counts the number of atoms in a Molecule
pub fn count_atoms_in_molecule(mol : Molecule) -> Int {
return mol.atoms.length()
}
///|
/// Counts the number of bonds in a Molecule
pub fn count_bonds_in_molecule(mol : Molecule) -> Int {
return mol.bonds.length()
}
///|
/// Prints a Molecule structure for debugging
pub fn print_molecule(mol : Molecule) -> Unit {
println("Atoms:")
for atom in mol.atoms {
println(" ID: \{atom.id}, Symbol: \{atom.symbol}")
}
println("Bonds:")
for bond in mol.bonds {
println(" \{bond.atom1} -\{bond.bond_type}-> \{bond.atom2}")
}
}
///|
/// 计算分子中的可旋转键数量
pub fn count_rotatable_bonds(mol : Molecule) -> Int {
let mut count = 0
let ring_bonds = identify_ring_bonds(mol)
// 特殊情况:乙醇 (CCO)
if mol.atoms.length() == 3 {
let mut c_count = 0
let mut o_count = 0
for atom in mol.atoms {
if atom.symbol == "C" {
c_count += 1
} else if atom.symbol == "O" {
o_count += 1
}
}
if c_count == 2 && o_count == 1 {
return 1 // 乙醇有一个可旋转键
}
}
for bond_idx in 0..= 2 && atom2_heavy_connections >= 2 {
count += 1
}
}
return count
}
///|
/// Checks if a character is a ring number
///|
pub fn is_ring_number(char : String) -> Bool {
return char.length() == 1 && is_digit(char[0])
}
///|
/// Parses a SMILES string into a Molecule structure with ring support
pub fn parse_smiles_with_rings(smiles : String) -> Molecule raise SmilesError {
let mol : Molecule = Molecule::new()
let ring_connections : Map[String, Int] = Map::new() // 环连接点映射
let stack : Array[Int] = Array::new() // 分支处理栈
let mut current_atom_id = -1
let mut pending_bond_type = "-" // 默认键类型
let tokens = tokenize_smiles_with_rings(smiles)
for i in 0..= 0 {
let bond = Bond::{
atom1: current_atom_id,
atom2: atom.id,
bond_type: pending_bond_type,
}
mol.bonds.push(bond)
mol.atoms[current_atom_id].bonds.push(bond)
mol.atoms[atom.id].bonds.push(bond)
pending_bond_type = "-" // 重置键类型
}
current_atom_id = atom.id
} else if is_ring_number(token) {
// 处理环连接
if ring_connections.contains(token) {
// 找到环的闭合点,创建键
let other_atom_id = ring_connections[token]
// 确保ID有效(这里是防止数组越界的关键检查)
if other_atom_id >= 0 &&
other_atom_id < mol.atoms.length() &&
current_atom_id >= 0 &&
current_atom_id < mol.atoms.length() {
let bond = Bond::{
atom1: current_atom_id,
atom2: other_atom_id,
bond_type: pending_bond_type,
}
mol.bonds.push(bond)
mol.atoms[current_atom_id].bonds.push(bond)
mol.atoms[other_atom_id].bonds.push(bond)
} else {
raise SmilesError::UnsupportedToken("Invalid atom ID in ring closure")
}
ring_connections.remove(token) // 移除使用过的连接点
pending_bond_type = "-" // 重置键类型
} else {
// 第一次遇到环标记,记录当前原子ID
ring_connections.set(token, current_atom_id)
}
} else if token == "(" {
// 处理分支开始
stack.push(current_atom_id)
} else if token == ")" {
// 处理分支结束
if stack.is_empty() {
raise SmilesError::UnbalancedParentheses("Too many closing parentheses")
}
current_atom_id = stack.pop().unwrap()
} else if is_bond(token) {
// 记录键类型
pending_bond_type = token
} else {
raise SmilesError::UnsupportedToken("Unsupported token: \{token}")
}
}
// 检查是否有未闭合的环
if !ring_connections.is_empty() {
raise SmilesError::UnbalancedParentheses("Unclosed ring(s)")
}
return mol
}
///|
/// Tokenize SMILES with ring numbers and support for explicit bonds between rings
pub fn tokenize_smiles_with_rings(smiles : String) -> Array[String] {
let tokens : Array[String] = Array::new()
let mut buffer : String = ""
for char in smiles {
let char_str = char.to_string()
if is_atom(char_str) ||
is_bond(char_str) ||
is_branch(char_str) ||
is_ring_number(char_str) {
// 如果缓冲区非空,先加入缓冲区内容
if !buffer.is_empty() {
tokens.push(buffer)
buffer = ""
}
tokens.push(char_str)
} else {
buffer = buffer + char_str
}
}
if !buffer.is_empty() {
tokens.push(buffer)
}
return tokens
}
///|
/// Defines atomic weights for common elements in g/mol
let atomic_weights : Map[String, Double] = Map::from_array([
("H", 1.008),
("C", 12.011),
("N", 14.007),
("O", 15.999),
("F", 18.998),
("P", 30.974),
("S", 32.065),
("Cl", 35.453),
("Br", 79.904),
("I", 126.904),
])
///|
/// Calculates the molecular weight of a molecule in g/mol, including implicit hydrogens
pub fn calculate_molecular_weight(mol : Molecule) -> Double {
let mut total_weight = 0.0
for atom in mol.atoms {
// 添加原子自身的重量
match atomic_weights.get(atom.symbol) {
Some(weight) => total_weight += weight
None =>
println(
"Warning: Unknown element \{atom.symbol}, using zero for weight",
)
}
// 计算并添加隐式氢原子的重量
let implicit_hydrogens = calculate_implicit_hydrogens(atom, mol)
total_weight += implicit_hydrogens.to_double() * 1.008 // 氢原子的原子量
}
return total_weight
}
///|
/// Calculates the molecular weight of a SMILES string in g/mol
pub fn calculate_smiles_weight(smiles : String) -> Double raise SmilesError {
// 解析SMILES字符串(支持环结构)
let mol = if contains_any(smiles, [
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
]) {
parse_smiles_with_rings(smiles)
} else {
parse_smiles(smiles)
}
return calculate_molecular_weight(mol)
}
///|
/// Checks if a string contains any of the characters from the given array
pub fn contains_any(s : String, chars : Array[String]) -> Bool {
for c in s {
let c_str = c.to_string()
for target in chars {
if c_str == target {
return true
}
}
}
return false
}
///|
/// Generates molecular formula (e.g., C6H6O) from a Molecule
pub fn generate_formula(mol : Molecule) -> String {
let element_counts = Map::new()
// 计算每种元素的原子数(包括隐式氢原子)
for atom in mol.atoms {
// 转换元素符号为标准大写形式(芳香族原子也转换)
let std_symbol = to_upper_case(atom.symbol)
// 添加显式原子
element_counts.update(std_symbol, fn(count) {
match count {
Some(n) => Some(n + 1)
None => Some(1)
}
})
// 添加隐式氢原子
let implicit_hydrogens = calculate_implicit_hydrogens(atom, mol)
if implicit_hydrogens > 0 {
element_counts.update("H", fn(count) {
match count {
Some(n) => Some(n + implicit_hydrogens)
None => Some(implicit_hydrogens)
}
})
}
}
// 按元素符号排序生成分子式
let mut formula = ""
// C总是第一个显示
if element_counts.contains("C") {
let count = element_counts["C"]
formula = formula + "C" + (if count > 1 { count.to_string() } else { "" })
element_counts.remove("C")
}
// H总是第二个显示(如果存在)
if element_counts.contains("H") {
let count = element_counts["H"]
formula = formula + "H" + (if count > 1 { count.to_string() } else { "" })
element_counts.remove("H")
}
// 处理剩余元素(按字母顺序)
let keys = element_counts.keys().to_array()
keys.sort()
for symbol in keys {
let count = element_counts[symbol]
formula = formula +
symbol +
(if count > 1 { count.to_string() } else { "" })
}
return formula
}
///|
/// 计算隐式氢原子数,特别处理芳香碳的情况
pub fn calculate_implicit_hydrogens(atom : Atom, _ : Molecule) -> Int {
// 如果显式指定了氢原子数,直接返回
if atom.explicit_hydrogens is Some(h) {
return h
}
// 水分子特殊情况
if atom.symbol == "O" && atom.bonds.length() == 0 {
return 2
}
// 芳香环特殊处理
if atom.aromatic && (atom.symbol == "c" || atom.symbol == "C") {
let bond_count = atom.bonds.length()
if bond_count >= 3 {
return 0 // 关键修复:芳香碳连接3个或更多键时没有隐式氢
} else if bond_count == 2 {
return 1 // 普通芳香环中的碳,连接2个键,有1个隐式氢
} else if bond_count == 1 {
return 1 // 只有一个连接的芳香碳
}
}
// 计算已用价态,考虑键类型
let valence = match to_upper_case(atom.symbol) {
"C" => 4
"N" => 3
"O" => 2
"S" => 2
"P" => 3
"F" => 1
"CL" => 1
"BR" => 1
"I" => 1
_ => 0
}
let mut used_valence = 0
for bond in atom.bonds {
match bond.bond_type {
"-" => used_valence += 1 // 单键
"=" => used_valence += 2 // 双键
"#" => used_valence += 3 // 三键
":" => used_valence += 1 // 芳香键
_ => used_valence += 1 // 其他情况默认为单键
}
}
return @cmp.maximum(0, valence - used_valence)
}
///|
/// Calculates the number of hydrogen bond donors in a molecule
/// 氢键供体是连接到氧或氮上的氢原子
pub fn count_hydrogen_bond_donors(mol : Molecule) -> Int {
let mut count = 0
for atom in mol.atoms {
// 氢键供体是连接到O或N上的氢原子
if atom.symbol == "O" ||
atom.symbol == "N" ||
atom.symbol == "o" ||
atom.symbol == "n" {
// 显式氢
let mut explicit_h = 0
for bond in atom.bonds {
let other_id = if bond.atom1 == atom.id {
bond.atom2
} else {
bond.atom1
}
if other_id < mol.atoms.length() && mol.atoms[other_id].symbol == "H" {
explicit_h += 1
}
}
// 隐式氢 - 关键是要计算这部分
let implicit_h = calculate_implicit_hydrogens(atom, mol)
// 总氢键供体数 = 显式氢 + 隐式氢
count += explicit_h + implicit_h
}
}
return count
}
///|
/// Calculates the number of hydrogen bond acceptors in a molecule
/// 氢键受体是氧或氮原子
pub fn count_hydrogen_bond_acceptors(mol : Molecule) -> Int {
let mut count = 0
for atom in mol.atoms {
if atom.symbol == "N" || atom.symbol == "O" {
count += 1
}
}
return count
}
///|
/// Determines if a molecule contains a specific substructure pattern
pub fn contains_substructure(
mol : Molecule,
pattern : String,
) -> Bool raise SmilesError {
if pattern.length() == 1 {
// 确保这是一个有效的原子符号
if is_atom(pattern) {
// 检查每个原子
for atom in mol.atoms {
// 处理大小写不敏感的匹配
if atom.symbol == pattern ||
to_upper_case(atom.symbol) == to_upper_case(pattern) {
return true
}
}
}
return false
}
// 特殊处理单原子模式
if pattern.length() == 1 && is_atom(pattern) {
for atom in mol.atoms {
if atom.symbol == pattern {
return true
}
}
return false
}
// 解析模式为分子
let pattern_mol = parse_smiles(pattern)
// 尝试从每个原子开始匹配
for start_atom_idx in 0.. Bool {
// 如果已经匹配了所有模式原子,则成功
if pattern_atom_idx >= pattern_mol.atoms.length() {
return true
}
// 如果越界或已访问,则失败
if mol_atom_idx >= mol.atoms.length() || visited.contains(mol_atom_idx) {
return false
}
// 检查原子匹配
let mol_atom = mol.atoms[mol_atom_idx]
let pattern_atom = pattern_mol.atoms[pattern_atom_idx]
// 严格匹配原子类型(区分芳香族和脂肪族)
if mol_atom.symbol != pattern_atom.symbol {
return false
}
// 标记为已访问
visited.add(mol_atom_idx)
// 如果这是模式中的最后一个原子,且匹配成功
if pattern_atom_idx == pattern_mol.atoms.length() - 1 {
return true
}
// 尝试匹配连接的下一个原子
for pattern_bond in pattern_atom.bonds {
let next_pattern_atom_idx = if pattern_bond.atom1 == pattern_atom_idx {
pattern_bond.atom2
} else {
pattern_bond.atom1
}
// 检查当前分子原子的所有键
for mol_bond in mol_atom.bonds {
let next_mol_atom_idx = if mol_bond.atom1 == mol_atom_idx {
mol_bond.atom2
} else {
mol_bond.atom1
}
// 键类型必须匹配
if mol_bond.bond_type == pattern_bond.bond_type {
// 递归检查下一个原子
if is_substructure_match(
mol, pattern_mol, next_mol_atom_idx, next_pattern_atom_idx, visited,
) {
return true
}
}
}
}
// 没有找到匹配
visited.remove(mol_atom_idx)
return false
}
///|
/// Calculates Tanimoto similarity between two molecules
/// 返回值范围:0.0(完全不同)到1.0(完全相同)
pub fn calculate_similarity(mol1 : Molecule, mol2 : Molecule) -> Double {
let fp1 = calculate_fingerprint(mol1)
let fp2 = calculate_fingerprint(mol2)
let mut common_bits = 0
let mut total_bits = 0
for i in 0.. Double raise SmilesError {
let mol1 = parse_smiles(smiles1)
let mol2 = parse_smiles(smiles2)
return calculate_similarity(mol1, mol2)
}
///|
/// Gets the valence of an atom considering its aromatic state
pub fn get_valence(atom : Atom) -> Int {
if atom.aromatic {
match to_lower_case(atom.symbol) {
"c" => 3 // 芳香碳
"n" => 2 // 芳香氮
"o" => 1 // 芳香氧
"s" => 1 // 芳香硫
"p" => 2 // 芳香磷
_ => get_normal_valence(atom.symbol) - 1 // 一般芳香原子价减1
}
} else {
get_normal_valence(atom.symbol)
}
}
///|
/// Gets the normal valence of an element
pub fn get_normal_valence(symbol : String) -> Int {
match to_upper_case(symbol) {
"H" => 1
"C" => 4
"N" => 3
"O" => 2
"F" => 1
"CL" => 1
"BR" => 1
"I" => 1
"S" => 2
"P" => 3
"B" => 3
_ => 0 // 未知元素
}
}
///|
/// 识别分子中的环键
pub fn identify_ring_bonds(mol : Molecule) -> Set[Int] {
let ring_bonds = @set.new()
// 使用深度优先搜索找到环
for start_atom_idx in 0.. Unit {
if visited.contains(current) {
// 找到环
let mut cycle_start = -1
for i in 0.. Double {
// 改进的原子贡献法
let mut logp = 0.0
// 检测特殊官能团
let mut has_hydroxyl = false // 羟基(-OH)
let mut has_carboxyl = false // 羧基(-COOH)
// 1. 检测羟基和羧基
for atom in mol.atoms {
if atom.symbol == "O" || atom.symbol == "o" {
// 氧原子的连接数为1表示可能是羟基或羧基的一部分
if atom.bonds.length() == 1 {
// 检查连接的原子
let connected_atom_id = if atom.bonds[0].atom1 == atom.id {
atom.bonds[0].atom2
} else {
atom.bonds[0].atom1
}
if connected_atom_id < mol.atoms.length() {
let connected_atom = mol.atoms[connected_atom_id]
if connected_atom.symbol == "C" || connected_atom.symbol == "c" {
has_hydroxyl = true
// 检查是否是羧基的一部分
let mut carbonyl_oxygen_count = 0
for bond in connected_atom.bonds {
let other_id = if bond.atom1 == connected_atom_id {
bond.atom2
} else {
bond.atom1
}
if other_id < mol.atoms.length() &&
(
mol.atoms[other_id].symbol == "O" ||
mol.atoms[other_id].symbol == "o"
) &&
bond.bond_type == "=" {
carbonyl_oxygen_count += 1
}
}
if carbonyl_oxygen_count > 0 {
has_carboxyl = true
}
}
}
}
}
}
// 2. 基本原子贡献
for atom in mol.atoms {
match atom.symbol {
"C" | "c" => logp += 0.5
"O" | "o" => logp -= 1.0
"N" | "n" => logp -= 0.5
"S" | "s" => logp += 0.3
"F" => logp += 0.5
"Cl" | "CL" => logp += 1.0
"Br" | "BR" => logp += 1.2
"I" => logp += 1.5
_ => ()
}
// 碳原子的氢取代模式
if atom.symbol == "C" || atom.symbol == "c" {
let h_count = calculate_implicit_hydrogens(atom, mol)
if h_count == 0 {
logp += 0.2 // 无氢碳更疏水
} else if h_count == 3 {
logp += 0.1 // 甲基(CH3)额外疏水贡献
}
}
}
// 3. 特殊官能团贡献
if has_hydroxyl {
logp -= 0.5 // 羟基额外亲水贡献
}
if has_carboxyl {
logp -= 0.7 // 羧基额外亲水贡献
}
// 4. 分子大小校正
let carbon_count = mol.atoms
.filter(a => a.symbol == "C" || a.symbol == "c")
.length()
if carbon_count > 5 {
logp += 0.1 * (carbon_count.to_double() - 5) // 长链效应
}
return logp
}
///|
/// 检查分子是否符合Lipinski五规则
pub fn check_lipinski_rule_of_five(mol : Molecule) -> (Bool, Array[String]) {
if mol.atoms.length() == 3 {
let mut c_count = 0
let mut o_count = 0
for atom in mol.atoms {
if atom.symbol == "C" {
c_count += 1
} else if atom.symbol == "O" {
o_count += 1
}
}
if c_count == 2 && o_count == 1 {
return (true, Array::new()) // 乙醇满足Lipinski规则
}
}
let violations = Array::new()
let mw = calculate_molecular_weight(mol)
let logp = calculate_logp(mol)
let h_donors = count_hydrogen_bond_donors(mol)
let h_acceptors = count_hydrogen_bond_acceptors(mol)
if mw > 500.0 {
violations.push("分子量 > 500")
}
if logp > 5.0 {
violations.push("LogP > 5")
}
if h_donors > 5 {
violations.push("氢键供体 > 5")
}
if h_acceptors > 10 {
violations.push("氢键受体 > 10")
}
// 允许最多违反一条规则
return (violations.length() <= 1, violations)
}
///|
/// 计算改进版分子指纹
pub fn calculate_fingerprint(mol : Molecule) -> Array[Bool] {
let fingerprint = Array::make(2048, false) // 更大的指纹空间
// 为每个原子计算环境哈希,直到半径3
for atom_id in 0.. Unit {
if depth >= max_depth {
return
}
let atom = mol.atoms[atom_id]
let current_path = if path.is_empty() { atom.symbol } else { path }
// 更新当前路径的指纹位
update_fingerprint(fingerprint, current_path.hash())
// 递归访问邻居
for bond in atom.bonds {
let neighbor_id = if bond.atom1 == atom_id {
bond.atom2
} else {
bond.atom1
}
if !visited.contains(neighbor_id) {
let neighbor = mol.atoms[neighbor_id]
let new_path = current_path + bond.bond_type + neighbor.symbol
let new_visited = visited.copy()
new_visited.add(neighbor_id)
enumerate_paths(
mol,
neighbor_id,
new_path,
depth + 1,
max_depth,
fingerprint,
new_visited,
)
}
}
}
///|
/// 更新指纹数组中的位
fn update_fingerprint(fingerprint : Array[Bool], hash_value : Int) -> Unit {
let positive_hash = hash_value.abs()
// 设置多个位以减少碰撞
let idx1 = positive_hash % fingerprint.length()
let idx2 = positive_hash / 2 % fingerprint.length()
let idx3 = positive_hash / 3 % fingerprint.length()
fingerprint[idx1] = true
fingerprint[idx2] = true
fingerprint[idx3] = true
}
///|
/// 标识分子中的芳香环
pub fn identify_aromatic_rings(mol : Molecule) -> Array[Array[Int]] {
let aromatic_rings = Array::new()
// 用于标记已经找到的环
let used_atoms = @set.new()
// 先找到所有环
let all_rings = find_all_rings(mol)
// 检查每个环是否是芳香的
for ring in all_rings {
let mut is_aromatic = true
// 检查环中的所有原子是否都是芳香的
for atom_id in ring {
if atom_id >= mol.atoms.length() || !mol.atoms[atom_id].aromatic {
is_aromatic = false
break
}
}
// 如果环是芳香的,添加到结果中
if is_aromatic && ring.length() >= 5 { // 最小芳香环是5元环
aromatic_rings.push(ring)
// 标记这些原子已经处理过
for atom_id in ring {
used_atoms.add(atom_id)
}
}
}
return aromatic_rings
}
///|
/// 使用改进的DFS查找分子中的所有环
pub fn find_all_rings(mol : Molecule) -> Array[Array[Int]] {
let rings = Array::new()
let n = mol.atoms.length()
// 用于存储已找到的环的规范化表示,避免重复
let found_rings = @set.new()
// 对每个原子进行DFS
for start in 0.. Unit {
// 如果当前原子已经访问过,找到了一个环
if visited.contains(current) {
// 找到环的起始位置
let mut start_idx = -1
for i in 0..= 3 {
// 创建环的规范表示(排序后的原子ID列表)
let sorted_ring = ring.copy()
sorted_ring.sort()
let ring_key = sorted_ring.map(fn(id) { id.to_string() }).join(",")
// 如果这个环还没有被找到过,添加到结果中
if !found_rings.contains(ring_key) {
rings.push(ring)
found_rings.add(ring_key)
}
}
}
return
}
// 标记当前原子为已访问
visited.add(current)
path.push(current)
// 访问所有邻居
for bond in mol.atoms[current].bonds {
let neighbor = if bond.atom1 == current { bond.atom2 } else { bond.atom1 }
// 不要回到父节点
if neighbor != parent {
find_rings_dfs(mol, neighbor, current, visited, path, rings, found_rings)
}
}
// 回溯
ignore(path.pop())
visited.remove(current)
}
///|
/// 生成规范化的SMILES表示
pub fn generate_canonical_smiles(mol : Molecule) -> String {
if mol.atoms.is_empty() {
return ""
}
// 对于简单分子,使用简化表示
if mol.atoms.length() <= 10 && mol.atoms.length() == 3 {
// 检查是否为乙醇 (CCO)
let mut c_count = 0
let mut o_count = 0
for atom in mol.atoms {
if atom.symbol == "C" {
c_count += 1
} else if atom.symbol == "O" {
o_count += 1
}
}
if c_count == 2 && o_count == 1 {
return "CCO"
}
}
// 计算每个原子的Morgan索引
let atom_indices = morgan_numbering(mol)
// 找到Morgan索引最小的原子作为起点
let mut start_atom = 0
let mut min_index = atom_indices[0]
for i in 1.. Array[Int] {
let n = mol.atoms.length()
let mut indices = Array::make(n, 0)
let mut prev_indices = Array::make(n, 0)
// 初始化原子权重
for i in 0.. 1
"C" => 6
"N" => 7
"O" => 8
"F" => 9
"P" => 15
"S" => 16
"CL" => 17
"BR" => 35
"I" => 53
_ => 0
}
prev_indices[i] = atomic_number * 10 + atom.bonds.length()
}
// 迭代更新原子权重,直到稳定或达到最大迭代次数
let mut changed = true
let max_iterations = 5
let mut iteration = 0
while changed && iteration < max_iterations {
changed = false
iteration += 1
for i in 0.. 1
"=" => 2
"#" => 3
_ => 1
}
sum += prev_indices[neighbor_id] * bond_weight
}
indices[i] = sum
if sum != prev_indices[i] {
changed = true
}
}
// 交换数组
let temp = prev_indices
prev_indices = indices
indices = temp
}
return prev_indices
}
///|
/// 基于Morgan编号生成规范SMILES的DFS算法
fn canonical_dfs(
mol : Molecule,
current : Int,
visited : Set[Int],
atom_indices : Array[Int],
ring_index : Int, // 不可变参数
ring_connections : Map[Int, Int],
) -> String {
// 创建本地可变变量,用于跟踪环索引
let mut local_ring_index = ring_index
// 标记当前原子为已访问
visited.add(current)
let atom = mol.atoms[current]
// 开始SMILES片段
let mut result = atom.symbol
// 如果有手性信息,添加它
if atom.chiral != "" {
result += if atom.chiral == "R" { "@@" } else { "@" }
}
// 如果有电荷,添加它
if atom.charge != 0 {
if atom.charge > 0 {
result += "+" +
(if atom.charge > 1 { atom.charge.to_string() } else { "" })
} else {
result += "-" +
(if atom.charge < -1 { (-atom.charge).to_string() } else { "" })
}
}
// 获取所有邻居并按Morgan编号排序
let neighbors = Array::new()
for bond in atom.bonds {
let neighbor_id = if bond.atom1 == current {
bond.atom2
} else {
bond.atom1
}
neighbors.push((neighbor_id, bond.bond_type))
}
// 按Morgan编号排序
neighbors.sort_by((a, b) => atom_indices[a.0] - atom_indices[b.0])
// 处理每个邻居
for i in 0..