1use crate::{element, AtomFlags, BondFlags, BondOrder, MolBuilder};
42
43#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct ValenceError {
46 pub atom: u32,
48 pub symbol: &'static str,
50 pub valence: i32,
52 pub kind: ValenceErrorKind,
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58#[non_exhaustive]
59pub enum ValenceErrorKind {
60 ExplicitValenceTooHigh,
62 AromaticValenceNotAllowed,
64 UnreasonableFormalCharge,
66}
67
68impl core::fmt::Display for ValenceError {
69 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
70 let what = match self.kind {
71 ValenceErrorKind::ExplicitValenceTooHigh => "显式价超出允许范围",
72 ValenceErrorKind::AromaticValenceNotAllowed => "芳香原子的显式价不在允许的价态中",
73 ValenceErrorKind::UnreasonableFormalCharge => "形式电荷不合理",
74 };
75 write!(
76 f,
77 "原子 #{}({}):{},显式价 = {}",
78 self.atom, self.symbol, what, self.valence
79 )
80 }
81}
82
83impl std::error::Error for ValenceError {}
84
85#[must_use]
87pub fn is_aromatic_atom(mol: &MolBuilder, idx: u32) -> bool {
88 if mol.atoms()[idx as usize]
89 .flags
90 .contains(AtomFlags::AROMATIC)
91 {
92 return true;
93 }
94 mol.neighbors(idx).any(|(_, bi)| {
95 let b = mol.bonds()[bi as usize];
96 b.flags.contains(BondFlags::AROMATIC) || b.order == BondOrder::Aromatic
97 })
98}
99
100#[must_use]
118pub fn valence_shift(z: u8, from: i8, to: i8) -> i32 {
119 let ovalens = valences_of(z);
120 if ovalens.len() == 1 && ovalens[0] == -1 {
121 return 0; }
123 let at = |q: i8| -> Option<i32> {
124 let eff = effective_atomic_num(z, q);
125 if eff == 0 {
126 return None;
127 }
128 let v = default_valence(eff);
129 if v < 0 {
130 None
131 } else {
132 Some(v)
133 }
134 };
135 match (at(from), at(to)) {
136 (Some(a), Some(b)) => b - a,
137 _ => 0,
138 }
139}
140
141fn effective_atomic_num(z: u8, charge: i8) -> u8 {
143 let max = (element::count() - 1) as i32;
144 (i32::from(z) - i32::from(charge)).clamp(0, max) as u8
145}
146
147fn can_be_hypervalent(z: u8, eff_z: u8) -> bool {
150 (eff_z > 16 && (z == 15 || z == 16)) || (eff_z > 34 && (z == 33 || z == 34))
151}
152
153fn valences_of(z: u8) -> &'static [i8] {
154 element::by_atomic_num(z).map_or(&[-1][..], |e| e.valences)
155}
156
157fn default_valence(z: u8) -> i32 {
159 valences_of(z).first().map_or(-1, |&v| i32::from(v))
160}
161
162fn last_valence(z: u8) -> i32 {
164 valences_of(z).last().map_or(-1, |&v| i32::from(v))
165}
166
167#[must_use]
172pub fn explicit_valence_nonstrict(mol: &MolBuilder, idx: u32) -> i32 {
173 explicit_valence_of(mol, idx, false).unwrap_or(0)
174}
175
176pub fn explicit_valence_of(mol: &MolBuilder, idx: u32, strict: bool) -> Result<i32, ValenceError> {
181 let hs = f32::from(mol.atoms()[idx as usize].num_explicit_hs);
182 explicit_valence_with(mol, idx, hs, strict)
183}
184
185fn explicit_valence_with(
190 mol: &MolBuilder,
191 idx: u32,
192 extra_hs: f32,
193 strict: bool,
194) -> Result<i32, ValenceError> {
195 let atom = mol.atoms()[idx as usize];
196 let z = atom.atomic_num;
197
198 let mut accum: f32 = mol
199 .neighbors(idx)
200 .map(|(_, bi)| mol.bonds()[bi as usize].valence_contribution_to(idx))
201 .sum();
202 accum += extra_hs;
203
204 let ovalens = valences_of(z);
205 let eff_z = if ovalens.len() > 1 || ovalens[0] != -1 {
207 effective_atomic_num(z, atom.formal_charge)
208 } else {
209 z
210 };
211 let dv = default_valence(eff_z);
212 let valens = valences_of(eff_z);
213
214 if dv >= 0 && accum > dv as f32 && is_aromatic_atom(mol, idx) {
216 let mut pval = dv;
217 for &val in valens {
218 let val = i32::from(val);
219 if val == -1 || val as f32 > accum {
220 break;
221 }
222 pval = val;
223 }
224 if accum - pval as f32 <= 1.5 {
227 accum = pval as f32;
228 }
229 }
230
231 accum += 0.1;
233 let res = accum.round() as i32;
234
235 if !strict {
237 return Ok(res);
238 }
239 let mut max_valence = last_valence(eff_z);
240 let mut offset = 0i32;
241 if can_be_hypervalent(z, eff_z) {
242 max_valence = last_valence(z);
243 offset -= i32::from(atom.formal_charge);
244 }
245 if z == 1 && atom.formal_charge == -1 {
247 max_valence = 2;
248 }
249 if max_valence >= 0 && last_valence(z) >= 0 && (res + offset) > max_valence {
251 return Err(ValenceError {
252 atom: idx,
253 symbol: element::by_atomic_num(z).map_or("?", |e| e.symbol),
254 valence: res,
255 kind: ValenceErrorKind::ExplicitValenceTooHigh,
256 });
257 }
258
259 Ok(res)
260}
261
262#[must_use]
264pub fn implicit_hs_nonstrict(mol: &MolBuilder, idx: u32, ev: i32) -> u8 {
265 implicit_hs_of(mol, idx, ev, false).unwrap_or(0)
266}
267
268#[must_use]
272pub fn total_valence_nonstrict(mol: &MolBuilder, idx: u32) -> i32 {
273 let ev = explicit_valence_nonstrict(mol, idx);
274 ev + i32::from(implicit_hs_nonstrict(mol, idx, ev))
275}
276
277pub fn implicit_hs_of(
282 mol: &MolBuilder,
283 idx: u32,
284 ev: i32,
285 strict: bool,
286) -> Result<u8, ValenceError> {
287 if mol.atoms()[idx as usize]
288 .flags
289 .contains(AtomFlags::NO_IMPLICIT)
290 {
291 return Ok(0);
292 }
293 implicit_hs_inner(mol, idx, ev, strict)
294}
295
296fn implicit_hs_inner(
301 mol: &MolBuilder,
302 idx: u32,
303 ev: i32,
304 strict: bool,
305) -> Result<u8, ValenceError> {
306 let atom = mol.atoms()[idx as usize];
307 let z = atom.atomic_num;
308 if z == 0 {
309 return Ok(0); }
311
312 let n_radicals = i32::from(atom.num_radical_electrons);
317
318 if ev == 0 && n_radicals == 0 && z == 1 {
320 return match atom.formal_charge {
321 1 | -1 => Ok(0),
322 0 => Ok(1),
323 _ if strict => Err(ValenceError {
324 atom: idx,
325 symbol: "H",
326 valence: ev,
327 kind: ValenceErrorKind::UnreasonableFormalCharge,
328 }),
329 _ => Ok(0),
330 };
331 }
332
333 let mut explicit_plus_rad = ev + n_radicals;
334
335 let ovalens = valences_of(z);
336 let mut eff_z = if ovalens.len() > 1 || ovalens[0] != -1 {
337 effective_atomic_num(z, atom.formal_charge)
338 } else {
339 z
340 };
341 if eff_z == 0 {
342 return Ok(0);
343 }
344
345 let dv = default_valence(eff_z);
347 if dv == -1 {
348 return Ok(0); }
350 if can_be_hypervalent(z, eff_z) {
351 eff_z = z;
352 explicit_plus_rad -= i32::from(atom.formal_charge);
353 }
354 let valens = valences_of(eff_z);
355
356 let res: i32 = if is_aromatic_atom(mol, idx) {
357 if explicit_plus_rad <= dv {
358 dv - explicit_plus_rad
359 } else {
360 let satisfied = valens
362 .iter()
363 .map(|&v| i32::from(v))
364 .take_while(|&v| v > 0)
365 .any(|v| explicit_plus_rad == v);
366 if !satisfied && strict {
367 return Err(ValenceError {
368 atom: idx,
369 symbol: element::by_atomic_num(z).map_or("?", |e| e.symbol),
370 valence: ev,
371 kind: ValenceErrorKind::AromaticValenceNotAllowed,
372 });
373 }
374 0
375 }
376 } else {
377 let found = valens
379 .iter()
380 .map(|&v| i32::from(v))
381 .take_while(|&v| v >= 0)
382 .find(|&v| explicit_plus_rad <= v);
383 match found {
384 Some(v) => v - explicit_plus_rad,
385 None => {
386 if strict && last_valence(eff_z) != -1 && last_valence(z) > 0 {
387 return Err(ValenceError {
388 atom: idx,
389 symbol: element::by_atomic_num(z).map_or("?", |e| e.symbol),
390 valence: ev,
391 kind: ValenceErrorKind::ExplicitValenceTooHigh,
392 });
393 }
394 0
395 }
396 }
397 };
398
399 Ok(u8::try_from(res.max(0)).unwrap_or(0))
400}
401
402#[must_use]
424pub fn implicit_hs_for_bare_form(mol: &MolBuilder, idx: u32) -> Option<u8> {
425 let atom = mol.atoms()[idx as usize];
426 if atom.formal_charge != 0 || atom.num_radical_electrons != 0 || atom.isotope != 0 {
427 return None;
428 }
429 let ev = explicit_valence_with(mol, idx, 0.0, false).ok()?;
430 implicit_hs_inner(mol, idx, ev, false).ok()
431}