Files
Jungfraujoch/gemmi_gph/gemmi/elem.hpp
T
leonarski_f dd0bffb283
Build Packages / Unit tests (push) Skipped
Build Packages / build:windows:nocuda (push) Successful in 11m6s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 10m27s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 10m54s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 9m25s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 10m5s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 11m33s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 11m19s
Build Packages / build:rpm (rocky8) (push) Successful in 12m23s
Build Packages / build:rpm (rocky9) (push) Successful in 13m21s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 12m30s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 11m55s
Build Packages / DIALS test (push) Successful in 13m42s
Build Packages / XDS test (durin plugin) (push) Successful in 9m26s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 6m41s
Build Packages / XDS test (neggia plugin) (push) Successful in 6m12s
Build Packages / Generate python client (push) Successful in 19s
Build Packages / Build documentation (push) Successful in 52s
Build Packages / Create release (push) Skipped
Build Packages / build:viewer-tgz:cpu (push) Successful in 5m29s
Build Packages / build:viewer-tgz:cuda (push) Successful in 6m12s
Build Packages / build:windows:cuda (push) Successful in 18m36s
v1.0.0-rc.159 (#69)
This is an UNSTABLE release. It includes many experimental features, as well as many AI generated fixes. We recommend using rc.152 for production use.

* rugnux: Add `--model model.pdb` - score the merged data against an atomic model and compute initial maps. It reports R-work/R-free (scaling the model to the observed amplitudes with an overall scale, an anisotropic B and a flat bulk solvent - the standard few-parameter model, so a batch of maps stays directly comparable) and writes 2Fo-Fc / Fo-Fc electron-density maps (CCP4) plus a map-coefficient MTZ. The structure itself is not refined; the model is only re-fractionalised into the data cell.
* rugnux: The merged reflection output now carries French-Wilson amplitudes (|F| and its sigma) next to the intensities - MTZ `F`/`SIGF`, mmCIF `_refln.F_meas_au`, and the text HKL - computed with the correct centric/acentric Wilson prior and epsilon multiplicity, so a downstream program (e.g. phenix.refine) can refine against amplitudes. The intensity columns are unchanged.
* rugnux: R-free test-set flags are now assigned deterministically and consistently across symmetry - a Bijvoet pair I(+)/I(-) is never split between the work and free sets, and the assignment is a reproducible per-hkl hash that depends only on the reflection index, so every dataset of one crystal form gets the same ~5% free set (what a multi-dataset campaign such as PanDDA needs). On small data the fraction is floored so the test set stays large enough for a stable R-free (~500 reflections, capped at 10%); it stays flat at 5% on ordinary data. When a reference MTZ carries a `FreeR_flag` column its test set is imported instead, letting a whole campaign inherit one shared free set.
* rugnux: A reference MTZ (`--reference-mtz`) can now fix the space group and cell for rotation data too (previously rejected), without being used to scale - the rotation merge stays self-consistent. When the crystal has an indexing (merohedral) ambiguity - a lattice symmetry higher than its Laue symmetry, e.g. P3/P4/P6/C2 - the reference also resolves it: each candidate reindexing (identity plus the twin-law cosets of the metric symmetry) is scored by its intensity correlation against the reference and the data are re-merged in the best-correlating one. This is a metric-preserving relabelling of hkl (the cell is unchanged) and a no-op for a holohedral crystal such as lysozyme.
* rugnux: `--model` validation now aligns the data to the model before scoring - the observed reflections are reindexed into the model's enantiomorph when the two differ only by hand (indistinguishable from merged intensities). A merohedral indexing ambiguity is resolved against the reference MTZ when one is given (so a whole campaign shares one indexing convention); only with a model and no reference does validation fall back to fitting each candidate reindexing and keeping the lowest R-free.
* rugnux: De-novo symmetry - recover a genuine high-symmetry group whose data are imperfectly scaled. Such a merge's within-orbit chi² lands just past the self-consistency bound (each real symmetry step adds a little systematic scatter), right where a merohedral twin also lands, so the chi² ratio alone cannot separate them. The candidate is now rescued when the extra intensity-proportional systematic error it invokes stays small relative to the confirmed subgroup - a genuine symmetry step gains multiplicity without inflating the merge error model's b, whereas a twin forces non-equivalent reflections together and b balloons. Fixes cubic insulin (I23 instead of I222) with no change to any other crystal in the test battery, including the twins that must stay in their lower symmetry.
* Docs: Document the French-Wilson amplitude estimation, R-free flagging, reference-based space-group/ambiguity resolution, and model-based validation/maps in CPU_DATA_ANALYSIS.md.
* Frontend: The status-bar pill now shows a progress bar during detector calibration (previously only during measurement), and the calibration state and its button are labelled "Calibration"/"CALIBRATE" (the internal `Pedestal` state name is unchanged for back-compatibility).Reviewed-on: #69

Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
2026-07-13 13:54:03 +02:00

392 lines
17 KiB
C++

// Copyright 2017 Global Phasing Ltd.
//
// Elements from the periodic table.
#ifndef GEMMI_ELEM_HPP_
#define GEMMI_ELEM_HPP_
#include <cstdint>
#include <string>
namespace gemmi {
// elements
enum class El : unsigned char {
X=0, // unknown element is marked as X in PDB entries
H, He, Li, Be, B, C, N, O, F, Ne, Na, Mg, Al, Si, P, S, Cl, Ar, // 1-3
K, Ca, Sc, Ti, V, Cr, Mn, Fe, Co, Ni, Cu, Zn, Ga, Ge, As, Se, Br, Kr, // 4
Rb, Sr, Y, Zr, Nb, Mo, Tc, Ru, Rh, Pd, Ag, Cd, In, Sn, Sb, Te, I, Xe, // 5
Cs, Ba, La, Ce, Pr, Nd, Pm, Sm, Eu, Gd, Tb, Dy, Ho, Er, Tm, Yb, Lu, // 6..
Hf, Ta, W, Re, Os, Ir, Pt, Au, Hg, Tl, Pb, Bi, Po, At, Rn, // ..6
Fr, Ra, Ac, Th, Pa, U, Np, Pu, Am, Cm, Bk, Cf, Es, Fm, Md, No, Lr, // 7..
Rf, Db, Sg, Bh, Hs, Mt, Ds, Rg, Cn, Nh, Fl, Mc, Lv, Ts, Og, // ..7
D, // heh, what should we do with Deuterium?
END
};
inline bool is_hydrogen(El el) { return el == El::H || el == El::D; }
inline std::int8_t element_row(El el) {
// Lookup table for periodic table periods (rows) by element ordinal (0-118)
static constexpr std::int8_t rows[119] = {
// 0: unknown
0,
// 1-2: H, He (period 1)
1, 1,
// 3-10: Li-Ne (period 2)
2, 2, 2, 2, 2, 2, 2, 2,
// 11-18: Na-Ar (period 3)
3, 3, 3, 3, 3, 3, 3, 3,
// 19-36: K-Kr (period 4)
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
// 37-54: Rb-Xe (period 5)
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
// 55-56: Cs, Ba (period 6)
6, 6,
// 57-71: La-Lu (lanthanides, period 6)
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
// 72-86: Hf-Rn (period 6)
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
// 87-88: Fr, Ra (period 7)
7, 7,
// 89-103: Ac-Lr (actinides, period 7)
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
// 104-118: Rf-Og (period 7)
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7
};
int n = static_cast<int>(el);
return (n >= 0 && n <= 118) ? rows[n] : (int8_t) 0;
}
// Periodic table row and group information
inline std::int8_t element_group(El el) {
// Lookup table for periodic table groups (1-18) by element ordinal (0-118)
// Lanthanides (57-71) and actinides (89-103) are assigned to group 3
static constexpr std::int8_t groups[119] = {
// 0: unknown
0,
// 1-2: H, He
1, 18,
// 3-10: Li-Ne
1, 2, 13, 14, 15, 16, 17, 18,
// 11-18: Na-Ar
1, 2, 13, 14, 15, 16, 17, 18,
// 19-36: K-Kr
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
// 37-54: Rb-Xe
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
// 55-56: Cs, Ba
1, 2,
// 57-71: La-Lu (lanthanides)
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
// 72-86: Hf-Rn
4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
// 87-88: Fr, Ra
1, 2,
// 89-103: Ac-Lr (actinides)
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
// 104-118: Rf-Og
4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18
};
int n = static_cast<int>(el);
return (n >= 0 && n <= 118) ? groups[n] : (std::int8_t)0;
}
// arbitrary division into metals and non-metals (Ge and Sb are metals here)
inline bool& is_metal_value(El el) {
static bool table[] = {
// X H He
false, false, false,
// Li Be B C N O F Ne
true, true, false, false, false, false, false, false,
// Na Mg Al Si P S Cl Ar
true, true, true, false, false, false, false, false,
// K Ca Sc Ti V Cr Mn Fe Co Ni Cu Zn
true, true, true, true, true, true, true, true, true, true, true, true,
// Ga Ge As Se Br Kr
true, true, false, false, false, false,
// Rb Sr Y Zr Nb Mo Tc Ru Rh Pd Ag Cd
true, true, true, true, true, true, true, true, true, true, true, true,
// In Sn Sb Te I Xe
true, true, true, false, false, false,
// Cs Ba La Ce Pr Nd Pm Sm Eu Gd Tb Dy
true, true, true, true, true, true, true, true, true, true, true, true,
// Ho Er Tm Yb Lu Hf Ta W Re Os Ir Pt
true, true, true, true, true, true, true, true, true, true, true, true,
// Au Hg Tl Pb Bi Po At Rn
true, true, true, true, true, true, false, false,
// Fr Ra Ac Th Pa U Np Pu Am Cm Bk Cf
true, true, true, true, true, true, true, true, true, true, true, true,
// Es Fm Md No Lr Rf Db Sg Bh Hs Mt Ds
true, true, true, true, true, true, true, true, true, true, true, true,
// Rg Cn Nh Fl Mc Lv Ts Og
true, true, true, true, true, true, false, false,
// D END
false, false
};
static_assert(sizeof(table) / sizeof(table[0]) == static_cast<int>(El::END) + 1, "Hmm");
return table[static_cast<int>(el)];
}
inline bool is_metal(El el) { return is_metal_value(el); }
inline void set_is_metal(El el, bool v) { is_metal_value(el) = v; }
// Helper function, not public. Replaces =='s in static_assert comparisons
// that were reported to fail on i386 / GCC 13: the numbers were compared
// as 80-bit, the tabulated value was double-rounded, the literal was not.
constexpr bool ce_almost_eq(double x, double y) {
return -1e-6 < x-y && x-y < 1e-6;
}
inline double molecular_weight(El el) {
static constexpr double weights[] = {
/*X*/ 1.0,
/*H*/ 1.00794, /*He*/ 4.0026,
/*Li*/ 6.941, /*Be*/ 9.012182, /*B*/ 10.811, /*C*/ 12.0107,
/*N*/ 14.0067, /*O*/ 15.9994, /*F*/ 18.998403, /*Ne*/ 20.1797,
/*Na*/ 22.98977, /*Mg*/ 24.305, /*Al*/ 26.981539, /*Si*/ 28.0855,
/*P*/ 30.973761, /*S*/ 32.065, /*Cl*/ 35.453, /*Ar*/ 39.948,
/*K*/ 39.0983, /*Ca*/ 40.078, /*Sc*/ 44.95591, /*Ti*/ 47.867,
/*V*/ 50.9415, /*Cr*/ 51.9961, /*Mn*/ 54.93805, /*Fe*/ 55.845,
/*Co*/ 58.9332, /*Ni*/ 58.6934, /*Cu*/ 63.546, /*Zn*/ 65.38,
/*Ga*/ 69.723, /*Ge*/ 72.64, /*As*/ 74.9216, /*Se*/ 78.96,
/*Br*/ 79.904, /*Kr*/ 83.798, /*Rb*/ 85.4678, /*Sr*/ 87.62,
/*Y*/ 88.90585, /*Zr*/ 91.224, /*Nb*/ 92.9064,
/*Mo*/ 95.95, /*Tc*/ 98, /*Ru*/ 101.07, /*Rh*/ 102.9055, /*Pd*/ 106.42,
/*Ag*/ 107.8682, /*Cd*/ 112.411, /*In*/ 114.818, /*Sn*/ 118.71,
/*Sb*/ 121.76, /*Te*/ 127.6, /*I*/ 126.90447, /*Xe*/ 131.293,
/*Cs*/ 132.905, /*Ba*/ 137.327, /*La*/ 138.905, /*Ce*/ 140.116,
/*Pr*/ 140.908, /*Nd*/ 144.24, /*Pm*/ 145, /*Sm*/ 150.36,
/*Eu*/ 151.964, /*Gd*/ 157.25, /*Tb*/ 158.925, /*Dy*/ 162.5,
/*Ho*/ 164.93, /*Er*/ 167.259, /*Tm*/ 168.934, /*Yb*/ 173.05,
/*Lu*/ 174.967, /*Hf*/ 178.49, /*Ta*/ 180.948, /*W*/ 183.84,
/*Re*/ 186.207, /*Os*/ 190.23, /*Ir*/ 192.217, /*Pt*/ 195.084,
/*Au*/ 196.967, /*Hg*/ 200.59, /*Tl*/ 204.383,
/*Pb*/ 207.2, /*Bi*/ 208.98, /*Po*/ 209, /*At*/ 210, /*Rn*/ 222,
/*Fr*/ 223, /*Ra*/ 226, /*Ac*/ 227, /*Th*/ 232.038, /*Pa*/ 231.036,
/*U*/ 238.029, /*Np*/ 237, /*Pu*/ 244, /*Am*/ 243, /*Cm*/ 247,
/*Bk*/ 247, /*Cf*/ 251, /*Es*/ 252, /*Fm*/ 257, /*Md*/ 258,
/*No*/ 259, /*Lr*/ 262, /*Rf*/ 267, /*Db*/ 268, /*Sg*/ 271,
/*Bh*/ 272, /*Hs*/ 270, /*Mt*/ 276, /*Ds*/ 281, /*Rg*/ 280, /*Cn*/ 285,
/*Nh*/ 284, /*Fl*/ 289, /*Mc*/ 288, /*Lv*/ 293, /*Ts*/ 294, /*Og*/ 294,
/*D*/ 2.0141, /*END*/ 0.0
};
static_assert(ce_almost_eq(weights[static_cast<int>(El::D)], 2.0141), "Hmm");
static_assert(sizeof(weights) / sizeof(weights[0]) ==
static_cast<int>(El::END) + 1, "Hmm");
return weights[static_cast<int>(el)];
}
// Covalent radius data from https://en.wikipedia.org/wiki/Covalent_radius
// which in turn comes from
// Cordero et al (2008). "Covalent radii revisited". Dalton Trans. 21, 2832
inline float covalent_radius(El el) {
static constexpr float radii[] = {
/*X*/ 0.50f,
/*H*/ 0.31f, /*He*/ 0.28f,
/*Li*/ 1.28f, /*Be*/ 0.96f, /*B*/ 0.84f, /*C*/ 0.73f, /*N*/ 0.71f,
/*O*/ 0.66f, /*F*/ 0.57f, /*Ne*/ 0.58f,
/*Na*/ 1.66f, /*Mg*/ 1.41f, /*Al*/ 1.21f, /*Si*/ 1.11f, /*P*/ 1.07f,
/*S*/ 1.05f, /*Cl*/ 1.02f, /*Ar*/ 1.06f,
/*K*/ 2.03f, /*Ca*/ 1.76f, /*Sc*/ 1.70f, /*Ti*/ 1.60f, /*V*/ 1.53f,
/*Cr*/ 1.39f, /*Mn*/ 1.39f, /*Fe*/ 1.32f, /*Co*/ 1.26f, /*Ni*/ 1.24f,
/*Cu*/ 1.32f, /*Zn*/ 1.22f, /*Ga*/ 1.22f, /*Ge*/ 1.20f, /*As*/ 1.19f,
/*Se*/ 1.20f, /*Br*/ 1.20f, /*Kr*/ 1.16f,
/*Rb*/ 2.20f, /*Sr*/ 1.95f, /*Y*/ 1.90f, /*Zr*/ 1.75f, /*Nb*/ 1.64f,
/*Mo*/ 1.54f, /*Tc*/ 1.47f, /*Ru*/ 1.46f, /*Rh*/ 1.42f, /*Pd*/ 1.39f,
/*Ag*/ 1.45f, /*Cd*/ 1.44f, /*In*/ 1.42f, /*Sn*/ 1.39f, /*Sb*/ 1.39f,
/*Te*/ 1.38f, /*I*/ 1.39f, /*Xe*/ 1.40f,
/*Cs*/ 2.44f, /*Ba*/ 2.15f, /*La*/ 2.07f, /*Ce*/ 2.04f, /*Pr*/ 2.03f,
/*Nd*/ 2.01f, /*Pm*/ 1.99f, /*Sm*/ 1.98f, /*Eu*/ 1.98f, /*Gd*/ 1.96f,
/*Tb*/ 1.94f, /*Dy*/ 1.92f, /*Ho*/ 1.92f, /*Er*/ 1.89f, /*Tm*/ 1.90f,
/*Yb*/ 1.87f, /*Lu*/ 1.75f, /*Hf*/ 1.87f, /*Ta*/ 1.70f, /*W*/ 1.62f,
/*Re*/ 1.51f, /*Os*/ 1.44f, /*Ir*/ 1.41f, /*Pt*/ 1.36f, /*Au*/ 1.36f,
/*Hg*/ 1.32f, /*Tl*/ 1.45f, /*Pb*/ 1.46f, /*Bi*/ 1.48f, /*Po*/ 1.40f,
/*At*/ 1.50f, /*Rn*/ 1.50f,
/*Fr*/ 2.60f, /*Ra*/ 2.21f, /*Ac*/ 2.15f, /*Th*/ 2.06f, /*Pa*/ 2.00f,
/*U*/ 1.96f, /*Np*/ 1.90f, /*Pu*/ 1.87f, /*Am*/ 1.80f, /*Cm*/ 1.69f,
/*Bk*/ 1.68f, /*Cf*/ 1.68f, /*Es*/ 1.65f, /*Fm*/ 1.67f, /*Md*/ 1.73f,
/*No*/ 1.76f, /*Lr*/ 1.61f, /*Rf*/ 1.57f, /*Db*/ 1.49f, /*Sg*/ 1.43f,
/*Bh*/ 1.41f, /*Hs*/ 1.34f, /*Mt*/ 1.29f, /*Ds*/ 1.28f, /*Rg*/ 1.21f,
/*Cn*/ 1.22f, /*Nh*/ 1.50f, /*Fl*/ 1.50f, /*Mc*/ 1.50f, /*Lv*/ 1.50f,
/*Ts*/ 1.50f, /*Og*/ 1.50f,
/*D*/ 0.31f, /*END*/ 0.0f
};
static_assert(ce_almost_eq(radii[static_cast<int>(El::D)], 0.31f), "Hmm");
static_assert(sizeof(radii) / sizeof(radii[0]) ==
static_cast<int>(El::END) + 1, "Hmm");
return radii[static_cast<int>(el)];
}
// Van der Waals radii. Taken from:
// https://en.wikipedia.org/wiki/Atomic_radii_of_the_elements_(data_page)
// which cites two sources:
// J. Phys. Chem. A 2009, 113, 19, 5806 https://doi.org/10.1021/jp8111556
// J. Phys. Chem. 1964, 68, 3, 441 https://doi.org/10.1021/j100785a001
// Missing values (and values for a lot of elements were missing)
// were substituted with values from cctbx van_der_waals_radii.py.
inline float vdw_radius(El el) {
static constexpr float radii[] = {
/*X*/ 1.00f,
/*H*/ 1.20f, /*He*/ 1.40f,
/*Li*/ 1.82f, /*Be*/ 1.53f, /*B*/ 1.92f, /*C*/ 1.70f, /*N*/ 1.55f,
/*O*/ 1.52f, /*F*/ 1.47f, /*Ne*/ 1.54f,
/*Na*/ 2.27f, /*Mg*/ 1.73f, /*Al*/ 1.84f, /*Si*/ 2.10f, /*P*/ 1.80f,
/*S*/ 1.80f, /*Cl*/ 1.75f, /*Ar*/ 1.88f,
/*K*/ 2.75f, /*Ca*/ 2.31f, /*Sc*/ 2.11f, /*Ti*/ 1.95f, /*V*/ 1.06f,
/*Cr*/ 1.13f, /*Mn*/ 1.19f, /*Fe*/ 1.26f, /*Co*/ 1.13f, /*Ni*/ 1.63f,
/*Cu*/ 1.40f, /*Zn*/ 1.39f, /*Ga*/ 1.87f, /*Ge*/ 2.11f, /*As*/ 1.85f,
/*Se*/ 1.90f, /*Br*/ 1.85f, /*Kr*/ 2.02f,
/*Rb*/ 3.03f, /*Sr*/ 2.49f, /*Y*/ 1.61f, /*Zr*/ 1.42f, /*Nb*/ 1.33f,
/*Mo*/ 1.75f, /*Tc*/ 2.00f, /*Ru*/ 1.20f, /*Rh*/ 1.22f, /*Pd*/ 1.63f,
/*Ag*/ 1.72f, /*Cd*/ 1.58f, /*In*/ 1.93f, /*Sn*/ 2.17f, /*Sb*/ 2.06f,
/*Te*/ 2.06f, /*I*/ 1.98f, /*Xe*/ 2.16f,
/*Cs*/ 3.43f, /*Ba*/ 2.68f, /*La*/ 1.83f, /*Ce*/ 1.86f, /*Pr*/ 1.62f,
/*Nd*/ 1.79f, /*Pm*/ 1.76f, /*Sm*/ 1.74f, /*Eu*/ 1.96f, /*Gd*/ 1.69f,
/*Tb*/ 1.66f, /*Dy*/ 1.63f, /*Ho*/ 1.61f, /*Er*/ 1.59f, /*Tm*/ 1.57f,
/*Yb*/ 1.54f, /*Lu*/ 1.53f, /*Hf*/ 1.40f, /*Ta*/ 1.22f, /*W*/ 1.26f,
/*Re*/ 1.30f, /*Os*/ 1.58f, /*Ir*/ 1.22f, /*Pt*/ 1.75f, /*Au*/ 1.66f,
/*Hg*/ 1.55f, /*Tl*/ 1.96f, /*Pb*/ 2.02f, /*Bi*/ 2.07f, /*Po*/ 1.97f,
/*At*/ 2.02f, /*Rn*/ 2.20f,
/*Fr*/ 3.48f, /*Ra*/ 2.83f, /*Ac*/ 2.12f, /*Th*/ 1.84f, /*Pa*/ 1.60f,
/*U*/ 1.86f, /*Np*/ 1.71f, /*Pu*/ 1.67f, /*Am*/ 1.66f, /*Cm*/ 1.65f,
/*Bk*/ 1.64f, /*Cf*/ 1.63f, /*Es*/ 1.62f, /*Fm*/ 1.61f, /*Md*/ 1.60f,
/*No*/ 1.59f, /*Lr*/ 1.58f, /*Rf*/ 1.00f, /*Db*/ 1.00f, /*Sg*/ 1.00f,
/*Bh*/ 1.00f, /*Hs*/ 1.00f, /*Mt*/ 1.00f, /*Ds*/ 1.00f, /*Rg*/ 1.00f,
/*Cn*/ 1.00f, /*Nh*/ 1.00f, /*Fl*/ 1.00f, /*Mc*/ 1.00f, /*Lv*/ 1.00f,
/*Ts*/ 1.00f, /*Og*/ 1.00f,
/*D*/ 1.20f, /*END*/0.f
};
static_assert(ce_almost_eq(radii[static_cast<int>(El::D)], 1.2f), "Hmm");
static_assert(sizeof(radii) / sizeof(radii[0]) ==
static_cast<int>(El::END) + 1, "Hmm");
return radii[static_cast<int>(el)];
}
typedef const char elname_t[3];
inline const char* element_name(El el) {
static constexpr elname_t names[] = {
"X", "H", "He", "Li", "Be", "B", "C", "N", "O", "F", "Ne",
"Na", "Mg", "Al", "Si", "P", "S", "Cl", "Ar",
"K", "Ca", "Sc", "Ti", "V", "Cr", "Mn", "Fe", "Co",
"Ni", "Cu", "Zn", "Ga", "Ge", "As", "Se", "Br", "Kr",
"Rb", "Sr", "Y", "Zr", "Nb", "Mo", "Tc", "Ru", "Rh",
"Pd", "Ag", "Cd", "In", "Sn", "Sb", "Te", "I", "Xe",
"Cs", "Ba", "La", "Ce", "Pr", "Nd", "Pm", "Sm", "Eu",
"Gd", "Tb", "Dy", "Ho", "Er", "Tm", "Yb", "Lu",
"Hf", "Ta", "W", "Re", "Os", "Ir", "Pt", "Au", "Hg",
"Tl", "Pb", "Bi", "Po", "At", "Rn",
"Fr", "Ra", "Ac", "Th", "Pa", "U", "Np", "Pu", "Am",
"Cm", "Bk", "Cf", "Es", "Fm", "Md", "No", "Lr",
"Rf", "Db", "Sg", "Bh", "Hs", "Mt", "Ds", "Rg", "Cn",
"Nh", "Fl", "Mc", "Lv", "Ts", "Og",
"D", ""
};
static_assert(static_cast<int>(El::Og) == 118, "Hmm");
static_assert(names[118][0] == 'O', "Hmm");
static_assert(sizeof(names) / sizeof(names[0]) ==
static_cast<int>(El::END) + 1, "Hmm");
return names[static_cast<int>(el)];
}
inline elname_t& element_uppercase_name(El el) {
static constexpr elname_t names[] = {
"X", "H", "HE", "LI", "BE", "B", "C", "N", "O", "F", "NE",
"NA", "MG", "AL", "SI", "P", "S", "CL", "AR",
"K", "CA", "SC", "TI", "V", "CR", "MN", "FE", "CO",
"NI", "CU", "ZN", "GA", "GE", "AS", "SE", "BR", "KR",
"RB", "SR", "Y", "ZR", "NB", "MO", "TC", "RU", "RH",
"PD", "AG", "CD", "IN", "SN", "SB", "TE", "I", "XE",
"CS", "BA", "LA", "CE", "PR", "ND", "PM", "SM", "EU",
"GD", "TB", "DY", "HO", "ER", "TM", "YB", "LU",
"HF", "TA", "W", "RE", "OS", "IR", "PT", "AU", "HG",
"TL", "PB", "BI", "PO", "AT", "RN",
"FR", "RA", "AC", "TH", "PA", "U", "NP", "PU", "AM",
"CM", "BK", "CF", "ES", "FM", "MD", "NO", "LR",
"RF", "DB", "SG", "BH", "HS", "MT", "DS", "RG", "CN",
"NH", "FL", "MC", "LV", "TS", "OG",
"D", "", ""
};
static_assert(sizeof(names) / sizeof(names[0]) == 122, "not 122");
return names[static_cast<int>(el)];
}
namespace impl {
// all the most common elements in the PDB are single-letter
inline El find_single_letter_element(char c) {
switch (c) {
case 'H': return El::H;
case 'B': return El::B;
case 'C': return El::C;
case 'N': return El::N;
case 'O': return El::O;
case 'F': return El::F;
case 'P': return El::P;
case 'S': return El::S;
case 'K': return El::K;
case 'V': return El::V;
case 'Y': return El::Y;
case 'I': return El::I;
case 'W': return El::W;
case 'U': return El::U;
case 'D': return El::D;
default: return El::X;
}
}
} // namespace impl
inline El find_element(const char* symbol) {
if (symbol == nullptr || symbol[0] == '\0')
return El::X;
char first = symbol[0] & ~0x20; // lower -> upper, space -> NUL
char second = symbol[1] & ~0x20;
if (first == '\0')
return impl::find_single_letter_element(second);
// To handle symbol being "X\n" we have the condition below.
// In addition to \t, \v, \r and \n it catches also !"#$%&'()*+,- and
// some control characters - inconsistent but not necessarily bad.
if (second < 14)
return impl::find_single_letter_element(first);
elname_t* names = &element_uppercase_name(El::X);
for (int i = 0; i != 120; ++i) {
if (names[i][0] == first && names[i][1] == second)
return static_cast<El>(i);
}
return El::X;
}
struct Element {
El elem;
/*implicit*/ Element(El e) noexcept : elem(e) {}
explicit Element(const char* str) noexcept : elem(find_element(str)) {}
explicit Element(const std::string& s) noexcept : Element(s.c_str()) {}
explicit Element(int number) noexcept
: elem(static_cast<El>(number > 0 && number <= 118 ? number : 0)) {}
/*implicit*/ operator El() const { return elem; }
bool operator==(El e) const { return elem == e; }
bool operator!=(El e) const { return elem != e; }
// avoid Clang -Wambiguous-reversed-operator in C++20
bool operator!=(Element o) const { return elem != o.elem; }
int ordinal() const { return static_cast<int>(elem); }
int atomic_number() const { return elem == El::D ? 1 : ordinal(); }
bool is_hydrogen() const { return gemmi::is_hydrogen(elem); }
double weight() const { return molecular_weight(elem); }
float covalent_r() const { return covalent_radius(elem); }
float vdw_r() const { return vdw_radius(elem); }
bool is_metal() const { return gemmi::is_metal(elem); }
// return name such as Mg (not MG)
const char* name() const { return element_name(elem); }
// return uppercase name such as MG
const char* uname() const { return element_uppercase_name(elem); }
};
} // namespace gemmi
#endif