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 _ => {
716 return Err(NumericTypeError(format!(
717 "unsupported element {:?} at atom {i}",
718 atom.element
719 )));
720 }
721 };
722 *ty = t;
723 }
724 Ok(types)
725}
726
727struct BondInfo {
730 neighbor: AtomIdx,
731 order: BondOrder,
732}
733
734fn bonds_of(mol: &Molecule, idx: AtomIdx) -> Vec<BondInfo> {
735 mol.bonds()
736 .filter_map(|(_, b)| {
737 if b.atom1 == idx {
738 Some(BondInfo {
739 neighbor: b.atom2,
740 order: b.order,
741 })
742 } else if b.atom2 == idx {
743 Some(BondInfo {
744 neighbor: b.atom1,
745 order: b.order,
746 })
747 } else {
748 None
749 }
750 })
751 .collect()
752}
753
754fn count_bond_order(mol: &Molecule, idx: AtomIdx, order: BondOrder) -> usize {
755 bonds_of(mol, idx)
756 .iter()
757 .filter(|b| b.order == order)
758 .count()
759}
760
761fn neighbor_elements(mol: &Molecule, idx: AtomIdx) -> Vec<Element> {
762 bonds_of(mol, idx)
763 .iter()
764 .map(|b| mol.atom(b.neighbor).element)
765 .collect()
766}
767
768fn is_bonded_to(mol: &Molecule, idx: AtomIdx, elem: Element, order: BondOrder) -> bool {
769 bonds_of(mol, idx)
770 .iter()
771 .any(|b| mol.atom(b.neighbor).element == elem && b.order == order)
772}
773
774fn is_neighbor(mol: &Molecule, idx: AtomIdx, elem: Element) -> bool {
776 neighbor_elements(mol, idx).contains(&elem)
777}
778
779fn assign_c_type(mol: &Molecule, idx: AtomIdx) -> Result<u8, NumericTypeError> {
782 let atom = mol.atom(idx);
783
784 if atom.aromatic {
786 return Ok(aromatic_c_type(mol, idx));
787 }
788
789 let double_bonds = count_bond_order(mol, idx, BondOrder::Double);
790 let triple_bonds = count_bond_order(mol, idx, BondOrder::Triple);
791
792 if triple_bonds > 0 {
794 return Ok(4); }
796
797 if double_bonds > 0 {
799 if is_bonded_to(mol, idx, Element::O, BondOrder::Double)
801 || is_bonded_to(mol, idx, Element::S, BondOrder::Double)
802 {
803 return Ok(3); }
805 return Ok(2); }
808
809 Ok(1) }
812
813fn aromatic_c_type(mol: &Molecule, idx: AtomIdx) -> u8 {
814 let ring_sizes = ring_sizes_for_atom(mol, idx.0 as usize);
816 let in_6 = ring_sizes.contains(&6);
817 let in_5 = ring_sizes.contains(&5);
818
819 if in_6 && !in_5 {
820 return 63; }
822
823 if in_5 {
825 let has_hetero_neighbor = neighbor_elements(mol, idx)
827 .into_iter()
828 .any(|e| matches!(e, Element::N | Element::O | Element::S));
829 if has_hetero_neighbor {
830 return 37; }
832 return 38; }
834
835 64 }
838
839fn assign_n_type(mol: &Molecule, idx: AtomIdx) -> Result<u8, NumericTypeError> {
842 let atom = mol.atom(idx);
843
844 if atom.aromatic {
846 return Ok(aromatic_n_type(mol, idx));
847 }
848
849 let double_bonds = count_bond_order(mol, idx, BondOrder::Double);
850 let triple_bonds = count_bond_order(mol, idx, BondOrder::Triple);
851 let nbrs = bonds_of(mol, idx);
852
853 if atom.charge > 0 {
855 return Ok(32); }
857
858 if triple_bonds > 0 {
860 return Ok(9); }
862
863 if double_bonds > 0 {
865 return Ok(9); }
867
868 let is_amide = nbrs.iter().any(|b| {
870 let nbr = mol.atom(b.neighbor);
871 nbr.element == Element::C && {
872 bonds_of(mol, b.neighbor).iter().any(|bb| {
874 bb.order == BondOrder::Double && mol.atom(bb.neighbor).element == Element::O
875 })
876 }
877 });
878
879 if is_amide {
880 return Ok(10); }
882
883 let double_o = bonds_of(mol, idx)
885 .iter()
886 .filter(|b| b.order == BondOrder::Double && mol.atom(b.neighbor).element == Element::O)
887 .count();
888 if double_o >= 2 {
889 return Ok(46); }
891
892 Ok(8) }
894
895fn aromatic_n_type(mol: &Molecule, idx: AtomIdx) -> u8 {
896 let ring_sizes = ring_sizes_for_atom(mol, idx.0 as usize);
897 let in_5 = ring_sizes.contains(&5);
898
899 let has_h = is_neighbor(mol, idx, Element::H);
901
902 if in_5 {
903 if has_h {
904 return 40; }
906 return 58; }
908
909 67 }
912
913fn assign_o_type(mol: &Molecule, idx: AtomIdx) -> Result<u8, NumericTypeError> {
916 if count_bond_order(mol, idx, BondOrder::Double) > 0 {
918 return Ok(7); }
920
921 if mol.atom(idx).charge < 0 {
923 return Ok(34); }
925
926 Ok(6) }
929
930fn assign_s_type(mol: &Molecule, idx: AtomIdx) -> Result<u8, NumericTypeError> {
933 let atom = mol.atom(idx);
934 if atom.aromatic {
935 return Ok(44); }
937
938 let double_o = bonds_of(mol, idx)
939 .iter()
940 .filter(|b| b.order == BondOrder::Double && mol.atom(b.neighbor).element == Element::O)
941 .count();
942
943 match double_o {
944 2.. => Ok(18), 1 => Ok(17), 0 => {
947 if count_bond_order(mol, idx, BondOrder::Double) > 0 {
949 return Ok(16); }
951 Ok(15) }
953 }
954}
955
956fn assign_p_type(mol: &Molecule, idx: AtomIdx) -> Result<u8, NumericTypeError> {
959 if is_bonded_to(mol, idx, Element::O, BondOrder::Double) {
961 return Ok(25); }
963 Ok(20) }
965
966fn assign_h_type(mol: &Molecule, idx: AtomIdx) -> Result<u8, NumericTypeError> {
969 let nbrs = bonds_of(mol, idx);
970 if nbrs.is_empty() {
971 return Ok(5); }
973 let nbr_atom = mol.atom(nbrs[0].neighbor);
974
975 Ok(match nbr_atom.element {
976 Element::C => 5, Element::O => 24, Element::S => 71, Element::N => {
980 let n_idx = nbrs[0].neighbor;
982 let n_atom = mol.atom(n_idx);
983 if n_atom.aromatic {
984 return Ok(23); }
986 let n_is_amide = bonds_of(mol, n_idx).iter().any(|b| {
987 b.order == BondOrder::Single
988 && mol.atom(b.neighbor).element == Element::C
989 && bonds_of(mol, b.neighbor).iter().any(|bb| {
990 bb.order == BondOrder::Double && mol.atom(bb.neighbor).element == Element::O
991 })
992 });
993 if n_is_amide {
994 28 } else if count_bond_order(mol, n_idx, BondOrder::Double) > 0 {
996 27 } else {
998 23 }
1000 }
1001 _ => 5,
1002 })
1003}
1004
1005pub fn mmff94_charges_numeric(mol: &Molecule) -> Result<Vec<f64>, NumericTypeError> {
1015 let types = assign_mmff94_numeric_types(mol)?;
1016 let n = mol.atom_count();
1017 let mut charges = vec![0.0f64; n];
1018
1019 for i in 0..n {
1021 let idx = AtomIdx(i as u32);
1022 let atom = mol.atom(idx);
1023 let (_, fcadj) = pbci_for(types[i]);
1024 let q0 = atom.charge as f64;
1025 let m = bonds_of(mol, idx).len() as f64;
1028 charges[i] = (1.0 - m * fcadj) * q0;
1029 }
1030
1031 for (_, bond) in mol.bonds() {
1033 let i = bond.atom1.0 as usize;
1034 let j = bond.atom2.0 as usize;
1035 let ti = types[i];
1036 let tj = types[j];
1037 let bt = bond_type_for(bond.order);
1038
1039 let ci =
1041 lookup_chg_contribution(bt, ti, tj).unwrap_or_else(|| pbci_for(ti).0 - pbci_for(tj).0);
1042
1043 let cj =
1045 lookup_chg_contribution(bt, tj, ti).unwrap_or_else(|| pbci_for(tj).0 - pbci_for(ti).0);
1046
1047 charges[i] += ci;
1048 charges[j] += cj;
1049 }
1050
1051 for i in 0..n {
1053 let idx = AtomIdx(i as u32);
1054 let (_, fcadj_i) = pbci_for(types[i]);
1055 if fcadj_i > 0.0 {
1056 let sum_fc: f64 = bonds_of(mol, idx)
1058 .iter()
1059 .map(|b| mol.atom(b.neighbor).charge as f64)
1060 .sum();
1061 charges[i] += fcadj_i * sum_fc;
1062 }
1063 for b in bonds_of(mol, idx) {
1066 let nbr = mol.atom(b.neighbor);
1067 if nbr.charge < 0 {
1068 let deg = bonds_of(mol, b.neighbor).len() as f64;
1069 charges[i] += (nbr.charge as f64) / (2.0 * deg);
1070 }
1071 }
1072 }
1073
1074 Ok(charges)
1075}
1076
1077#[cfg(test)]
1080mod tests {
1081 #![allow(clippy::needless_range_loop)]
1082
1083 use super::*;
1084 use chematic_smiles::parse;
1085
1086 fn mol(s: &str) -> Molecule {
1087 parse(s).unwrap()
1088 }
1089
1090 #[test]
1093 fn glycine_types_match_mmff94_reference() {
1094 let m = mol("NCC(=O)O");
1098 let types = assign_mmff94_numeric_types(&m).unwrap();
1099
1100 let mut n_types: Vec<u8> = Vec::new();
1102 let mut c_types: Vec<u8> = Vec::new();
1103 let mut o_types: Vec<u8> = Vec::new();
1104
1105 for i in 0..m.atom_count() {
1106 let a = m.atom(AtomIdx(i as u32));
1107 match a.element {
1108 Element::N => n_types.push(types[i]),
1109 Element::C => c_types.push(types[i]),
1110 Element::O => o_types.push(types[i]),
1111 _ => {}
1112 }
1113 }
1114
1115 assert!(
1117 n_types.iter().all(|&t| t == 8),
1118 "N should be type 8 (NR), got {:?}",
1119 n_types
1120 );
1121 assert!(
1123 c_types.contains(&1),
1124 "should have sp3 C (type 1), got {:?}",
1125 c_types
1126 );
1127 assert!(
1128 c_types.contains(&3),
1129 "should have carbonyl C (type 3), got {:?}",
1130 c_types
1131 );
1132 assert!(
1134 o_types.contains(&6),
1135 "should have OR oxygen (type 6), got {:?}",
1136 o_types
1137 );
1138 assert!(
1139 o_types.contains(&7),
1140 "should have O=C oxygen (type 7), got {:?}",
1141 o_types
1142 );
1143 }
1144
1145 #[test]
1146 fn benzene_aromatic_c_is_type_63() {
1147 let m = mol("c1ccccc1");
1148 let types = assign_mmff94_numeric_types(&m).unwrap();
1149 for i in 0..m.atom_count() {
1150 let a = m.atom(AtomIdx(i as u32));
1151 if a.element == Element::C {
1152 assert_eq!(types[i], 63, "benzene C should be type 63 (CB)");
1153 }
1154 }
1155 }
1156
1157 #[test]
1158 fn pyridine_n_is_type_67() {
1159 let m = mol("c1ccncc1");
1160 let types = assign_mmff94_numeric_types(&m).unwrap();
1161 for i in 0..m.atom_count() {
1162 let a = m.atom(AtomIdx(i as u32));
1163 if a.element == Element::N {
1164 assert_eq!(types[i], 67, "pyridine N should be type 67 (N6A)");
1165 }
1166 }
1167 }
1168
1169 #[test]
1170 fn halogens_map_correctly() {
1171 let m = mol("CF");
1172 let types = assign_mmff94_numeric_types(&m).unwrap();
1173 for i in 0..m.atom_count() {
1174 let a = m.atom(AtomIdx(i as u32));
1175 match a.element {
1176 Element::F => assert_eq!(types[i], 11),
1177 Element::C => assert_eq!(types[i], 1),
1178 _ => {}
1179 }
1180 }
1181 let m2 = mol("CCl");
1182 let types2 = assign_mmff94_numeric_types(&m2).unwrap();
1183 for i in 0..m2.atom_count() {
1184 if m2.atom(AtomIdx(i as u32)).element == Element::CL {
1185 assert_eq!(types2[i], 12);
1186 }
1187 }
1188 }
1189
1190 #[test]
1191 fn amide_n_is_type_10() {
1192 let m = mol("NC(=O)C");
1194 let types = assign_mmff94_numeric_types(&m).unwrap();
1195 for i in 0..m.atom_count() {
1196 let a = m.atom(AtomIdx(i as u32));
1197 if a.element == Element::N {
1198 assert_eq!(types[i], 10, "amide N should be type 10 (NC=O)");
1199 }
1200 }
1201 }
1202
1203 #[test]
1204 fn sulfoxide_is_type_17_sulfone_is_type_18() {
1205 let m_so = mol("CS(=O)C"); let types_so = assign_mmff94_numeric_types(&m_so).unwrap();
1207 let m_s2 = mol("CS(=O)(=O)C"); let types_s2 = assign_mmff94_numeric_types(&m_s2).unwrap();
1209
1210 for i in 0..m_so.atom_count() {
1211 if m_so.atom(AtomIdx(i as u32)).element == Element::S {
1212 assert_eq!(types_so[i], 17, "DMSO S should be type 17 (SO)");
1213 }
1214 }
1215 for i in 0..m_s2.atom_count() {
1216 if m_s2.atom(AtomIdx(i as u32)).element == Element::S {
1217 assert_eq!(types_s2[i], 18, "DMSO2 S should be type 18 (SO2)");
1218 }
1219 }
1220 }
1221
1222 #[test]
1225 fn charge_sum_equals_formal_charge_methane() {
1226 let m = mol("C");
1227 let q = mmff94_charges_numeric(&m).unwrap();
1228 let total: f64 = q.iter().sum();
1229 assert!(total.abs() < 0.1, "methane net charge = {total:.4}");
1230 }
1231
1232 #[test]
1233 fn charge_sum_equals_formal_charge_glycine() {
1234 let m = mol("NCC(=O)O");
1235 let q = mmff94_charges_numeric(&m).unwrap();
1236 let total: f64 = q.iter().sum();
1237 assert!(total.abs() < 0.15, "glycine net charge = {total:.4}");
1238 }
1239
1240 #[test]
1241 fn carbonyl_oxygen_is_most_negative() {
1242 let m = mol("CC(=O)C");
1244 let types = assign_mmff94_numeric_types(&m).unwrap();
1245 let q = mmff94_charges_numeric(&m).unwrap();
1246 let (o_idx, _) = m.atoms().find(|(_, a)| a.element == Element::O).unwrap();
1247 let o_charge = q[o_idx.0 as usize];
1248 assert!(
1249 o_charge < -0.3,
1250 "ketone O charge = {o_charge:.3}, expected < -0.3"
1251 );
1252 assert_eq!(types[o_idx.0 as usize], 7, "ketone O should be type 7");
1254 }
1255
1256 #[test]
1257 fn amine_n_is_negative() {
1258 let m = mol("CCN");
1259 let q = mmff94_charges_numeric(&m).unwrap();
1260 let n_charge = m
1261 .atoms()
1262 .find(|(_, a)| a.element == Element::N)
1263 .map(|(i, _)| q[i.0 as usize])
1264 .unwrap();
1265 assert!(
1266 n_charge < -0.1,
1267 "amine N charge = {n_charge:.3}, expected negative"
1268 );
1269 }
1270
1271 #[test]
1272 fn h_on_nitrogen_is_positive() {
1273 let m = mol("C[NH2]");
1275 let q = mmff94_charges_numeric(&m).unwrap();
1276 let types = assign_mmff94_numeric_types(&m).unwrap();
1277 let h_charges: Vec<f64> = m
1279 .atoms()
1280 .filter(|(i, a)| a.element == Element::H && types[i.0 as usize] == 23)
1281 .map(|(i, _)| q[i.0 as usize])
1282 .collect();
1283 if h_charges.is_empty() {
1284 let n_charge = m
1286 .atoms()
1287 .find(|(_, a)| a.element == Element::N)
1288 .map(|(i, _)| q[i.0 as usize])
1289 .unwrap();
1290 assert!(
1291 n_charge < 0.0,
1292 "amine N charge = {n_charge:.3}, expected negative"
1293 );
1294 } else {
1295 for &hq in &h_charges {
1296 assert!(hq > 0.05, "H-N charge = {hq:.3}, expected positive");
1297 }
1298 }
1299 }
1300
1301 #[test]
1302 fn pbci_table_has_99_entries() {
1303 assert_eq!(MMFF94_PBCI.len(), 99);
1304 }
1305
1306 #[test]
1307 fn chg_table_has_498_entries() {
1308 assert_eq!(MMFF94_CHG.len(), 498);
1309 }
1310
1311 #[test]
1312 fn glycine_h_types_correct() {
1313 let m = mol("[NH2]CC(=O)O");
1317 let types = assign_mmff94_numeric_types(&m).unwrap();
1318 for i in 0..m.atom_count() {
1319 let a = m.atom(AtomIdx(i as u32));
1320 if a.element == Element::H {
1321 let t = types[i];
1322 assert!(
1323 matches!(t, 5 | 23 | 24),
1324 "H type should be 5/23/24, got {t}"
1325 );
1326 }
1327 }
1328 }
1329
1330 #[test]
1331 fn furan_o_is_type_43() {
1332 let m = mol("o1cccc1"); let types_result = assign_mmff94_numeric_types(&m);
1338 if let Ok(types) = types_result {
1341 for i in 0..m.atom_count() {
1342 if m.atom(AtomIdx(i as u32)).element == Element::O {
1343 let t = types[i];
1345 assert!(matches!(t, 43 | 6), "furan O type = {t}");
1346 }
1347 }
1348 }
1349 }
1350}