|
19 | 19 |
|
20 | 20 | #include <algorithm> |
21 | 21 | #include <cassert> |
| 22 | +#include <cmath> |
22 | 23 | #include <cstddef> |
23 | 24 | #include <iostream> |
24 | 25 | #include <utility> |
@@ -953,35 +954,48 @@ const Cube* Molecule::activeCube() const |
953 | 954 |
|
954 | 955 | std::string Molecule::formula(const std::string& delimiter, int over) const |
955 | 956 | { |
956 | | - // Adapted from chemkit: |
957 | | - // A map of atomic symbols to their quantity. |
958 | | - std::map<unsigned char, size_t> componentsCount = composition(); |
| 957 | + // A map of element symbols (including isotopes) to their quantity. |
| 958 | + std::map<std::string, size_t> componentsCount = formulaComposition(); |
959 | 959 |
|
960 | 960 | std::stringstream result; |
961 | | - std::map<unsigned char, size_t>::iterator iter; |
| 961 | + std::map<std::string, size_t>::iterator iter; |
962 | 962 |
|
963 | 963 | // Carbons first |
964 | | - iter = componentsCount.find(6); |
| 964 | + iter = componentsCount.find("C"); |
965 | 965 | if (iter != componentsCount.end()) { |
966 | 966 | result << "C"; |
967 | 967 | if (iter->second > static_cast<size_t>(over)) |
968 | 968 | result << delimiter << iter->second; |
969 | 969 | componentsCount.erase(iter); |
970 | 970 |
|
971 | | - // If carbon is present, hydrogens are next. |
972 | | - iter = componentsCount.find(1); |
| 971 | + // If carbon is present, hydrogens are next (including D and T). |
| 972 | + iter = componentsCount.find("H"); |
973 | 973 | if (iter != componentsCount.end()) { |
974 | 974 | result << delimiter << "H"; |
975 | 975 | if (iter->second > static_cast<size_t>(over)) |
976 | 976 | result << delimiter << iter->second; |
977 | 977 | componentsCount.erase(iter); |
978 | 978 | } |
| 979 | + iter = componentsCount.find("D"); |
| 980 | + if (iter != componentsCount.end()) { |
| 981 | + result << delimiter << "D"; |
| 982 | + if (iter->second > static_cast<size_t>(over)) |
| 983 | + result << delimiter << iter->second; |
| 984 | + componentsCount.erase(iter); |
| 985 | + } |
| 986 | + iter = componentsCount.find("T"); |
| 987 | + if (iter != componentsCount.end()) { |
| 988 | + result << delimiter << "T"; |
| 989 | + if (iter->second > static_cast<size_t>(over)) |
| 990 | + result << delimiter << iter->second; |
| 991 | + componentsCount.erase(iter); |
| 992 | + } |
979 | 993 | } |
980 | 994 |
|
981 | | - // The rest: |
| 995 | + // The rest (alphabetically, since std::map<std::string> sorts by key): |
982 | 996 | iter = componentsCount.begin(); |
983 | 997 | while (iter != componentsCount.end()) { |
984 | | - result << delimiter << Elements::symbol(iter->first); |
| 998 | + result << delimiter << iter->first; |
985 | 999 | if (iter->second > static_cast<size_t>(over)) |
986 | 1000 | result << delimiter << iter->second; |
987 | 1001 | ++iter; |
@@ -1555,11 +1569,130 @@ bool Molecule::hasCustomElements() const |
1555 | 1569 |
|
1556 | 1570 | std::map<unsigned char, size_t> Molecule::composition() const |
1557 | 1571 | { |
1558 | | - // A map of atomic symbols to their quantity. |
| 1572 | + const double tolerance = 1.0e-3; |
| 1573 | + |
| 1574 | + // A map of atomic numbers to their quantity (using double for fractional |
| 1575 | + // contributions when there's a unit cell) |
| 1576 | + std::map<unsigned char, double> compositionDouble; |
| 1577 | + |
| 1578 | + // Check if we have a unit cell - if so, we need to account for fractional |
| 1579 | + // atoms at corners, edges, and faces |
| 1580 | + if (m_unitCell != nullptr) { |
| 1581 | + for (Index i = 0; i < atomCount(); ++i) { |
| 1582 | + unsigned char atomicNum = m_atomicNumbers[i]; |
| 1583 | + Vector3 fracCoords = m_unitCell->toFractional(m_positions3d[i]); |
| 1584 | + |
| 1585 | + // Count how many coordinates are at boundaries (0 or 1) |
| 1586 | + int boundaryCount = 0; |
| 1587 | + for (int j = 0; j < 3; ++j) { |
| 1588 | + double coord = fracCoords[j]; |
| 1589 | + // Check if close to 0 or 1 |
| 1590 | + if (std::fabs(coord) < tolerance || |
| 1591 | + std::fabs(coord - 1.0) < tolerance) { |
| 1592 | + ++boundaryCount; |
| 1593 | + } |
| 1594 | + } |
| 1595 | + |
| 1596 | + // Calculate fractional contribution based on boundary count |
| 1597 | + // Corner atoms (3 boundaries): 1/8 |
| 1598 | + // Edge atoms (2 boundaries): 1/4 |
| 1599 | + // Face atoms (1 boundary): 1/2 |
| 1600 | + // Interior atoms (0 boundaries): 1 |
| 1601 | + double weight = 1.0; |
| 1602 | + if (boundaryCount == 3) { |
| 1603 | + weight = 1.0 / 8.0; |
| 1604 | + } else if (boundaryCount == 2) { |
| 1605 | + weight = 1.0 / 4.0; |
| 1606 | + } else if (boundaryCount == 1) { |
| 1607 | + weight = 1.0 / 2.0; |
| 1608 | + } |
| 1609 | + |
| 1610 | + compositionDouble[atomicNum] += weight; |
| 1611 | + } |
| 1612 | + } else { |
| 1613 | + // No unit cell, just count atoms normally |
| 1614 | + for (unsigned char atomicNum : m_atomicNumbers) { |
| 1615 | + compositionDouble[atomicNum] += 1.0; |
| 1616 | + } |
| 1617 | + } |
| 1618 | + |
| 1619 | + // Convert to size_t by rounding to nearest integer |
1559 | 1620 | std::map<unsigned char, size_t> composition; |
1560 | | - for (unsigned char m_atomicNumber : m_atomicNumbers) { |
1561 | | - ++composition[m_atomicNumber]; |
| 1621 | + for (const auto& pair : compositionDouble) { |
| 1622 | + size_t roundedCount = static_cast<size_t>(std::round(pair.second)); |
| 1623 | + if (roundedCount > 0) { |
| 1624 | + composition[pair.first] = roundedCount; |
| 1625 | + } |
1562 | 1626 | } |
| 1627 | + |
| 1628 | + return composition; |
| 1629 | +} |
| 1630 | + |
| 1631 | +std::map<std::string, size_t> Molecule::formulaComposition() const |
| 1632 | +{ |
| 1633 | + const double tolerance = 1.0e-3; |
| 1634 | + |
| 1635 | + // A map of element symbols (with isotopes) to their quantity |
| 1636 | + // Using double to accumulate fractional contributions from unit cells |
| 1637 | + std::map<std::string, double> compositionDouble; |
| 1638 | + |
| 1639 | + for (Index i = 0; i < atomCount(); ++i) { |
| 1640 | + unsigned char atomicNum = m_atomicNumbers[i]; |
| 1641 | + std::string atomSymbol(Elements::symbol(atomicNum)); |
| 1642 | + |
| 1643 | + // Handle isotopes |
| 1644 | + unsigned short iso = isotope(i); |
| 1645 | + if (iso > 0) { |
| 1646 | + if (atomicNum == 1 && iso == 1) |
| 1647 | + atomSymbol = "H"; |
| 1648 | + else if (atomicNum == 1 && iso == 2) |
| 1649 | + atomSymbol = "D"; |
| 1650 | + else if (atomicNum == 1 && iso == 3) |
| 1651 | + atomSymbol = "T"; |
| 1652 | + else |
| 1653 | + // e.g., 13C |
| 1654 | + atomSymbol = std::to_string(iso) + atomSymbol; |
| 1655 | + } |
| 1656 | + |
| 1657 | + // Calculate fractional contribution for unit cells |
| 1658 | + double weight = 1.0; |
| 1659 | + if (m_unitCell != nullptr) { |
| 1660 | + Vector3 fracCoords = m_unitCell->toFractional(m_positions3d[i]); |
| 1661 | + |
| 1662 | + // Count how many coordinates are at boundaries (0 or 1) |
| 1663 | + int boundaryCount = 0; |
| 1664 | + for (int j = 0; j < 3; ++j) { |
| 1665 | + double coord = fracCoords[j]; |
| 1666 | + if (std::fabs(coord) < tolerance || |
| 1667 | + std::fabs(coord - 1.0) < tolerance) { |
| 1668 | + ++boundaryCount; |
| 1669 | + } |
| 1670 | + } |
| 1671 | + |
| 1672 | + // Corner atoms (3 boundaries): 1/8 |
| 1673 | + // Edge atoms (2 boundaries): 1/4 |
| 1674 | + // Face atoms (1 boundary): 1/2 |
| 1675 | + if (boundaryCount == 3) { |
| 1676 | + weight = 1.0 / 8.0; |
| 1677 | + } else if (boundaryCount == 2) { |
| 1678 | + weight = 1.0 / 4.0; |
| 1679 | + } else if (boundaryCount == 1) { |
| 1680 | + weight = 1.0 / 2.0; |
| 1681 | + } |
| 1682 | + } |
| 1683 | + |
| 1684 | + compositionDouble[atomSymbol] += weight; |
| 1685 | + } |
| 1686 | + |
| 1687 | + // Convert to size_t by rounding to nearest integer |
| 1688 | + std::map<std::string, size_t> composition; |
| 1689 | + for (const auto& pair : compositionDouble) { |
| 1690 | + size_t roundedCount = static_cast<size_t>(std::round(pair.second)); |
| 1691 | + if (roundedCount > 0) { |
| 1692 | + composition[pair.first] = roundedCount; |
| 1693 | + } |
| 1694 | + } |
| 1695 | + |
1563 | 1696 | return composition; |
1564 | 1697 | } |
1565 | 1698 |
|
|
0 commit comments