1#![allow(clippy::approx_constant)]
22
23use chematic_core::{AtomIdx, BondOrder, Element, Molecule};
24use chematic_perception::ring_sizes_for_atom;
25
26#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct NumericTypeError(pub String);
30
31impl std::fmt::Display for NumericTypeError {
32 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 write!(f, "MMFF94 numeric type error: {}", self.0)
34 }
35}
36
37static MMFF94_PBCI: &[(u8, f64, f64)] = &[
41 (1, 0.000, 0.000), (2, -0.135, 0.000), (3, -0.095, 0.000), (4, -0.200, 0.000), (5, -0.023, 0.000), (6, -0.243, 0.000), (7, -0.687, 0.000), (8, -0.253, 0.000), (9, -0.306, 0.000), (10, -0.244, 0.000), (11, -0.317, 0.000), (12, -0.304, 0.000), (13, -0.238, 0.000), (14, -0.208, 0.000), (15, -0.236, 0.000), (16, -0.475, 0.000), (17, -0.191, 0.000), (18, -0.118, 0.000), (19, 0.094, 0.000), (20, -0.019, 0.000), (21, 0.157, 0.000), (22, -0.095, 0.000), (23, 0.193, 0.000), (24, 0.257, 0.000), (25, 0.012, 0.000), (26, -0.142, 0.000), (27, 0.094, 0.000), (28, 0.058, 0.000), (29, 0.207, 0.000), (30, -0.166, 0.000), (31, 0.161, 0.000), (32, -0.732, 0.500), (33, 0.257, 0.000), (34, -0.491, 0.000), (35, -0.456, 0.500), (36, -0.031, 0.000), (37, -0.127, 0.000), (38, -0.437, 0.000), (39, -0.104, 0.000), (40, -0.264, 0.000), (41, 0.052, 0.000), (42, -0.757, 0.000), (43, -0.326, 0.000), (44, -0.237, 0.000), (45, -0.260, 0.000), (46, -0.429, 0.000), (47, -0.418, 0.000), (48, -0.525, 0.000), (49, -0.283, 0.000), (50, 0.284, 0.000), (51, -1.046, 0.000), (52, -0.546, 0.000), (53, -0.048, 0.000), (54, -0.424, 0.000), (55, -0.476, 0.000), (56, -0.438, 0.000), (57, -0.105, 0.000), (58, -0.488, 0.000), (59, -0.337, 0.000), (60, -0.635, 0.000), (61, -0.265, 0.000), (62, -0.125, 0.250), (63, -0.180, 0.000), (64, -0.181, 0.000), (65, -0.475, 0.000), (66, -0.467, 0.000), (67, -0.099, 0.000), (68, -0.135, 0.000), (69, -0.099, 0.000), (70, -0.269, 0.000), (71, -0.071, 0.000), (72, -0.580, 0.500), (73, -0.200, 0.000), (74, -0.301, 0.000), (75, -0.255, 0.000), (76, -0.568, 0.250), (77, -0.282, 0.000), (78, -0.168, 0.000), (79, -0.471, 0.000), (80, -0.144, 0.000), (81, -0.514, 0.000), (82, -0.099, 0.000), (83, 0.000, 0.000), (84, 0.000, 0.000), (85, 0.000, 0.000), (86, 0.000, 0.000), (87, 2.000, 0.000), (88, 3.000, 0.000), (89, -1.000, 0.000), (90, -1.000, 0.000), (91, -1.000, 0.000), (92, 1.000, 0.000), (93, 1.000, 0.000), (94, 1.000, 0.000), (95, 2.000, 0.000), (96, 2.000, 0.000), (97, 1.000, 0.000), (98, 2.000, 0.000), (99, 2.000, 0.000), ];
141
142static MMFF94_CHG: &[(u8, u8, u8, f64)] = &[
146 (0, 1, 1, 0.0),
147 (0, 1, 2, -0.1382),
148 (0, 1, 3, -0.061),
149 (0, 1, 4, -0.2),
150 (0, 1, 5, 0.0),
151 (0, 1, 6, -0.28),
152 (0, 1, 8, -0.27),
153 (0, 1, 9, -0.246),
154 (0, 1, 10, -0.3001),
155 (0, 1, 11, -0.34),
156 (0, 1, 12, -0.29),
157 (0, 1, 13, -0.23),
158 (0, 1, 14, -0.19),
159 (0, 1, 15, -0.23),
160 (0, 1, 17, -0.1935),
161 (0, 1, 18, -0.1052),
162 (0, 1, 19, 0.0805),
163 (0, 1, 20, 0.0),
164 (0, 1, 22, -0.095),
165 (0, 1, 25, 0.0),
166 (0, 1, 26, -0.1669),
167 (0, 1, 34, -0.503),
168 (0, 1, 35, -0.4274),
169 (0, 1, 37, -0.1435),
170 (0, 1, 39, -0.2556),
171 (0, 1, 40, -0.3691),
172 (0, 1, 41, 0.106),
173 (0, 1, 43, -0.3557),
174 (0, 1, 45, -0.2402),
175 (0, 1, 46, -0.3332),
176 (0, 1, 54, -0.3461),
177 (0, 1, 55, -0.4895),
178 (0, 1, 56, -0.3276),
179 (0, 1, 57, -0.105),
180 (0, 1, 58, -0.488),
181 (0, 1, 61, -0.2657),
182 (0, 1, 62, -0.2),
183 (0, 1, 63, -0.18),
184 (0, 1, 64, -0.181),
185 (0, 1, 67, -0.099),
186 (0, 1, 68, -0.256),
187 (0, 1, 72, -0.55),
188 (0, 1, 73, -0.0877),
189 (0, 1, 75, -0.255),
190 (0, 1, 78, -0.168),
191 (0, 1, 80, -0.144),
192 (0, 1, 81, -0.514),
193 (0, 2, 2, 0.0),
194 (1, 2, 2, 0.0),
195 (1, 2, 3, -0.0144),
196 (0, 2, 4, -0.065),
197 (1, 2, 4, -0.065),
198 (0, 2, 5, 0.15),
199 (0, 2, 6, -0.0767),
200 (1, 2, 9, -0.171),
201 (0, 2, 10, -0.109),
202 (0, 2, 11, -0.1495),
203 (0, 2, 12, -0.14),
204 (0, 2, 13, -0.11),
205 (0, 2, 14, -0.09),
206 (0, 2, 15, -0.101),
207 (0, 2, 17, -0.056),
208 (0, 2, 18, 0.017),
209 (0, 2, 19, 0.229),
210 (0, 2, 20, 0.116),
211 (0, 2, 22, 0.04),
212 (0, 2, 25, 0.147),
213 (0, 2, 30, -0.031),
214 (0, 2, 34, -0.356),
215 (0, 2, 35, -0.35),
216 (1, 2, 37, 0.0284),
217 (1, 2, 39, 0.031),
218 (0, 2, 40, -0.1),
219 (0, 2, 41, 0.25),
220 (0, 2, 43, -0.191),
221 (0, 2, 45, -0.2044),
222 (0, 2, 46, -0.294),
223 (0, 2, 55, -0.341),
224 (0, 2, 56, -0.303),
225 (0, 2, 62, -0.05),
226 (1, 2, 63, -0.045),
227 (1, 2, 64, -0.046),
228 (1, 2, 67, 0.036),
229 (0, 2, 72, -0.45),
230 (1, 2, 81, -0.379),
231 (1, 3, 3, 0.0),
232 (1, 3, 4, -0.105),
233 (0, 3, 5, 0.06),
234 (0, 3, 6, -0.15),
235 (0, 3, 7, -0.57),
236 (0, 3, 9, -0.45),
237 (1, 3, 9, -0.211),
238 (0, 3, 10, -0.06),
239 (0, 3, 11, -0.222),
240 (0, 3, 12, -0.209),
241 (0, 3, 15, -0.141),
242 (0, 3, 16, -0.38),
243 (0, 3, 17, -0.096),
244 (0, 3, 18, -0.023),
245 (0, 3, 20, 0.053),
246 (0, 3, 22, 0.0),
247 (0, 3, 25, 0.107),
248 (1, 3, 30, -0.071),
249 (0, 3, 35, -0.361),
250 (1, 3, 37, 0.0862),
251 (1, 3, 39, -0.009),
252 (0, 3, 40, -0.05),
253 (0, 3, 41, 0.147),
254 (0, 3, 43, -0.2363),
255 (0, 3, 45, -0.165),
256 (0, 3, 48, -0.43),
257 (0, 3, 51, -0.95),
258 (0, 3, 53, -0.0134),
259 (0, 3, 54, -0.4),
260 (1, 3, 54, -0.329),
261 (0, 3, 55, -0.381),
262 (0, 3, 56, -0.343),
263 (1, 3, 57, -0.01),
264 (1, 3, 58, -0.393),
265 (0, 3, 62, -0.03),
266 (1, 3, 63, -0.085),
267 (1, 3, 64, -0.086),
268 (0, 3, 67, -0.004),
269 (0, 3, 74, -0.319),
270 (0, 3, 75, -0.2474),
271 (1, 3, 78, -0.073),
272 (1, 3, 80, -0.049),
273 (0, 4, 5, 0.177),
274 (0, 4, 6, -0.043),
275 (0, 4, 7, -0.487),
276 (0, 4, 9, -0.3),
277 (1, 4, 9, -0.106),
278 (0, 4, 10, -0.044),
279 (0, 4, 15, -0.036),
280 (0, 4, 20, 0.181),
281 (0, 4, 22, 0.105),
282 (0, 4, 30, 0.034),
283 (1, 4, 37, 0.073),
284 (0, 4, 40, -0.064),
285 (0, 4, 42, -0.5571),
286 (0, 4, 43, -0.126),
287 (1, 4, 63, 0.02),
288 (1, 4, 64, 0.019),
289 (0, 5, 19, 0.2),
290 (0, 5, 20, 0.0),
291 (0, 5, 22, -0.1),
292 (0, 5, 30, -0.15),
293 (0, 5, 37, -0.15),
294 (0, 5, 41, 0.2203),
295 (0, 5, 57, -0.15),
296 (0, 5, 63, -0.15),
297 (0, 5, 64, -0.15),
298 (0, 5, 78, -0.15),
299 (0, 5, 80, -0.15),
300 (0, 6, 6, 0.0),
301 (0, 6, 8, -0.1),
302 (0, 6, 9, -0.063),
303 (0, 6, 10, 0.0355),
304 (0, 6, 15, 0.007),
305 (0, 6, 17, 0.052),
306 (0, 6, 18, 0.1837),
307 (0, 6, 19, 0.2974),
308 (0, 6, 20, 0.2579),
309 (0, 6, 21, 0.4),
310 (0, 6, 22, 0.148),
311 (0, 6, 24, 0.5),
312 (0, 6, 25, 0.2712),
313 (0, 6, 26, 0.101),
314 (0, 6, 29, 0.45),
315 (0, 6, 30, 0.077),
316 (0, 6, 33, 0.5),
317 (0, 6, 37, 0.0825),
318 (0, 6, 39, 0.139),
319 (0, 6, 40, -0.021),
320 (0, 6, 41, 0.295),
321 (0, 6, 43, -0.083),
322 (0, 6, 45, -0.009),
323 (0, 6, 54, -0.181),
324 (0, 6, 55, -0.233),
325 (0, 6, 57, 0.138),
326 (0, 6, 58, -0.245),
327 (0, 6, 63, 0.063),
328 (0, 6, 64, 0.062),
329 (0, 7, 17, 0.5),
330 (0, 7, 46, 0.1618),
331 (0, 7, 74, 0.5),
332 (0, 8, 8, 0.0),
333 (0, 8, 9, -0.053),
334 (0, 8, 10, 0.009),
335 (0, 8, 12, -0.051),
336 (0, 8, 15, 0.017),
337 (0, 8, 17, 0.062),
338 (0, 8, 19, 0.347),
339 (0, 8, 20, 0.2096),
340 (0, 8, 22, 0.158),
341 (0, 8, 23, 0.36),
342 (0, 8, 25, 0.2679),
343 (0, 8, 26, 0.111),
344 (0, 8, 34, -0.238),
345 (0, 8, 39, 0.149),
346 (0, 8, 40, -0.011),
347 (0, 8, 43, -0.073),
348 (0, 8, 45, -0.007),
349 (0, 8, 46, -0.176),
350 (0, 8, 55, -0.223),
351 (0, 8, 56, -0.185),
352 (0, 9, 9, 0.0),
353 (0, 9, 10, 0.062),
354 (0, 9, 12, 0.002),
355 (0, 9, 15, 0.07),
356 (0, 9, 18, 0.188),
357 (0, 9, 19, 0.4),
358 (0, 9, 20, 0.287),
359 (0, 9, 25, 0.318),
360 (0, 9, 27, 0.4),
361 (0, 9, 34, -0.185),
362 (0, 9, 35, -0.15),
363 (1, 9, 37, 0.179),
364 (1, 9, 39, 0.202),
365 (0, 9, 40, 0.042),
366 (0, 9, 41, 0.358),
367 (0, 9, 45, 0.046),
368 (0, 9, 53, 0.3179),
369 (0, 9, 54, -0.118),
370 (0, 9, 55, -0.17),
371 (0, 9, 56, -0.132),
372 (1, 9, 57, 0.201),
373 (0, 9, 62, 0.181),
374 (1, 9, 63, 0.126),
375 (1, 9, 64, 0.125),
376 (0, 9, 67, 0.207),
377 (1, 9, 78, 0.138),
378 (1, 9, 81, -0.208),
379 (0, 10, 10, 0.0),
380 (0, 10, 13, 0.006),
381 (0, 10, 14, 0.036),
382 (0, 10, 15, 0.008),
383 (0, 10, 17, 0.053),
384 (0, 10, 20, 0.225),
385 (0, 10, 22, 0.149),
386 (0, 10, 25, 0.256),
387 (0, 10, 26, 0.102),
388 (0, 10, 28, 0.37),
389 (0, 10, 34, -0.247),
390 (0, 10, 35, -0.212),
391 (0, 10, 37, 0.117),
392 (0, 10, 39, 0.14),
393 (0, 10, 40, -0.02),
394 (0, 10, 41, 0.296),
395 (0, 10, 45, -0.016),
396 (0, 10, 63, 0.064),
397 (0, 10, 64, 0.063),
398 (0, 11, 20, 0.298),
399 (0, 11, 22, 0.2317),
400 (0, 11, 25, 0.329),
401 (0, 11, 26, 0.175),
402 (0, 11, 37, 0.19),
403 (0, 11, 40, 0.053),
404 (0, 12, 15, 0.068),
405 (0, 12, 18, 0.186),
406 (0, 12, 19, 0.3701),
407 (0, 12, 20, 0.29),
408 (0, 12, 22, 0.2273),
409 (0, 12, 25, 0.316),
410 (0, 12, 26, 0.2112),
411 (0, 12, 37, 0.177),
412 (0, 12, 40, 0.04),
413 (0, 12, 57, 0.199),
414 (0, 12, 63, 0.124),
415 (0, 12, 64, 0.123),
416 (0, 13, 20, 0.219),
417 (0, 13, 22, 0.143),
418 (0, 13, 37, 0.111),
419 (0, 13, 64, 0.057),
420 (0, 14, 20, 0.189),
421 (0, 14, 37, 0.081),
422 (0, 15, 15, 0.0),
423 (0, 15, 18, 0.118),
424 (0, 15, 19, 0.33),
425 (0, 15, 20, 0.217),
426 (0, 15, 22, 0.141),
427 (0, 15, 25, 0.248),
428 (0, 15, 26, 0.094),
429 (0, 15, 30, 0.07),
430 (0, 15, 37, 0.1015),
431 (0, 15, 40, -0.028),
432 (0, 15, 43, -0.09),
433 (0, 15, 57, 0.131),
434 (0, 15, 63, 0.056),
435 (0, 15, 64, 0.055),
436 (0, 15, 71, 0.18),
437 (0, 16, 16, 0.0),
438 (0, 17, 17, 0.0),
439 (0, 17, 20, 0.172),
440 (0, 17, 22, 0.096),
441 (0, 17, 37, 0.064),
442 (0, 17, 43, -0.135),
443 (0, 18, 18, 0.0),
444 (0, 18, 20, 0.099),
445 (0, 18, 22, 0.023),
446 (0, 18, 32, -0.65),
447 (0, 18, 37, -0.009),
448 (0, 18, 39, 0.014),
449 (0, 18, 43, -0.138),
450 (0, 18, 48, -0.5895),
451 (0, 18, 55, -0.358),
452 (0, 18, 58, -0.37),
453 (0, 18, 62, 0.2099),
454 (0, 18, 63, -0.062),
455 (0, 18, 64, -0.063),
456 (0, 18, 80, -0.026),
457 (0, 19, 19, 0.0),
458 (0, 19, 20, -0.113),
459 (0, 19, 37, -0.221),
460 (0, 19, 40, -0.358),
461 (0, 19, 63, -0.274),
462 (0, 19, 75, -0.349),
463 (0, 20, 20, 0.0),
464 (0, 20, 22, -0.076),
465 (0, 20, 25, 0.031),
466 (0, 20, 26, -0.123),
467 (0, 20, 30, -0.138),
468 (0, 20, 34, -0.472),
469 (0, 20, 37, -0.108),
470 (0, 20, 40, -0.245),
471 (0, 20, 41, 0.071),
472 (0, 20, 43, -0.307),
473 (0, 20, 45, -0.241),
474 (0, 22, 22, 0.0),
475 (0, 22, 30, -0.071),
476 (0, 22, 34, -0.396),
477 (0, 22, 37, -0.032),
478 (0, 22, 40, -0.169),
479 (0, 22, 41, 0.147),
480 (0, 22, 43, -0.231),
481 (0, 22, 45, -0.165),
482 (0, 23, 39, -0.27),
483 (0, 23, 62, -0.4),
484 (0, 23, 67, -0.292),
485 (0, 23, 68, -0.36),
486 (0, 25, 25, 0.0),
487 (0, 25, 32, -0.7),
488 (0, 25, 37, -0.139),
489 (0, 25, 39, -0.116),
490 (0, 25, 40, -0.276),
491 (0, 25, 43, -0.338),
492 (0, 25, 57, -0.117),
493 (0, 25, 63, -0.192),
494 (0, 25, 71, -0.0362),
495 (0, 25, 72, -0.6773),
496 (0, 26, 26, 0.0),
497 (0, 26, 34, -0.349),
498 (0, 26, 37, 0.015),
499 (0, 26, 40, -0.122),
500 (0, 26, 71, 0.096),
501 (0, 28, 40, -0.4),
502 (0, 28, 43, -0.42),
503 (0, 28, 48, -0.4),
504 (0, 30, 30, 0.0),
505 (0, 30, 40, -0.098),
506 (1, 30, 67, 0.067),
507 (0, 31, 70, -0.43),
508 (0, 32, 41, 0.65),
509 (0, 32, 45, 0.52),
510 (0, 32, 67, 0.633),
511 (0, 32, 68, 0.75),
512 (0, 32, 69, 0.75),
513 (0, 32, 73, 0.35),
514 (0, 32, 77, 0.45),
515 (0, 32, 82, 0.633),
516 (0, 34, 36, 0.45),
517 (0, 34, 37, 0.364),
518 (0, 34, 43, 0.165),
519 (0, 35, 37, 0.329),
520 (0, 35, 63, 0.276),
521 (0, 36, 54, -0.4),
522 (0, 36, 55, -0.45),
523 (0, 36, 56, -0.45),
524 (0, 36, 58, -0.457),
525 (4, 36, 58, -0.45),
526 (0, 36, 81, -0.45),
527 (0, 37, 37, 0.0),
528 (1, 37, 37, 0.0),
529 (0, 37, 38, -0.31),
530 (0, 37, 39, 0.023),
531 (1, 37, 39, 0.023),
532 (0, 37, 40, -0.1),
533 (0, 37, 41, 0.179),
534 (0, 37, 43, -0.199),
535 (0, 37, 45, -0.133),
536 (0, 37, 46, -0.302),
537 (0, 37, 55, -0.349),
538 (0, 37, 56, -0.311),
539 (1, 37, 57, 0.022),
540 (0, 37, 58, -0.361),
541 (1, 37, 58, -0.361),
542 (4, 37, 58, -0.35),
543 (0, 37, 61, -0.138),
544 (0, 37, 62, 0.002),
545 (0, 37, 63, 0.0),
546 (1, 37, 63, -0.053),
547 (0, 37, 64, 0.0),
548 (1, 37, 64, -0.054),
549 (1, 37, 67, 0.028),
550 (0, 37, 69, -0.0895),
551 (0, 37, 78, -0.041),
552 (0, 37, 81, -0.387),
553 (1, 37, 81, -0.387),
554 (0, 38, 38, 0.0),
555 (0, 38, 63, 0.257),
556 (0, 38, 64, 0.256),
557 (0, 38, 69, 0.338),
558 (0, 38, 78, 0.269),
559 (1, 39, 39, 0.0),
560 (0, 39, 40, -0.16),
561 (0, 39, 45, -0.156),
562 (0, 39, 63, -0.1516),
563 (1, 39, 63, -0.076),
564 (0, 39, 64, -0.077),
565 (1, 39, 64, -0.077),
566 (0, 39, 65, -0.418),
567 (0, 39, 78, -0.064),
568 (0, 40, 40, 0.0),
569 (0, 40, 45, 0.004),
570 (0, 40, 46, -0.165),
571 (0, 40, 54, -0.16),
572 (0, 40, 63, 0.084),
573 (0, 40, 64, 0.083),
574 (0, 40, 78, 0.096),
575 (0, 41, 41, 0.0),
576 (0, 41, 55, -0.528),
577 (0, 41, 62, -0.177),
578 (0, 41, 72, -0.5),
579 (0, 41, 80, -0.196),
580 (0, 42, 61, 0.492),
581 (0, 43, 43, 0.0),
582 (0, 43, 45, 0.066),
583 (0, 43, 64, 0.145),
584 (0, 44, 63, 0.04),
585 (0, 44, 65, -0.2207),
586 (0, 44, 78, 0.069),
587 (0, 44, 80, 0.093),
588 (0, 45, 63, 0.08),
589 (0, 45, 64, 0.079),
590 (0, 45, 78, 0.092),
591 (0, 47, 53, 0.37),
592 (0, 49, 50, 0.5673),
593 (0, 51, 52, 0.5),
594 (0, 55, 57, 0.3544),
595 (0, 55, 62, 0.351),
596 (0, 55, 64, 0.295),
597 (0, 55, 80, 0.332),
598 (0, 56, 57, 0.4),
599 (0, 56, 63, 0.258),
600 (0, 56, 80, 0.27),
601 (4, 57, 58, -0.4),
602 (1, 57, 63, -0.075),
603 (1, 57, 64, -0.076),
604 (0, 58, 63, 0.308),
605 (0, 58, 64, 0.307),
606 (0, 59, 63, 0.14),
607 (0, 59, 65, -0.1209),
608 (0, 59, 78, 0.169),
609 (0, 59, 80, 0.193),
610 (0, 59, 82, 0.238),
611 (0, 60, 61, 0.37),
612 (0, 62, 63, -0.055),
613 (0, 62, 64, -0.056),
614 (0, 63, 63, 0.0),
615 (1, 63, 63, 0.0),
616 (0, 63, 64, 0.0),
617 (0, 63, 66, -0.3381),
618 (0, 63, 72, -0.4),
619 (0, 63, 78, 0.012),
620 (0, 63, 81, -0.334),
621 (0, 64, 64, 0.0),
622 (0, 64, 65, -0.2888),
623 (0, 64, 66, -0.2272),
624 (0, 64, 78, 0.013),
625 (0, 64, 81, -0.333),
626 (0, 64, 82, 0.082),
627 (0, 65, 66, 0.0),
628 (0, 65, 78, 0.307),
629 (0, 65, 81, -0.039),
630 (0, 65, 82, 0.376),
631 (0, 66, 66, 0.0),
632 (0, 66, 78, 0.299),
633 (0, 66, 81, -0.047),
634 (0, 71, 75, -0.0958),
635 (0, 72, 73, 0.45),
636 (0, 76, 76, 0.0),
637 (0, 76, 78, 0.4),
638 (0, 78, 78, 0.0),
639 (1, 78, 78, 0.0),
640 (0, 78, 79, -0.303),
641 (0, 78, 81, -0.35),
642 (0, 79, 81, -0.043),
643 (0, 80, 81, -0.4),
644];
645
646pub fn pbci_for(atom_type: u8) -> (f64, f64) {
650 for &(t, pbci, fcadj) in MMFF94_PBCI {
651 if t == atom_type {
652 return (pbci, fcadj);
653 }
654 }
655 (0.0, 0.0)
656}
657
658fn lookup_chg_contribution(bond_type: u8, type_i: u8, type_j: u8) -> Option<f64> {
665 for &(bt, a, b, bci) in MMFF94_CHG {
667 if bt == bond_type && a == type_j && b == type_i {
668 return Some(bci); }
670 }
671 for &(bt, a, b, bci) in MMFF94_CHG {
673 if bt == bond_type && a == type_i && b == type_j {
674 return Some(-bci); }
676 }
677 None
678}
679
680fn bond_type_for(order: BondOrder) -> u8 {
681 match order {
682 BondOrder::Single | BondOrder::Up | BondOrder::Down => 0,
683 BondOrder::Double => 1,
684 BondOrder::Triple => 2,
685 BondOrder::Aromatic => 4,
686 _ => 0,
687 }
688}
689
690pub fn assign_mmff94_numeric_types(mol: &Molecule) -> Result<Vec<u8>, NumericTypeError> {
697 let n = mol.atom_count();
698 let mut types = vec![0u8; n];
699
700 for (i, ty) in types.iter_mut().enumerate().take(n) {
701 let idx = AtomIdx(i as u32);
702 let atom = mol.atom(idx);
703 let t = match atom.element {
704 Element::C => assign_c_type(mol, idx)?,
705 Element::N => assign_n_type(mol, idx)?,
706 Element::O => assign_o_type(mol, idx)?,
707 Element::S => assign_s_type(mol, idx)?,
708 Element::P => assign_p_type(mol, idx)?,
709 Element::SI => 19,
710 Element::F => 11,
711 Element::CL => 12,
712 Element::BR => 13,
713 Element::I => 14,
714 Element::H => assign_h_type(mol, idx)?,
715 _ => return Err(NumericTypeError(format!(
716 "unsupported element {:?} at atom {i}", atom.element
717 ))),
718 };
719 *ty = t;
720 }
721 Ok(types)
722}
723
724struct BondInfo {
727 neighbor: AtomIdx,
728 order: BondOrder,
729}
730
731fn bonds_of(mol: &Molecule, idx: AtomIdx) -> Vec<BondInfo> {
732 mol.bonds()
733 .filter_map(|(_, b)| {
734 if b.atom1 == idx {
735 Some(BondInfo { neighbor: b.atom2, order: b.order })
736 } else if b.atom2 == idx {
737 Some(BondInfo { neighbor: b.atom1, order: b.order })
738 } else {
739 None
740 }
741 })
742 .collect()
743}
744
745fn count_bond_order(mol: &Molecule, idx: AtomIdx, order: BondOrder) -> usize {
746 bonds_of(mol, idx).iter().filter(|b| b.order == order).count()
747}
748
749fn neighbor_elements(mol: &Molecule, idx: AtomIdx) -> Vec<Element> {
750 bonds_of(mol, idx).iter().map(|b| mol.atom(b.neighbor).element).collect()
751}
752
753fn is_bonded_to(mol: &Molecule, idx: AtomIdx, elem: Element, order: BondOrder) -> bool {
754 bonds_of(mol, idx).iter().any(|b| mol.atom(b.neighbor).element == elem && b.order == order)
755}
756
757fn is_neighbor(mol: &Molecule, idx: AtomIdx, elem: Element) -> bool {
759 neighbor_elements(mol, idx).contains(&elem)
760}
761
762fn assign_c_type(mol: &Molecule, idx: AtomIdx) -> Result<u8, NumericTypeError> {
765 let atom = mol.atom(idx);
766
767 if atom.aromatic {
769 return Ok(aromatic_c_type(mol, idx));
770 }
771
772 let double_bonds = count_bond_order(mol, idx, BondOrder::Double);
773 let triple_bonds = count_bond_order(mol, idx, BondOrder::Triple);
774
775 if triple_bonds > 0 {
777 return Ok(4); }
779
780 if double_bonds > 0 {
782 if is_bonded_to(mol, idx, Element::O, BondOrder::Double)
784 || is_bonded_to(mol, idx, Element::S, BondOrder::Double)
785 {
786 return Ok(3); }
788 return Ok(2); }
791
792 Ok(1) }
795
796fn aromatic_c_type(mol: &Molecule, idx: AtomIdx) -> u8 {
797 let ring_sizes = ring_sizes_for_atom(mol, idx.0 as usize);
799 let in_6 = ring_sizes.contains(&6);
800 let in_5 = ring_sizes.contains(&5);
801
802 if in_6 && !in_5 {
803 return 63; }
805
806 if in_5 {
808 let has_hetero_neighbor = neighbor_elements(mol, idx)
810 .into_iter()
811 .any(|e| matches!(e, Element::N | Element::O | Element::S));
812 if has_hetero_neighbor {
813 return 37; }
815 return 38; }
817
818 64 }
821
822fn assign_n_type(mol: &Molecule, idx: AtomIdx) -> Result<u8, NumericTypeError> {
825 let atom = mol.atom(idx);
826
827 if atom.aromatic {
829 return Ok(aromatic_n_type(mol, idx));
830 }
831
832 let double_bonds = count_bond_order(mol, idx, BondOrder::Double);
833 let triple_bonds = count_bond_order(mol, idx, BondOrder::Triple);
834 let nbrs = bonds_of(mol, idx);
835
836 if atom.charge > 0 {
838 return Ok(32); }
840
841 if triple_bonds > 0 {
843 return Ok(9); }
845
846 if double_bonds > 0 {
848 return Ok(9); }
850
851 let is_amide = nbrs.iter().any(|b| {
853 let nbr = mol.atom(b.neighbor);
854 nbr.element == Element::C && {
855 bonds_of(mol, b.neighbor)
857 .iter()
858 .any(|bb| bb.order == BondOrder::Double && mol.atom(bb.neighbor).element == Element::O)
859 }
860 });
861
862 if is_amide {
863 return Ok(10); }
865
866 let double_o = bonds_of(mol, idx)
868 .iter()
869 .filter(|b| b.order == BondOrder::Double && mol.atom(b.neighbor).element == Element::O)
870 .count();
871 if double_o >= 2 {
872 return Ok(46); }
874
875 Ok(8) }
877
878fn aromatic_n_type(mol: &Molecule, idx: AtomIdx) -> u8 {
879 let ring_sizes = ring_sizes_for_atom(mol, idx.0 as usize);
880 let in_5 = ring_sizes.contains(&5);
881
882 let has_h = is_neighbor(mol, idx, Element::H);
884
885 if in_5 {
886 if has_h {
887 return 40; }
889 return 58; }
891
892 67 }
895
896fn assign_o_type(mol: &Molecule, idx: AtomIdx) -> Result<u8, NumericTypeError> {
899 if count_bond_order(mol, idx, BondOrder::Double) > 0 {
901 return Ok(7); }
903
904 if mol.atom(idx).charge < 0 {
906 return Ok(34); }
908
909 Ok(6) }
912
913fn assign_s_type(mol: &Molecule, idx: AtomIdx) -> Result<u8, NumericTypeError> {
916 let atom = mol.atom(idx);
917 if atom.aromatic {
918 return Ok(44); }
920
921 let double_o = bonds_of(mol, idx)
922 .iter()
923 .filter(|b| b.order == BondOrder::Double && mol.atom(b.neighbor).element == Element::O)
924 .count();
925
926 match double_o {
927 2.. => Ok(18), 1 => Ok(17), 0 => {
930 if count_bond_order(mol, idx, BondOrder::Double) > 0 {
932 return Ok(16); }
934 Ok(15) }
936 }
937}
938
939fn assign_p_type(mol: &Molecule, idx: AtomIdx) -> Result<u8, NumericTypeError> {
942 if is_bonded_to(mol, idx, Element::O, BondOrder::Double) {
944 return Ok(25); }
946 Ok(20) }
948
949fn assign_h_type(mol: &Molecule, idx: AtomIdx) -> Result<u8, NumericTypeError> {
952 let nbrs = bonds_of(mol, idx);
953 if nbrs.is_empty() {
954 return Ok(5); }
956 let nbr_atom = mol.atom(nbrs[0].neighbor);
957
958 Ok(match nbr_atom.element {
959 Element::C => 5, Element::O => 24, Element::S => 71, Element::N => {
963 let n_idx = nbrs[0].neighbor;
965 let n_atom = mol.atom(n_idx);
966 if n_atom.aromatic {
967 return Ok(23); }
969 let n_is_amide = bonds_of(mol, n_idx).iter().any(|b| {
970 b.order == BondOrder::Single && mol.atom(b.neighbor).element == Element::C
971 && bonds_of(mol, b.neighbor).iter().any(|bb| {
972 bb.order == BondOrder::Double
973 && mol.atom(bb.neighbor).element == Element::O
974 })
975 });
976 if n_is_amide {
977 28 } else if count_bond_order(mol, n_idx, BondOrder::Double) > 0 {
979 27 } else {
981 23 }
983 }
984 _ => 5,
985 })
986}
987
988pub fn mmff94_charges_numeric(
998 mol: &Molecule,
999) -> Result<Vec<f64>, NumericTypeError> {
1000 let types = assign_mmff94_numeric_types(mol)?;
1001 let n = mol.atom_count();
1002 let mut charges = vec![0.0f64; n];
1003
1004 for i in 0..n {
1006 let idx = AtomIdx(i as u32);
1007 let atom = mol.atom(idx);
1008 let (_, fcadj) = pbci_for(types[i]);
1009 let q0 = atom.charge as f64;
1010 let m = bonds_of(mol, idx).len() as f64;
1013 charges[i] = (1.0 - m * fcadj) * q0;
1014 }
1015
1016 for (_, bond) in mol.bonds() {
1018 let i = bond.atom1.0 as usize;
1019 let j = bond.atom2.0 as usize;
1020 let ti = types[i];
1021 let tj = types[j];
1022 let bt = bond_type_for(bond.order);
1023
1024 let ci = lookup_chg_contribution(bt, ti, tj)
1026 .unwrap_or_else(|| pbci_for(ti).0 - pbci_for(tj).0);
1027
1028 let cj = lookup_chg_contribution(bt, tj, ti)
1030 .unwrap_or_else(|| pbci_for(tj).0 - pbci_for(ti).0);
1031
1032 charges[i] += ci;
1033 charges[j] += cj;
1034 }
1035
1036 for i in 0..n {
1038 let idx = AtomIdx(i as u32);
1039 let (_, fcadj_i) = pbci_for(types[i]);
1040 if fcadj_i > 0.0 {
1041 let sum_fc: f64 = bonds_of(mol, idx)
1043 .iter()
1044 .map(|b| mol.atom(b.neighbor).charge as f64)
1045 .sum();
1046 charges[i] += fcadj_i * sum_fc;
1047 }
1048 for b in bonds_of(mol, idx) {
1051 let nbr = mol.atom(b.neighbor);
1052 if nbr.charge < 0 {
1053 let deg = bonds_of(mol, b.neighbor).len() as f64;
1054 charges[i] += (nbr.charge as f64) / (2.0 * deg);
1055 }
1056 }
1057 }
1058
1059 Ok(charges)
1060}
1061
1062#[cfg(test)]
1065mod tests {
1066 #![allow(clippy::needless_range_loop)]
1067
1068 use super::*;
1069 use chematic_smiles::parse;
1070
1071 fn mol(s: &str) -> Molecule {
1072 parse(s).unwrap()
1073 }
1074
1075 #[test]
1078 fn glycine_types_match_mmff94_reference() {
1079 let m = mol("NCC(=O)O");
1083 let types = assign_mmff94_numeric_types(&m).unwrap();
1084
1085 let mut n_types: Vec<u8> = Vec::new();
1087 let mut c_types: Vec<u8> = Vec::new();
1088 let mut o_types: Vec<u8> = Vec::new();
1089
1090 for i in 0..m.atom_count() {
1091 let a = m.atom(AtomIdx(i as u32));
1092 match a.element {
1093 Element::N => n_types.push(types[i]),
1094 Element::C => c_types.push(types[i]),
1095 Element::O => o_types.push(types[i]),
1096 _ => {}
1097 }
1098 }
1099
1100 assert!(n_types.iter().all(|&t| t == 8), "N should be type 8 (NR), got {:?}", n_types);
1102 assert!(c_types.contains(&1), "should have sp3 C (type 1), got {:?}", c_types);
1104 assert!(c_types.contains(&3), "should have carbonyl C (type 3), got {:?}", c_types);
1105 assert!(o_types.contains(&6), "should have OR oxygen (type 6), got {:?}", o_types);
1107 assert!(o_types.contains(&7), "should have O=C oxygen (type 7), got {:?}", o_types);
1108 }
1109
1110 #[test]
1111 fn benzene_aromatic_c_is_type_63() {
1112 let m = mol("c1ccccc1");
1113 let types = assign_mmff94_numeric_types(&m).unwrap();
1114 for i in 0..m.atom_count() {
1115 let a = m.atom(AtomIdx(i as u32));
1116 if a.element == Element::C {
1117 assert_eq!(types[i], 63, "benzene C should be type 63 (CB)");
1118 }
1119 }
1120 }
1121
1122 #[test]
1123 fn pyridine_n_is_type_67() {
1124 let m = mol("c1ccncc1");
1125 let types = assign_mmff94_numeric_types(&m).unwrap();
1126 for i in 0..m.atom_count() {
1127 let a = m.atom(AtomIdx(i as u32));
1128 if a.element == Element::N {
1129 assert_eq!(types[i], 67, "pyridine N should be type 67 (N6A)");
1130 }
1131 }
1132 }
1133
1134 #[test]
1135 fn halogens_map_correctly() {
1136 let m = mol("CF");
1137 let types = assign_mmff94_numeric_types(&m).unwrap();
1138 for i in 0..m.atom_count() {
1139 let a = m.atom(AtomIdx(i as u32));
1140 match a.element {
1141 Element::F => assert_eq!(types[i], 11),
1142 Element::C => assert_eq!(types[i], 1),
1143 _ => {}
1144 }
1145 }
1146 let m2 = mol("CCl");
1147 let types2 = assign_mmff94_numeric_types(&m2).unwrap();
1148 for i in 0..m2.atom_count() {
1149 if m2.atom(AtomIdx(i as u32)).element == Element::CL {
1150 assert_eq!(types2[i], 12);
1151 }
1152 }
1153 }
1154
1155 #[test]
1156 fn amide_n_is_type_10() {
1157 let m = mol("NC(=O)C");
1159 let types = assign_mmff94_numeric_types(&m).unwrap();
1160 for i in 0..m.atom_count() {
1161 let a = m.atom(AtomIdx(i as u32));
1162 if a.element == Element::N {
1163 assert_eq!(types[i], 10, "amide N should be type 10 (NC=O)");
1164 }
1165 }
1166 }
1167
1168 #[test]
1169 fn sulfoxide_is_type_17_sulfone_is_type_18() {
1170 let m_so = mol("CS(=O)C"); let types_so = assign_mmff94_numeric_types(&m_so).unwrap();
1172 let m_s2 = mol("CS(=O)(=O)C"); let types_s2 = assign_mmff94_numeric_types(&m_s2).unwrap();
1174
1175 for i in 0..m_so.atom_count() {
1176 if m_so.atom(AtomIdx(i as u32)).element == Element::S {
1177 assert_eq!(types_so[i], 17, "DMSO S should be type 17 (SO)");
1178 }
1179 }
1180 for i in 0..m_s2.atom_count() {
1181 if m_s2.atom(AtomIdx(i as u32)).element == Element::S {
1182 assert_eq!(types_s2[i], 18, "DMSO2 S should be type 18 (SO2)");
1183 }
1184 }
1185 }
1186
1187 #[test]
1190 fn charge_sum_equals_formal_charge_methane() {
1191 let m = mol("C");
1192 let q = mmff94_charges_numeric(&m).unwrap();
1193 let total: f64 = q.iter().sum();
1194 assert!(total.abs() < 0.1, "methane net charge = {total:.4}");
1195 }
1196
1197 #[test]
1198 fn charge_sum_equals_formal_charge_glycine() {
1199 let m = mol("NCC(=O)O");
1200 let q = mmff94_charges_numeric(&m).unwrap();
1201 let total: f64 = q.iter().sum();
1202 assert!(total.abs() < 0.15, "glycine net charge = {total:.4}");
1203 }
1204
1205 #[test]
1206 fn carbonyl_oxygen_is_most_negative() {
1207 let m = mol("CC(=O)C");
1209 let types = assign_mmff94_numeric_types(&m).unwrap();
1210 let q = mmff94_charges_numeric(&m).unwrap();
1211 let (o_idx, _) = m.atoms()
1212 .find(|(_, a)| a.element == Element::O)
1213 .unwrap();
1214 let o_charge = q[o_idx.0 as usize];
1215 assert!(o_charge < -0.3, "ketone O charge = {o_charge:.3}, expected < -0.3");
1216 assert_eq!(types[o_idx.0 as usize], 7, "ketone O should be type 7");
1218 }
1219
1220 #[test]
1221 fn amine_n_is_negative() {
1222 let m = mol("CCN");
1223 let q = mmff94_charges_numeric(&m).unwrap();
1224 let n_charge = m.atoms()
1225 .find(|(_, a)| a.element == Element::N)
1226 .map(|(i, _)| q[i.0 as usize])
1227 .unwrap();
1228 assert!(n_charge < -0.1, "amine N charge = {n_charge:.3}, expected negative");
1229 }
1230
1231 #[test]
1232 fn h_on_nitrogen_is_positive() {
1233 let m = mol("C[NH2]");
1235 let q = mmff94_charges_numeric(&m).unwrap();
1236 let types = assign_mmff94_numeric_types(&m).unwrap();
1237 let h_charges: Vec<f64> = m.atoms()
1239 .filter(|(i, a)| a.element == Element::H && types[i.0 as usize] == 23)
1240 .map(|(i, _)| q[i.0 as usize])
1241 .collect();
1242 if h_charges.is_empty() {
1243 let n_charge = m.atoms()
1245 .find(|(_, a)| a.element == Element::N)
1246 .map(|(i, _)| q[i.0 as usize])
1247 .unwrap();
1248 assert!(n_charge < 0.0, "amine N charge = {n_charge:.3}, expected negative");
1249 } else {
1250 for &hq in &h_charges {
1251 assert!(hq > 0.05, "H-N charge = {hq:.3}, expected positive");
1252 }
1253 }
1254 }
1255
1256 #[test]
1257 fn pbci_table_has_99_entries() {
1258 assert_eq!(MMFF94_PBCI.len(), 99);
1259 }
1260
1261 #[test]
1262 fn chg_table_has_498_entries() {
1263 assert_eq!(MMFF94_CHG.len(), 498);
1264 }
1265
1266 #[test]
1267 fn glycine_h_types_correct() {
1268 let m = mol("[NH2]CC(=O)O");
1272 let types = assign_mmff94_numeric_types(&m).unwrap();
1273 for i in 0..m.atom_count() {
1274 let a = m.atom(AtomIdx(i as u32));
1275 if a.element == Element::H {
1276 let t = types[i];
1277 assert!(
1278 matches!(t, 5 | 23 | 24),
1279 "H type should be 5/23/24, got {t}"
1280 );
1281 }
1282 }
1283 }
1284
1285 #[test]
1286 fn furan_o_is_type_43() {
1287 let m = mol("o1cccc1"); let types_result = assign_mmff94_numeric_types(&m);
1293 if let Ok(types) = types_result {
1296 for i in 0..m.atom_count() {
1297 if m.atom(AtomIdx(i as u32)).element == Element::O {
1298 let t = types[i];
1300 assert!(matches!(t, 43 | 6), "furan O type = {t}");
1301 }
1302 }
1303 }
1304 }
1305}