From 5addb4483c72494fa80748f9997cf0b797b2fc4c Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Sun, 12 Jul 2026 20:35:02 +0200 Subject: [PATCH 01/17] gemmi_gph: vendor the model / structure-factor headers into the single gemmi target Extend the vendored GEMMI subset (v0.7.5) with the atomic-model, structure-factor, bulk-solvent and map machinery so the whole thing builds as one static `gemmi` library instead of a separate target: - add the model/SF/map sources compiled into `gemmi`: pdb, resinfo, polyheur, calculate, eig3, ccp4 - add the v0.7.5 headers these pull in (model.hpp, dencalc.hpp, sfcalc.hpp, solmask.hpp, scaling.hpp, fourier.hpp, grid.hpp, ccp4.hpp, it92.hpp, ...) plus third_party/pocketfft (FFT), half, tinydir Only the low-level string/math/symmetry headers were present before; this makes the vendored copy a complete, self-consistent gemmi that can read a PDB and do density / structure-factor / map calculations. Co-Authored-By: Claude Opus 4.8 (1M context) --- gemmi_gph/CMakeLists.txt | 9 +- gemmi_gph/calculate.cpp | 77 + gemmi_gph/ccp4.cpp | 30 + gemmi_gph/eig3.cpp | 238 + gemmi_gph/gemmi/ace_cc.hpp | 27 + gemmi_gph/gemmi/ace_graph.hpp | 76 + gemmi_gph/gemmi/acedrg_tables.hpp | 395 ++ gemmi_gph/gemmi/addends.hpp | 35 + gemmi_gph/gemmi/align.hpp | 317 ++ gemmi_gph/gemmi/assembly.hpp | 129 + gemmi_gph/gemmi/asudata.hpp | 254 + gemmi_gph/gemmi/asumask.hpp | 344 ++ gemmi_gph/gemmi/bessel.hpp | 128 + gemmi_gph/gemmi/binner.hpp | 178 + gemmi_gph/gemmi/blob.hpp | 140 + gemmi_gph/gemmi/bond_idx.hpp | 92 + gemmi_gph/gemmi/c4322.hpp | 165 + gemmi_gph/gemmi/calculate.hpp | 224 + gemmi_gph/gemmi/ccp4.hpp | 504 ++ gemmi_gph/gemmi/chemcomp.hpp | 747 +++ gemmi_gph/gemmi/cif.hpp | 385 ++ gemmi_gph/gemmi/cif2mtz.hpp | 553 ++ gemmi_gph/gemmi/cifdoc.hpp | 1204 +++++ gemmi_gph/gemmi/contact.hpp | 135 + gemmi_gph/gemmi/crd.hpp | 24 + gemmi_gph/gemmi/ddl.hpp | 85 + gemmi_gph/gemmi/dencalc.hpp | 209 + gemmi_gph/gemmi/dirwalk.hpp | 232 + gemmi_gph/gemmi/dssp.hpp | 167 + gemmi_gph/gemmi/ecalc.hpp | 97 + gemmi_gph/gemmi/eig3.hpp | 15 + gemmi_gph/gemmi/elem.hpp | 391 ++ gemmi_gph/gemmi/enumstr.hpp | 110 + gemmi_gph/gemmi/flat.hpp | 50 + gemmi_gph/gemmi/floodfill.hpp | 162 + gemmi_gph/gemmi/formfact.hpp | 209 + gemmi_gph/gemmi/fourier.hpp | 357 ++ gemmi_gph/gemmi/fprime.hpp | 34 + gemmi_gph/gemmi/fstream.hpp | 88 + gemmi_gph/gemmi/glob.hpp | 43 + gemmi_gph/gemmi/grid.hpp | 819 +++ gemmi_gph/gemmi/intensit.hpp | 238 + gemmi_gph/gemmi/interop.hpp | 59 + gemmi_gph/gemmi/it92.hpp | 426 ++ gemmi_gph/gemmi/json.hpp | 33 + gemmi_gph/gemmi/levmar.hpp | 267 + gemmi_gph/gemmi/linkhunt.hpp | 179 + gemmi_gph/gemmi/metadata.hpp | 390 ++ gemmi_gph/gemmi/mmcif.hpp | 112 + gemmi_gph/gemmi/mmcif_impl.hpp | 37 + gemmi_gph/gemmi/mmdb.hpp | 501 ++ gemmi_gph/gemmi/mmread.hpp | 122 + gemmi_gph/gemmi/mmread_gz.hpp | 29 + gemmi_gph/gemmi/model.hpp | 1107 ++++ gemmi_gph/gemmi/modify.hpp | 335 ++ gemmi_gph/gemmi/monlib.hpp | 256 + gemmi_gph/gemmi/mtz2cif.hpp | 115 + gemmi_gph/gemmi/neighbor.hpp | 376 ++ gemmi_gph/gemmi/neutron92.hpp | 87 + gemmi_gph/gemmi/numb.hpp | 58 + gemmi_gph/gemmi/pdb.hpp | 87 + gemmi_gph/gemmi/pdb_id.hpp | 71 + gemmi_gph/gemmi/pirfasta.hpp | 86 + gemmi_gph/gemmi/polyheur.hpp | 203 + gemmi_gph/gemmi/pymol_select.hpp | 649 +++ gemmi_gph/gemmi/qcp.hpp | 356 ++ gemmi_gph/gemmi/read_cif.hpp | 36 + gemmi_gph/gemmi/recgrid.hpp | 175 + gemmi_gph/gemmi/refln.hpp | 253 + gemmi_gph/gemmi/resinfo.hpp | 106 + gemmi_gph/gemmi/riding_h.hpp | 33 + gemmi_gph/gemmi/scaling.hpp | 435 ++ gemmi_gph/gemmi/select.hpp | 229 + gemmi_gph/gemmi/seqalign.hpp | 324 ++ gemmi_gph/gemmi/seqid.hpp | 168 + gemmi_gph/gemmi/seqtools.hpp | 68 + gemmi_gph/gemmi/serialize.hpp | 190 + gemmi_gph/gemmi/sfcalc.hpp | 146 + gemmi_gph/gemmi/small.hpp | 248 + gemmi_gph/gemmi/smcif.hpp | 190 + gemmi_gph/gemmi/solmask.hpp | 538 ++ gemmi_gph/gemmi/span.hpp | 137 + gemmi_gph/gemmi/stats.hpp | 136 + gemmi_gph/gemmi/third_party/half.hpp | 4603 +++++++++++++++++ .../gemmi/third_party/pocketfft_hdronly.h | 3744 ++++++++++++++ gemmi_gph/gemmi/third_party/tinydir.h | 838 +++ gemmi_gph/gemmi/to_chemcomp.hpp | 108 + gemmi_gph/gemmi/to_cif.hpp | 279 + gemmi_gph/gemmi/to_json.hpp | 53 + gemmi_gph/gemmi/to_mmcif.hpp | 75 + gemmi_gph/gemmi/to_pdb.hpp | 67 + gemmi_gph/gemmi/topo.hpp | 307 ++ gemmi_gph/gemmi/twin.hpp | 226 + gemmi_gph/gemmi/version.hpp | 10 + gemmi_gph/gemmi/xds2mtz.hpp | 167 + gemmi_gph/pdb.cpp | 1269 +++++ gemmi_gph/polyheur.cpp | 463 ++ gemmi_gph/resinfo.cpp | 884 ++++ 98 files changed, 32160 insertions(+), 2 deletions(-) create mode 100644 gemmi_gph/calculate.cpp create mode 100644 gemmi_gph/ccp4.cpp create mode 100644 gemmi_gph/eig3.cpp create mode 100644 gemmi_gph/gemmi/ace_cc.hpp create mode 100644 gemmi_gph/gemmi/ace_graph.hpp create mode 100644 gemmi_gph/gemmi/acedrg_tables.hpp create mode 100644 gemmi_gph/gemmi/addends.hpp create mode 100644 gemmi_gph/gemmi/align.hpp create mode 100644 gemmi_gph/gemmi/assembly.hpp create mode 100644 gemmi_gph/gemmi/asudata.hpp create mode 100644 gemmi_gph/gemmi/asumask.hpp create mode 100644 gemmi_gph/gemmi/bessel.hpp create mode 100644 gemmi_gph/gemmi/binner.hpp create mode 100644 gemmi_gph/gemmi/blob.hpp create mode 100644 gemmi_gph/gemmi/bond_idx.hpp create mode 100644 gemmi_gph/gemmi/c4322.hpp create mode 100644 gemmi_gph/gemmi/calculate.hpp create mode 100644 gemmi_gph/gemmi/ccp4.hpp create mode 100644 gemmi_gph/gemmi/chemcomp.hpp create mode 100644 gemmi_gph/gemmi/cif.hpp create mode 100644 gemmi_gph/gemmi/cif2mtz.hpp create mode 100644 gemmi_gph/gemmi/cifdoc.hpp create mode 100644 gemmi_gph/gemmi/contact.hpp create mode 100644 gemmi_gph/gemmi/crd.hpp create mode 100644 gemmi_gph/gemmi/ddl.hpp create mode 100644 gemmi_gph/gemmi/dencalc.hpp create mode 100644 gemmi_gph/gemmi/dirwalk.hpp create mode 100644 gemmi_gph/gemmi/dssp.hpp create mode 100644 gemmi_gph/gemmi/ecalc.hpp create mode 100644 gemmi_gph/gemmi/eig3.hpp create mode 100644 gemmi_gph/gemmi/elem.hpp create mode 100644 gemmi_gph/gemmi/enumstr.hpp create mode 100644 gemmi_gph/gemmi/flat.hpp create mode 100644 gemmi_gph/gemmi/floodfill.hpp create mode 100644 gemmi_gph/gemmi/formfact.hpp create mode 100644 gemmi_gph/gemmi/fourier.hpp create mode 100644 gemmi_gph/gemmi/fprime.hpp create mode 100644 gemmi_gph/gemmi/fstream.hpp create mode 100644 gemmi_gph/gemmi/glob.hpp create mode 100644 gemmi_gph/gemmi/grid.hpp create mode 100644 gemmi_gph/gemmi/intensit.hpp create mode 100644 gemmi_gph/gemmi/interop.hpp create mode 100644 gemmi_gph/gemmi/it92.hpp create mode 100644 gemmi_gph/gemmi/json.hpp create mode 100644 gemmi_gph/gemmi/levmar.hpp create mode 100644 gemmi_gph/gemmi/linkhunt.hpp create mode 100644 gemmi_gph/gemmi/metadata.hpp create mode 100644 gemmi_gph/gemmi/mmcif.hpp create mode 100644 gemmi_gph/gemmi/mmcif_impl.hpp create mode 100644 gemmi_gph/gemmi/mmdb.hpp create mode 100644 gemmi_gph/gemmi/mmread.hpp create mode 100644 gemmi_gph/gemmi/mmread_gz.hpp create mode 100644 gemmi_gph/gemmi/model.hpp create mode 100644 gemmi_gph/gemmi/modify.hpp create mode 100644 gemmi_gph/gemmi/monlib.hpp create mode 100644 gemmi_gph/gemmi/mtz2cif.hpp create mode 100644 gemmi_gph/gemmi/neighbor.hpp create mode 100644 gemmi_gph/gemmi/neutron92.hpp create mode 100644 gemmi_gph/gemmi/numb.hpp create mode 100644 gemmi_gph/gemmi/pdb.hpp create mode 100644 gemmi_gph/gemmi/pdb_id.hpp create mode 100644 gemmi_gph/gemmi/pirfasta.hpp create mode 100644 gemmi_gph/gemmi/polyheur.hpp create mode 100644 gemmi_gph/gemmi/pymol_select.hpp create mode 100644 gemmi_gph/gemmi/qcp.hpp create mode 100644 gemmi_gph/gemmi/read_cif.hpp create mode 100644 gemmi_gph/gemmi/recgrid.hpp create mode 100644 gemmi_gph/gemmi/refln.hpp create mode 100644 gemmi_gph/gemmi/resinfo.hpp create mode 100644 gemmi_gph/gemmi/riding_h.hpp create mode 100644 gemmi_gph/gemmi/scaling.hpp create mode 100644 gemmi_gph/gemmi/select.hpp create mode 100644 gemmi_gph/gemmi/seqalign.hpp create mode 100644 gemmi_gph/gemmi/seqid.hpp create mode 100644 gemmi_gph/gemmi/seqtools.hpp create mode 100644 gemmi_gph/gemmi/serialize.hpp create mode 100644 gemmi_gph/gemmi/sfcalc.hpp create mode 100644 gemmi_gph/gemmi/small.hpp create mode 100644 gemmi_gph/gemmi/smcif.hpp create mode 100644 gemmi_gph/gemmi/solmask.hpp create mode 100644 gemmi_gph/gemmi/span.hpp create mode 100644 gemmi_gph/gemmi/stats.hpp create mode 100644 gemmi_gph/gemmi/third_party/half.hpp create mode 100644 gemmi_gph/gemmi/third_party/pocketfft_hdronly.h create mode 100644 gemmi_gph/gemmi/third_party/tinydir.h create mode 100644 gemmi_gph/gemmi/to_chemcomp.hpp create mode 100644 gemmi_gph/gemmi/to_cif.hpp create mode 100644 gemmi_gph/gemmi/to_json.hpp create mode 100644 gemmi_gph/gemmi/to_mmcif.hpp create mode 100644 gemmi_gph/gemmi/to_pdb.hpp create mode 100644 gemmi_gph/gemmi/topo.hpp create mode 100644 gemmi_gph/gemmi/twin.hpp create mode 100644 gemmi_gph/gemmi/version.hpp create mode 100644 gemmi_gph/gemmi/xds2mtz.hpp create mode 100644 gemmi_gph/pdb.cpp create mode 100644 gemmi_gph/polyheur.cpp create mode 100644 gemmi_gph/resinfo.cpp diff --git a/gemmi_gph/CMakeLists.txt b/gemmi_gph/CMakeLists.txt index c7353257..99e8fbab 100644 --- a/gemmi_gph/CMakeLists.txt +++ b/gemmi_gph/CMakeLists.txt @@ -1,8 +1,13 @@ -ADD_LIBRARY(gemmi STATIC symmetry.cpp gz.cpp mtz.cpp sprintf.cpp xds_ascii.cpp +# Vendored subset of GEMMI (Global Phasing), v0.7.5. A single static `gemmi` target with all +# the compiled sources: symmetry/MTZ/XDS I/O plus the atomic-model reading and structure-factor / +# bulk-solvent / map machinery (pdb, ccp4, calculate, eig3, resinfo, polyheur). +ADD_LIBRARY(gemmi STATIC + symmetry.cpp gz.cpp mtz.cpp sprintf.cpp xds_ascii.cpp + pdb.cpp resinfo.cpp polyheur.cpp calculate.cpp eig3.cpp ccp4.cpp gemmi/cellred.hpp gemmi/symmetry.hpp gemmi/fail.hpp gemmi/unitcell.hpp gemmi/math.hpp) TARGET_INCLUDE_DIRECTORIES(gemmi PUBLIC .) -TARGET_LINK_LIBRARIES(gemmi PRIVATE ZLIB::ZLIB) \ No newline at end of file +TARGET_LINK_LIBRARIES(gemmi PRIVATE ZLIB::ZLIB) diff --git a/gemmi_gph/calculate.cpp b/gemmi_gph/calculate.cpp new file mode 100644 index 00000000..1c454918 --- /dev/null +++ b/gemmi_gph/calculate.cpp @@ -0,0 +1,77 @@ +// Copyright 2018 Global Phasing Ltd. + +#include "gemmi/calculate.hpp" +#include "gemmi/eig3.hpp" + +namespace gemmi { + +std::array find_best_plane(const std::vector& atoms) { + Vec3 mean; + for (const Atom* atom : atoms) + mean += atom->pos; + mean /= (double) atoms.size(); + SMat33 m{0, 0, 0, 0, 0, 0}; + for (const Atom* atom : atoms) { + Vec3 p = Vec3(atom->pos) - mean; + m.u11 += p.x * p.x; + m.u22 += p.y * p.y; + m.u33 += p.z * p.z; + m.u12 += p.x * p.y; + m.u13 += p.x * p.z; + m.u23 += p.y * p.z; + } + double eig[3] = {}; + Mat33 V = eigen_decomposition(m, eig); + int smallest_idx = std::fabs(eig[0]) < std::fabs(eig[1]) ? 0 : 1; + if (std::fabs(eig[2]) < std::fabs(eig[smallest_idx])) + smallest_idx = 2; + Vec3 eigvec = V.column_copy(smallest_idx); + if (eigvec.x < 0) + eigvec *= -1; + return {{eigvec.x, eigvec.y, eigvec.z, -eigvec.dot(mean)}}; +} + +FTransform parse_triplet_as_ftransform(const std::string& s) { + // c.f. parse_triplet() + if (std::count(s.begin(), s.end(), ',') != 2) + fail("expected exactly two commas in triplet"); + size_t comma1 = s.find(','); + size_t comma2 = s.find(',', comma1 + 1); + FTransform frac_tr; + auto set_ftransform_row = [](FTransform& tr, int i, const std::string& part) { + const double mult = 1. / Op::DEN; + double decfr[4] = {}; + char x_ = 'x'; + const auto op_row = parse_triplet_part(part, x_, decfr); + for (int j = 0; j < 3; ++j) + tr.mat[i][j] = decfr[j] == 0. ? mult * op_row[j] : decfr[j]; + tr.vec.at(i) = decfr[3] == 0. ? mult * op_row[3] : decfr[3]; + }; + set_ftransform_row(frac_tr, 0, s.substr(0, comma1)); + set_ftransform_row(frac_tr, 1, s.substr(comma1 + 1, comma2 - (comma1 + 1))); + set_ftransform_row(frac_tr, 2, s.substr(comma2 + 1)); + return frac_tr; +} + +SMat33 calculate_u_from_tls(const TlsGroup& tls, const Position& pos) { + Position r = (pos - tls.origin) * rad(1); + SMat33 l_contrib = { + r.y * r.y * tls.L.u33 + r.z * r.z * tls.L.u22 - 2 * r.z * r.y * tls.L.u23, + r.x * r.x * tls.L.u33 + r.z * r.z * tls.L.u11 - 2 * r.z * r.x * tls.L.u13, + r.x * r.x * tls.L.u22 + r.y * r.y * tls.L.u11 - 2 * r.y * r.x * tls.L.u12, + -r.x * r.y * tls.L.u33 + r.z * ( r.x * tls.L.u23 + r.y * tls.L.u13 - r.z * tls.L.u12), + -r.x * r.z * tls.L.u22 + r.y * ( r.x * tls.L.u23 - r.y * tls.L.u13 + r.z * tls.L.u12), + -r.y * r.z * tls.L.u11 + r.x * (-r.x * tls.L.u23 + r.y * tls.L.u13 + r.z * tls.L.u12) + }; + SMat33 s_contrib = { + 2 * (tls.S[1][0] * r.z - tls.S[2][0] * r.y), + 2 * (tls.S[2][1] * r.x - tls.S[0][1] * r.z), + 2 * (tls.S[0][2] * r.y - tls.S[1][2] * r.x), + tls.S[2][0] * r.x - tls.S[2][1] * r.y + (tls.S[1][1] - tls.S[0][0]) * r.z, + tls.S[1][2] * r.z - tls.S[1][0] * r.x + (tls.S[0][0] - tls.S[2][2]) * r.y, + tls.S[0][1] * r.y - tls.S[0][2] * r.z + (tls.S[2][2] - tls.S[1][1]) * r.x + }; + return tls.T + l_contrib + s_contrib; +} + +} // namespace gemmi diff --git a/gemmi_gph/ccp4.cpp b/gemmi_gph/ccp4.cpp new file mode 100644 index 00000000..f1e073cb --- /dev/null +++ b/gemmi_gph/ccp4.cpp @@ -0,0 +1,30 @@ +// Copyright 2021 Global Phasing Ltd. + +#include "gemmi/ccp4.hpp" +#include "gemmi/gz.hpp" // for MaybeGzipped + +namespace gemmi { + +Ccp4 read_ccp4_map(const std::string& path, bool setup) { + Ccp4 ccp4; + ccp4.read_ccp4(MaybeGzipped(path)); + if (setup) + ccp4.setup(NAN); + return ccp4; +} + +Ccp4 read_ccp4_mask(const std::string& path, bool setup) { + Ccp4 ccp4; + ccp4.read_ccp4(MaybeGzipped(path)); + if (setup) + ccp4.setup(-1); + return ccp4; +} + +Ccp4Base read_ccp4_header(const std::string& path) { + Ccp4Base ccp4; + ccp4.read_ccp4_header_(nullptr, *MaybeGzipped(path).create_stream(), path); + return ccp4; +} + +} // namespace gemmi diff --git a/gemmi_gph/eig3.cpp b/gemmi_gph/eig3.cpp new file mode 100644 index 00000000..060ccf76 --- /dev/null +++ b/gemmi_gph/eig3.cpp @@ -0,0 +1,238 @@ +// Based on public domain code from Connelly Barnes: +// http://barnesc.blogspot.com/2007/02/eigenvectors-of-3x3-symmetric-matrix.html +// which in turn is based on the public domain Java Matrix library JAMA. + +#include +#include + +// Symmetric Householder reduction to tridiagonal form. +// This is derived from the Algol procedures tred2 by +// Bowdler, Martin, Reinsch, and Wilkinson, Handbook for +// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding +// Fortran subroutine in EISPACK. +static void tred2(gemmi::Mat33& V, double d[3], double e[3]) { + for (int j = 0; j < 3; j++) { + d[j] = V[3-1][j]; + } + + // Householder reduction to tridiagonal form. + for (int i = 3-1; i > 0; i--) { + // Scale to avoid under/overflow. + double scale = 0.0; + double h = 0.0; + for (int k = 0; k < i; k++) { + scale = scale + std::fabs(d[k]); + } + if (scale == 0.0) { + e[i] = d[i-1]; + for (int j = 0; j < i; j++) { + d[j] = V[i-1][j]; + V[i][j] = 0.0; + V[j][i] = 0.0; + } + } else { + // Generate Householder vector. + for (int k = 0; k < i; k++) { + d[k] /= scale; + h += d[k] * d[k]; + } + double f = d[i-1]; + double g = sqrt(h); + if (f > 0) { + g = -g; + } + e[i] = scale * g; + h = h - f * g; + d[i-1] = f - g; + for (int j = 0; j < i; j++) { + e[j] = 0.0; + } + + // Apply similarity transformation to remaining columns. + for (int j = 0; j < i; j++) { + f = d[j]; + V[j][i] = f; + g = e[j] + V[j][j] * f; + for (int k = j+1; k <= i-1; k++) { + g += V[k][j] * d[k]; + e[k] += V[k][j] * f; + } + e[j] = g; + } + f = 0.0; + for (int j = 0; j < i; j++) { + e[j] /= h; + f += e[j] * d[j]; + } + double hh = f / (h + h); + for (int j = 0; j < i; j++) { + e[j] -= hh * d[j]; + } + for (int j = 0; j < i; j++) { + f = d[j]; + g = e[j]; + for (int k = j; k <= i-1; k++) { + V[k][j] -= (f * e[k] + g * d[k]); + } + d[j] = V[i-1][j]; + V[i][j] = 0.0; + } + } + d[i] = h; + } + + // Accumulate transformations. + for (int i = 0; i < 3-1; i++) { + V[3-1][i] = V[i][i]; + V[i][i] = 1.0; + double h = d[i+1]; + if (h != 0.0) { + for (int k = 0; k <= i; k++) { + d[k] = V[k][i+1] / h; + } + for (int j = 0; j <= i; j++) { + double g = 0.0; + for (int k = 0; k <= i; k++) { + g += V[k][i+1] * V[k][j]; + } + for (int k = 0; k <= i; k++) { + V[k][j] -= g * d[k]; + } + } + } + for (int k = 0; k <= i; k++) { + V[k][i+1] = 0.0; + } + } + for (int j = 0; j < 3; j++) { + d[j] = V[3-1][j]; + V[3-1][j] = 0.0; + } + V[3-1][3-1] = 1.0; + e[0] = 0.0; +} + +// Symmetric tridiagonal QL algorithm. +// This is derived from the Algol procedures tql2, by +// Bowdler, Martin, Reinsch, and Wilkinson, Handbook for +// Auto. Comp., Vol.ii-Linear Algebra, and the corresponding +// Fortran subroutine in EISPACK. +static void tql2(gemmi::Mat33& V, double d[3], double e[3]) { + for (int i = 1; i < 3; i++) { + e[i-1] = e[i]; + } + e[3-1] = 0.0; + + double f = 0.0; + double tst1 = 0.0; + double eps = std::pow(2.0,-52.0); + for (int l = 0; l < 3; l++) { + + // Find small subdiagonal element + tst1 = std::max(tst1, std::fabs(d[l]) + std::fabs(e[l])); + int m = l; + // MW: I changed "m < 3" to "m < 2", because if m==2 is followed by m++, + // we access out of bounds d[3] in "p = d[m];" and later on e[3], getting + // *** stack smashing detected ***. + while (m < 2) { + if (std::fabs(e[m]) <= eps*tst1) { + break; + } + m++; + } + + // If m == l, d[l] is an eigenvalue, + // otherwise, iterate. + if (m > l) { + int iter = 0; + do { + iter = iter + 1; // (Could check iteration count here.) + + // Compute implicit shift + double g = d[l]; + double p = (d[l+1] - g) / (2.0 * e[l]); + double r = std::sqrt(p*p + 1.0); + if (p < 0) { + r = -r; + } + d[l] = e[l] / (p + r); + d[l+1] = e[l] * (p + r); + double dl1 = d[l+1]; + double h = g - d[l]; + for (int i = l+2; i < 3; i++) { + d[i] -= h; + } + f = f + h; + + // Implicit QL transformation. + p = d[m]; + double c = 1.0; + double c2 = c; + double c3 = c; + double el1 = e[l+1]; + double s = 0.0; + double s2 = 0.0; + for (int i = m-1; i >= l; i--) { + c3 = c2; + c2 = c; + s2 = s; + g = c * e[i]; + h = c * p; + r = std::sqrt(p*p + e[i]*e[i]); + e[i+1] = s * r; + s = e[i] / r; + c = p / r; + p = c * d[i] - s * g; + d[i+1] = h + s * (c * g + s * d[i]); + + // Accumulate transformation. + for (int k = 0; k < 3; k++) { + h = V[k][i+1]; + V[k][i+1] = s * V[k][i] + c * h; + V[k][i] = c * V[k][i] - s * h; + } + } + p = -s * s2 * c3 * el1 * e[l] / dl1; + e[l] = s * p; + d[l] = c * p; + + // Check for convergence. + } while (std::fabs(e[l]) > eps*tst1); + } + d[l] = d[l] + f; + e[l] = 0.0; + } + + // Sort eigenvalues and corresponding vectors. + for (int i = 0; i < 3-1; i++) { + int k = i; + double p = d[i]; + for (int j = i+1; j < 3; j++) { + if (d[j] < p) { + k = j; + p = d[j]; + } + } + if (k != i) { + d[k] = d[i]; + d[i] = p; + for (int j = 0; j < 3; j++) { + p = V[j][i]; + V[j][i] = V[j][k]; + V[j][k] = p; + } + } + } +} + +namespace gemmi { + +Mat33 eigen_decomposition(const SMat33& A, double (&d)[3]) { + double e[3]; + Mat33 V = A.as_mat33(); + tred2(V, d, e); + tql2(V, d, e); + return V; +} + +} // namespace gemmi diff --git a/gemmi_gph/gemmi/ace_cc.hpp b/gemmi_gph/gemmi/ace_cc.hpp new file mode 100644 index 00000000..08a562ac --- /dev/null +++ b/gemmi_gph/gemmi/ace_cc.hpp @@ -0,0 +1,27 @@ +// Copyright 2025 Global Phasing Ltd. +// +// prepare_chemcomp() — restraint generation pipeline. + +#ifndef GEMMI_ACE_CC_HPP_ +#define GEMMI_ACE_CC_HPP_ + +#include +#include +#include "chemcomp.hpp" // for ChemComp + +namespace gemmi { + +struct AcedrgTables; + +/// Run the full restraint-generation pipeline on a ChemComp: +/// chemical-group adjustments, protonation, fill_restraints, +/// torsion/chirality/plane generation, and CCP4 type assignment. +/// \param atom_stereo maps atom names to pdbx_stereo_config strings +/// (needed for chirality generation). +GEMMI_DLL void prepare_chemcomp(ChemComp& cc, const AcedrgTables& tables, + const std::map& atom_stereo = {}, + bool no_angles = false, + const std::map* sugar_coord_overrides = nullptr); + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/ace_graph.hpp b/gemmi_gph/gemmi/ace_graph.hpp new file mode 100644 index 00000000..c39e530b --- /dev/null +++ b/gemmi_gph/gemmi/ace_graph.hpp @@ -0,0 +1,76 @@ +// Copyright 2026 Global Phasing Ltd. +// +// Shared graph/ring helpers for AceDRG-style ChemComp processing. + +#ifndef GEMMI_ACE_GRAPH_HPP_ +#define GEMMI_ACE_GRAPH_HPP_ + +#include +#include +#include +#include "gemmi/chemcomp.hpp" + +namespace gemmi { + +struct AceBondNeighbor { + size_t idx; + BondType type; + bool aromatic; +}; + +using AceBondAdjacency = std::vector>; + +struct AceGraphView { + std::map atom_index; + AceBondAdjacency adjacency; + std::vector> neighbors; +}; + +std::map> +make_neighbor_names(const ChemComp& cc); + +bool atoms_in_same_ring_by_alt_path( + const std::string& atom1, const std::string& atom2, + const std::map>& neighbors); + +AceBondAdjacency build_bond_adjacency( + const ChemComp& cc, const std::map& atom_index); + +std::vector> build_neighbors(const AceBondAdjacency& adj); + +AceGraphView make_ace_graph_view(const ChemComp& cc); + +std::vector neighbor_indices_except( + const AceBondAdjacency& adj, size_t center_idx, size_t exclude_idx); + +std::vector non_hydrogen_neighbors( + const ChemComp& cc, const AceBondAdjacency& adj, + size_t center_idx, size_t exclude_idx = static_cast(-1)); + +std::vector hydrogen_neighbors( + const ChemComp& cc, const AceBondAdjacency& adj, + size_t center_idx, size_t exclude_idx = static_cast(-1)); + +bool is_carbonyl_carbon( + const ChemComp& cc, const AceBondAdjacency& adj, size_t idx); + +bool share_ring_ids(const std::vector& rings_a, + const std::vector& rings_b); + +int shared_ring_size_from_ring_ids(const std::vector& rings_a, int min_ring_a, + const std::vector& rings_b, int min_ring_b); + +int expected_valence_for_nonmetal(Element el); + +bool has_metal_and_non_metal_heavy_neighbor( + const ChemComp& cc, const AceBondAdjacency& adj, size_t idx); + +float sum_non_metal_bond_order( + const ChemComp& cc, const AceBondAdjacency& adj, size_t idx); + +bool has_non_hydrogen_neighbor( + const ChemComp& cc, const std::vector& neighbor_indices); + +} // namespace gemmi + +#endif // GEMMI_ACE_GRAPH_HPP_ diff --git a/gemmi_gph/gemmi/acedrg_tables.hpp b/gemmi_gph/gemmi/acedrg_tables.hpp new file mode 100644 index 00000000..5c47583d --- /dev/null +++ b/gemmi_gph/gemmi/acedrg_tables.hpp @@ -0,0 +1,395 @@ +// Copyright 2025 Global Phasing Ltd. +// +// AcedrgTables - COD/CSD-based atom classification and restraint value lookup +// Port of AceDRG codClassify system to gemmi. + +#ifndef GEMMI_ACEDRG_TABLES_HPP_ +#define GEMMI_ACEDRG_TABLES_HPP_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "chemcomp.hpp" +#include "elem.hpp" +#include "fail.hpp" + +namespace gemmi { + +// Hybridization states used in atom classification +enum class Hybridization { + SP1, // sp hybridization (linear) + SP2, // sp2 hybridization (trigonal planar) + SP3, // sp3 hybridization (tetrahedral) + SPD5, // d-orbital involvement (5-coordinate) + SPD6, // d-orbital involvement (6-coordinate) + SPD7, // d-orbital involvement (7-coordinate) + SPD8, // d-orbital involvement (8-coordinate) + SP_NON // non-standard/unknown hybridization +}; + +GEMMI_DLL const char* hybridization_to_string(Hybridization h); +GEMMI_DLL Hybridization hybridization_from_string(const std::string& s); + +// Metal coordination geometry types +enum class CoordGeometry { + LINEAR, // CN=2: 180° + TRIGONAL_PLANAR, // CN=3: 120° + T_SHAPED, // CN=3: 90°, 180° + TETRAHEDRAL, // CN=4: 109.47° + SQUARE_PLANAR, // CN=4: 90°, 180° + TRIGONAL_BIPYRAMIDAL, // CN=5: 90°, 120°, 180° + SQUARE_PYRAMIDAL, // CN=5: 90°, 180° + OCTAHEDRAL, // CN=6: 90° + TRIGONAL_PRISM, // CN=6: alternative + PENTAGONAL_BIPYRAMIDAL, // CN=7 + CAPPED_OCTAHEDRAL, // CN=7 + SQUARE_ANTIPRISM, // CN=8 + UNKNOWN +}; + +// Classification information for a single atom +struct CodAtomInfo { + int index; // Index in ChemComp.atoms + std::string id; // Atom id (name) + int hashing_value; // 0-1000+ hash code + Element el; // Element + Hybridization hybrid; // Hybridization state + std::string cod_class; // Full COD class (e.g., "C[6a](C[6a]C[6a])(C[6a])(H)") + std::string cod_class_no_charge; // COD class computed without formal charges (for COD table lookup) + std::string cod_main; // COD main type (codAtmMain) + std::string cod_root; // COD root type (codAtmRoot) + std::string nb_symb; // codNBSymb + std::string nb2_symb; // codNB2Symb + std::string nb3_symb; // codNB3Symb + std::string nb1nb2_sp; // codNB1NB2_SP + std::vector conn_atoms_no_metal; // Non-metal neighbors (index list) + int connectivity; // Number of bonded atoms + int metal_connectivity; // Number of metal neighbors + int min_ring_size; // Minimum ring size (0 = not in ring) + bool is_aromatic; // In aromatic ring + bool is_metal; // Is a metal atom + int excess_electrons; // Formal charge/lone pair info + float charge; // Formal/partial charge + float par_charge; // Partial charge (AceDRG parCharge) + int bonding_idx; // AceDRG bonding index (1=sp1,2=sp2,3=sp3,...) + + // Ring bookkeeping (AceDRG-style). + std::map ring_rep; + std::map ring_rep_s; + std::vector in_rings; + + CodAtomInfo() + : index(-1), hashing_value(0), el(El::X), hybrid(Hybridization::SP_NON), + connectivity(0), metal_connectivity(0), min_ring_size(0), + is_aromatic(false), is_metal(false), excess_electrons(0), charge(0.0f), + par_charge(0.0f), bonding_idx(0) {} +}; + +// Statistical value with count +struct CodStats { + double value = NAN; + double sigma = NAN; + int count = 0; + int level = 0; // match specificity: 0=none, 1-4=aggregated, 10=full + + CodStats() = default; + CodStats(double v, double s, int c, int lvl = 0) : value(v), sigma(s), count(c), level(lvl) {} +}; + +// Protonated hydrogen distances (both electron cloud and nucleus) +struct ProtHydrDist { + double electron_val = NAN; + double electron_sigma = NAN; + double nucleus_val = NAN; + double nucleus_sigma = NAN; +}; + +// Metal bond entry +struct MetalBondEntry { + Element metal = El::X; + Element ligand = El::X; + int metal_coord = 0; + int ligand_coord = 0; + std::string ligand_class; + double pre_value = NAN; + double pre_sigma = NAN; + int pre_count = 0; + double value = NAN; + double sigma = NAN; + int count = 0; +}; + +// Metal coordination angle entry +struct MetalAngleEntry { + Element metal = El::X; + int coord_number = 0; + CoordGeometry geometry = CoordGeometry::UNKNOWN; + double angle = NAN; + double sigma = NAN; +}; + +struct MetalCoordOverride { + Element metal = El::X; + int coord_number = 0; + CoordGeometry geometry = CoordGeometry::UNKNOWN; +}; + +struct TorsionEntry { + double value = 0.0; + double sigma = 0.0; + int period = 0; + int priority = 0; + std::string id; +}; + +// ============================================================================ +// Main AcedrgTables class +// ============================================================================ + +struct GEMMI_DLL AcedrgTables { + AcedrgTables() = default; + + // Load all tables from directory + void load_tables(const std::string& tables_dir, bool skip_angles = false); + + // Process a ChemComp - fill all missing restraint values + void fill_restraints(ChemComp& cc) const; + + // Assign CCP4 atom energy types (type_energy) following AceDRG rules + void assign_ccp4_types(ChemComp& cc) const; + bool lookup_pep_tors(const std::string& a1, const std::string& a2, + const std::string& a3, const std::string& a4, + TorsionEntry& out) const; + bool lookup_nucl_tors(const std::string& a1, const std::string& a2, + const std::string& a3, const std::string& a4, + std::vector& out) const; + + // Individual lookups - returns match level (10=full, 4+=neighbor matched, 0-3=aggregated) + int fill_bond(const ChemComp& cc, + const std::vector& atom_info, + Restraints::Bond& bond) const; + int fill_angle(const ChemComp& cc, + const std::vector& atom_info, + Restraints::Angle& angle, + const std::set& needed_files) const; + + // Atom classification - returns info for all atoms + std::vector classify_atoms(const ChemComp& cc) const; + + // Compute acedrg_type string (like acedrg --typeOut) + // Format: CentralElement(Neighbor1_desc)(Neighbor2_desc)... + // where each neighbor description = neighbor element + sorted neighbor's other neighbors + std::string compute_acedrg_type(const CodAtomInfo& atom, + const std::vector& atoms, + const std::vector>& neighbors) const; + std::vector compute_acedrg_types(const ChemComp& cc) const; + + // Configuration + double upper_bond_sigma = 0.02; + double lower_bond_sigma = 0.01; + double upper_angle_sigma = 3.0; + double lower_angle_sigma = 1.5; + int min_observations_angle = 3; // AceDRG default for angles + int min_observations_angle_fallback = 3; + int min_observations_bond = 3; // AceDRG default for bonds (aNumTh=3) + int metal_class_min_count = 5; // AceDRG uses >5 for metal class selection + int verbose = 0; // Debug output level (0=off, 1=basic, 2=detailed) + + static constexpr int HASH_SIZE = 1000; + // Table directory + std::string tables_dir_; + bool tables_loaded_ = false; + + // Hash code tables + std::map digit_keys_; // hash -> footprint + std::map linked_hash_; // hash -> linked hash + + // HRS (High-Resolution Summary) bond tables + // Key: hash1, hash2, hybrid_pair, in_ring + struct BondHRSKey { + int hash1, hash2; + std::string hybrid_pair; + std::string in_ring; + bool operator<(const BondHRSKey& o) const { + return std::tie(hash1, hash2, hybrid_pair, in_ring) + < std::tie(o.hash1, o.hash2, o.hybrid_pair, o.in_ring); + } + }; + std::map bond_hrs_; + + // HRS angle tables + // Key: hash1, hash2, hash3, value_key (ring:hybr_tuple) + struct AngleHRSKey { + int hash1, hash2, hash3; + std::string value_key; + bool operator<(const AngleHRSKey& o) const { + return std::tie(hash1, hash2, hash3, value_key) + < std::tie(o.hash1, o.hash2, o.hash3, o.value_key); + } + }; + std::map angle_hrs_; + + struct Ccp4BondEntry { + double length = NAN; + double sigma = NAN; + }; + + std::map>> ccp4_bonds_; + + void load_ccp4_bonds(const std::string& path); + std::vector compute_ccp4_types(const ChemComp& cc, + const std::vector& atom_info, + const std::vector>& neighbors) const; + bool search_ccp4_bond(const std::string& type1, + const std::string& type2, + const std::string& order, + CodStats& out) const; + + // Detailed indexed bond tables from allOrgBondTables/*.table + // Flattened: 8-component compound key (ha1|ha2|hybr|ring|a1nb2|a2nb2|a1nb|a2nb) + // -> 2 inner map levels (a1_type_m -> a2_type_m -> vector) + using BondIdx1D = std::unordered_map>>>; + BondIdx1D bond_idx_1d_; + + // Exact match with full COD class + // Flattened: 10-component key (ha1|..|a1_type_m|a2_type_m) + // -> 2 inner levels (a1_type_f -> a2_type_f -> CodStats) + using BondIdxFull = std::unordered_map>>; + BondIdxFull bond_idx_full_; + + // Levels 3-8: 4-component key (ha1|ha2|hybr|ring) + // -> 4 inner levels (a1nb2 -> a2nb2 -> a1nb -> a2nb -> vector) + using BondIdx2D = std::unordered_map>>>>>; + BondIdx2D bond_idx_2d_; + + // Level Nb2D: 6-component key (ha1|ha2|hybr|ring|a1nb2|a2nb2) + using BondNb2D = std::unordered_map>; + BondNb2D bond_nb2d_; // populated but not read (dead data) + + // Level Nb2DType: 8-component key (ha1|ha2|hybr|ring|a1nb2|a2nb2|root1|root2) + using BondNb2DType = std::unordered_map>; + BondNb2DType bond_nb2d_type_; // populated but not read (dead data) + + // Levels 9-11: Hash+Sp fallback, fully flat compound keys + using BondHaSp2D = std::unordered_map>; + BondHaSp2D bond_hasp_2d_; // 4-component key (ha1|ha2|hybr|ring) + using BondHaSp1D = std::unordered_map>; + BondHaSp1D bond_hasp_1d_; // 3-component key (ha1|ha2|hybr) + using BondHaSp0D = std::unordered_map>; + BondHaSp0D bond_hasp_0d_; // 2-component key (ha1|ha2) + + // Bond file index: flat 2-component key (ha1|ha2) -> table file number + std::unordered_map bond_file_index_; + + // Side sets for prefix-existence checks in bond lookup + std::unordered_set bond_2d_hybr_keys_; // 3-component (ha1|ha2|hybr) + std::unordered_set bond_full_4prefix_keys_; // 4-component (ha1|ha2|hybr|ring) + + // Atom type code mapping: coded -> full type string + std::unordered_map atom_type_codes_; + + // Detailed indexed angle tables from allOrgAngleTables/*.table + // Fully flat compound keys for levels 1D-4D, 6D + using AngleIdx1D = std::unordered_map>; + AngleIdx1D angle_idx_1d_; // 16-component key + using AngleIdx2D = std::unordered_map>; + AngleIdx2D angle_idx_2d_; // 13-component key + using AngleIdx3D = std::unordered_map>; + AngleIdx3D angle_idx_3d_; // 10-component key + using AngleIdx4D = std::unordered_map>; + AngleIdx4D angle_idx_4d_; // 7-component key + + // Level 5D: kept nested for wildcard iteration in fill_angle + using AngleIdx5D = std::unordered_map>>>>; + AngleIdx5D angle_idx_5d_; + + // Level 6D: flat 3-component key (ha1|ha2|ha3) + using AngleIdx6D = std::unordered_map>; + AngleIdx6D angle_idx_6d_; + + // Angle file index: kept nested for individual-level access in fill_angle + std::unordered_map>> angle_file_index_; + + // Element + hybridization based fallback bonds + using ENBonds = std::map>>>>; + ENBonds en_bonds_; + + // Metal bond tables + std::vector metal_bonds_; + std::array(El::END) + 1> covalent_radii_; + std::vector metal_angles_; + std::vector metal_coord_geo_overrides_; + std::map pep_tors_; + std::map> nucl_tors_; + + // Protonated hydrogen distances: maps type (e.g., "H_sp3_C") -> ProtHydrDist + std::map prot_hydr_dists_; + + // Internal helper functions + + // Loading functions + void load_hash_codes(const std::string& path); + void load_bond_hrs(const std::string& path); + void load_angle_hrs(const std::string& path); + void load_metal_tables(const std::string& dir); + void load_covalent_radii(const std::string& path); + void load_en_bonds(const std::string& path); + void load_atom_type_codes(const std::string& path); + void load_bond_index(const std::string& path); + void load_bond_tables(const std::string& dir); + void load_pep_tors(const std::string& path); + void load_nucl_tors(const std::string& path); + void load_prot_hydr_dists(const std::string& path); + void load_angle_index(const std::string& path); + void load_angle_tables(const std::string& dir); + + private: + void compute_hash(CodAtomInfo& atom) const; + + // Bond search helpers + CodStats search_bond_multilevel(const CodAtomInfo& a1, + const CodAtomInfo& a2) const; + CodStats search_bond_hrs(const CodAtomInfo& a1, const CodAtomInfo& a2, + bool in_ring) const; + CodStats search_bond_en(const CodAtomInfo& a1, const CodAtomInfo& a2) const; + ProtHydrDist search_prot_hydr_dist(const CodAtomInfo& h_atom, + const CodAtomInfo& heavy_atom) const; + CodStats search_metal_bond(const CodAtomInfo& metal, + const CodAtomInfo& ligand, + const std::vector& atoms) const; + // Angle search helpers + CodStats search_angle_multilevel(const CodAtomInfo& a1, + const CodAtomInfo& center, + const CodAtomInfo& a3, + int* out_level = nullptr) const; + CodStats search_angle_hrs(const CodAtomInfo& a1, const CodAtomInfo& center, + const CodAtomInfo& a3, int ring_size) const; + std::vector get_metal_angles(Element metal, int coord_number) const; + + // Utility: clamp sigma to reasonable range + double clamp_bond_sigma(double sigma) const { + return std::max(lower_bond_sigma, std::min(upper_bond_sigma, sigma)); + } + double clamp_angle_sigma(double sigma) const { + return std::max(lower_angle_sigma, std::min(upper_angle_sigma, sigma)); + } +}; + +} // namespace gemmi + +#endif diff --git a/gemmi_gph/gemmi/addends.hpp b/gemmi_gph/gemmi/addends.hpp new file mode 100644 index 00000000..a7081e0e --- /dev/null +++ b/gemmi_gph/gemmi/addends.hpp @@ -0,0 +1,35 @@ +// Copyright 2020 Global Phasing Ltd. +// +// Addends to scattering form factors used in DensityCalculator +// and StructureFactorCalculator. + +#ifndef GEMMI_ADDENDS_HPP_ +#define GEMMI_ADDENDS_HPP_ + +#include +#include "elem.hpp" // for El, Element + +namespace gemmi { + +struct Addends { + std::array values = {}; + + void set(Element el, float val) { values[el.ordinal()] = val; } + float get(Element el) const { return values[el.ordinal()]; } + size_t size() const { return values.size(); } + void clear() { + for (size_t i = 0; i != size(); ++i) + values[i] = 0.; + } + void subtract_z(bool except_hydrogen=false) { + for (int z = 2; z < (int)El::D; ++z) + values[z] -= z; + if (!except_hydrogen) { + values[(int)El::H] -= 1; + values[(int)El::D] -= 1; + } + } +}; + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/align.hpp b/gemmi_gph/gemmi/align.hpp new file mode 100644 index 00000000..ceb8327f --- /dev/null +++ b/gemmi_gph/gemmi/align.hpp @@ -0,0 +1,317 @@ +// Copyright 2020 Global Phasing Ltd. +// +// Sequence alignment, label_seq_id assignment, structure superposition. + +#ifndef GEMMI_ALIGN_HPP_ +#define GEMMI_ALIGN_HPP_ + +#include "model.hpp" +#include "seqalign.hpp" // for align_sequences +#include "qcp.hpp" // for superpose_positions +#include "polyheur.hpp" // for are_connected3 + +namespace gemmi { + +// Sequence alignment and label_seq_id assignment + +// helper function for sequence alignment +inline std::vector prepare_target_gapo(const ConstResidueSpan& polymer, + PolymerType polymer_type, + const AlignmentScoring* scoring=nullptr) { + if (!scoring) + scoring = AlignmentScoring::partial_model(); + std::vector gaps; + gaps.reserve(polymer.size()); + gaps.push_back(0); // free gap opening at the beginning of sequence + if (is_polypeptide(polymer_type) || is_polynucleotide(polymer_type)) { + auto first_conformer = polymer.first_conformer(); + auto res = first_conformer.begin(); + for (auto next_res = res; ++next_res != first_conformer.end(); res = next_res) { + bool connected = are_connected3(*res, *next_res, polymer_type); + gaps.push_back(connected ? scoring->bad_gapo : scoring->good_gapo); + } + gaps.push_back(0); // free gap after the end of chain + } + return gaps; +} + +inline AlignmentResult align_sequence_to_polymer( + const std::vector& full_seq, + const ConstResidueSpan& polymer, + PolymerType polymer_type, + const AlignmentScoring* scoring=nullptr) { + if (!polymer) + return AlignmentResult(); + std::map encoding; + if (!scoring) + scoring = AlignmentScoring::partial_model(); + for (const std::string& res_name : scoring->matrix_encoding) + encoding.emplace(res_name, (std::uint8_t)encoding.size()); + for (const Residue& res : polymer) + encoding.emplace(res.name, (std::uint8_t)encoding.size()); + for (const std::string& mon_list : full_seq) + encoding.emplace(Entity::first_mon(mon_list), (std::uint8_t)encoding.size()); + if (encoding.size() > 255) + return AlignmentResult(); + + std::vector encoded_full_seq(full_seq.size()); + for (size_t i = 0; i != full_seq.size(); ++i) + encoded_full_seq[i] = encoding.at(Entity::first_mon(full_seq[i])); + + std::vector encoded_model_seq; + encoded_model_seq.reserve(polymer.size()); + for (const Residue& res : polymer.first_conformer()) + encoded_model_seq.push_back(encoding.at(res.name)); + + return align_sequences(encoded_full_seq, encoded_model_seq, + prepare_target_gapo(polymer, polymer_type, scoring), + (std::uint8_t)encoding.size(), *scoring); +} + +// check for exact match between model sequence and full sequence (SEQRES) +inline bool seqid_matches_seqres(const ConstResidueSpan& polymer, + const Entity& ent) { + if (ent.full_sequence.size() != polymer.size()) + return false; + int idx = 0; + for (const Residue& res : polymer) { + if (ent.full_sequence[idx] != res.name || + ++idx != *res.seqid.num || res.seqid.has_icode()) + return false; + } + return true; +} + +inline void clear_sequences(Structure& st) { + for (Entity& ent : st.entities) { + ent.full_sequence.clear(); + ent.dbrefs.clear(); + ent.sifts_unp_acc.clear(); + } +} + +GEMMI_DLL +void assign_best_sequences(Structure& st, const std::vector& fasta_sequences); + +// Uses sequence alignment (model to SEQRES) to assign label_seq. +// force: assign label_seq even if full sequence is not known (assumes no gaps) +inline void assign_label_seq_to_polymer(ResidueSpan& polymer, + const Entity* ent, bool force) { + AlignmentResult result; + + // sequence not known + if (!ent || ent->full_sequence.empty()) { + if (!force) + return; + PolymerType ptype = get_or_check_polymer_type(ent, polymer); + const Residue* prev = nullptr; + for (const Residue& res : polymer.first_conformer()) { + if (prev && !are_connected3(*prev, res, ptype)) + result.push_cigar(1, 1); // assume a single insertion + result.push_cigar(0, 1); + prev = &res; + } + + // exact match - common case that doesn't require alignment + } else if (seqid_matches_seqres(polymer, *ent)) { + result.push_cigar(0, (int)ent->full_sequence.size()); + + // sequence alignment + } else { + PolymerType ptype = get_or_check_polymer_type(ent, polymer); + result = align_sequence_to_polymer(ent->full_sequence, polymer, ptype); + } + + auto res_group = polymer.first_conformer().begin(); + int id = 1; + for (AlignmentResult::Item item : result.cigar) { + switch (item.op()) { + case 'I': + id += item.len(); + break; + case 'D': // leaving label_seq as it is + for (uint32_t i = 0; i < item.len(); ++i) + res_group++; + break; + case 'M': // not checking for mismatches + for (uint32_t i = 0; i < item.len(); ++i, ++id) + for (Residue* res = &*res_group++; res != &*res_group; ++res) + res->label_seq = id; + break; + } + } +} + +inline void clear_label_seq_id(Structure& st) { + for (Model& model : st.models) + for (Chain& chain : model.chains) + for (Residue& res : chain.residues) + res.label_seq = Residue::OptionalNum(); +} + +inline void assign_label_seq_id(Structure& st, bool force) { + for (Model& model : st.models) + for (Chain& chain : model.chains) + if (ResidueSpan polymer = chain.get_polymer()) + if (!polymer.front().label_seq || !polymer.back().label_seq) { + const Entity* ent = st.get_entity_of(polymer); + assign_label_seq_to_polymer(polymer, ent, force); + } +} + + +// superposition + +enum class SupSelect { + CaP, // only Ca (aminoacids) or P (nucleotides) atoms + MainChain, // only main chain atoms + All +}; + +inline void prepare_positions_for_superposition(std::vector& pos1, + std::vector& pos2, + ConstResidueSpan fixed, + ConstResidueSpan movable, + PolymerType ptype, + SupSelect sel, + char altloc='\0', + std::vector* ca_offsets=nullptr) { + AlignmentResult result = align_sequence_to_polymer(fixed.extract_sequence(), + movable, ptype, + AlignmentScoring::blosum62()); + auto it1 = fixed.first_conformer().begin(); + auto it2 = movable.first_conformer().begin(); + std::vector used_atoms; + bool is_na = is_polynucleotide(ptype); + const AtomNameElement* ca_p = nullptr; + if (sel == SupSelect::CaP) { + used_atoms.push_back({is_na ? "P" : "CA", is_na ? El::P : El::C}); + } else if (sel == SupSelect::MainChain) { + used_atoms = get_mainchain_atoms(ptype); + ca_p = &used_atoms[is_na ? 0 : 1]; + } + for (AlignmentResult::Item item : result.cigar) { + char op = item.op(); + for (uint32_t i = 0; i < item.len(); ++i) { + int ca_offset = -1; + if (op == 'M' && it1->name == it2->name) { + if (!used_atoms.empty()) { + for (const AtomNameElement& ane : used_atoms) { + const Atom* a1 = it1->find_atom(ane.atom_name, altloc, ane.el); + const Atom* a2 = it2->find_atom(ane.atom_name, altloc, ane.el); + if (a1 && a2) { + if (&ane == ca_p) + ca_offset = (int)pos1.size(); + pos1.push_back(a1->pos); + pos2.push_back(a2->pos); + } + } + } else { + for (const Atom& a1 : it1->atoms) + if (a1.altloc_matches(altloc)) + if (const Atom* a2 = it2->find_atom(a1.name, altloc, a1.element)) { + pos1.push_back(a1.pos); + pos2.push_back(a2->pos); + } + } + } + if (op == 'M' || op == 'I') { + ++it1; + if (ca_offsets) + ca_offsets->push_back(ca_offset); + } + if (op == 'M' || op == 'D') + ++it2; + } + } +} + +inline SupResult calculate_current_rmsd(ConstResidueSpan fixed, + ConstResidueSpan movable, + PolymerType ptype, + SupSelect sel, + char altloc='\0') { + std::vector pos1, pos2; + prepare_positions_for_superposition(pos1, pos2, fixed, movable, ptype, sel, altloc); + SupResult r; + r.count = pos1.size(); + double sd = 0; + for (size_t i = 0; i != pos1.size(); ++i) + sd += pos1[i].dist_sq(pos2[i]); + r.rmsd = std::sqrt(sd / r.count); + return r; +} + +inline SupResult calculate_superposition(ConstResidueSpan fixed, + ConstResidueSpan movable, + PolymerType ptype, + SupSelect sel, + int trim_cycles=0, + double trim_cutoff=2.0, + char altloc='\0') { + std::vector pos1, pos2; + prepare_positions_for_superposition(pos1, pos2, fixed, movable, ptype, sel, altloc); + const double* weights = nullptr; + size_t len = pos1.size(); + SupResult sr = superpose_positions(pos1.data(), pos2.data(), len, weights); + + for (int n = 0; n < trim_cycles; ++n) { + double max_dist_sq = sq(trim_cutoff * sr.rmsd); + size_t p = 0; + for (size_t i = 0; i != len; ++i) { + Vec3 m2 = sr.transform.apply(pos2[i]); + if (m2.dist_sq(pos1[i]) <= max_dist_sq) { + if (i != p) { + pos1[p] = pos1[i]; + pos2[p] = pos2[i]; + } + ++p; + } + } + if (p == len) + break; + len = p; + if (len < 3) + fail("in calculate_superposition(): only ", std::to_string(len), + " atoms after trimming"); + sr = superpose_positions(pos1.data(), pos2.data(), len, weights); + } + + return sr; +} + +// Returns superpositions for all residues in fixed.first_conformer(), +// performed by superposing backbone in radius=10.0 from residue's Ca. +inline std::vector calculate_superpositions_in_moving_window( + ConstResidueSpan fixed, + ConstResidueSpan movable, + PolymerType ptype, + double radius=10.0) { + const double radius_sq = radius * radius; + std::vector pos1, pos2; + char altloc = '\0'; + SupSelect sel = SupSelect::MainChain; + std::vector ca_offsets; + prepare_positions_for_superposition(pos1, pos2, fixed, movable, ptype, + sel, altloc, &ca_offsets); + const double* weights = nullptr; + std::vector result; + for (int offset : ca_offsets) { + if (offset == -1) { + result.push_back(SupResult{NAN, 0, {}, {}, {}}); + continue; + } + const Position& ca_pos = pos1[offset]; + int a = offset; + while (a > 0 && ca_pos.dist_sq(pos1[a-1]) < radius_sq) + --a; + int b = offset; + while (b+1 < (int)pos1.size() && ca_pos.dist_sq(pos1[b+1]) < radius_sq) + ++b; + result.push_back(superpose_positions(&pos1[a], &pos2[a], b-a+1, weights)); + } + return result; +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/assembly.hpp b/gemmi_gph/gemmi/assembly.hpp new file mode 100644 index 00000000..9d1ab2eb --- /dev/null +++ b/gemmi_gph/gemmi/assembly.hpp @@ -0,0 +1,129 @@ +// Copyright 2020 Global Phasing Ltd. +// +// Generating biological assemblies by applying operations +// from struct Assembly to a Model. +// Includes chain (re)naming utilities. + +#ifndef GEMMI_ASSEMBLY_HPP_ +#define GEMMI_ASSEMBLY_HPP_ + +#include "model.hpp" // for Model +#include "util.hpp" // for in_vector +#include "logger.hpp" // for Logger + +namespace gemmi { + +enum class HowToNameCopiedChain { Short, AddNumber, Dup }; + +struct ChainNameGenerator { + HowToNameCopiedChain how; + std::vector used_names; + + ChainNameGenerator(HowToNameCopiedChain how_) : how(how_) {} + ChainNameGenerator(const Model& model, HowToNameCopiedChain how_) : how(how_) { + if (how != HowToNameCopiedChain::Dup) + for (const Chain& chain : model.chains) + used_names.push_back(chain.name); + } + bool try_add(const std::string& name) { + if (in_vector(name, used_names)) + return false; + used_names.push_back(name); + return true; + } + std::string make_short_name(const std::string& preferred) { + static const char symbols[] = { + 'A','B','C','D','E','F','G','H','I','J','K','L','M', + 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z', + 'a','b','c','d','e','f','g','h','i','j','k','l','m', + 'n','o','p','q','r','s','t','u','v','w','x','y','z', + '0','1','2','3','4','5','6','7','8','9' + }; + if (try_add(preferred)) + return preferred; + std::string name(1, 'A'); + for (char symbol : symbols) { + name[0] = symbol; + if (try_add(name)) + return name; + } + name += 'A'; + for (char symbol1 : symbols) { + name[0] = symbol1; + for (char symbol2 : symbols) { + name[1] = symbol2; + if (try_add(name)) + return name; + } + } + fail("run out of 1- and 2-letter chain names"); + } + + std::string make_name_with_numeric_postfix(const std::string& base, int n) { + std::string name = base; + name += std::to_string(n); + while (!try_add(name)) { + name.resize(base.size()); + name += std::to_string(++n); + } + return name; + } + + std::string make_new_name(const std::string& old, int n) { + switch (how) { + case HowToNameCopiedChain::Short: return make_short_name(old); + case HowToNameCopiedChain::AddNumber: return make_name_with_numeric_postfix(old, n); + case HowToNameCopiedChain::Dup: return old; + } + unreachable(); + } +}; + +inline void ensure_unique_chain_name(const Model& model, Chain& chain) { + ChainNameGenerator namegen(HowToNameCopiedChain::Short); + for (const Chain& ch : model.chains) + if (&ch != &chain) + namegen.try_add(ch.name); + chain.name = namegen.make_short_name(chain.name); +} + +GEMMI_DLL Model make_assembly(const Assembly& assembly, const Model& model, + HowToNameCopiedChain how, const Logger& logging); + +inline Assembly pseudo_assembly_for_unit_cell(const UnitCell& cell) { + Assembly assembly("unit_cell"); + std::vector operators(cell.images.size() + 1); + // operators[0] stays as identity + for (size_t i = 1; i != operators.size(); ++i) { + const FTransform& op = cell.images[i-1]; + operators[i].transform = cell.orth.combine(op.combine(cell.frac)); + } + assembly.generators.push_back({{"(all)"}, {}, operators}); + return assembly; +} + +/// If called with assembly_name="unit_cell" changes structure to unit cell (P1). +/// \par keep_spacegroup preserves space group and unit cell - is it needed? +GEMMI_DLL void transform_to_assembly(Structure& st, const std::string& assembly_name, + HowToNameCopiedChain how, const Logger& logging, + bool keep_spacegroup=false, double merge_dist=0.2); + + +GEMMI_DLL Model expand_ncs_model(const Model& model, const std::vector& ncs, + HowToNameCopiedChain how); + +/// Searches and merges overlapping equivalent atoms from different chains. +/// To be used after expand_ncs() and make_assembly(). +GEMMI_DLL void merge_atoms_in_expanded_model(Model& model, const UnitCell& cell, + double max_dist=0.2, bool compare_serial=true); + + +GEMMI_DLL void shorten_chain_names(Structure& st); + +GEMMI_DLL void expand_ncs(Structure& st, HowToNameCopiedChain how, double merge_dist=0.2); + +/// HowToNameCopiedChain::Dup adds segment name to chain name +GEMMI_DLL void split_chains_by_segments(Model& model, HowToNameCopiedChain how); + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/asudata.hpp b/gemmi_gph/gemmi/asudata.hpp new file mode 100644 index 00000000..ccb44ee0 --- /dev/null +++ b/gemmi_gph/gemmi/asudata.hpp @@ -0,0 +1,254 @@ +// Copyright 2020 Global Phasing Ltd. +// +// AsuData for storing reflection data. + +#ifndef GEMMI_ASUDATA_HPP_ +#define GEMMI_ASUDATA_HPP_ + +#include // for arg, abs +#include // for sort, is_sorted +#include "unitcell.hpp" +#include "symmetry.hpp" +#include "stats.hpp" // for Correlation +#include "util.hpp" // for vector_remove_if + +namespace gemmi { + +struct ComplexCorrelation { + int n = 0; + double sum_xx = 0.; + double sum_yy = 0.; + std::complex sum_xy = 0.; + std::complex mean_x = 0.; + std::complex mean_y = 0.; + void add_point(std::complex x, std::complex y) { + ++n; + double inv_n = 1.0 / n; + double weight = (n - 1.0) * inv_n; + std::complex dx = x - mean_x; + std::complex dy = y - mean_y; + sum_xx += weight * std::norm(dx); + sum_yy += weight * std::norm(dy); + sum_xy += weight * (dx * std::conj(dy)); + mean_x += dx * inv_n; + mean_y += dy * inv_n; + } + void add_point(std::complex x, std::complex y) { + add_point(std::complex(x), std::complex(y)); + } + std::complex coefficient() const { return sum_xy / std::sqrt(sum_xx * sum_yy); } + double mean_ratio() const { return std::abs(mean_y) / std::abs(mean_x); } +}; + + +/// \pre a and b are sorted +template +void for_matching_reflections(const std::vector& a, + const std::vector& b, + const Func& func) { + auto r1 = a.begin(); + auto r2 = b.begin(); + while (r1 != a.end() && r2 != b.end()) { + if (r1->hkl == r2->hkl) { + func(*r1, *r2); + ++r1; + ++r2; + } else if (r1->hkl < r2->hkl) { + ++r1; + } else { + ++r2; + } + } +} + +/// \pre a and b are sorted +template +Correlation calculate_hkl_value_correlation(const std::vector& a, + const std::vector& b) { + Correlation cor; + for_matching_reflections(a, b, [&cor](const T& x, const T& y) { + cor.add_point(x.value, y.value); + }); + return cor; +} + +/// \pre a and b are sorted +template +ComplexCorrelation calculate_hkl_complex_correlation(const std::vector& a, + const std::vector& b) { + ComplexCorrelation cor; + for_matching_reflections(a, b, [&cor](const T& x, const T& y) { + cor.add_point(x.value, y.value); + }); + return cor; +} + +/// \pre a and b are sorted +template +int count_equal_values(const std::vector& a, const std::vector& b) { + int count = 0; + for_matching_reflections(a, b, [&count](const T& x, const T& y) { + if (x.value == y.value) + ++count; + }); + return count; +} + +template +struct HklValue { + alignas(8) Miller hkl; + T value; + + bool operator<(const Miller& m) const { return hkl < m; } + bool operator<(const HklValue& o) const { return operator<(o.hkl); } +}; + +template +struct ValueSigma { + using value_type = T; + T value, sigma; + + bool operator==(const ValueSigma& o) const { + return value == o.value && sigma == o.sigma; + } +}; + +namespace impl { +template +void move_to_asu(const GroupOps&, const Miller& hkl, int, HklValue& hkl_value) { + hkl_value.hkl = hkl; +} + +template +void move_to_asu(const GroupOps& gops, const Miller& hkl, int isym, + HklValue>& v) { + v.hkl = hkl; + // cf. Mtz::ensure_asu() + const Op& op = gops.sym_ops[(isym - 1) / 2]; + double shift = op.phase_shift(hkl); + if (shift != 0) { + double phase = std::arg(v.value) + shift; + v.value = std::polar(std::abs(v.value), (R)phase); + } + if (isym % 2 == 0) + v.value.imag(-v.value.imag()); +} +} // namespace impl + +template +struct AsuData { + std::vector> v; + UnitCell unit_cell_; + const SpaceGroup* spacegroup_ = nullptr; + // function defining FPhiProxy interface + size_t stride() const { return 1; } + size_t size() const { return v.size(); } + Miller get_hkl(size_t n) const { return v[n].hkl; } + double get_f(size_t n) const { return std::abs(v[n].value); } + double get_phi(size_t n) const { return std::arg(v[n].value); } + const UnitCell& unit_cell() const { return unit_cell_; } + const SpaceGroup* spacegroup() const { return spacegroup_; } + void ensure_sorted() { + if (!std::is_sorted(v.begin(), v.end())) + std::sort(v.begin(), v.end()); + } + + void ensure_asu(bool tnt_asu=false) { + if (!spacegroup_) + fail("AsuData::ensure_asu(): space group not set"); + GroupOps gops = spacegroup_->operations(); + ReciprocalAsu asu(spacegroup_, tnt_asu); + for (HklValue& hkl_value : v) { + const Miller& hkl = hkl_value.hkl; + if (asu.is_in(hkl)) + continue; + auto result = asu.to_asu(hkl, gops); + impl::move_to_asu(gops, result.first, result.second, hkl_value); + } + } + + // load values from one column + template + void load_values(const DataProxy& proxy, const std::string& label, + bool as_is=false) { + std::size_t col = proxy.column_index(label); + unit_cell_ = proxy.unit_cell(); + spacegroup_ = proxy.spacegroup(); + for (size_t i = 0; i < proxy.size(); i += proxy.stride()) { + auto num = proxy.get_num(i + col); + if (!std::isnan(num)) + v.push_back({proxy.get_hkl(i), (T)num}); + } + if (!as_is) { + ensure_asu(); + ensure_sorted(); + } + } + + // load values from two or more columns + template + void load_values(const DataProxy& proxy, const std::array& labels, + bool as_is=false) { + std::array cols; + for (int i = 0; i < N; ++i) + cols[i] = proxy.column_index(labels[i]); + unit_cell_ = proxy.unit_cell(); + spacegroup_ = proxy.spacegroup(); + using Val = typename T::value_type; + for (size_t i = 0; i < proxy.size(); i += proxy.stride()) { + std::array nums; + for (int j = 0; j < N; ++j) + nums[j] = (Val) proxy.get_num(i + cols[j]); + if (!std::any_of(nums.begin(), nums.end(), [](Val f) { return std::isnan(f); })) { + v.emplace_back(); + v.back().hkl = proxy.get_hkl(i); + set_value_from_array(v.back().value, nums); + } + } + if (!as_is) { + ensure_asu(); + ensure_sorted(); + } + } + +private: + // for T being a number, std::array and std::complex, respectively: + static void set_value_from_array(T& val, const std::array& nums) { val = nums[0]; } + static void set_value_from_array(T& val, const T& nums) { val = nums; } + template + static void set_value_from_array(std::complex& val, const std::array& nums) { + R theta = (R)rad(nums[1]); + val = {nums[0] * std::cos(theta), nums[0] * std::sin(theta)}; + } + template + static void set_value_from_array(ValueSigma& val, const std::array& nums) { + val.value = nums[0]; + val.sigma = nums[1]; + } +}; + +template +AsuData make_asu_data(const Data& data, const std::array& labels, + bool as_is=false) { + AsuData asu_data; + asu_data.template load_values(data_proxy(data), labels, as_is); + return asu_data; +} +template +AsuData make_asu_data(const Data& data, const std::string& label, bool as_is) { + AsuData asu_data; + asu_data.load_values(data_proxy(data), label, as_is); + return asu_data; +} + +/// retains only points with positive SIGF and F/SIGF > cutoff +template +void discard_by_sigma_ratio(AsuData>& asu_data, double cutoff) { + vector_remove_if(asu_data.v, [cutoff](const HklValue>& p) { + auto& v = p.value; + return v.sigma <= 0 || v.value <= cutoff * v.sigma; + }); +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/asumask.hpp b/gemmi_gph/gemmi/asumask.hpp new file mode 100644 index 00000000..0fb915cb --- /dev/null +++ b/gemmi_gph/gemmi/asumask.hpp @@ -0,0 +1,344 @@ +// Copyright 2022 Global Phasing Ltd. +// +// AsuBrick and MaskedGrid that is used primarily as direct-space asu mask. + +#ifndef GEMMI_ASUMASK_HPP_ +#define GEMMI_ASUMASK_HPP_ + +#include + +#include "util.hpp" // for in_vector +#include "grid.hpp" + +namespace gemmi { + +struct AsuBrick { + // The brick is 0<=x<=size[0]/24, 0<=y<=size[1]/24, 0<=z<=size[2]/24 + static constexpr int denom = 24; + std::array size; + std::array incl; + int volume; + + AsuBrick(int a, int b, int c) + : size({a,b,c}), incl({a < denom, b < denom, c < denom}), volume(a*b*c) {} + + std::string str() const { + std::string s; + for (int i = 0; i < 3; ++i) { + if (i != 0) + s += "; "; + s += "0<="; + s += "xyz"[i]; + s += incl[i] ? "<=" : "<"; + append_op_fraction(s, size[i]); + } + return s; + } + + Fractional get_upper_limit() const { + double inv_denom = 1.0 / denom; + return Fractional(inv_denom * size[0] + (incl[0] ? 1e-9 : -1e-9), + inv_denom * size[1] + (incl[1] ? 1e-9 : -1e-9), + inv_denom * size[2] + (incl[2] ? 1e-9 : -1e-9)); + } + + // cf. Ccp4Base::get_extent() + Box get_extent() const { + Box box; + box.minimum = Fractional(-1e-9, -1e-9, -1e-9); + box.maximum = get_upper_limit(); + return box; + } + + std::array uvw_end(const GridMeta& meta) const { + if (meta.axis_order != AxisOrder::XYZ) + fail("grid is not fully setup"); + Fractional f = get_upper_limit(); + // upper limit is positive and never exact integer + auto iceil = [](double x) { return int(x) + 1; }; + return {iceil(f.x * meta.nu), iceil(f.y * meta.nv), iceil(f.z * meta.nw)}; + } +}; + +// Returns asu brick upper bound. Lower bound is always (0,0,0). +// Brute force method that considers 8^3 sizes. +inline AsuBrick find_asu_brick(const SpaceGroup* sg) { + if (sg == nullptr) + fail("Missing space group"); + + using Point = std::array; + static_assert(AsuBrick::denom == 24, ""); + const int allowed_sizes[] = {3, 4, 6, 8, 12, 16, 18, 24}; + const GroupOps gops = sg->operations(); + const int n_ops = gops.order(); + + Grid grid; + grid.spacegroup = sg; + // Testing with grid size 24 can't distinguish x<=1/8 and x<1/6, + // but it happens to give the same results as grid size 48 for all + // space groups tabulated in gemmi, so it's fine. + // M=1 -> grid size 24; M=2 -> grid size 48 + constexpr int M = 1; + grid.set_size(M*AsuBrick::denom, M*AsuBrick::denom, M*AsuBrick::denom); + const std::vector ops = grid.get_scaled_ops_except_id(); + + auto is_asu_brick = [&](const AsuBrick& brick, bool check_size) -> bool { + // The most effective screening points for grid size 24. + // These points were determined by doing calculations for all space groups + // from gemmi::spacegroup_tables. + static const Point size_checkpoints[] = { + {7, 17, 7}, // eliminates 9866 out of 14726 wrong candidates + {11, 1, 23}, // eliminates 2208 out of the rest + {11, 10, 11}, // eliminates 1108 + {19, 1, 1}, // eliminates 665 + {3, 7, 19}, // eliminates 305 + {13, 9, 3}, + {23, 23, 23}, + {21, 10, 7}, + {11, 22, 1}, + {9, 15, 3}, + {5, 17, 23}, + {1, 5, 23}, + {5, 7, 17}, + {7, 5, 15}, + {20, 4, 5}, + {9, 23, 23}, + {9, 13, 13}, // eliminates the last wrong candidates + }; + static const Point boundary_checkpoints[] = { + {6, 18, 18}, + {12, 12, 12}, + {8, 16, 0}, + {0, 12, 3}, + {12, 3, 2}, + {0, 0, 12}, + {3, 12, 6}, + {16, 8, 9}, + {9, 21, 21}, + {8, 16, 6}, + {12, 12, 7}, + {20, 15, 0}, + {12, 0, 1}, + {16, 0, 0}, + {0, 6, 18}, + // ... + }; + if (M == 1) { + auto is_in = [&](const Point& p) { + return p[0] < brick.size[0] + int(brick.incl[0]) + && p[1] < brick.size[1] + int(brick.incl[1]) + && p[2] < brick.size[2] + int(brick.incl[2]); + }; + auto check_point = [&](const Point& point) { + if (is_in(point)) + return true; + for (const GridOp& op : ops) { + Point t = op.apply(point[0], point[1], point[2]); + grid.index_n_ref(t[0], t[1], t[2]); + if (is_in(t)) + return true; + } + return false; + }; + + auto it = check_size ? std::begin(size_checkpoints) : std::begin(boundary_checkpoints); + auto end = check_size ? std::end(size_checkpoints) : std::end(boundary_checkpoints); + for (; it != end; ++it) + if (!check_point(*it)) + return false; + } + + // full check (it could be skipped for M==1 and check_size) + grid.fill(0); + int w_lim = M * brick.size[2] + int(brick.incl[2]); + int v_lim = M * brick.size[1] + int(brick.incl[1]); + int u_lim = M * brick.size[0] + int(brick.incl[0]); + for (int w = 0; w < w_lim; ++w) + for (int v = 0; v < v_lim; ++v) + for (int u = 0; u < u_lim; ++u) { + size_t idx = grid.index_q(u, v, w); + if (grid.data[idx] == 0) { + grid.data[idx] = 1; + for (const GridOp& op : ops) { + Point t = op.apply(u, v, w); + size_t mate_idx = grid.index_n(t[0], t[1], t[2]); + grid.data[mate_idx] = 1; + } + } + } +#if 0 + // this code was used for determining checkpoints + bool found = false; + for (size_t n = 0; n != grid.data.size(); ++n) + if (grid.data[n] == 0) { + auto p = grid.index_to_point(n); + printf("[debug1] %d %d %d is missing\n", p.u, p.v, p.w); + found = true; + } + if (found) + printf("[debug2] checkpoints failed\n"); +#endif + return !in_vector(std::int8_t(0), grid.data); + }; + + std::vector possible_bricks; + for (int a : allowed_sizes) + for (int b : allowed_sizes) + for (int c : allowed_sizes) { + AsuBrick brick(a, b, c); + if (brick.volume * n_ops >= AsuBrick::denom * AsuBrick::denom * AsuBrick::denom) + possible_bricks.push_back(brick); + } + // the last item is the full unit cell, no need to check it + possible_bricks.pop_back(); + // if two bricks have the same size, prefer the more cube-shaped one + std::stable_sort(possible_bricks.begin(), possible_bricks.end(), + [](const AsuBrick& a, const AsuBrick& b) { + return a.volume < b.volume || + (a.volume == b.volume && a.size[0] + a.size[1] + a.size[2] < + b.size[0] + b.size[1] + b.size[2]); + }); + for (AsuBrick& brick : possible_bricks) + if (is_asu_brick(brick, true)) { + for (int i = 0; i < 3; ++i) { + if (brick.incl[i] && brick.size[i] != 4) { + brick.incl[i] = false; + if (!is_asu_brick(brick, false)) + brick.incl[i] = true; + } + } + return brick; + } + return AsuBrick(AsuBrick::denom, AsuBrick::denom, AsuBrick::denom); +} + + +template struct MaskedGrid { + std::vector mask; + Grid* grid; + + struct iterator { + typename GridBase::iterator grid_iterator; + const std::vector& mask_ref; + iterator(typename GridBase::iterator it, const std::vector& mask) + : grid_iterator(it), mask_ref(mask) {} + iterator& operator++() { + do { + ++grid_iterator; + } while (grid_iterator.index != mask_ref.size() && mask_ref[grid_iterator.index] != 0); + return *this; + } + typename GridBase::Point operator*() { return *grid_iterator; } + bool operator==(const iterator &o) const { return grid_iterator == o.grid_iterator; } + bool operator!=(const iterator &o) const { return grid_iterator != o.grid_iterator; } + }; + iterator begin() { return {grid->begin(), mask}; } + iterator end() { return {grid->end(), mask}; } +}; + +template +std::vector get_asu_mask(const GridMeta& grid) { + std::vector mask(grid.point_count(), 2); + std::vector ops = grid.get_scaled_ops_except_id(); + auto end = find_asu_brick(grid.spacegroup).uvw_end(grid); + for (int w = 0; w < end[2]; ++w) + for (int v = 0; v < end[1]; ++v) + for (int u = 0; u < end[0]; ++u) { + size_t idx = grid.index_q(u, v, w); + if (mask[idx] == 2) { + mask[idx] = 0; + for (const GridOp& op : ops) { + std::array t = op.apply(u, v, w); + size_t mate_idx = grid.index_n(t[0], t[1], t[2]); + // grid point can be on special position + if (mate_idx != idx) + mask[mate_idx] = 1; + } + } + } + if (std::find(mask.begin(), mask.end(), 2) != mask.end()) + fail("get_asu_mask(): internal error"); + return mask; +} + +template +MaskedGrid masked_asu(Grid& grid) { + return {get_asu_mask(grid), &grid}; +} + + +// Calculating bounding box (brick) with the data (non-zero and non-NaN). + +namespace impl { +// find the shortest span (possibly wrapped) that contains all true values +inline std::pair trim_false_values(const std::vector& vec) { + const int n = (int) vec.size(); + assert(n != 0); + std::pair span{n, n}; // return value for all-true vector + int max_trim = 0; + // first calculate the wrapped span (initial + final non-zero values) + if (!vec[0] || !vec[n-1]) { + // determine trailing-false length and store it in span.first + while (span.first != 0 && !vec[span.first-1]) + --span.first; + if (span.first == 0) // all-false vector + return span; // i.e. {0,n} + // determine leading-false length and store it in span.second + span.second = 0; + while (span.second != n && !vec[span.second]) + ++span.second; + max_trim = span.second + (n - span.first); + } + for (int start = 0; ;) { + for (;;) { + if (start == n) + return span; + if (!vec[start]) + break; + ++start; + } + int end = start + 1; + while (end != n && !vec[end]) + ++end; + if (end - start > max_trim) { + max_trim = end - start; + span.first = start; + span.second = end; + } + start = end; + } + unreachable(); +} +} // namespace impl + +// Get the smallest box with non-zero (and non-NaN) values. +template +Box get_nonzero_extent(const GridBase& grid) { + grid.check_not_empty(); + std::vector nonzero[3]; + nonzero[0].resize(grid.nu, false); + nonzero[1].resize(grid.nv, false); + nonzero[2].resize(grid.nw, false); + size_t idx = 0; + for (int w = 0; w != grid.nw; ++w) + for (int v = 0; v != grid.nv; ++v) + for (int u = 0; u != grid.nu; ++u, ++idx) { + T val = grid.data[idx]; + if (!(impl::is_nan(val) || val == 0)) { + nonzero[0][u] = true; + nonzero[1][v] = true; + nonzero[2][w] = true; + } + } + Box box; + for (int i = 0; i < 3; ++i) { + auto span = impl::trim_false_values(nonzero[i]); + double inv_n = 1.0 / nonzero[i].size(); + box.minimum.at(i) = (span.second - 0.5) * inv_n - int(span.second >= span.first); + box.maximum.at(i) = (span.first - 0.5) * inv_n; + } + return box; +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/bessel.hpp b/gemmi_gph/gemmi/bessel.hpp new file mode 100644 index 00000000..cdd6d04d --- /dev/null +++ b/gemmi_gph/gemmi/bessel.hpp @@ -0,0 +1,128 @@ +// Functions derived from modified Bessel functions I1(x) and I0(x). +// +// Crystallographic codes (including Refmac and cctbx) often use polynomial +// approximation of I0 and I1 from p. 378 of Abramowitz and Stegun. +// Gemmi uses approximation based on polynomial coefficients from bessel_i0 +// and bessel_i1(float) from Boost.Math: +// https://www.boost.org/doc/libs/1_76_0/libs/math/doc/html/math_toolkit/bessel/mbessel.html +// This approximation was derived in 2017 by John Maddock, +// building on the work of Pavel Holoborodko: +// https://www.advanpix.com/2015/11/11/rational-approximations-for-the-modified-bessel-function-of-the-first-kind-i0-computations-double-precision/ +// The efficiency is similar to that of scitbx.math.bessel_i1_over_i0. +// Using std::cyl_bessel_if was not considered, because it requires C++17, + +#ifndef GEMMI_BESSEL_HPP_ +#define GEMMI_BESSEL_HPP_ + +#include + +namespace gemmi { + +template +inline double evaluate_polynomial(const double(&poly)[N], double x) { + static_assert(N > 1, ""); + double result = poly[N-1]; + for (int i = N-2; i >= 0; --i) + result = result * x + poly[i]; + return result; +} + +template +struct BesselTables_ +{ + static const double P1[8]; + static const double Q1[9]; + static const double P2[5]; + static const double Q2[5]; + static const double Q3[3]; +}; +template const double BesselTables_::P1[8] = { + 8.333333221e-02, + 6.944453712e-03, + 3.472097211e-04, + 1.158047174e-05, + 2.739745142e-07, + 5.135884609e-09, + 5.262251502e-11, + 1.331933703e-12 +}; +template const double BesselTables_::Q1[9] = { + 1.00000003928615375e+00, + 2.49999576572179639e-01, + 2.77785268558399407e-02, + 1.73560257755821695e-03, + 6.96166518788906424e-05, + 1.89645733877137904e-06, + 4.29455004657565361e-08, + 3.90565476357034480e-10, + 1.48095934745267240e-11 +}; +template const double BesselTables_::P2[5] = { + 3.98942115977513013e-01, + -1.49581264836620262e-01, + -4.76475741878486795e-02, + -2.65157315524784407e-02, + -1.47148600683672014e-01 +}; +template const double BesselTables_::Q2[5] = { + 3.98942651588301770e-01, + 4.98327234176892844e-02, + 2.91866904423115499e-02, + 1.35614940793742178e-02, + 1.31409251787866793e-01 +}; +template const double BesselTables_::Q3[3] = { + 3.98942391532752700e-01, + 4.98455950638200020e-02, + 2.94835666900682535e-02 +}; + + +inline double bessel_i1_over_i0(double x) { + using B = BesselTables_; + if (x < 0) + return -bessel_i1_over_i0(-x); + if (x < 7.75) { + double a = x * x / 4; + double bessel0 = a * evaluate_polynomial(B::Q1, a) + 1; + double R[3] = { 1, 0.5f, evaluate_polynomial(B::P1, a) }; + double bessel1 = x * evaluate_polynomial(R, a) / 2; + return bessel1 / bessel0; + } + double p = evaluate_polynomial(B::P2, 1 / x); + double q = x < 50 ? evaluate_polynomial(B::Q2, 1 / x) + : evaluate_polynomial(B::Q3, 1 / x); + return p / q; +} + +// Simplified function from Boost.Math. +// Similar to std::cyl_bessel_i(0, x), but much faster, less exact and doesn't +// throw out_of_range on negative argument. Relative error < 5.02e-08. +inline double bessel_i0(double x) { + using B = BesselTables_; + x = std::fabs(x); + if (x < 7.75) { + double a = x * x / 4; + return a * evaluate_polynomial(B::Q1, a) + 1; + } + if (x < 50) + return std::exp(x) * evaluate_polynomial(B::Q2, 1 / x) / std::sqrt(x); + double ex = std::exp(x / 2); + return ex * evaluate_polynomial(B::Q3, 1 / x) / std::sqrt(x) * ex; +} + +// Relative error < 4e-08. +inline double log_bessel_i0(double x) { + using B = BesselTables_; + x = std::fabs(x); + if (x < 7.75) { + double a = x * x / 4; + return std::log1p(a * evaluate_polynomial(B::Q1, a)); + } + double q = x < 50 ? evaluate_polynomial(B::Q2, 1 / x) + : evaluate_polynomial(B::Q3, 1 / x); + return x + std::log(q / std::sqrt(x)); +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/binner.hpp b/gemmi_gph/gemmi/binner.hpp new file mode 100644 index 00000000..8e732563 --- /dev/null +++ b/gemmi_gph/gemmi/binner.hpp @@ -0,0 +1,178 @@ +// Copyright 2022 Global Phasing Ltd. +// +// Binning - resolution shells for reflections. + +#ifndef GEMMI_BINNER_HPP_ +#define GEMMI_BINNER_HPP_ + +#include +#include // for numeric_limits +#include "unitcell.hpp" // for UnitCell + +namespace gemmi { + +struct Binner { + enum class Method { + EqualCount, + Dstar, + Dstar2, + Dstar3, + }; + + void setup_from_1_d2(int nbins, Method method, std::vector&& inv_d2, + const UnitCell* cell_) { + if (nbins < 1) + fail("Binner: nbins argument must be positive"); + if (inv_d2.empty()) + fail("Binner: no data"); + if (cell_) + cell = *cell_; + if (!cell.is_crystal()) + fail("Binner: unknown unit cell"); + // first setup 2N bins to get both bin limits and middle points + limits.resize(2 * nbins); + if (method == Method::EqualCount) { + std::sort(inv_d2.begin(), inv_d2.end()); + min_1_d2 = inv_d2.front(); + max_1_d2 = inv_d2.back(); + } else { + min_1_d2 = max_1_d2 = inv_d2.front(); + for (double x : inv_d2) { + if (x < min_1_d2) + min_1_d2 = x; + if (x > max_1_d2) + max_1_d2 = x; + } + } + switch (method) { + case Method::EqualCount: { + double avg_count = double(inv_d2.size()) / limits.size(); + for (size_t i = 1; i < limits.size(); ++i) + limits[i-1] = inv_d2[int(avg_count * i)]; + break; + } + case Method::Dstar2: { + double step = (max_1_d2 - min_1_d2) / limits.size(); + for (size_t i = 1; i < limits.size(); ++i) + limits[i-1] = min_1_d2 + i * step; + break; + } + case Method::Dstar: { + double min_1_d = std::sqrt(min_1_d2); + double max_1_d = std::sqrt(max_1_d2); + double step = (max_1_d - min_1_d) / limits.size(); + for (size_t i = 1; i < limits.size(); ++i) + limits[i-1] = sq(min_1_d + i * step); + break; + } + case Method::Dstar3: { + double min_1_d3 = min_1_d2 * std::sqrt(min_1_d2); + double max_1_d3 = max_1_d2 * std::sqrt(max_1_d2); + double step = (max_1_d3 - min_1_d3) / limits.size(); + for (size_t i = 1; i < limits.size(); ++i) + limits[i-1] = sq(std::cbrt(min_1_d3 + i * step)); + break; + } + } + limits.back() = std::numeric_limits::infinity(); + + mids.resize(nbins); + for (int i = 0; i < nbins; ++i) { + mids[i] = limits[2*i]; + limits[i] = limits[2*i+1]; + } + limits.resize(nbins); + } + + template + void setup(int nbins, Method method, const DataProxy& proxy, + const UnitCell* cell_=nullptr, size_t col_idx=0) { + if (col_idx >= proxy.stride()) + fail("wrong col_idx in Binner::setup()"); + cell = cell_ ? *cell_ : proxy.unit_cell(); + std::vector inv_d2; + inv_d2.reserve(proxy.size() / proxy.stride()); + for (size_t offset = 0; offset < proxy.size(); offset += proxy.stride()) + if (col_idx == 0 || !std::isnan(proxy.get_num(offset + col_idx))) + inv_d2.push_back(cell.calculate_1_d2(proxy.get_hkl(offset))); + setup_from_1_d2(nbins, method, std::move(inv_d2), nullptr); + } + + void ensure_limits_are_set() const { + if (limits.empty()) + fail("Binner not set up"); + } + + // Generic. Method-specific versions could be faster. + int get_bin_from_1_d2(double inv_d2) { + ensure_limits_are_set(); + auto it = std::lower_bound(limits.begin(), limits.end(), inv_d2); + // it can't be limits.end() b/c limits.back() is +inf + return int(it - limits.begin()); + } + + int get_bin(const Miller& hkl) { + double inv_d2 = cell.calculate_1_d2(hkl); + return get_bin_from_1_d2(inv_d2); + } + + // We assume that bins are seeked mostly for sorted reflections, + // so it's usually either the same bin as previously, or the next one. + int get_bin_from_1_d2_hinted(double inv_d2, int& hint) const { + if (inv_d2 <= limits[hint]) { + while (hint != 0 && limits[hint-1] > inv_d2) + --hint; + } else { + // limits.back() is +inf, so we won't overrun + while (limits[hint] < inv_d2) + ++hint; + } + return hint; + } + + int get_bin_hinted(const Miller& hkl, int& hint) const { + double inv_d2 = cell.calculate_1_d2(hkl); + return get_bin_from_1_d2_hinted(inv_d2, hint); + } + + template + std::vector get_bins(const DataProxy& proxy) const { + ensure_limits_are_set(); + int hint = 0; + std::vector nums(proxy.size() / proxy.stride()); + for (size_t i = 0, offset = 0; i < nums.size(); ++i, offset += proxy.stride()) + nums[i] = get_bin_hinted(proxy.get_hkl(offset), hint); + return nums; + } + + std::vector get_bins_from_1_d2(const double* inv_d2, size_t size) const { + ensure_limits_are_set(); + int hint = 0; + std::vector nums(size); + for (size_t i = 0; i < size; ++i) + nums[i] = get_bin_from_1_d2_hinted(inv_d2[i], hint); + return nums; + } + + std::vector get_bins_from_1_d2(const std::vector& inv_d2) const { + return get_bins_from_1_d2(inv_d2.data(), inv_d2.size()); + } + + double dmin_of_bin(int n) const { + return 1. / std::sqrt(n == (int) size() - 1 ? max_1_d2 : limits.at(n)); + } + double dmax_of_bin(int n) const { + return 1. / std::sqrt(n == 0 ? min_1_d2 : limits.at(n-1)); + } + + size_t size() const { return limits.size(); } + + UnitCell cell; + double min_1_d2; + double max_1_d2; + std::vector limits; // upper limit of each bin + std::vector mids; // the middle of each bin +}; + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/blob.hpp b/gemmi_gph/gemmi/blob.hpp new file mode 100644 index 00000000..4b07a972 --- /dev/null +++ b/gemmi_gph/gemmi/blob.hpp @@ -0,0 +1,140 @@ +// Copyright 2021 Global Phasing Ltd. +// +// Finding maxima or "blobs" in a Grid (map). +// Similar to CCP4 PEAKMAX and COOT's "Unmodelled blobs". +// +// Implementation of the flood fill algorithm in find_blobs_by_flood_fill() +// differs from from FloodFill in floodfill.hpp. +// FloodFill uses more efficient scanline fill, but doesn't use symmetry. + +#ifndef GEMMI_BLOB_HPP_ +#define GEMMI_BLOB_HPP_ + +#include "grid.hpp" // for Grid +#include "asumask.hpp" // for get_asu_mask + +namespace gemmi { + +struct Blob { + double volume = 0.0; + double score = 0.0; + double peak_value = 0.0; + gemmi::Position centroid; + gemmi::Position peak_pos; + explicit operator bool() const { return volume != 0. ; } +}; + +struct BlobCriteria { + double cutoff; + double min_volume = 10.0; + double min_score = 15.0; + double min_peak = 0.0; +}; + +namespace impl { + +struct GridConstPoint { + int u, v, w; + float value; +}; + +inline Blob make_blob_of_points(const std::vector& points, + const GridMeta& grid, + const BlobCriteria& criteria) { + Blob blob; + if (points.size() < 3) + return blob; + double volume_per_point = grid.unit_cell.volume / grid.point_count(); + double volume = points.size() * volume_per_point; + if (volume < criteria.min_volume) + return blob; + double sum[3] = {0., 0., 0.}; + const GridConstPoint* peak_point = &points[0]; + blob.peak_value = points[0].value; + double score = 0.; + for (const GridConstPoint& point : points) { + score += point.value; + if (point.value > blob.peak_value) { + blob.peak_value = point.value; + peak_point = &point; + } + sum[0] += double(point.u) * point.value; + sum[1] += double(point.v) * point.value; + sum[2] += double(point.w) * point.value; + } + if (blob.peak_value < criteria.min_peak) + return blob; + blob.score = score * volume_per_point; + if (blob.score < criteria.min_score) + return blob; + gemmi::Fractional fract(sum[0] / (score * grid.nu), + sum[1] / (score * grid.nv), + sum[2] / (score * grid.nw)); + blob.centroid = grid.unit_cell.orthogonalize(fract); + blob.peak_pos = grid.get_position(peak_point->u, peak_point->v, peak_point->w); + blob.volume = volume; + return blob; +} + +} // namespace impl + +// with negate=true grid negatives of grid values are used +inline std::vector find_blobs_by_flood_fill(const gemmi::Grid& grid, + const BlobCriteria& criteria, + bool negate=false) { + std::vector blobs; + std::array, 6> moves = {{{{-1, 0, 0}}, {{1, 0, 0}}, + {{0 ,-1, 0}}, {{0, 1, 0}}, + {{0, 0, -1}}, {{0, 0, 1}}}}; + // the mask will be used as follows: + // -1=in blob, 0=in asu, not in blob (so far), 1=in neither + std::vector mask = gemmi::get_asu_mask(grid); + std::vector ops = grid.get_scaled_ops_except_id(); + size_t idx = 0; + for (int w = 0; w != grid.nw; ++w) + for (int v = 0; v != grid.nv; ++v) + for (int u = 0; u != grid.nu; ++u, ++idx) { + assert(idx == grid.index_q(u, v, w)); + if (mask[idx] != 0) + continue; + float value = grid.data[idx]; + if (negate) + value = -value; + if (value < criteria.cutoff) + continue; + std::vector points; + points.push_back({u, v, w, value}); + mask[idx] = -1; + for (size_t j = 0; j < points.size()/*increasing!*/; ++j) + for (const std::array& mv : moves) { + int nabe_u = points[j].u + mv[0]; + int nabe_v = points[j].v + mv[1]; + int nabe_w = points[j].w + mv[2]; + size_t nabe_idx = grid.index_s(nabe_u, nabe_v, nabe_w); + if (mask[nabe_idx] == -1) + continue; + float nabe_value = grid.data[nabe_idx]; + if (negate) + nabe_value = -nabe_value; + if (nabe_value > criteria.cutoff) { + if (mask[nabe_idx] != 0) + for (const gemmi::GridOp& op : ops) { + auto t = op.apply(nabe_u, nabe_v, nabe_w); + size_t mate_idx = grid.index_s(t[0], t[1], t[2]); + if (mask[mate_idx] == 0) + mask[mate_idx] = 1; + } + mask[nabe_idx] = -1; + points.push_back({nabe_u, nabe_v, nabe_w, nabe_value}); + } + } + if (Blob blob = impl::make_blob_of_points(points, grid, criteria)) + blobs.push_back(blob); + } + std::sort(blobs.begin(), blobs.end(), + [](const Blob& a, const Blob& b) { return a.score > b.score; }); + return blobs; +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/bond_idx.hpp b/gemmi_gph/gemmi/bond_idx.hpp new file mode 100644 index 00000000..4f262786 --- /dev/null +++ b/gemmi_gph/gemmi/bond_idx.hpp @@ -0,0 +1,92 @@ +// Copyright 2018 Global Phasing Ltd. +// +// BondIndex: for checking which atoms are bonded, calculating graph distance. + +#ifndef GEMMI_BOND_IDX_HPP_ +#define GEMMI_BOND_IDX_HPP_ + +#include +#include "model.hpp" // for Residue, Atom +#include "monlib.hpp" // for MonLib + +namespace gemmi { + +struct BondIndex { + const Model& model; + + struct AtomImage { + int atom_serial; + bool same_image; + bool operator==(const AtomImage& o) const { + return atom_serial == o.atom_serial && same_image == o.same_image; + } + }; + std::map> index; + + BondIndex(const Model& model_) : model(model_) { + for (const_CRA cra : model.all()) + if (!index.emplace(cra.atom->serial, std::vector()).second) + fail("duplicated serial numbers"); + } + + void add_oneway_link(const Atom& a, const Atom& b, bool same_image) { + std::vector& list_a = index.at(a.serial); + AtomImage ai{b.serial, same_image}; + if (!in_vector(ai, list_a)) + list_a.push_back(ai); + } + + void add_link(const Atom& a, const Atom& b, bool same_image) { + add_oneway_link(a, b, same_image); + add_oneway_link(b, a, same_image); + } + + // add_monomer_bonds() is not aware of modifications associated with links. + // Modifications that add bonds are rare, but to be more correct, use bonds + // from topology (Topo::bonds). + void add_monomer_bonds(MonLib& monlib) { + for (const Chain& chain : model.chains) + for (const Residue& res : chain.residues) { + std::string altlocs; + add_distinct_altlocs(res, altlocs); + if (altlocs.empty()) + altlocs += '*'; + auto monomer = monlib.monomers.find(res.name); + if (monomer == monlib.monomers.end()) + fail("Monomer description not found: " + res.name); + for (const Restraints::Bond& bond : monomer->second.rt.bonds) + for (char alt : altlocs) + if (const Atom* at1 = res.find_atom(bond.id1.atom, alt)) + if (const Atom* at2 = res.find_atom(bond.id2.atom, alt)) { + add_link(*at1, *at2, true); + if (!at1->altloc && !at2->altloc) + break; + } + } + } + + bool are_linked(const Atom& a, const Atom& b, bool same_image) const { + return in_vector({b.serial, same_image}, index.at(a.serial)); + } + + int graph_distance(const Atom& a, const Atom& b, bool same_image, + int max_distance=4) const { + std::vector neighbors(1, {a.serial, true}); + for (int distance = 1; distance <= max_distance; ++distance) { + for (size_t n = neighbors.size(); n--; ) { + for (AtomImage ai : index.at(neighbors[n].atom_serial)) { + if (!neighbors[n].same_image) + ai.same_image = !ai.same_image; + if (ai.atom_serial == b.serial && ai.same_image == same_image) + return distance; + if (!in_vector(ai, neighbors)) + neighbors.push_back(ai); + } + } + } + return max_distance + 1; + } +}; + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/c4322.hpp b/gemmi_gph/gemmi/c4322.hpp new file mode 100644 index 00000000..abf8c982 --- /dev/null +++ b/gemmi_gph/gemmi/c4322.hpp @@ -0,0 +1,165 @@ +// Copyright 2020 Global Phasing Ltd. + +// Electron scattering factor coefficients from the International Tables. +// +// Based on International Tables for Crystallography Volume C, edition 2011, +// table 4.3.2.2 (pp. 282-283): "Elastic atomic scattering factors of electrons +// for neutral atoms and s up to 2.0 A^-1". +// The same data is included in cctbx and in CCP4 (file atomsf_electron.lib). + +#ifndef GEMMI_C4322_HPP_ +#define GEMMI_C4322_HPP_ + +#include +#include "formfact.hpp" // for GaussianCoef +#include "elem.hpp" // for El +#include "fail.hpp" // for El + +namespace gemmi { + +#if defined(__GNUC__) && __GNUC__-0 > 4 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-conversion" +#endif + +template +struct C4322 { + using Coef = GaussianCoef<5, 0, Real>; + static Coef data[99]; + + static bool has(El el) { + return el <= El::Cf || el == El::D; + } + + static Coef& get(El el, signed char /*charge*/=0, int /*serial*/=0) { + // ordinal for X, H, ... Cf; H=1 for D; X=0 for Es, ... Og + int pos = el <= El::Cf ? (int)el : (int)(el == El::D); + return data[pos]; + } +}; + +template +typename C4322::Coef C4322::data[99] = { + // a1, a2, a3, a4, a5, b1, b2, b3, b4, b5 + {0.0974, 0.2921, 0.691, 0.699, 0.2039, 0.2067, 1.3815, 4.6943, 12.7105, 32.4726}, // X=O + {0.0349, 0.1201, 0.197, 0.0573, 0.1195, 0.5347, 3.5867, 12.3471, 18.9525, 38.6269}, // H + {0.0317, 0.0838, 0.1526, 0.1334, 0.0164, 0.2507, 1.4751, 4.4938, 12.6646, 31.1653}, // He + {0.075, 0.2249, 0.5548, 1.4954, 0.9354, 0.3864, 2.9383, 15.3829, 53.5545, 138.7337}, // Li + {0.078, 0.221, 0.674, 1.3867, 0.6925, 0.3131, 2.2381, 10.1517, 30.9061, 78.3273}, // Be + {0.0909, 0.2551, 0.7738, 1.2136, 0.4606, 0.2995, 2.1155, 8.3816, 24.1292, 63.1314}, // B + {0.0893, 0.2563, 0.757, 1.0487, 0.3575, 0.2465, 1.71, 6.4094, 18.6113, 50.2523}, // C + {0.1022, 0.3219, 0.7982, 0.8197, 0.1715, 0.2451, 1.7481, 6.1925, 17.3894, 48.1431}, // N + {0.0974, 0.2921, 0.691, 0.699, 0.2039, 0.2067, 1.3815, 4.6943, 12.7105, 32.4726}, // O + {0.1083, 0.3175, 0.6487, 0.5846, 0.1421, 0.2057, 1.3439, 4.2788, 11.3932, 28.7881}, // F + {0.1269, 0.3535, 0.5582, 0.4674, 0.146, 0.22, 1.3779, 4.0203, 9.4934, 23.1278}, // Ne + {0.2142, 0.6853, 0.7692, 1.6589, 1.4482, 0.3334, 2.3446, 10.083, 48.3037, 138.27}, // Na + {0.2314, 0.6866, 0.9677, 2.1882, 1.1339, 0.3278, 2.272, 10.9241, 39.2898, 101.9748}, // Mg + {0.239, 0.6573, 1.2011, 2.5586, 1.2312, 0.3138, 2.1063, 10.4163, 34.4552, 98.5344}, // Al + {0.2519, 0.6372, 1.3795, 2.5082, 1.05, 0.3075, 2.0174, 9.6746, 29.3744, 80.4732}, // Si + {0.2548, 0.6106, 1.4541, 2.3204, 0.8477, 0.2908, 1.874, 8.5176, 24.3434, 63.2996}, // P + {0.2497, 0.5628, 1.3899, 2.1865, 0.7715, 0.2681, 1.6711, 7.0267, 19.5377, 50.3888}, // S + {0.2443, 0.5397, 1.3919, 2.0197, 0.6621, 0.2468, 1.5242, 6.1537, 16.6687, 42.3086}, // Cl + {0.2385, 0.5017, 1.3428, 1.8899, 0.6079, 0.2289, 1.3694, 5.2561, 14.0928, 35.5361}, // Ar + {0.4115, 1.4031, 2.2784, 2.6742, 2.2162, 0.3703, 3.3874, 13.1029, 68.9592, 194.4329}, // K + {0.4054, 1.388, 2.1602, 3.7532, 2.2063, 0.3499, 3.0991, 11.9608, 53.9353, 142.3892}, // Ca + {0.3787, 1.2181, 2.0594, 3.2618, 2.387, 0.3133, 2.5856, 9.5813, 41.7688, 116.7282}, // Sc + {0.3825, 1.2598, 2.0008, 3.0617, 2.0694, 0.304, 2.4863, 9.2783, 39.0751, 109.4583}, // Ti + {0.3876, 1.275, 1.9109, 2.8314, 1.8979, 0.2967, 2.378, 8.7981, 35.9528, 101.7201}, // V + {0.4046, 1.3696, 1.8941, 2.08, 1.2196, 0.2986, 2.3958, 9.1406, 37.4701, 113.7121}, // Cr + {0.3796, 1.2094, 1.7815, 2.542, 1.5937, 0.2699, 2.0455, 7.4726, 31.0604, 91.5622}, // Mn + {0.3946, 1.2725, 1.7031, 2.314, 1.4795, 0.2717, 2.0443, 7.6007, 29.9714, 86.2265}, // Fe + {0.4118, 1.3161, 1.6493, 2.193, 1.283, 0.2742, 2.0372, 7.7205, 29.968, 84.9383}, // Co + {0.386, 1.1765, 1.5451, 2.073, 1.3814, 0.2478, 1.766, 6.3107, 25.2204, 74.3146}, // Ni + {0.4314, 1.3208, 1.5236, 1.4671, 0.8562, 0.2694, 1.9223, 7.3474, 28.9892, 90.6246}, // Cu + {0.4288, 1.2646, 1.4472, 1.8294, 1.0934, 0.2593, 1.7998, 6.75, 25.586, 73.5284}, // Zn + {0.4818, 1.4032, 1.6561, 2.4605, 1.1054, 0.2825, 1.9785, 8.7546, 32.5238, 98.5523}, // Ga + {0.4655, 1.3014, 1.6088, 2.6998, 1.3003, 0.2647, 1.7926, 7.6071, 26.5541, 77.5238}, // Ge + {0.4517, 1.2229, 1.5852, 2.7958, 1.2638, 0.2493, 1.6436, 6.8154, 22.3681, 62.039}, // As + {0.4477, 1.1678, 1.5843, 2.8087, 1.1956, 0.2405, 1.5442, 6.3231, 19.461, 52.0233}, // Se + {0.4798, 1.1948, 1.8695, 2.6953, 0.8203, 0.2504, 1.5963, 6.9653, 19.8492, 50.3233}, // Br + {0.4546, 1.0993, 1.7696, 2.7068, 0.8672, 0.2309, 1.4279, 5.9449, 16.6752, 42.2243}, // Kr + {1.016, 2.8528, 3.5466, -7.7804, 12.1148, 0.4853, 5.0925, 25.7851, 130.4515, 138.6775},// Rb + {0.6703, 1.4926, 3.3368, 4.46, 3.1501, 0.319, 2.2287, 10.3504, 52.3291, 151.2216}, // Sr + {0.6894, 1.5474, 3.245, 4.2126, 2.9764, 0.3189, 2.2904, 10.0062, 44.0771, 125.012}, // Y + {0.6719, 1.4684, 3.1668, 3.9557, 2.892, 0.3036, 2.1249, 8.9236, 36.8458, 108.2049}, // Zr + {0.6123, 1.2677, 3.0348, 3.3841, 2.3683, 0.2709, 1.7683, 7.2489, 27.9465, 98.5624}, // Nb + {0.6773, 1.4798, 3.1788, 3.0824, 1.8384, 0.292, 2.0606, 8.1129, 30.5336, 100.0658}, // Mo + {0.7082, 1.6392, 3.1993, 3.4327, 1.8711, 0.2976, 2.2106, 8.5246, 33.1456, 96.6377}, // Tc + {0.6735, 1.4934, 3.0966, 2.7254, 1.5597, 0.2773, 1.9716, 7.3249, 26.6891, 90.5581}, // Ru + {0.6413, 1.369, 2.9854, 2.6952, 1.5433, 0.258, 1.7721, 6.3854, 23.2549, 85.1517}, // Rh + {0.5904, 1.1775, 2.6519, 2.2875, 0.8689, 0.2324, 1.5019, 5.1591, 15.5428, 46.8213}, // Pd + {0.6377, 1.379, 2.8294, 2.3631, 1.4553, 0.2466, 1.6974, 5.7656, 20.0943, 76.7372}, // Ag + {0.6364, 1.4247, 2.7802, 2.5973, 1.7886, 0.2407, 1.6823, 5.6588, 20.7219, 69.1109}, // Cd + {0.6768, 1.6589, 2.774, 3.1835, 2.1326, 0.2522, 1.8545, 6.2936, 25.1457, 84.5448}, // In + {0.7224, 1.961, 2.7161, 3.5603, 1.8972, 0.2651, 2.0604, 7.3011, 27.5493, 81.3349}, // Sn + {0.7106, 1.9247, 2.6149, 3.8322, 1.8899, 0.2562, 1.9646, 6.8852, 24.7648, 68.9168}, // Sb + {0.6947, 1.869, 2.5356, 4.0013, 1.8955, 0.2459, 1.8542, 6.4411, 22.173, 59.2206}, // Te + {0.7047, 1.9484, 2.594, 4.1526, 1.5057, 0.2455, 1.8638, 6.7639, 21.8007, 56.4395}, // I + {0.6737, 1.7908, 2.4129, 4.21, 1.7058, 0.2305, 1.689, 5.8218, 18.3928, 47.2496}, // Xe + {1.2704, 3.8018, 5.6618, 0.9205, 4.8105, 0.4356, 4.2058, 23.4342, 136.7783, 171.7561}, // Cs + {0.9049, 2.6076, 4.8498, 5.1603, 4.7388, 0.3066, 2.4363, 12.1821, 54.6135, 161.9978}, // Ba + {0.8405, 2.3863, 4.6139, 5.1514, 4.7949, 0.2791, 2.141, 10.34, 41.9148, 132.0204}, // La + {0.8551, 2.3915, 4.5772, 5.0278, 4.5118, 0.2805, 2.12, 10.1808, 42.0633, 130.9893}, // Ce + {0.9096, 2.5313, 4.5266, 4.6376, 4.369, 0.2939, 2.2471, 10.8266, 48.8842, 147.602}, // Pr + {0.8807, 2.4183, 4.4448, 4.6858, 4.1725, 0.2802, 2.0836, 10.0357, 47.4506, 146.9976}, // Nd + {0.9471, 2.5463, 4.3523, 4.4789, 3.908, 0.2977, 2.2276, 10.5762, 49.3619, 145.358}, // Pm + {0.9699, 2.5837, 4.2778, 4.4575, 3.5985, 0.3003, 2.2447, 10.6487, 50.7994, 146.4179}, // Sm + {0.8694, 2.2413, 3.9196, 3.9694, 4.5498, 0.2653, 1.859, 8.3998, 36.7397, 125.7089}, // Eu + {0.9673, 2.4702, 4.1148, 4.4972, 3.2099, 0.2909, 2.1014, 9.7067, 43.427, 125.9474}, // Gd + {0.9325, 2.3673, 3.8791, 3.9674, 3.7996, 0.2761, 1.9511, 8.9296, 41.5937, 131.0122}, // Tb + {0.9505, 2.3705, 3.8218, 4.0471, 3.4451, 0.2773, 1.9469, 8.8862, 43.0938, 133.1396}, // Dy + {0.9248, 2.2428, 3.6182, 3.791, 3.7912, 0.266, 1.8183, 7.9655, 33.1129, 101.8139}, // Ho + {1.0373, 2.4824, 3.6558, 3.8925, 3.0056, 0.2944, 2.0797, 9.4156, 45.8056, 132.772}, // Er + {1.0075, 2.3787, 3.544, 3.6932, 3.1759, 0.2816, 1.9486, 8.7162, 41.842, 125.032}, // Tm + {1.0347, 2.3911, 3.4619, 3.6556, 3.0052, 0.2855, 1.9679, 8.7619, 42.3304, 125.6499}, // Yb + {0.9927, 2.2436, 3.3554, 3.7813, 3.0994, 0.2701, 1.8073, 7.8112, 34.4849, 103.3526}, // Lu + {1.0295, 2.2911, 3.411, 3.9497, 2.4925, 0.2761, 1.8625, 8.0961, 34.2712, 98.5295}, // Hf + {1.019, 2.2291, 3.4097, 3.9252, 2.2679, 0.2694, 1.7962, 7.6944, 31.0942, 91.1089}, // Ta + {0.9853, 2.1167, 3.357, 3.7981, 2.2798, 0.2569, 1.6745, 7.0098, 26.9234, 81.391}, // W + {0.9914, 2.0858, 3.4531, 3.8812, 1.8526, 0.2548, 1.6518, 6.8845, 26.7234, 81.7215}, // Re + {0.9813, 2.0322, 3.3665, 3.6235, 1.9741, 0.2487, 1.5973, 6.4737, 23.2817, 70.9254}, // Os + {1.0194, 2.0645, 3.4425, 3.4914, 1.6976, 0.2554, 1.6475, 6.5966, 23.2269, 70.0272}, // Ir + {0.9148, 1.8096, 3.2134, 3.2953, 1.5754, 0.2263, 1.3813, 5.3243, 17.5987, 60.0171}, // Pt + {0.9674, 1.8916, 3.3993, 3.0524, 1.2607, 0.2358, 1.4712, 5.6758, 18.7119, 61.5286}, // Au + {1.0033, 1.9469, 3.4396, 3.1548, 1.418, 0.2413, 1.5298, 5.8009, 19.452, 60.5753}, // Hg + {1.0689, 2.1038, 3.6039, 3.4927, 1.8283, 0.254, 1.6715, 6.3509, 23.1531, 78.7099}, // Tl + {1.0891, 2.1867, 3.616, 3.8031, 1.8994, 0.2552, 1.7174, 6.5131, 23.917, 74.7039}, // Pb + {1.1007, 2.2306, 3.5689, 4.1549, 2.0382, 0.2546, 1.7351, 6.4948, 23.6464, 70.378}, // Bi + {1.1568, 2.4353, 3.6459, 4.4064, 1.7179, 0.2648, 1.8786, 7.1749, 25.1766, 69.2821}, // Po + {1.0909, 2.1976, 3.3831, 4.67, 2.1277, 0.2466, 1.6707, 6.0197, 20.7657, 57.2663}, // At + {1.0756, 2.163, 3.3178, 4.8852, 2.0489, 0.2402, 1.6169, 5.7644, 19.4568, 52.5009}, // Rn + {1.4282, 3.5081, 5.6767, 4.1964, 3.8946, 0.3183, 2.6889, 13.4816, 54.3866, 200.8321}, // Fr + {1.3127, 3.1243, 5.2988, 5.3891, 5.4133, 0.2887, 2.2897, 10.8276, 43.5389, 145.6109}, // Ra + {1.3128, 3.1021, 5.3385, 5.9611, 4.7562, 0.2861, 2.2509, 10.5287, 41.7796, 128.2973}, // Ac + {1.2553, 2.9178, 5.0862, 6.1206, 4.7122, 0.2701, 2.0636, 9.3051, 34.5977, 107.92}, // Th + {1.3218, 3.1444, 5.4371, 5.6444, 4.0107, 0.2827, 2.225, 10.2454, 41.1162, 124.4449}, // Pa + {1.3382, 3.2043, 5.4558, 5.4839, 3.6342, 0.2838, 2.2452, 10.2519, 41.7251, 124.9023}, // U + {1.5193, 4.0053, 6.5327, -0.1402, 6.7489, 0.3213, 2.8206, 14.8878, 68.9103, 81.7257}, // Np + {1.3517, 3.2937, 5.3213, 4.6466, 3.5714, 0.2813, 2.2418, 9.9952, 42.7939, 132.1739}, // Pu + {1.2135, 2.7962, 4.7545, 4.5731, 4.4786, 0.2483, 1.8437, 7.5421, 29.3841, 112.4579}, // Am + {1.2937, 3.11, 5.0393, 4.7546, 3.5031, 0.2638, 2.0341, 8.7101, 35.2992, 109.4972}, // Cm + {1.2915, 3.1023, 4.9309, 4.6009, 3.4661, 0.2611, 2.0023, 8.4377, 34.1559, 105.8911}, // Bk + {1.2089, 2.7391, 4.3482, 4.0047, 4.6497, 0.2421, 1.7487, 6.7262, 23.2153, 80.3108}, // Cf +}; + +#if defined(__GNUC__) && __GNUC__-0 > 4 +#pragma GCC diagnostic pop +#endif + +template +struct CustomCoef { + using Coef = GaussianCoef<5, 0, Real>; + static std::vector data; + + static Coef& get(El, signed char /*charge*/=0, int serial=0) { + if ((size_t)serial >= data.size()) + fail("CustomCoef: serial number exceeds coefficient table length"); + return data[serial]; + } +}; + +template +typename std::vector::Coef> CustomCoef::data; + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/calculate.hpp b/gemmi_gph/gemmi/calculate.hpp new file mode 100644 index 00000000..03a10765 --- /dev/null +++ b/gemmi_gph/gemmi/calculate.hpp @@ -0,0 +1,224 @@ +// Copyright 2017-2018 Global Phasing Ltd. +// +// Calculate various properties of the model. + +#ifndef GEMMI_CALCULATE_HPP_ +#define GEMMI_CALCULATE_HPP_ + +#include +#include // for std::min, std::minmax +#include "model.hpp" +#include "select.hpp" + +namespace gemmi { + +template bool has_hydrogen(const T& obj) { + for (const auto& child : obj.children()) + if (has_hydrogen(child)) + return true; + return false; +} +template<> inline bool has_hydrogen(const Atom& atom) { + return atom.is_hydrogen(); +} + +template size_t count_atom_sites(const T& obj, const Selection* sel=nullptr) { + size_t sum = 0; + if (!sel || sel->matches(obj)) + for (const auto& child : obj.children()) + sum += count_atom_sites(child, sel); + return sum; +} +template<> inline size_t count_atom_sites(const Atom& atom, const Selection* sel) { + return (!sel || sel->matches(atom)) ? 1 : 0; +} + +template double count_occupancies(const T& obj, const Selection* sel=nullptr) { + double sum = 0; + if (!sel || sel->matches(obj)) + for (const auto& child : obj.children()) + sum += count_occupancies(child, sel); + return sum; +} +template<> inline double count_occupancies(const Atom& atom, const Selection* sel) { + return (!sel || sel->matches(atom)) ? atom.occ : 0; +} + + +template double calculate_mass(const T& obj) { + double sum = 0; + for (const auto& child : obj.children()) + sum += calculate_mass(child); + return sum; +} +template<> inline double calculate_mass(const Atom& atom) { + return atom.occ * atom.element.weight(); +} + +struct CenterOfMass { + Position weighted_sum; + double mass; + Position get() const { return Position(weighted_sum / mass); } +}; + +template CenterOfMass calculate_center_of_mass(const T& obj) { + CenterOfMass total{{}, 0.}; + for (const auto& child : obj.children()) { + CenterOfMass part = calculate_center_of_mass(child); + total = {total.weighted_sum + part.weighted_sum, total.mass + part.mass}; + } + return total; +} +template<> inline CenterOfMass calculate_center_of_mass(const Atom& atom) { + double w_mass = atom.element.weight() * atom.occ; + return CenterOfMass{Position(atom.pos * w_mass), w_mass}; +} + +template std::pair calculate_b_iso_range(const T& obj) { + std::pair range{INFINITY, -INFINITY}; + for (const auto& child : obj.children()) { + auto r = calculate_b_iso_range(child); + range.first = std::min(range.first, r.first); + range.second = std::max(range.second, r.second); + } + return range; +} +template<> inline std::pair calculate_b_iso_range(const Atom& atom) { + return {atom.b_iso, atom.b_iso}; +} + +/// uses min/max eigenvalues of Baniso, or Biso if B-factor is isotropic +inline std::pair calculate_b_aniso_range(const Model& model) { + std::pair range{INFINITY, -INFINITY}; + for (const Chain& chain : model.chains) + for (const Residue& residue : chain.residues) + for (const Atom& atom : residue.atoms) { + if (atom.occ == 0) + continue; + if (atom.aniso.nonzero()) { + std::array eig = atom.aniso.calculate_eigenvalues(); + auto u = std::minmax({eig[0], eig[1], eig[2]}); + range.first = std::min(range.first, u.first * u_to_b()); + range.second = std::max(range.second, u.second * u_to_b()); + } else { + range.first = std::min(range.first, (double) atom.b_iso); + range.second = std::max(range.second, (double) atom.b_iso); + } + } + return range; +} + + +template void expand_box(const T& obj, Box& box) { + for (const auto& child : obj.children()) + expand_box(child, box); +} +template<> inline void expand_box(const Atom& atom, Box& box) { + box.extend(atom.pos); +} + +// we don't take NCS into account here (cf. NeighborSearch::set_bounding_cell()) +inline Box calculate_box(const Structure& st, double margin=0.) { + Box box; + expand_box(st, box); + if (margin != 0.) + box.add_margin(margin); + return box; +} + +inline Box calculate_fractional_box(const Structure& st, double margin=0.) { + if (!st.cell.is_crystal()) + fail("calculate_fractional_box(): Structure has no unit cell for fractionalization"); + Box box; + for (const Model& model : st.models) + for (const Chain& chain : model.chains) + for (const Residue& res : chain.residues) + for (const Atom& atom : res.atoms) + box.extend(st.cell.fractionalize(atom.pos)); + if (margin != 0.) + box.add_margins({margin * st.cell.ar, margin * st.cell.br, margin * st.cell.cr}); + return box; +} + + +// Calculate B_est from E. Merritt, Some B_eq are more equivalent than others, +// Acta Cryst. A67, 512 (2011) +// http://skuld.bmsc.washington.edu/parvati/ActaA_67_512.pdf +inline double calculate_b_est(const Atom& atom) { + auto eig = atom.aniso.calculate_eigenvalues(); + return u_to_b() * std::sqrt((eig[0] + eig[1] + eig[2]) / + (1/eig[0] + 1/eig[1] + 1/eig[2])); +} + +inline double calculate_angle(const Position& p0, const Position& p1, + const Position& p2) { + return (p0 - p1).angle(p2 - p1); +} + +// discussion: https://stackoverflow.com/questions/20305272/ +inline double calculate_dihedral(const Position& p0, const Position& p1, + const Position& p2, const Position& p3) { + Vec3 b0 = p1 - p0; + Vec3 b1 = p2 - p1; + Vec3 b2 = p3 - p2; + Vec3 u = b1.cross(b0); + Vec3 w = b2.cross(b1); + double y = u.cross(w).dot(b1); + double x = u.dot(w) * b1.length(); + return std::atan2(y, x); +} + +/// the return value is in the same range as that of atan2(), i.e. [-pi, pi] +inline double calculate_dihedral_from_atoms(const Atom* a, const Atom* b, + const Atom* c, const Atom* d) { + if (a && b && c && d) + return calculate_dihedral(a->pos, b->pos, c->pos, d->pos); + return NAN; +} + +inline double calculate_omega(const Residue& res, const Residue& next) { + return calculate_dihedral_from_atoms(res.get_ca(), res.get_c(), + next.get_n(), next.get_ca()); +} + +inline bool is_peptide_bond_cis(const Atom* ca1, const Atom* c, + const Atom* n, const Atom* ca2, + double tolerance_deg) { + double omega = calculate_dihedral_from_atoms(ca1, c, n, ca2); + return std::fabs(omega) < rad(tolerance_deg); +} + +inline double calculate_chiral_volume(const Position& actr, const Position& a1, + const Position& a2, const Position& a3) { + return (a1 - actr).dot((a2 - actr).cross(a3 - actr)); +} + +inline std::array calculate_phi_psi(const Residue* prev, + const Residue& res, + const Residue* next) { + std::array phi_psi{{NAN, NAN}}; + if (prev || next) { + const Atom* CA = res.get_ca(); + const Atom* C = res.get_c(); + const Atom* N = res.get_n(); + if (prev) + phi_psi[0] = calculate_dihedral_from_atoms(prev->get_c(), N, CA, C); + if (next) + phi_psi[1] = calculate_dihedral_from_atoms(N, CA, C, next->get_n()); + } + return phi_psi; +} + +GEMMI_DLL std::array find_best_plane(const std::vector& atoms); + +inline double get_distance_from_plane(const Position& pos, + const std::array& coeff) { + return coeff[0] * pos.x + coeff[1] * pos.y + coeff[2] * pos.z + coeff[3]; +} + +GEMMI_DLL FTransform parse_triplet_as_ftransform(const std::string& s); + +GEMMI_DLL SMat33 calculate_u_from_tls(const TlsGroup& tls, const Position& pos); + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/ccp4.hpp b/gemmi_gph/gemmi/ccp4.hpp new file mode 100644 index 00000000..468333cb --- /dev/null +++ b/gemmi_gph/gemmi/ccp4.hpp @@ -0,0 +1,504 @@ +// Copyright 2018 Global Phasing Ltd. +// +// CCP4 format for maps and masks. + +#ifndef GEMMI_CCP4_HPP_ +#define GEMMI_CCP4_HPP_ + +#include +#include // for ceil, fabs, floor, round +#include // for uint16_t, int32_t +#include // for FILE +#include // for memcpy +#include +#include +#include // for is_same +#include +#include "symmetry.hpp" +#include "fail.hpp" // for fail +#include "fileutil.hpp" // for file_open, is_little_endian, ... +#include "input.hpp" // for AnyStream, FileStream +#include "grid.hpp" + +#include "third_party/half.hpp" +using float16_type = half_float::half; + +namespace gemmi { + +using std::int32_t; + +// options for Ccp4<>::setup +enum class MapSetup { + Full, // reorder and expand to the whole unit cell + NoSymmetry, // reorder and resize to the whole cell, but no symmetry ops + ReorderOnly // reorder axes to X, Y, Z +}; + +struct Ccp4Base { + DataStats hstats; // data statistics read from / written to ccp4 map + // stores raw headers if the grid was read from ccp4 map + std::vector ccp4_header; + bool same_byte_order = true; + + // methods to access info from ccp4 headers, w is word number from the spec + void* header_word(int w) { return &ccp4_header.at(w - 1); } + const void* header_word(int w) const { return &ccp4_header.at(w - 1); } + + int32_t header_i32(int w) const { + int32_t value = ccp4_header.at(w - 1); + if (!same_byte_order) + swap_four_bytes(&value); + return value; + } + std::array header_3i32(int w) const { + return {{ header_i32(w), header_i32(w+1), header_i32(w+2) }}; + } + float header_float(int w) const { + int32_t int_value = header_i32(w); + float f; + std::memcpy(&f, &int_value, 4); + return f; + } + std::string header_str(int w, size_t len=80) const { + if (4 * ccp4_header.size() < 4 * (w - 1) + len) + fail("invalid end of string"); + return std::string(reinterpret_cast(header_word(w)), len); + } + void set_header_i32(int w, int32_t value) { + if (!same_byte_order) + swap_four_bytes(&value); + ccp4_header.at(w - 1) = value; + } + void set_header_3i32(int w, int32_t x, int32_t y, int32_t z) { + set_header_i32(w, x); + set_header_i32(w+1, y); + set_header_i32(w+2, z); + } + void set_header_float(int w, float value) { + int32_t int32_value; + std::memcpy(&int32_value, &value, 4); + set_header_i32(w, int32_value); + } + void set_header_str(int w, const std::string& str) { + std::memcpy(header_word(w), str.c_str(), str.size()); + } + + std::array axis_positions() const { + if (ccp4_header.empty()) + return {{0, 1, 2}}; // assuming it's X,Y,Z + std::array pos{{-1, -1, -1}}; + for (int i = 0; i != 3; ++i) { + int mapi = header_i32(17 + i); + if (mapi <= 0 || mapi > 3 || pos[mapi - 1] != -1) + fail("Incorrect MAPC/MAPR/MAPS records"); + pos[mapi - 1] = i; + } + return pos; + } + + double header_rfloat(int w) const { // rounded to 5 digits + return std::round(1e5 * header_float(w)) / 1e5; + } + + Box get_extent() const { + Box box; + // cf. setup() + auto pos = axis_positions(); + std::array start = header_3i32(5); + std::array size = header_3i32(1); + std::array sampl = header_3i32(8); + for (int i = 0; i < 3; ++i) { + double scale = 1. / sampl[i]; + int p = pos[i]; + box.minimum.at(i) = scale * start[p] - 1e-9; + box.maximum.at(i) = scale * (start[p] + size[p] - 1) + 1e-9; + } + return box; + } + + // Skew transformation (words 25-37) is supported by CCP4 maplib and PyMOL, + // but it's not in the MRC format and is not supported by most programs. + // From maplib.html: Skew transformation is from standard orthogonal + // coordinate frame (as used for atoms) to orthogonal map frame, as + // Xo(map) = S * (Xo(atoms) - t) + bool has_skew_transformation() const { + return header_i32(25) != 0; // LSKFLG should be 0 or 1 + } + Transform get_skew_transformation() const { + return { + // 26-34 SKWMAT + { header_float(26), header_float(27), header_float(28), + header_float(29), header_float(30), header_float(31), + header_float(32), header_float(33), header_float(34) }, + // 35-37 SKWTRN + { header_float(35), header_float(36), header_float(37) } + }; + } + + // ORIGIN (words 50-52), used in MRC format, zeros in CCP4 format + Position get_origin() const { + return Position(header_float(50), header_float(51), header_float(52)); + } + + // this function assumes that the whole unit cell is covered with offset 0 + void prepare_ccp4_header_except_mode_and_stats(GridMeta& grid) { + GroupOps ops; + if (grid.spacegroup) + ops = grid.spacegroup->operations(); + ccp4_header.clear(); + ccp4_header.resize(256 + ops.order() * 20, 0); + set_header_3i32(1, grid.nu, grid.nv, grid.nw); // NX, NY, NZ + set_header_3i32(5, 0, 0, 0); // NXSTART, NYSTART, NZSTART + if (grid.axis_order == AxisOrder::XYZ) + set_header_3i32(8, grid.nu, grid.nv, grid.nw); // MX, MY, MZ + else // grid.axis_order == AxisOrder::ZYX + set_header_3i32(8, grid.nw, grid.nv, grid.nu); + set_header_float(11, (float) grid.unit_cell.a); + set_header_float(12, (float) grid.unit_cell.b); + set_header_float(13, (float) grid.unit_cell.c); + set_header_float(14, (float) grid.unit_cell.alpha); + set_header_float(15, (float) grid.unit_cell.beta); + set_header_float(16, (float) grid.unit_cell.gamma); + if (grid.axis_order == AxisOrder::XYZ) + set_header_3i32(17, 1, 2, 3); // MAPC, MAPR, MAPS + else // grid.axis_order == AxisOrder::ZYX + set_header_3i32(17, 3, 2, 1); + set_header_i32(23, grid.spacegroup ? grid.spacegroup->ccp4 : 1); // ISPG + set_header_i32(24, ops.order() * 80); // NSYMBT + set_header_str(27, "CCP4"); // EXTTYP + set_header_i32(28, 20140); // NVERSION + set_header_str(53, "MAP "); + set_header_i32(54, is_little_endian() ? 0x00004144 : 0x11110000); // MACHST + set_header_i32(56, 1); // labels + std::memset(header_word(57), ' ', 800 + ops.order() * 80); + set_header_str(57, "written by GEMMI"); + int n = 257; + for (Op op : ops) { + set_header_str(n, op.triplet()); + n += 20; + } + } + + void update_header_mode_and_stats(int mode) { + set_header_i32(4, mode); + set_header_float(20, (float) hstats.dmin); + set_header_float(21, (float) hstats.dmax); + set_header_float(22, (float) hstats.dmean); + set_header_float(55, (float) hstats.rms); + // labels could be modified but it's not important + } + + bool full_cell_(const GridMeta& grid) const { + if (ccp4_header.empty()) + return true; // assuming it's full cell + return + // NXSTART et al. must be 0 + header_i32(5) == 0 && header_i32(6) == 0 && header_i32(7) == 0 && + // MX == NX + header_i32(8) == grid.nu && header_i32(9) == grid.nv && header_i32(10) == grid.nw; + } + + void read_ccp4_header_(GridMeta* grid, AnyStream& f, const std::string& path) { + const size_t hsize = 256; + ccp4_header.resize(hsize); + if (!f.read(ccp4_header.data(), 4 * hsize)) + fail("Failed to read map header: " + path); + if (header_str(53, 4) != "MAP ") + fail("Not a CCP4 map: " + path); + std::string machst = header_str(54, 4); + if (machst[0] != 0x44 && machst[0] != 0x11) + fail("Unsupported machine stamp (endianness) in the file?"); + same_byte_order = machst[0] == (is_little_endian() ? 0x44 : 0x11); + size_t ext_w = header_i32(24) / 4; // NSYMBT in words + if (ext_w != 0) { + if (ext_w > 1000000) + fail("Unexpectedly long extended header: " + path); + ccp4_header.resize(hsize + ext_w); + if (!f.read(ccp4_header.data() + hsize, 4 * ext_w)) + fail("Failed to read extended header: " + path); + } + for (int i = 0; i < 3; ++i) { + int axis = header_i32(17 + i); + if (axis < 1 || axis > 3) + fail("Unexpected axis value in word " + std::to_string(17 + i) + + ": " + std::to_string(axis)); + } + hstats.dmin = header_float(20); + hstats.dmax = header_float(21); + hstats.dmean = header_float(22); + hstats.rms = header_float(55); + if (grid) { + grid->unit_cell.set(header_rfloat(11), header_rfloat(12), header_rfloat(13), + header_rfloat(14), header_rfloat(15), header_rfloat(16)); + grid->nu = header_i32(1); + grid->nv = header_i32(2); + grid->nw = header_i32(3); + grid->spacegroup = find_spacegroup_by_number(header_i32(23)); + auto pos = axis_positions(); + grid->axis_order = AxisOrder::Unknown; + if (pos[0] == 0 && pos[1] == 1 && pos[2] == 2 && full_cell_(*grid)) + grid->axis_order = AxisOrder::XYZ; + } + } +}; + +template +struct Ccp4 : public Ccp4Base { + Grid grid; + + /// If the header is empty, prepare it; otherwise, update only MODE + /// and, if update_stats==true, also DMIN, DMAX, DMEAN and RMS. + void update_ccp4_header(int mode=-1, bool update_stats=true) { + if (mode > 2 && mode != 6) + fail("Only modes 0, 1, 2 and 6 are supported."); + if (grid.point_count() == 0) + fail("update_ccp4_header(): set the grid first (it has size 0)"); + if (grid.axis_order == AxisOrder::Unknown) + fail("update_ccp4_header(): run setup() first"); + if (update_stats) + hstats = calculate_data_statistics(grid.data); + if (ccp4_header.empty()) + prepare_ccp4_header_except_mode_and_stats(grid); + assert(ccp4_header.size() >= 256); + if (mode < 0) { + mode = mode_for_data(); + if (mode < 0) + fail("update_ccp4_header: specify map mode explicitly (usually 2)"); + } + update_header_mode_and_stats(mode); + } + + static int mode_for_data() { + if (std::is_same::value) + return 0; + if (std::is_same::value) + return 1; + if (std::is_same::value) + return 2; + if (std::is_same::value) + return 6; + return -1; + } + + bool full_cell() const { return full_cell_(grid); } + + void read_ccp4_header(AnyStream& f, const std::string& path) { + read_ccp4_header_(&grid, f, path); + if (grid.axis_order != AxisOrder::Unknown) + grid.calculate_spacing(); + } + + void setup(T default_value, MapSetup mode=MapSetup::Full); + void set_extent(const Box& box); + + void read_ccp4_stream(AnyStream& f, const std::string& path); + + void read_ccp4_file(const std::string& path) { + FileStream stream(path.c_str(), "rb"); + read_ccp4_stream(stream, path); + } + + void read_ccp4_from_memory(const char* data, size_t size, const std::string& name) { + MemoryStream stream(data, size); + read_ccp4_stream(stream, name); + } + + template + void read_ccp4(Input&& input) { + std::unique_ptr stream = input.create_stream(); + read_ccp4_stream(*stream, input.path()); + } + + void write_ccp4_map(const std::string& path) const; +}; + + +namespace impl { + +template +To translate_map_point(From f) { return static_cast(f); } +// We convert map 2 to 0 by translating non-zero values to 1. +template<> inline +std::int8_t translate_map_point(float f) { return f != 0; } +template<> inline +std::int8_t translate_map_point(float16_type f) { return f != (float16_type)0; } + +template +void read_data(AnyStream& f, std::vector& content) { + if (std::is_same::value) { + size_t len = content.size(); + if (!f.read(content.data(), sizeof(TMem) * len)) + fail("Failed to read all the data from the map file."); + } else { + constexpr size_t chunk_size = 64 * 1024; + std::vector work(chunk_size); + for (size_t i = 0; i < content.size(); i += chunk_size) { + size_t len = std::min(chunk_size, content.size() - i); + if (!f.read(work.data(), sizeof(TFile) * len)) + fail("Failed to read all the data from the map file."); + for (size_t j = 0; j < len; ++j) + content[i+j] = translate_map_point(work[j]); + } + } +} + +template +void write_data(const std::vector& content, FILE* f) { + if (std::is_same::value) { + size_t len = content.size(); + if (std::fwrite(content.data(), sizeof(TFile), len, f) != len) + sys_fail("Failed to write data to the map file"); + } else { + constexpr size_t chunk_size = 64 * 1024; + std::vector work(chunk_size); + for (size_t i = 0; i < content.size(); i += chunk_size) { + size_t len = std::min(chunk_size, content.size() - i); + for (size_t j = 0; j < len; ++j) + work[j] = static_cast(content[i+j]); + if (std::fwrite(work.data(), sizeof(TFile), len, f) != len) + sys_fail("Failed to write data to the map file"); + } + } +} + +} // namespace impl + +// This function was tested only on little-endian machines, +// let us know if you need support for other architectures. +template +void Ccp4::read_ccp4_stream(AnyStream& f, const std::string& path) { + read_ccp4_header(f, path); + grid.data.resize(grid.point_count()); + int mode = header_i32(4); + if (mode == 0) + impl::read_data(f, grid.data); + else if (mode == 1) + impl::read_data(f, grid.data); + else if (mode == 2) + impl::read_data(f, grid.data); + else if (mode == 6) + impl::read_data(f, grid.data); + else if (mode == 12) + impl::read_data(f, grid.data); + else + fail("Mode " + std::to_string(mode) + " is not supported " + "(only 0, 1, 2 and 6 are supported)."); + //if (std::fgetc(f) != EOF) + // fail("The map file is longer then expected."); + + if (!same_byte_order) { + if (sizeof(T) == 2) + for (T& value : grid.data) + swap_two_bytes(&value); + else if (sizeof(T) == 4) + for (T& value : grid.data) + swap_four_bytes(&value); + } +} + +template +void Ccp4::setup(T default_value, MapSetup mode) { + if (grid.axis_order == AxisOrder::XYZ || ccp4_header.empty()) + return; + // cell sampling does not change + const std::array sampl = header_3i32(8); + // get old metadata + const std::array pos = axis_positions(); + std::array start = header_3i32(5); + int end[3] = { start[0] + grid.nu, start[1] + grid.nv, start[2] + grid.nw }; + // set new metadata + if (mode == MapSetup::ReorderOnly) { + set_header_3i32(5, start[pos[0]], start[pos[1]], start[pos[2]]); + for (int i = 0; i < 3; ++i) { + end[i] -= start[i]; + start[i] = 0; + } + int crs[3] = { grid.nu, grid.nv, grid.nw }; + grid.nu = crs[pos[0]]; + grid.nv = crs[pos[1]]; + grid.nw = crs[pos[2]]; + } else { + grid.nu = sampl[0]; + grid.nv = sampl[1]; + grid.nw = sampl[2]; + set_header_3i32(5, 0, 0, 0); // start + } + set_header_3i32(1, grid.nu, grid.nv, grid.nw); // NX, NY, NZ + set_header_3i32(17, 1, 2, 3); // axes (MAPC, MAPR, MAPS) + grid.axis_order = full_cell() ? AxisOrder::XYZ : AxisOrder::Unknown; + if (grid.axis_order == AxisOrder::XYZ) + grid.calculate_spacing(); + + // now set the data + { + std::vector full(grid.point_count(), default_value); + int it[3]; + int idx = 0; + for (it[2] = start[2]; it[2] < end[2]; it[2]++) // sections + for (it[1] = start[1]; it[1] < end[1]; it[1]++) // rows + for (it[0] = start[0]; it[0] < end[0]; it[0]++) { // cols + T val = grid.data[idx++]; + size_t new_index = grid.index_s(it[pos[0]], it[pos[1]], it[pos[2]]); + full[new_index] = val; + } + grid.data = std::move(full); + } + + if (mode == MapSetup::Full && + // no need to apply symmetry if we started with the whole cell + (end[pos[0]] - start[pos[0]] < sampl[0] || + end[pos[1]] - start[pos[1]] < sampl[1] || + end[pos[2]] - start[pos[2]] < sampl[2])) + grid.symmetrize_nondefault(default_value); +} + +template +void Ccp4::set_extent(const Box& box) { + if (ccp4_header.empty()) + fail("set_extent(): no header in the map. Call update_ccp4_header() first"); + if (!full_cell()) + fail("Ccp4::set_extent() works only after setup()"); + if (grid.axis_order != AxisOrder::XYZ) + fail("Ccp4::set_extent() works only with XYZ order"); + int u0 = (int)std::ceil(box.minimum.x * grid.nu); + int v0 = (int)std::ceil(box.minimum.y * grid.nv); + int w0 = (int)std::ceil(box.minimum.z * grid.nw); + int nu = (int)std::floor(box.maximum.x * grid.nu) - u0 + 1; + int nv = (int)std::floor(box.maximum.y * grid.nv) - v0 + 1; + int nw = (int)std::floor(box.maximum.z * grid.nw) - w0 + 1; + // set the data + std::vector new_data((size_t)nu * nv * nw); + grid.get_subarray(new_data.data(), {u0, v0, w0}, {nu, nv, nw}); + grid.data.swap(new_data); + // and metadata + grid.nu = nu; + grid.nv = nv; + grid.nw = nw; + set_header_3i32(1, grid.nu, grid.nv, grid.nw); // NX, NY, NZ + set_header_3i32(5, u0, v0, w0); + // AxisOrder::XYZ is used only for grid covering full cell + grid.axis_order = AxisOrder::Unknown; +} + +template +void Ccp4::write_ccp4_map(const std::string& path) const { + assert(ccp4_header.size() >= 256); + fileptr_t f = file_open(path.c_str(), "wb"); + std::fwrite(ccp4_header.data(), 4, ccp4_header.size(), f.get()); + int mode = header_i32(4); + if (mode == 0) + impl::write_data(grid.data, f.get()); + else if (mode == 1) + impl::write_data(grid.data, f.get()); + else if (mode == 2) + impl::write_data(grid.data, f.get()); + else if (mode == 6) + impl::write_data(grid.data, f.get()); +} + +GEMMI_DLL Ccp4 read_ccp4_map(const std::string& path, bool setup); +GEMMI_DLL Ccp4 read_ccp4_mask(const std::string& path, bool setup); +GEMMI_DLL Ccp4Base read_ccp4_header(const std::string& path); + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/chemcomp.hpp b/gemmi_gph/gemmi/chemcomp.hpp new file mode 100644 index 00000000..4123e782 --- /dev/null +++ b/gemmi_gph/gemmi/chemcomp.hpp @@ -0,0 +1,747 @@ +// Copyright 2018 Global Phasing Ltd. +// +// ChemComp - chemical component that represents a monomer from Refmac +// monomer library, or from PDB CCD. + +#ifndef GEMMI_CHEMCOMP_HPP_ +#define GEMMI_CHEMCOMP_HPP_ + +#include +#include +#include +#include "cifdoc.hpp" +#include "elem.hpp" // for Element +#include "fail.hpp" // for fail, unreachable +#include "numb.hpp" // for as_number +#include "util.hpp" // for istarts_with, join_str +#include "model.hpp" // for Residue, Atom + +namespace gemmi { + +enum class BondType { + Unspec, Single, Double, Triple, Aromatic, Deloc, Metal +}; +enum class ChiralityType { Positive, Negative, Both }; + +struct Restraints { + struct AtomId { + int comp; + std::string atom; + + bool operator==(const AtomId& o) const { + return comp == o.comp && atom == o.atom; + } + bool operator!=(const AtomId& o) const { return !operator==(o); } + + bool operator==(const std::string& name) const { return atom == name; } + bool operator!=(const std::string& name) const { return atom != name; } + + bool operator<(const AtomId& o) const { + return comp == o.comp ? atom < o.atom : comp < o.comp; + } + + // altloc2 is needed only in rare case when we have a link between + // atoms with different altloc (example: 2e7z). + Atom* get_from(Residue& res1, Residue* res2, char alt, char altloc2) const { + Residue* residue = &res1; + if (comp == 2 && res2 != nullptr) { + residue = res2; + if (altloc2 != '\0') + alt = altloc2; + } + Atom* a = residue->find_atom(atom, alt, El::X, false); + // Special case: microheterogeneity may have shared atoms only in + // the first residue. Example: in 1ejg N is shared between PRO and SER. + if (a == nullptr && alt != '\0' && residue->group_idx > 0) + a = (residue - residue->group_idx)->find_atom(atom, alt, El::X, false); + return a; + } + const Atom* get_from(const Residue& res1, const Residue* res2, + char alt, char alt2) const { + return get_from(const_cast(res1), const_cast(res2), alt, alt2); + } + }; + + static std::string lexicographic_str(const std::string& name1, + const std::string& name2) { + return name1 < name2 ? cat(name1, '-', name2) : cat(name2, '-', name1); + } + + enum class DistanceOf { ElectronCloud, Nucleus }; + + struct Bond { + static const char* what() { return "bond"; } + AtomId id1, id2; + BondType type; + bool aromatic; + double value; + double esd; + double value_nucleus; + double esd_nucleus; + std::string str() const { return cat(id1.atom, '-', id2.atom); } + std::string lexicographic_str() const { + return Restraints::lexicographic_str(id1.atom, id2.atom); + } + double distance(DistanceOf of) const { + return of == DistanceOf::ElectronCloud ? value : value_nucleus; + } + template const AtomId* other(const T& a) const { + if (id1 == a) return &id2; + if (id2 == a) return &id1; + return nullptr; + } + }; + + struct Angle { + static const char* what() { return "angle"; } + AtomId id1, id2, id3; + double value; // degrees + double esd; + double radians() const { return rad(value); } + std::string str() const { + return cat(id1.atom, '-', id2.atom, '-', id3.atom); + } + }; + + struct Torsion { + static const char* what() { return "torsion"; } + std::string label; + AtomId id1, id2, id3, id4; + double value; + double esd; + int period; + std::string str() const { + return cat(id1.atom, '-', id2.atom, '-', id3.atom, '-', id4.atom); + } + }; + + struct Chirality { + static const char* what() { return "chirality"; } + AtomId id_ctr, id1, id2, id3; + ChiralityType sign; + + bool is_wrong(double volume) const { + return (sign == ChiralityType::Positive && volume < 0) || + (sign == ChiralityType::Negative && volume > 0); + } + std::string str() const { + return cat(id_ctr.atom, ',', id1.atom, ',', id2.atom, ',', id3.atom); + } + }; + + struct Plane { + static const char* what() { return "plane"; } + std::string label; + std::vector ids; + double esd; + std::string str() const { + return join_str(ids, ',', [](const AtomId& a) { return a.atom; }); + } + }; + + std::vector bonds; + std::vector angles; + std::vector torsions; + std::vector chirs; + std::vector planes; + + bool empty() const { + return bonds.empty() && angles.empty() && torsions.empty() && + chirs.empty() && planes.empty(); + } + + template + std::vector::iterator find_bond(const T& a1, const T& a2) { + return std::find_if(bonds.begin(), bonds.end(), [&](const Bond& b) { + return (b.id1 == a1 && b.id2 == a2) || (b.id1 == a2 && b.id2 == a1); + }); + } + template + std::vector::const_iterator find_bond(const T& a1, const T& a2) const { + return const_cast(this)->find_bond(a1, a2); + } + const Bond& get_bond(const AtomId& a1, const AtomId& a2) const { + auto it = find_bond(a1, a2); + if (it == bonds.end()) + fail("Bond restraint not found: ", a1.atom, '-', a2.atom); + return *it; + } + + template + bool are_bonded(const T& a1, const T& a2) const { + return find_bond(a1, a2) != bonds.end(); + } + + template + const AtomId* first_bonded_atom(const T& a) const { + for (const Bond& bond : bonds) + if (const AtomId* other = bond.other(a)) + return other; + return nullptr; + } + + // BFS + std::vector find_shortest_path(const AtomId& a, const AtomId& b, + std::vector visited, + int min_length=1) const { + int start = (int) visited.size(); + int end = -1; + visited.push_back(b); + std::vector parent(visited.size(), -1); + std::vector depth(visited.size(), -1); + depth[start] = 0; + for (int n = start; end == -1 && n != (int) visited.size(); ++n) { + for (const Bond& bond : bonds) + if (const AtomId* id = bond.other(visited[n])) { + if (*id == a) { + int cand_len = depth[n] + 1; + if (cand_len >= min_length) { + end = (int) visited.size(); + if (!in_vector(*id, visited)) { + visited.push_back(*id); + parent.push_back(n); + depth.push_back(cand_len); + } + } + } else if (!in_vector(*id, visited)) { + visited.push_back(*id); + parent.push_back(n); + depth.push_back(depth[n] + 1); + } + } + } + std::vector path; + for (int n = end; n != -1; n = parent[n]) + path.push_back(visited[n]); + return path; + } + + template + std::vector::iterator find_angle(const T& a, const T& b, const T& c) { + return std::find_if(angles.begin(), angles.end(), [&](const Angle& ang) { + return ang.id2 == b && ((ang.id1 == a && ang.id3 == c) || + (ang.id1 == c && ang.id3 == a)); + }); + } + template std::vector::const_iterator + find_angle(const T& a, const T& b, const T& c) const { + return const_cast(this)->find_angle(a, b, c); + } + const Angle& get_angle(const AtomId& a, const AtomId& b, const AtomId& c) const { + auto it = const_cast(this)->find_angle(a, b, c); + if (it == angles.end()) + fail("Angle restraint not found: ", a.atom, '-', b.atom, '-', c.atom); + return *it; + } + + template + std::vector::iterator find_torsion(const T& a, const T& b, + const T& c, const T& d) { + return std::find_if(torsions.begin(), torsions.end(), + [&](const Torsion& t) { + return (t.id1 == a && t.id2 == b && t.id3 == c && t.id4 == d) || + (t.id1 == d && t.id2 == c && t.id3 == b && t.id4 == a); + }); + } + template std::vector::const_iterator + find_torsion(const T& a, const T& b, const T& c, const T& d) const { + return const_cast(this)->find_torsion(a, b, c, d); + } + + template + std::vector::iterator find_chir(const T& ctr, const T& a, + const T& b, const T& c) { + return std::find_if(chirs.begin(), chirs.end(), [&](const Chirality& t) { + return t.id_ctr == ctr && ((t.id1 == a && t.id2 == b && t.id3 == c) || + (t.id1 == b && t.id2 == c && t.id3 == a) || + (t.id1 == c && t.id2 == a && t.id3 == b)); + }); + } + template std::vector::const_iterator + find_chir(const T& ctr, const T& a, const T& b, const T& c) const { + return const_cast(this)->find_chir(ctr, a, b, c); + } + + double chiral_abs_volume(const Restraints::Chirality& ch) const; + + std::vector::iterator get_plane(const std::string& label) { + return std::find_if(planes.begin(), planes.end(), + [&label](const Plane& p) { return p.label == label; }); + } + + Plane& get_or_add_plane(const std::string& label) { + std::vector::iterator it = get_plane(label); + if (it != planes.end()) + return *it; + planes.push_back(Plane{label, {}, 0.0}); + return planes.back(); + } + + void rename_atom(const AtomId& atom_id, const std::string& new_name) { + auto rename_atom = [&](AtomId& id) { + if (id == atom_id) + id.atom = new_name; + }; + for (Bond& bond : bonds) { + rename_atom(bond.id1); + rename_atom(bond.id2); + } + for (Angle& angle : angles) { + rename_atom(angle.id1); + rename_atom(angle.id2); + rename_atom(angle.id3); + } + for (Torsion& tor : torsions) { + rename_atom(tor.id1); + rename_atom(tor.id2); + rename_atom(tor.id3); + rename_atom(tor.id4); + } + for (Chirality& chir : chirs) { + rename_atom(chir.id_ctr); + rename_atom(chir.id1); + rename_atom(chir.id2); + rename_atom(chir.id3); + } + for (Plane& plane : planes) + for (AtomId& id : plane.ids) + rename_atom(id); + } +}; + +template +double angle_z(double value_rad, const Restr& restr, double full=360.) { + return angle_abs_diff(deg(value_rad), restr.value, full) / restr.esd; +} + +inline double chiral_abs_volume(double bond1, double bond2, double bond3, + double angle1, double angle2, double angle3) { + double mult = bond1 * bond2 * bond3; + double x = 1; + double y = 2; + for (double a : {angle1, angle2, angle3}) { + double cosine = a == 90. ? 0. : std::cos(rad(a)); + x -= cosine * cosine; + y *= cosine; + } + return mult * std::sqrt(std::max(0., x + y)); +} + +inline double Restraints::chiral_abs_volume(const Restraints::Chirality& ch) const { + return gemmi::chiral_abs_volume(get_bond(ch.id_ctr, ch.id1).value, + get_bond(ch.id_ctr, ch.id2).value, + get_bond(ch.id_ctr, ch.id3).value, + get_angle(ch.id1, ch.id_ctr, ch.id2).value, + get_angle(ch.id2, ch.id_ctr, ch.id3).value, + get_angle(ch.id3, ch.id_ctr, ch.id1).value); +} + +struct ChemComp { + // Items used in _chem_comp.group and _chem_link.group_comp_N in CCP4. + enum class Group { + Peptide, // "peptide" + PPeptide, // "P-peptide" + MPeptide, // "M-peptide" + Dna, // "DNA" - used in _chem_comp.group + Rna, // "RNA" - used in _chem_comp.group + DnaRna, // "DNA/RNA" - used in _chem_link.group_comp_N + Pyranose, // "pyranose" + Ketopyranose, // "ketopyranose" + Furanose, // "furanose" + NonPolymer, // "non-polymer" + Null + }; + + struct Atom { + std::string id; + std::string old_id; // read from _chem_comp_atom.alt_atom_id + Element el = El::X; + // _chem_comp_atom.partial_charge can be non-integer, + // _chem_comp_atom.charge is always integer (but sometimes has format + // '0.000' which is not correct but we ignore it). + float charge = 0; + std::string chem_type; + std::string acedrg_type; // read from _chem_comp_atom.atom_type + Position xyz{NAN, NAN, NAN}; + + bool is_hydrogen() const { return gemmi::is_hydrogen(el); } + }; + + struct Aliasing { + Group group; + // pairs of (name in chem_comp, usual name in this group) + std::vector> related; + + const std::string* name_from_alias(const std::string& atom_id) const { + for (const auto& item : related) + if (item.second == atom_id) + return &item.first; + return nullptr; + } + }; + + std::string name; + std::string type_or_group; // _chem_comp.type or _chem_comp.group + Group group = Group::Null; + bool has_descriptor = false; // true if _pdbx_chem_comp_descriptor present + bool has_coordinates = false; + std::vector atoms; + std::vector aliases; + Restraints rt; + + const Aliasing& get_aliasing(Group g) const { + for (const Aliasing& aliasing : aliases) + if (aliasing.group == g) + return aliasing; + fail("aliasing not found"); + } + + static Group read_group(const std::string& str) { + if (str.size() >= 3) { + const char* cstr = str.c_str(); + if ((str[0] == '\'' || str[0] == '"') && str.size() >= 5) + ++cstr; + switch (ialpha4_id(cstr)) { + case ialpha4_id("non-"): return Group::NonPolymer; + case ialpha4_id("pept"): return Group::Peptide; + case ialpha4_id("l-pe"): return Group::Peptide; + case ialpha4_id("p-pe"): return Group::PPeptide; + case ialpha4_id("m-pe"): return Group::MPeptide; + case ialpha4_id("dna"): return Group::Dna; + case ialpha4_id("rna"): return Group::Rna; + case ialpha4_id("dna/"): return Group::DnaRna; + case ialpha4_id("pyra"): return Group::Pyranose; + case ialpha4_id("keto"): return Group::Ketopyranose; + case ialpha4_id("fura"): return Group::Furanose; + } + } + return Group::Null; + } + + static const char* group_str(Group g) { + switch (g) { + case Group::Peptide: return "peptide"; + case Group::PPeptide: return "P-peptide"; + case Group::MPeptide: return "M-peptide"; + case Group::Dna: return "DNA"; + case Group::Rna: return "RNA"; + case Group::DnaRna: return "DNA/RNA"; + case Group::Pyranose: return "pyranose"; + case Group::Ketopyranose: return "ketopyranose"; + case Group::Furanose: return "furanose"; + case Group::NonPolymer: return "non-polymer"; + case Group::Null: return "."; + } + unreachable(); + } + + void set_group(const std::string& s) { + type_or_group = s; + group = read_group(s); + } + + std::vector::iterator find_atom(const std::string& atom_id) { + return std::find_if(atoms.begin(), atoms.end(), + [&](const Atom& a) { return a.id == atom_id; }); + } + std::vector::const_iterator find_atom(const std::string& atom_id) const { + return const_cast(this)->find_atom(atom_id); + } + bool has_atom(const std::string& atom_id) const { + return find_atom(atom_id) != atoms.end(); + } + + std::vector::iterator find_atom_by_old_name(const std::string& old_id) { + return std::find_if(atoms.begin(), atoms.end(), + [&](const Atom& a) { return a.old_id == old_id; }); + } + std::vector::const_iterator find_atom_by_old_name(const std::string& old_id) const { + return const_cast(this)->find_atom_by_old_name(old_id); + } + bool has_old_names() const { + return std::any_of(atoms.begin(), atoms.end(), + [&](const Atom& a) { return !a.old_id.empty() && a.old_id != a.id; }); + } + + int get_atom_index(const std::string& atom_id) const { + auto it = find_atom(atom_id); + if (it == atoms.end()) + fail("Chemical component ", name, " has no atom ", atom_id); + return int(it - atoms.begin()); + } + + int find_atom_index(const std::string& atom_id) const { + auto it = find_atom(atom_id); + return it != atoms.end() ? int(it - atoms.begin()) : -1; + } + + std::map make_atom_index() const { + std::map atom_index; + for (size_t i = 0; i < atoms.size(); ++i) + atom_index[atoms[i].id] = i; + return atom_index; + } + + const Atom& get_atom(const std::string& atom_id) const { + return atoms[get_atom_index(atom_id)]; + } + + /// Check if the group (M-|P-)peptide + static bool is_peptide_group(Group g) { + return g == Group::Peptide || g == Group::PPeptide || g == Group::MPeptide; + } + + /// Check if the group is DNA/RNA + static bool is_nucleotide_group(Group g) { + return g == Group::Dna || g == Group::Rna || g == Group::DnaRna; + } + + void remove_nonmatching_restraints() { + vector_remove_if(rt.bonds, [&](const Restraints::Bond& x) { + return !has_atom(x.id1.atom) || + !has_atom(x.id2.atom); + }); + vector_remove_if(rt.angles, [&](const Restraints::Angle& x) { + return !has_atom(x.id1.atom) || + !has_atom(x.id2.atom) || + !has_atom(x.id3.atom); + }); + vector_remove_if(rt.torsions, [&](const Restraints::Torsion& x) { + return !has_atom(x.id1.atom) || + !has_atom(x.id2.atom) || + !has_atom(x.id3.atom) || + !has_atom(x.id4.atom); + }); + vector_remove_if(rt.chirs, [&](const Restraints::Chirality& x) { + return !has_atom(x.id_ctr.atom) || + !has_atom(x.id1.atom) || + !has_atom(x.id2.atom) || + !has_atom(x.id3.atom); + }); + for (Restraints::Plane& plane : rt.planes) + vector_remove_if(plane.ids, [&](const Restraints::AtomId& x) { + return !has_atom(x.atom); + }); + } + + ChemComp& remove_hydrogens() { + vector_remove_if(atoms, [](const ChemComp::Atom& a) { + return a.is_hydrogen(); + }); + remove_nonmatching_restraints(); + return *this; + } +}; + +inline BondType bond_type_from_string(const std::string& s) { + if (s.size() >= 3) + switch (ialpha4_id(s.c_str())) { + case ialpha4_id("sing"): return BondType::Single; + case ialpha4_id("doub"): return BondType::Double; + case ialpha4_id("trip"): return BondType::Triple; + case ialpha4_id("arom"): return BondType::Aromatic; + case ialpha4_id("meta"): return BondType::Metal; + case ialpha4_id("delo"): return BondType::Deloc; + case ialpha4_id("1.5"): return BondType::Deloc; // rarely used + // program PDB2TNT produces a restraint file with bond type 'coval' + case ialpha4_id("cova"): return BondType::Unspec; + } + if (cif::is_null(s)) + return BondType::Unspec; + throw std::out_of_range("Unexpected bond type: " + s); +} + +inline const char* bond_type_to_string(BondType btype) { + switch (btype) { + case BondType::Unspec: return "."; + case BondType::Single: return "single"; + case BondType::Double: return "double"; + case BondType::Triple: return "triple"; + case BondType::Aromatic: return "aromatic"; + case BondType::Deloc: return "deloc"; + case BondType::Metal: return "metal"; + } + unreachable(); +} + +inline float order_of_bond_type(BondType btype) { + switch (btype) { + case BondType::Single: return 1.0f; + case BondType::Double: return 2.0f; + case BondType::Triple: return 3.0f; + case BondType::Aromatic: return 1.5f; + case BondType::Deloc: return 1.5f; + case BondType::Metal: return 1.0f; + case BondType::Unspec: return 0.0f; + } + unreachable(); +} + +// it doesn't handle crossN types from the monomer library +inline ChiralityType chirality_from_string(const std::string& s) { + switch (s[0] | 0x20) { + case 'p': return ChiralityType::Positive; + case 'n': return ChiralityType::Negative; + case 'b': return ChiralityType::Both; + case '.': return ChiralityType::Both; + default: throw std::out_of_range("Unexpected chirality: " + s); + } +} + +inline ChiralityType chirality_from_flag_and_volume(const std::string& s, + double volume) { + switch (s[0] | 0x20) { + case 's': return volume > 0 ? ChiralityType::Positive + : ChiralityType::Negative; + case 'n': return ChiralityType::Both; + default: throw std::out_of_range("Unexpected volume_flag: " + s); + } +} + +inline const char* chirality_to_string(ChiralityType chir_type) { + switch (chir_type) { + case ChiralityType::Positive: return "positive"; + case ChiralityType::Negative: return "negative"; + case ChiralityType::Both: return "both"; + } + unreachable(); +} + +inline ChemComp make_chemcomp_from_block(const cif::Block& block_) { + ChemComp cc; + cc.name = block_.name.substr(starts_with(block_.name, "comp_") ? 5 : 0); + cif::Block& block = const_cast(block_); + // CCD uses _chem_comp.type, monomer libraries use .group in a separate block + // named comp_list, but it'd be a better idea to have _chem_comp.group in the + // same block, so we try read it. + if (cif::Column group_col = block.find_values("_chem_comp.group")) + cc.set_group(group_col.str(0)); + else if (cif::Column type_col = block.find_values("_chem_comp.type")) + cc.type_or_group = type_col.str(0); + // Presence of descriptor category (AceDRG uses it to gate torsion corrections) + cc.has_descriptor = block.find("_pdbx_chem_comp_descriptor.", {"comp_id"}).ok(); + for (auto row : block.find("_chem_comp_atom.", + {"atom_id", "type_symbol", "?type_energy", + "?charge", "?partial_charge", "?alt_atom_id", + "?atom_type", + "?model_Cartn_x", + "?model_Cartn_y", + "?model_Cartn_z", + "?x", + "?y", + "?z", + "?pdbx_model_Cartn_x_ideal", + "?pdbx_model_Cartn_y_ideal", + "?pdbx_model_Cartn_z_ideal"})) { + ChemComp::Atom atom; + atom.id = row.str(0); + atom.old_id = row.has(5) ? row.str(5) : ""; + atom.el = Element(row.str(1)); + atom.charge = (float) cif::as_number(row.one_of(3, 4), 0.0); + atom.chem_type = row.has(2) ? row.str(2) : ""; + atom.acedrg_type = row.has(6) ? row.str(6) : ""; + auto set_xyz_if_finite = [&](int ix, int iy, int iz) { + if (!row.has(ix) || !row.has(iy) || !row.has(iz)) + return false; + double x = cif::as_number(row[ix]); + double y = cif::as_number(row[iy]); + double z = cif::as_number(row[iz]); + if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(z)) + return false; + atom.xyz = Position(x, y, z); + return true; + }; + if (!set_xyz_if_finite(7, 8, 9) && + !set_xyz_if_finite(10, 11, 12)) + set_xyz_if_finite(13, 14, 15); + cc.atoms.push_back(std::move(atom)); + } + // Also check _chem_comp_acedrg table for atom types (used by acedrg output) + for (auto row : block.find("_chem_comp_acedrg.", {"atom_id", "atom_type"})) { + std::string atom_id = row.str(0); + std::string atom_type = row.str(1); + for (auto& atom : cc.atoms) + if (atom.id == atom_id && atom.acedrg_type.empty()) { + atom.acedrg_type = atom_type; + break; + } + } + for (auto row : block.find("_chem_comp_bond.", + {"atom_id_1", "atom_id_2", // 0, 1 + "?type", "?value_order", // 2, 3 + "?aromatic", "?pdbx_aromatic_flag", // 4, 5 + "?value_dist", "?value_dist_esd", // 6, 7 + "?value_dist_nucleus", "?value_dist_nucleus_esd"})) { // 8, 9 + bool aromatic_flag = (row.one_of(4, 5)[0] | 0x20) == 'y'; + double dist = row.has(6) ? cif::as_number(row[6]) : NAN; + double esd = row.has(7) ? cif::as_number(row[7]) : NAN; + double dist_nucl = row.has(8) ? cif::as_number(row[8]) : NAN; + double esd_nucl = row.has(9) ? cif::as_number(row[9]) : NAN; + cc.rt.bonds.push_back({{1, row.str(0)}, {1, row.str(1)}, + bond_type_from_string(row.one_of(2, 3)), + aromatic_flag, dist, esd, dist_nucl, esd_nucl}); + } + for (auto row : block.find("_chem_comp_angle.", + {"atom_id_1", "atom_id_2", "atom_id_3", + "value_angle", "value_angle_esd"})) + cc.rt.angles.push_back({{1, row.str(0)}, {1, row.str(1)}, {1, row.str(2)}, + cif::as_number(row[3]), cif::as_number(row[4])}); + for (auto row : block.find("_chem_comp_tor.", + {"id", + "atom_id_1", "atom_id_2", + "atom_id_3", "atom_id_4", + "value_angle", "value_angle_esd", + "period"})) + cc.rt.torsions.push_back({row.str(0), + {1, row.str(1)}, {1, row.str(2)}, + {1, row.str(3)}, {1, row.str(4)}, + cif::as_number(row[5]), cif::as_number(row[6]), + cif::as_int(row[7])}); + for (auto row : block.find("_chem_comp_chir.", + {"atom_id_centre", + "atom_id_1", "atom_id_2", "atom_id_3", + "volume_sign"})) + if (row[4][0] != 'c') // ignore crossN + cc.rt.chirs.push_back({{1, row.str(0)}, + {1, row.str(1)}, {1, row.str(2)}, {1, row.str(3)}, + chirality_from_string(row[4])}); + // mmCIF compliant + cif::Table chir_tab = block.find("_chem_comp_chir_atom.", + {"chir_id", "atom_id"}); + if (chir_tab.ok()) { + std::map> chir_atoms; + for (auto chir : chir_tab) + chir_atoms[chir[0]].push_back(chir[1]); + for (auto row : block.find("_chem_comp_chir.", + {"id", "atom_id", "volume_flag", "volume_three"})) { + auto atoms = chir_atoms.find(row.str(0)); + if (atoms != chir_atoms.end() && atoms->second.size() == 3) + cc.rt.chirs.push_back({{1, row.str(1)}, + {1, atoms->second[0]}, {1, atoms->second[1]}, + {1, atoms->second[2]}, + chirality_from_flag_and_volume(row[2], + cif::as_number(row[3]))}); + } + } + for (auto row : block.find("_chem_comp_plane_atom.", + {"plane_id", "atom_id" , "dist_esd"})) { + Restraints::Plane& plane = cc.rt.get_or_add_plane(row.str(0)); + if (plane.esd == 0.0) + plane.esd = cif::as_number(row[2]); + plane.ids.push_back({1, row.str(1)}); + } + for (auto row : block.find("_chem_comp_alias.", + {"group", "atom_id", "atom_id_standard"})) { + ChemComp::Group group = ChemComp::read_group(row.str(0)); + if (cc.aliases.empty() || cc.aliases.back().group != group) { + cc.aliases.emplace_back(); + cc.aliases.back().group = group; + } + cc.aliases.back().related.emplace_back(row.str(1), row.str(2)); + } + + return cc; +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/cif.hpp b/gemmi_gph/gemmi/cif.hpp new file mode 100644 index 00000000..8ac46fc8 --- /dev/null +++ b/gemmi_gph/gemmi/cif.hpp @@ -0,0 +1,385 @@ +// Copyright 2017 Global Phasing Ltd. +// +// CIF parser (based on PEGTL) with pluggable actions, +// and a set of actions that prepare Document. +// To just read the CIF format, include read_cif.hpp instead. + +#ifndef GEMMI_CIF_HPP_ +#define GEMMI_CIF_HPP_ +#include +#include // for FILE +#include // for size_t, istream +#include + +#include "third_party/tao/pegtl.hpp" // IWYU pragma: keep +//#include "third_party/tao/pegtl/contrib/tracer.hpp" // for debugging + +#include "cifdoc.hpp" // for Document, etc +#include "fileutil.hpp" // for CharArray, file_open + +#if defined(_MSC_VER) +#pragma warning(push) +// warning C4244: an integer type is converted to a smaller integer type +#pragma warning(disable: 4244) +// warning C4267: conversion from 'size_t' to 'type', possible loss of data +#pragma warning(disable: 4267) +#endif + +namespace gemmi { +namespace cif { +using std::size_t; +namespace pegtl = tao::pegtl; + + +// **** grammar rules, named similarly as in the CIF 1.1 spec **** +namespace rules { + + template struct lookup_char { + using analyze_t = pegtl::analysis::generic; + template static bool match(Input& in) { + if (!in.empty() && cif::char_table(in.peek_char()) == TableVal) { + if (TableVal == 2) // this set includes new-line + in.bump(1); + else + in.bump_in_this_line(1); + return true; + } + return false; + } + }; + + // (letter) refers to sections in Table 2.2.7.1 in Vol.G of ITfC (2006). + + // (g) Character sets. + // OrdinaryCharacter: ! % & ()*+,-./0-9: <=>?@A-Z[] \ ^ `a-z{|}~ + using ordinary_char = lookup_char<1>; + + using ws_char = lookup_char<2>; + + // !"#$%&'()*+,-./0-9:;<=>?@A-Z[\]^_`a-z{|}~ + struct nonblank_ch : pegtl::range<'!', '~'> {}; + + // ascii space is just before '!' + struct anyprint_ch : pegtl::ranges<' ', '~', '\t'> {}; + + + // (f) White space and comments. + struct comment : pegtl::if_must, pegtl::until>{}; + struct whitespace : pegtl::plus> {}; + struct ws_or_eof : pegtl::sor {}; + + // (b) Reserved words. + struct str_data : TAOCPP_PEGTL_ISTRING("data_") {}; + struct str_loop : pegtl::seq> {}; + struct str_global : pegtl::seq> {}; + struct str_save : TAOCPP_PEGTL_ISTRING("save_") {}; + struct str_stop : pegtl::seq> {}; + struct keyword : pegtl::sor {}; + + // (e) Character strings and text fields. + template + struct endq : pegtl::seq, + pegtl::eof>>> {}; + // strict rule would be: + // template struct quoted_tail : until, anyprint_ch> {}; + // but it was relaxed after PDB accepted 5q1h with non-ascii character + template + struct quoted_tail : pegtl::until, pegtl::not_one<'\n'>> {}; + template + struct quoted : pegtl::if_must> {}; + struct singlequoted : quoted> {}; + struct doublequoted : quoted> {}; + struct field_sep : pegtl::seq> {}; + // CIF 2.0 requires whitespace after text field, so it'd be: + // until> instead of until. + struct textfield : pegtl::if_must> {}; + struct unquoted : pegtl::seq, + pegtl::not_at>, + pegtl::plus> {}; + + // (a) Basic structure of CIF. (c) Tags and values. + // datablockname in STAR/CIF should not be empty, but we made an exception + // for RELION which writes blocks starting with bare data_ + struct datablockname : pegtl::star {}; + struct datablockheading : pegtl::sor, str_global> {}; + struct tag : pegtl::seq, pegtl::plus> {}; + // unquoted value made of ordinary characters only - for a typical mmCIF file + // it is faster to check it first even if we backtrack on some values_. + struct simunq : pegtl::seq, pegtl::at> {}; + struct value : pegtl::sor {}; + struct item_tag : tag {}; + struct item_value : value {}; + struct loop_tag : tag {}; + struct loop_value : value {}; + struct loop_end : pegtl::opt {}; + struct loop : pegtl::if_must>, + pegtl::sor>, + // handle incorrect CIF with empty loop + pegtl::at>>, + loop_end> {}; + struct missing_value : pegtl::bol {}; + struct dataitem : pegtl::if_must, + pegtl::discard> {}; + struct framename : pegtl::plus {}; + struct endframe : str_save {}; + struct frame : pegtl::if_must>, + endframe, ws_or_eof> {}; + struct datablock : pegtl::seq>> {}; + struct content : pegtl::plus {}; + struct file : pegtl::seq, + pegtl::if_must, + content, pegtl::eof>> {}; + struct one_block : pegtl::seq, + pegtl::if_must, datablock>> {}; + + +} // namespace rules + + +// **** error messages **** + +template const std::string& error_message() { + static const std::string s = "parse error"; + return s; +} +#define error_msg(rule, msg) \ + template<> inline const std::string& error_message() { \ + static const std::string s = msg; \ + return s; \ + } +error_msg(rules::quoted_tail>, "unterminated 'string'") +error_msg(rules::quoted_tail>, "unterminated \"string\"") +error_msg(pegtl::until, "unterminated text field") +error_msg(rules::framename, "unnamed save_ frame") +error_msg(rules::content, "expected block header (data_)") +#undef error_msg + +template struct Errors : public pegtl::normal { + template + static void raise(const Input& in, States&& ...) { + throw pegtl::parse_error(error_message() + //+ " matching " + pegtl::internal::demangle() + , in); + } +}; + +// **** parsing actions that fill the storage **** + +template struct Action : pegtl::nothing {}; + +// We don't store comments here. We don't have a proper storage for comments. +// They can be stored as Items, but this leaves out comments before +// the first block, comments inside loops, or between tag and value. +// Additionally, a comment after a loop cannot be processed immediately +// b/c at that point we don't know if the loop is finished yet. +// If we were to store (a subset of) comments, we'd need to check first +// how it affects performance. +//template<> struct Action { +// template static void apply(const Input& in, Document& out) { +// } +//}; + +template<> struct Action { + template static void apply(const Input& in, Document& out) { + out.blocks.emplace_back(in.string()); + Block& block = out.blocks.back(); + // Empty block name (just data_ ) is not STAR/CIF conformant, + // but it's written by RELION and buccaneer; we must support it. + if (block.name.empty()) + block.name += ' '; + out.items_ = &block.items; + } +}; +template<> struct Action { + template static void apply(const Input&, Document& out) { + out.blocks.emplace_back(); + out.items_ = &out.blocks.back().items; + } +}; +template<> struct Action { + template static void apply(const Input& in, Document& out) { + out.items_->emplace_back(FrameArg{in.string()}); + out.items_->back().line_number = in.iterator().line; + out.items_ = &out.items_->back().frame.items; + } +}; +template<> struct Action { + template static void apply(const Input&, Document& out) { + out.items_ = &out.blocks.back().items; + } +}; +template<> struct Action { + template static void apply(const Input& in, Document& out) { + out.items_->emplace_back(in.string()); + out.items_->back().line_number = in.iterator().line; + } +}; +template<> struct Action { + template static void apply(const Input& in, Document& out) { + Item& last_item = out.items_->back(); + assert(last_item.type == ItemType::Pair); + last_item.pair[1] = in.string(); + } +}; +template<> struct Action { + template static void apply(const Input& in, Document& out) { + out.items_->emplace_back(LoopArg{}); + out.items_->back().line_number = in.iterator().line; + } +}; +template<> struct Action { + template static void apply(const Input& in, Document& out) { + Item& last_item = out.items_->back(); + assert(last_item.type == ItemType::Loop); + last_item.loop.tags.emplace_back(in.string()); + } +}; +template<> struct Action { + template static void apply(const Input& in, Document& out) { + Item& last_item = out.items_->back(); + assert(last_item.type == ItemType::Loop); + last_item.loop.values.emplace_back(in.string()); + } +}; +template<> struct Action { + template static void apply(const Input& in, Document& out) { + Item& last_item = out.items_->back(); + assert(last_item.type == ItemType::Loop); + const Loop& loop = last_item.loop; + if (loop.values.size() % loop.tags.size() != 0) + throw pegtl::parse_error( + "Wrong number of values in loop " + loop.common_prefix() + "*", + in); + } +}; + + +template void parse_input(Document& d, Input&& in) { + pegtl::parse(in, d); +} + +template Document read_input(Input&& in, int check_level=1) { + Document doc; + doc.source = in.source(); + parse_input(doc, in); + if (check_level > 0) { + check_for_missing_values(doc); + check_for_duplicates(doc); + if (check_level > 1) { + for (const cif::Block& block : doc.blocks) { + if (block.name == " ") + fail(doc.source + ": missing block name (bare data_)"); + check_empty_loops(block, doc.source); + } + } + } + return doc; +} + +template +size_t parse_one_block(Document& d, Input&& in) { + pegtl::parse(in, d); + return in.byte(); +} + +// pegtl::file_input may use mmap and be faster, but does not work +// on Windows with Unicode filenames. +#if defined(_WIN32) +#define GEMMI_CIF_FILE_INPUT(in, path) \ + tao::pegtl::read_input<> in(gemmi::file_open(path.c_str(), "rb").release(), path) +#else +#define GEMMI_CIF_FILE_INPUT(in, path) \ + tao::pegtl::file_input<> in(path) +#endif + +inline Document read_file(const std::string& filename, int check_level=1) { + GEMMI_CIF_FILE_INPUT(in, filename); + return read_input(in, check_level); +} + +inline Document read_memory(const char* data, size_t size, const char* name, int check_level=1) { + pegtl::memory_input<> in(data, size, name); + return read_input(in, check_level); +} + +inline Document read_cstream(std::FILE *f, size_t bufsize, const char* name, int check_level=1) { + pegtl::cstream_input<> in(f, bufsize, name); + return read_input(in, check_level); +} + +inline Document read_istream(std::istream &is, size_t bufsize, const char* name, + int check_level=1) { + pegtl::istream_input<> in(is, bufsize, name); + return read_input(in, check_level); +} + + +template struct CheckAction : pegtl::nothing {}; + +template<> struct CheckAction { + template static void apply(const Input& in) { + throw pegtl::parse_error("tag without value", in); + } +}; + +template bool try_parse(Input&& in, std::string* msg) { + try { + return pegtl::parse(in); + } catch (pegtl::parse_error& e) { + if (msg) + *msg = e.what(); + return false; + } +} + +// A function for transparent reading of normal and compressed files. +// T should have the same traits as BasicInput and MaybeGzipped. +template +Document read(T&& input, int check_level=1) { + if (CharArray mem = input.uncompress_into_buffer()) + return read_memory(mem.data(), mem.size(), input.path().c_str(), check_level); + if (input.is_stdin()) + return read_cstream(stdin, 16*1024, "stdin", check_level); + return read_file(input.path(), check_level); +} + +template +bool check_syntax(T&& input, std::string* msg) { + if (CharArray mem = input.uncompress_into_buffer()) { + pegtl::memory_input<> in(mem.data(), mem.size(), input.path()); + return try_parse(in, msg); + } + GEMMI_CIF_FILE_INPUT(in, input.path()); + return try_parse(in, msg); +} + +template +size_t read_one_block(Document& d, T&& input, size_t limit) { + if (input.is_compressed()) { + CharArray mem = input.uncompress_into_buffer(limit); + return parse_one_block(d, pegtl::memory_input<>(mem.data(), mem.size(), + input.path().c_str())); + } + if (input.is_stdin()) + return parse_one_block(d, pegtl::cstream_input<>(stdin, 16*1024, "stdin")); + GEMMI_CIF_FILE_INPUT(in, input.path()); + return parse_one_block(d, std::move(in)); +} + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +} // namespace cif +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/cif2mtz.hpp b/gemmi_gph/gemmi/cif2mtz.hpp new file mode 100644 index 00000000..4300bb09 --- /dev/null +++ b/gemmi_gph/gemmi/cif2mtz.hpp @@ -0,0 +1,553 @@ +// Copyright 2021 Global Phasing Ltd. +// +// A class for converting SF-mmCIF to MTZ (merged or unmerged). + +#ifndef GEMMI_CIF2MTZ_HPP_ +#define GEMMI_CIF2MTZ_HPP_ + +#include +#include +#include +#include +#include "cifdoc.hpp" // for Loop, as_int, ... +#include "fail.hpp" // for fail +#include "intensit.hpp" // for DataType +#include "logger.hpp" // for Logger +#include "mtz.hpp" // for Mtz +#include "numb.hpp" // for as_number +#include "refln.hpp" // for ReflnBlock +#include "version.hpp" // for GEMMI_VERSION + +namespace gemmi { + +// "Old-style" anomalous or unmerged data is expected to have only these tags. +inline bool possible_old_style(const ReflnBlock& rb, DataType data_type) { + if (rb.refln_loop == nullptr) + return false; + for (const std::string& tag : rb.refln_loop->tags) { + if (tag.size() < 7 + 6) + return false; + int tag_id = ialpha4_id(tag.c_str() + 7); + if (tag_id != ialpha4_id("inde") && // index_[hkl] + tag_id != ialpha4_id("wave") && // wavelength_id + tag_id != ialpha4_id("crys") && // crystal_id + tag_id != ialpha4_id("scal") && // scale_group_code + tag_id != ialpha4_id("stat") && // status + tag_id != ialpha4_id("inte") && // intensity_meas, intensity_sigma + (data_type == DataType::Unmerged || + (tag_id != ialpha4_id("F_me") && // F_meas_au, F_meas_sigma_au + tag != "_refln.pdbx_r_free_flag"))) + return false; + } + return true; +} + + +/// Before _refln.pdbx_F_plus/minus was introduced, anomalous data was +/// stored as two F_meas_au reflections, say (1,1,3) and (-1,-1,-3). +/// This function transcribes it to how the anomalous data is stored +/// in PDBx/mmCIF nowadays: +/// _refln.F_meas_au -> pdbx_F_plus / pdbx_F_minus, +/// _refln.F_meas_sigma_au -> pdbx_F_plus_sigma / pdbx_F_minus_sigma. +/// _refln.intensity_{meas,sigma} -> _refln.pdbx_F_plus{,_sigma} / ... +inline cif::Loop transcript_old_anomalous_to_standard(const cif::Loop& loop, + const SpaceGroup* sg) { + std::vector positions; + positions.reserve(13); // usually less, but it doesn't matter + for (const char* tag : {"_refln.index_h", "_refln.index_k", "_refln.index_l"}) { + int pos = loop.find_tag_lc(tag); + if (pos == -1) + fail("while reading old anomalous: _refln.index_{h,k,l} not found"); + positions.push_back(pos); + } + for (const char* tag : {"_refln.status", "_refln.pdbx_r_free_flag"}) { + int pos = loop.find_tag_lc(tag); + if (pos != -1) + positions.push_back(pos); + } + + cif::Loop ret; + ret.tags.reserve(positions.size()); + for (int p : positions) + ret.tags.push_back(loop.tags[p]); + const char* old_labels[2][2] = { + {"_refln.F_meas_au", "_refln.F_meas_sigma_au"}, + {"_refln.intensity_meas", "_refln.intensity_sigma"} + }; + const char* new_labels[2][4] = { + {"_refln.pdbx_F_plus", "_refln.pdbx_F_plus_sigma", + "_refln.pdbx_F_minus", "_refln.pdbx_F_minus_sigma"}, + {"_refln.pdbx_I_plus", "_refln.pdbx_I_plus_sigma", + "_refln.pdbx_I_minus", "_refln.pdbx_I_minus_sigma"} + }; + size_t common_tags = positions.size(); + for (int n = 0; n < 2; ++n) { + int idx = loop.find_tag(old_labels[n][0]); + if (idx >= 0 && idx+1 < (int)loop.width() && loop.tags[idx+1] == old_labels[n][1]) { + positions.insert(positions.end(), {idx, idx+1, idx, idx+1}); + ret.tags.insert(ret.tags.end(), new_labels[n], new_labels[n] + 4); + } + } + if (common_tags == positions.size()) + fail("while reading old anomalous: _refln has neither F_meas_au nor intensity_meas"); + + ret.values.reserve(loop.length() * ret.width()); // upper bound + std::unordered_map seen; + if (!sg) + sg = &get_spacegroup_p1(); + ReciprocalAsu asu(sg); + GroupOps gops = sg->operations(); + for (size_t i = 0; i < loop.values.size(); i += loop.width()) { + const std::string* row = &loop.values[i]; + Miller hkl; + for (size_t j = 0; j < 3; ++j) + hkl[j] = cif::as_int(row[positions[j]]); + auto hkl_sign = asu.to_asu_sign(hkl, gops); + // pointers don't change, .reserve() above prevents re-allocations + std::string* new_row = ret.values.data() + ret.values.size(); + auto r = seen.emplace(hkl_sign.first, new_row); + bool sign = hkl_sign.second; + if (r.second) { // adding a new row + for (int p : positions) + ret.values.push_back(row[p]); + // Don't move hkl to asu here, only change the sign if F- is before F+. + if (!sign) // negative sign + for (int j = 0; j < 3; ++j) + new_row[j] = std::to_string(-hkl[j]); + size_t first_absent = common_tags + (sign ? 2 : 0); + for (size_t j = first_absent; j < ret.width(); j += 4) { + new_row[j] = "."; + new_row[j+1] = "."; + } + } else { // modifying existing row + std::string* modified_row = r.first->second; + if (sign) // positive sign - this hkl might be better + for (int j = 0; j < 3; ++j) + modified_row[j] = row[positions[j]]; + // if a status or free flag value differs, set it to null + for (size_t j = 3; j < common_tags; ++j) + if (modified_row[j] != row[positions[j]]) + modified_row[j] = "."; + for (size_t j = common_tags + (sign ? 0 : 2); j < ret.width(); j += 4) { + modified_row[j] = row[positions[j]]; + modified_row[j+1] = row[positions[j+1]]; + } + } + } + + return ret; +} + + +struct CifToMtz { + // Alternative mmCIF tags for the same MTZ label should be consecutive + static const char** default_spec(bool for_merged) { + static const char* merged[] = { + "pdbx_r_free_flag FreeR_flag I 0", + "status FreeR_flag I 0 o=1,f=0", + "intensity_meas IMEAN J 1", + "F_squared_meas IMEAN J 1", + "intensity_sigma SIGIMEAN Q 1", + "F_squared_sigma SIGIMEAN Q 1", + "pdbx_I_plus I(+) K 1", + "pdbx_I_plus_sigma SIGI(+) M 1", + "pdbx_I_minus I(-) K 1", + "pdbx_I_minus_sigma SIGI(-) M 1", + "F_meas FP F 1", + "F_meas_au FP F 1", + "F_meas_sigma SIGFP Q 1", + "F_meas_sigma_au SIGFP Q 1", + "pdbx_F_plus F(+) G 1", + "pdbx_F_plus_sigma SIGF(+) L 1", + "pdbx_F_minus F(-) G 1", + "pdbx_F_minus_sigma SIGF(-) L 1", + "pdbx_anom_difference DP D 1", + "pdbx_anom_difference_sigma SIGDP Q 1", + "F_calc FC F 1", + "F_calc_au FC F 1", + "phase_calc PHIC P 1", + "pdbx_F_calc_with_solvent F-model F 1", + "pdbx_phase_calc_with_solvent PHIF-model P 1", + "fom FOM W 1", + "weight FOM W 1", + "pdbx_HL_A_iso HLA A 1", + "pdbx_HL_B_iso HLB A 1", + "pdbx_HL_C_iso HLC A 1", + "pdbx_HL_D_iso HLD A 1", + "pdbx_FWT FWT F 1", + "pdbx_PHWT PHWT P 1", + "pdbx_DELFWT DELFWT F 1", + "pdbx_DELPHWT PHDELWT P 1", + nullptr + }; + static const char* unmerged[] = { + "intensity_meas I J 0", // for unmerged data is category refln + "intensity_net I J 0", + "intensity_sigma SIGI Q 0", + "pdbx_detector_x XDET R 0", + "pdbx_detector_y YDET R 0", + "pdbx_scan_angle ROT R 0", + nullptr + }; + return for_merged ? merged : unmerged; + } + + struct Entry { + std::string refln_tag; + std::string col_label; + char col_type; + int dataset_id; + std::vector> code_to_number; + + Entry(const std::string& line) { + std::vector tokens; + tokens.reserve(4); + split_str_into_multi(line, " \t\r\n", tokens); + if (tokens.size() != 4 && tokens.size() != 5) + fail("line should have 4 or 5 words: " + line); + if (tokens[2].size() != 1 || tokens[3].size() != 1 || + (tokens[3][0] != '0' && tokens[3][0] != '1')) + fail("incorrect line: " + line); + refln_tag = tokens[0]; + col_label = tokens[1]; + col_type = tokens[2][0]; + dataset_id = tokens[3][0] - '0'; + // for compatibility with older spec + if (col_type == 's' && tokens.size() == 4) { + col_type = 'I'; + tokens.push_back("o=1,f=0"); + } + if (tokens.size() == 5) { + std::vector items = split_str(tokens[4], ','); + code_to_number.reserve(items.size()); + for (const std::string& item : items) { + size_t pos = item.find('='); + if (pos == std::string::npos) + fail("wrong mapping (", item, ") in: ", line); + float f; + auto result = fast_float::from_chars(item.c_str() + pos + 1, + item.c_str() + item.size(), f); + if (result.ec != std::errc()) + fail("failed to parse value in ", item, " in: ", line); + code_to_number.emplace_back(item.substr(0, pos), f); + } + } + } + + float translate_code_to_number(const std::string& v) const { + if (v.size() == 1) { + for (const auto& c2n : code_to_number) + if (c2n.first.size() == 1 && c2n.first[0] == v[0]) + return c2n.second; + } else { + std::string s = cif::as_string(v); + for (const auto& c2n : code_to_number) + if (c2n.first == s) + return c2n.second; + } + return NAN; + } + }; + + bool force_unmerged = false; + std::string title; + std::vector history = { "From gemmi-cif2mtz " GEMMI_VERSION }; + double wavelength = NAN; + std::vector spec_lines; + + Mtz convert_block_to_mtz(const ReflnBlock& rb, Logger& logger) const { + Mtz mtz; + mtz.title = title.empty() ? "Converted from mmCIF block " + rb.block.name : title; + if (!history.empty()) { + mtz.history.reserve(mtz.history.size() + history.size()); + mtz.history.insert(mtz.history.end(), history.begin(), history.end()); + } + mtz.cell = rb.cell; + mtz.spacegroup = rb.spacegroup; + mtz.add_dataset("HKL_base"); + + const cif::Loop* loop = rb.refln_loop ? rb.refln_loop : rb.diffrn_refln_loop; + if (!loop) + fail("_refln category not found in mmCIF block: " + rb.block.name); + bool unmerged = force_unmerged || !rb.refln_loop; + + if (!unmerged) { + Mtz::Dataset& ds = mtz.add_dataset("unknown"); + if (!std::isnan(wavelength)) + ds.wavelength = wavelength; + else if (rb.wavelength_count > 1) + logger.note("ignoring wavelengths, ", rb.wavelength_count, + " are present in block ", rb.block.name, '.'); + else + ds.wavelength = rb.wavelength; + } + + logger.level<7>("Searching tags with known MTZ equivalents ..."); + std::vector indices; + std::vector entries; // used for code_to_number only + std::string tag = loop->tags[0]; + const size_t tag_offset = rb.tag_offset(); + // NOLINTNEXTLINE(readability-suspicious-call-argument): clang-tidy finds npos suspicious + auto set_tag = [&](const std::string& t) { tag.replace(tag_offset, std::string::npos, t); }; + + std::vector spec_entries; + if (!spec_lines.empty()) { + spec_entries.reserve(spec_lines.size()); + for (const std::string& line : spec_lines) + spec_entries.emplace_back(line); + } else { + const char** line = default_spec(!unmerged); + for (; *line != nullptr; ++line) + spec_entries.emplace_back(*line); + } + + // always start with H, K, L + set_tag("index_h"); + for (char c : {'h', 'k', 'l'}) { + tag.back() = c; + int index = loop->find_tag(tag); + if (index == -1) + fail("Miller index tag not found: " + tag); + indices.push_back(index); + entries.push_back(nullptr); + auto col = mtz.columns.emplace(mtz.columns.end()); + col->dataset_id = 0; + col->type = 'H'; + col->label = alpha_up(c); + } + + // M/ISYM and BATCH + if (unmerged) { + auto col = mtz.columns.emplace(mtz.columns.end()); + col->dataset_id = 0; + col->type = 'Y'; + col->label = "M/ISYM"; + + col = mtz.columns.emplace(mtz.columns.end()); + col->dataset_id = 0; + col->type = 'B'; + col->label = "BATCH"; + } + + // other columns according to the spec + bool column_added = false; + for (const Entry& entry : spec_entries) { + set_tag(entry.refln_tag); + int index = loop->find_tag(tag); + if (index == -1) + continue; + if (mtz.column_with_label(entry.col_label)) + continue; + column_added = true; + indices.push_back(index); + entries.push_back(entry.code_to_number.empty() ? nullptr : &entry); + auto col = mtz.columns.emplace(mtz.columns.end()); + // dataset_id is meaningless in unmerged MTZ files + col->dataset_id = unmerged ? 0 : entry.dataset_id; + col->type = entry.col_type; + col->label = entry.col_label; + logger.level<7>(" ", tag, " -> ", col->label); + } + if (!column_added) + fail(force_unmerged ? "Unmerged d" : "D", "ata not found in block ", rb.block.name); + + for (size_t i = 0; i != mtz.columns.size(); ++i) { + mtz.columns[i].parent = &mtz; + mtz.columns[i].idx = i; + } + mtz.nreflections = (int) loop->length(); + + std::unique_ptr hkl_mover; + struct BatchInfo { + int sweep_id; + int frame_id; + }; + std::vector batch_nums; + if (unmerged) { + hkl_mover.reset(new UnmergedHklMover(mtz.spacegroup)); + set_tag("diffrn_id"); + int sweep_id_index = loop->find_tag(tag); + if (sweep_id_index == -1) + logger.level<7>("No diffrn_id. Assuming a single sweep."); + set_tag("pdbx_image_id"); + int image_id_index = loop->find_tag(tag); + if (logger.threshold >= 7) { + if (image_id_index == -1) + logger.mesg("No pdbx_image_id, setting BATCH to a dummy value."); + else + logger.mesg(" ", tag, " & diffrn_id -> BATCH"); + } + struct SweepInfo { + std::set frame_ids; + int offset; + float wavelength = 0.f; + std::string crystal_id = "unknown"; + int dataset_id; + }; + std::map sweeps; + cif::Block& block = const_cast(rb.block); + cif::Table tab_w0 = block.find("_diffrn.", {"id", "crystal_id"}); + cif::Table tab_w1 = block.find("_diffrn_radiation.", + {"diffrn_id", "wavelength_id"}); + cif::Table tab_w2 = block.find("_diffrn_radiation_wavelength.", + {"id", "wavelength"}); + // store sweep and frame numbers corresponding to reflections + batch_nums.reserve(loop->length()); + for (size_t i = 0; i < loop->values.size(); i += loop->tags.size()) { + int sweep_id = 1; + if (sweep_id_index >= 0) + sweep_id = cif::as_int(loop->values[i + sweep_id_index], 1); + int frame = 1; + if (image_id_index >= 0) { + double d = cif::as_number(loop->values[i + image_id_index], 1.); + frame = (int) std::ceil(d); + } + batch_nums.push_back({sweep_id, frame}); + if (frame >= 0) { + SweepInfo& sweep = sweeps[sweep_id]; + // if new sweep was added - try to set crystal_id and wavelength + if (sweep.frame_ids.empty() && sweep_id_index >= 0) { + const std::string& sweep_str = loop->values[i + sweep_id_index]; + try { + sweep.crystal_id = tab_w0.find_row(sweep_str).str(1); + } catch(std::exception&) {} + try { + const std::string& wave_id = tab_w1.find_row(sweep_str)[1]; + const std::string& wavelen = tab_w2.find_row(wave_id)[1]; + sweep.wavelength = (float) cif::as_number(wavelen, 0.); + } catch(std::exception&) {} + } + sweep.frame_ids.insert(frame); + } + } + + // add datasets and set SweepInfo::dataset_id + for (auto& sweep_pair : sweeps) { + SweepInfo& si = sweep_pair.second; + Mtz::Dataset* ds = mtz.dataset_with_name(si.crystal_id); + if (ds == nullptr) { + ds = &mtz.add_dataset(si.crystal_id); + // both crystal name and dataset name are set to crystal_id + ds->project_name = "unknown"; + ds->wavelength = si.wavelength; + } + si.dataset_id = ds->id; + } + + // determine offset that makes frame numbers unique + int cap = 0; + for (auto& it : sweeps) { + it.second.offset = cap; + cap += *--it.second.frame_ids.end() + 1100; + cap -= cap % 1000; + } + // add offset to BatchInfo::frame_id + for (BatchInfo& p : batch_nums) + if (p.sweep_id >= 0 && p.frame_id >= 0) + p.frame_id += sweeps.at(p.sweep_id).offset; + + // add MTZ batches + for (const auto& sweep_pair : sweeps) { + const SweepInfo& sweep_info = sweep_pair.second; + Mtz::Batch batch; + batch.set_dataset_id(sweep_info.dataset_id); + batch.set_cell(mtz.cell); + batch.set_wavelength(sweep_info.wavelength); + // FIXME should we set more properties in BATCH header? + int min_frame = *sweep_info.frame_ids.begin(); + int max_frame = *--sweep_info.frame_ids.end(); + if (2 * sweep_info.frame_ids.size() > size_t(max_frame - min_frame)) { + // probably consecutive range, even if some frames are missing + for (int n = min_frame; n <= max_frame; ++n) { + batch.number = n + sweep_info.offset; + mtz.batches.push_back(batch); + } + } else { + for (int n : sweep_info.frame_ids) { + batch.number = n + sweep_info.offset; + mtz.batches.push_back(batch); + } + } + } + } // - if (unmerged) + + // fill in the data + mtz.data.resize(mtz.columns.size() * mtz.nreflections); + size_t k = 0, row = 0; + for (size_t i = 0; i < loop->values.size(); i += loop->tags.size()) { + if (unmerged) { + std::array hkl; + for (size_t ii = 0; ii != 3; ++ii) + hkl[ii] = cif::as_int(loop->values[i + indices[ii]]); + int isym = hkl_mover->move_to_asu(hkl); + for (size_t j = 0; j != 3; ++j) + mtz.data[k++] = (float) hkl[j]; + mtz.data[k++] = (float) isym; + mtz.data[k++] = batch_nums.empty() ? 1.f : (float) batch_nums[row++].frame_id; + } else { + for (size_t j = 0; j != 3; ++j) + mtz.data[k++] = (float) cif::as_int(loop->values[i + indices[j]]); + } + for (size_t j = 3; j != indices.size(); ++j) { + const std::string& v = loop->values[i + indices[j]]; + if (cif::is_null(v)) { + mtz.data[k] = (float) NAN; + } else if (entries[j] != nullptr) { + mtz.data[k] = entries[j]->translate_code_to_number(v); + } else { + mtz.data[k] = (float) cif::as_number(v); + if (std::isnan(mtz.data[k])) + logger.mesg("Value #", i + indices[j], " in the loop is not a number: ", v); + } + ++k; + } + } + return mtz; + } + + Mtz auto_convert_block_to_mtz(ReflnBlock& rb, Logger& logger, char mode) const { + if (mode == 'f' && possible_old_style(rb, DataType::Anomalous)) + *rb.refln_loop = transcript_old_anomalous_to_standard(*rb.refln_loop, rb.spacegroup); + Mtz mtz = convert_block_to_mtz(rb, logger); + if (mtz.is_merged() && mode == 'a') { + auto type_unique = check_data_type_under_symmetry(MtzDataProxy{mtz}); + if (type_unique.first == DataType::Anomalous) { + if (possible_old_style(rb, DataType::Anomalous)) { + logger.note("data in ", rb.block.name, + " is read as \"old-style\" anomalous (", rb.refln_loop->length(), + " -> ", type_unique.second, " rows)."); + *rb.refln_loop = transcript_old_anomalous_to_standard(*rb.refln_loop, + rb.spacegroup); + // this is rare, so it's OK to run the conversion twice + mtz = convert_block_to_mtz(rb, logger); + } else { + logger.err("in ", rb.block.name, ", out of ", + rb.refln_loop->length(), " HKLs, only ", type_unique.second, + " are unique under symmetry; the rest are equivalent to Friedel mates"); + } + } else if (type_unique.first == DataType::Unmerged) { + logger.err("in ", rb.block.name, ", out of ", + rb.refln_loop->length(), " HKLs, only ", type_unique.second, + " are unique under symmetry"); + if (possible_old_style(rb, gemmi::DataType::Unmerged)) + logger.mesg("Possibly unmerged data - you may use option --refln-to=unmerged"); + } + } + return mtz; + } + +private: + static float status_to_freeflag(const std::string& str) { + char c = str[0]; + if (c == '\'' || c == '"') + c = str[1]; + if (c == 'o') + return 1.f; + if (c == 'f') + return 0.f; + return NAN; + } +}; + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/cifdoc.hpp b/gemmi_gph/gemmi/cifdoc.hpp new file mode 100644 index 00000000..029dfd52 --- /dev/null +++ b/gemmi_gph/gemmi/cifdoc.hpp @@ -0,0 +1,1204 @@ +// Copyright 2017 Global Phasing Ltd. +// +// struct Document that represents the CIF file (but can also be +// read from a different representation, such as CIF-JSON or mmJSON). + +#ifndef GEMMI_CIFDOC_HPP_ +#define GEMMI_CIFDOC_HPP_ +#include "iterator.hpp" // for StrideIter, IndirectIter +#include "atox.hpp" // for string_to_int +#include "fail.hpp" // for fail +#include "util.hpp" // for starts_with, to_lower, cat +#include // for size_t +#include // for memchr +#include // for move, find_if, all_of, min, rotate +#include +#include +#include +#include +#include +#include +#include + +#if defined(_MSC_VER) +#pragma warning(push) +// warning C4244: an integer type is converted to a smaller integer type +#pragma warning(disable: 4244) +// warning C4267: conversion from 'size_t' to 'type', possible loss of data +#pragma warning(disable: 4267) +#endif + +namespace gemmi { +namespace cif { +using std::size_t; +using gemmi::fail; + +enum class ItemType : unsigned char { + Pair, + Loop, + Frame, + Comment, + Erased, +}; + +inline uint8_t char_table(char c) { + static const uint8_t table[256] = { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 2, 0, 0, // 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1 + 2, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, // 2 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, // 3 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, // 5 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, // 7 + // 128-255 + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + }; + return table[static_cast(c)]; +} + +inline void assert_tag(const std::string& tag) { + if (tag[0] != '_') + fail("Tag should start with '_', got: " + tag); +} + +inline void ensure_mmcif_category(std::string& cat) { + if (cat[0] != '_') + fail("Category should start with '_', got: " + cat); + if (*(cat.end() - 1) != '.') + cat += '.'; +} + +inline bool is_null(const std::string& value) { + return value.size() == 1 && (value[0] == '?' || value[0] == '.'); +} + +inline std::string as_string(const std::string& value) { + if (value.empty() || is_null(value)) + return ""; + if (value[0] == '"' || value[0] == '\'') + return std::string(value.begin() + 1, value.end() - 1); + if (value[0] == ';' && value.size() > 2 && *(value.end() - 2) == '\n') { + bool crlf = *(value.end() - 3) == '\r'; + return std::string(value.begin() + 1, value.end() - (crlf ? 3 : 2)); + } + return value; +} + +inline std::string as_string(const std::string* value) { + return value ? as_string(*value) : std::string(); +} + +inline char as_char(const std::string& value, char null) { + if (is_null(value)) + return null; + if (value.size() < 2) + return value[0]; + const std::string s = as_string(value); + if (s.size() < 2) + return s[0]; + fail("Not a single character: " + value); +} + +inline int as_int(const std::string& str) { + return string_to_int(str, true); +} + +inline int as_int(const std::string& str, int null) { + return is_null(str) ? null : as_int(str); +} + +// for use in templates (see also as_any() functions in numb.hpp) +inline int as_any(const std::string& s, int null) { return as_int(s, null); } +inline char as_any(const std::string& s, char null) { return as_char(s, null); } + + +using Pair = std::array; + +// used only as arguments when creating Item +struct LoopArg {}; +struct FrameArg { std::string str; }; +struct CommentArg { std::string str; }; + +struct Loop { + std::vector tags; + std::vector values; + + // search and access + int find_tag_lc(const std::string& lctag) const { + auto f = std::find_if(tags.begin(), tags.end(), + [&lctag](const std::string& t) { return gemmi::iequal(t, lctag); }); + return f == tags.end() ? -1 : f - tags.begin(); + } + int find_tag(const std::string& tag) const { + return find_tag_lc(gemmi::to_lower(tag)); + } + bool has_tag(const std::string& tag) const { return find_tag(tag) != -1; } + size_t width() const { return tags.size(); } + size_t length() const { return values.size() / tags.size(); } + + std::string& val(size_t row, size_t col) { return values[row * tags.size() + col]; } + const std::string& val(size_t row, size_t col) const { + return const_cast(this)->val(row, col); + } + + void clear() { tags.clear(); values.clear(); } + + template void add_values(T new_values, int pos=-1) { + auto it = values.end(); + if (pos >= 0 && pos * width() < values.size()) + it = values.begin() + pos * tags.size(); + values.insert(it, new_values.begin(), new_values.end()); + } + void add_values(std::initializer_list new_values, int pos=-1) { + add_values>(new_values, pos); + } + template void add_row(T new_values, int pos=-1) { + if (new_values.size() != tags.size()) + fail("add_row(): wrong row length."); + add_values(new_values, pos); + } + void add_row(std::initializer_list new_values, int pos=-1) { + add_row>(new_values, pos); + } + // comments are added relying on how cif writing works + void add_comment_and_row(std::initializer_list ss) { + if (ss.size() != tags.size() + 1) + fail("add_comment_and_row(): wrong row length."); + std::vector vec(ss.begin() + 1, ss.end()); + vec[0] = cat('#', *ss.begin(), '\n', vec[0]); + add_row(vec); + } + void pop_row() { + if (values.size() < tags.size()) + fail("pop_row() called on empty Loop"); + values.resize(values.size() - tags.size()); + } + + // the arguments must be valid row indices + void move_row(int old_pos, int new_pos) { + size_t w = width(); + auto src = values.begin() + old_pos * w; + auto dst = values.begin() + new_pos * w; + if (src < dst) + std::rotate(src, src+w, dst+w); + else + std::rotate(dst, src, src+w); + } + + // column_names are not checked for duplicates nor for category name + void add_columns(const std::vector& column_names, + const std::string& value, int pos=-1) { + for (const std::string& name : column_names) + assert_tag(name); + size_t old_width = tags.size(); + size_t len = length(); + size_t upos = std::min((size_t)pos, old_width); + tags.insert(tags.begin() + upos, column_names.begin(), column_names.end()); + vector_insert_columns(values, old_width, len, column_names.size(), upos, value); + } + + void remove_column(const std::string& column_name) { + int n = find_tag(column_name); + if (n == -1) + fail("remove_column(): tag not found: " + column_name); + remove_column_at(n); + } + + /// \pre: n < tags.size() + void remove_column_at(size_t n) { + tags.erase(tags.begin() + n); + vector_remove_column(values, tags.size(), n); + } + + void set_all_values(std::vector> columns); + + std::string common_prefix() const { + if (tags.empty()) + return {}; + size_t len = tags[0].size(); + for (auto it = tags.begin() + 1; it != tags.end(); ++it) + for (size_t n = 0; n != len; ++n) + if (!isame(tags[0][n], (*it)[n])) { + len = n; + break; + } + return tags[0].substr(0, len); + } +}; + + +struct Item; +struct Block; + +// Accessor to a specific loop column, or to a single value from a Pair. +class Column { +public: + Column() : item_(nullptr) {} + Column(Item* item, size_t col) : item_(item), col_(col) {} + using iterator = StrideIter; + iterator begin(); + iterator end(); + using const_iterator = StrideIter; + const_iterator begin() const { return const_cast(this)->begin(); } + const_iterator end() const { return const_cast(this)->end(); } + + Loop* get_loop() const; + std::string* get_tag(); + const std::string* get_tag() const { + return const_cast(this)->get_tag(); + } + int length() const { + if (const Loop* loop = get_loop()) + return loop->length(); + return item_ ? 1 : 0; + } + explicit operator bool() const { return item_ != nullptr ; } + std::string& operator[](int n); + std::string& at(int n) { + if (n < 0) + n += length(); + if (n < 0 || n >= length()) + throw std::out_of_range("Cannot access element " + std::to_string(n) + + " in Column with length " + std::to_string(length())); + return operator[](n); + } + const std::string& at(int n) const { + return const_cast(this)->at(n); + } + + std::string str(int n) const { return as_string(at(n)); } + const Item* item() const { return item_; } + Item* item() { return item_; } + size_t col() const { return col_; } + + void erase(); + +private: + Item* item_; + size_t col_; // for loop this is a column index in item_->loop +}; + +// Some values can be given either in loop or as tag-value pairs. +// The latter case is equivalent to a loop with a single row. +// We optimized for loops, and in case of tag-values we copy the values +// into the `values` vector. +struct Table { + Item* loop_item; + Block& bloc; + std::vector positions; + size_t prefix_length; + + struct Row { + Table& tab; + int row_index; + + std::string& value_at_unsafe(int pos); + std::string& value_at(int pos) { + if (pos == -1) + throw std::out_of_range("Cannot access missing optional tag."); + return value_at_unsafe(pos); + } + const std::string& value_at(int pos) const { + return const_cast(this)->value_at(pos); + } + + std::string& at(int n) { + return value_at(tab.positions.at(n < 0 ? n + size() : n)); + } + const std::string& at(int n) const { return const_cast(this)->at(n); } + + std::string& operator[](size_t n); + const std::string& operator[](size_t n) const { + return const_cast(this)->operator[](n); + } + + std::string* ptr_at(int n) { + int pos = tab.positions.at(n < 0 ? n + size() : n); + return pos >= 0 ? &value_at(pos) : nullptr; + } + const std::string* ptr_at(int n) const { + return const_cast(this)->ptr_at(n); + } + + bool has(size_t n) const { return tab.positions.at(n) >= 0; } + bool has2(size_t n) const { return has(n) && !cif::is_null(operator[](n)); } + + const std::string& one_of(size_t n1, size_t n2) const { + static const std::string nul(1, '.'); + if (has2(n1)) + return operator[](n1); + if (has(n2)) + return operator[](n2); + return nul; + } + + size_t size() const { return tab.width(); } + + std::string str(int n) const { return as_string(at(n)); } + + using iterator = IndirectIter; + using const_iterator = IndirectIter; + iterator begin() { return iterator({this, tab.positions.begin()}); } + iterator end() { return iterator({this, tab.positions.end()}); } + const_iterator begin() const { + return const_iterator({this, tab.positions.begin()}); + } + const_iterator end() const { + return const_iterator({this, tab.positions.end()}); + } + }; + + Loop* get_loop(); + bool ok() const { return !positions.empty(); } + size_t width() const { return positions.size(); } + size_t length() const; + size_t size() const { return length(); } + bool has_column(int n) const { return ok() && positions.at(n) >= 0; } + Row tags() { return Row{*this, -1}; } + Row operator[](int n) { return Row{*this, n}; } + + void at_check(int& n) const { + if (n < 0) + n += length(); + if (n < 0 || static_cast(n) >= length()) + throw std::out_of_range("No row with index " + std::to_string(n)); + } + Row at(int n) { + at_check(n); + return (*this)[n]; + } + + Row one() { + if (length() != 1) + fail("Expected one value, found " + std::to_string(length())); + return (*this)[0]; + } + + std::string get_prefix() const { + for (int pos : positions) + if (pos >= 0) + return const_cast(this)->tags() + .value_at(pos).substr(0, prefix_length); + fail("The table has no columns."); + } + + Row find_row(const std::string& s); + + template void append_row(const T& new_values); + void append_row(std::initializer_list new_values) { + append_row>(new_values); + } + void remove_row(int row_index) { remove_rows(row_index, row_index+1); } + void remove_rows(int start, int end); + Column column_at_pos(int pos); + Column column(int n) { + int pos = positions.at(n); + if (pos == -1) + fail("Cannot access absent column"); + return column_at_pos(pos); + } + + void move_row(int old_pos, int new_pos) { + at_check(old_pos); + at_check(new_pos); + if (Loop* loop = get_loop()) + loop->move_row(old_pos, new_pos); + } + + // prefix is optional + int find_column_position(const std::string& tag) const { + std::string lctag = gemmi::to_lower(tag); + Row tag_row = const_cast(this)->tags(); + for (int pos : positions) { + const std::string& v = tag_row.value_at_unsafe(pos); + if (v.length() == lctag.length() ? gemmi::iequal(v, lctag) + : gemmi::iequal_from(v, prefix_length, lctag)) + return pos; + } + fail("Column name not found: " + tag); + } + + Column find_column(const std::string& tag) { + return column_at_pos(find_column_position(tag)); + } + + void erase(); + + /// if it's pairs, convert it to loop + void ensure_loop(); + + // It is not a proper input iterator, but just enough for using range-for. + struct iterator { + Table& parent; + int index; + void operator++() { index++; } + bool operator==(const iterator& o) const { return index == o.index; } + bool operator!=(const iterator& o) const { return index != o.index; } + Row operator*() { return parent[index]; } + const std::string& get(int n) const { return parent[index].at(n); } + }; + iterator begin() { return iterator{*this, 0}; } + iterator end() { return iterator{*this, (int)length()}; } +}; + +struct Block { + std::string name; + std::vector items; + + explicit Block(const std::string& name_); + Block(); + + void swap(Block& o) noexcept { name.swap(o.name); items.swap(o.items); } + // access functions + const Item* find_pair_item(const std::string& tag) const; + const Pair* find_pair(const std::string& tag) const; + Column find_loop(const std::string& tag); + const Item* find_loop_item(const std::string& tag) const; + const std::string* find_value(const std::string& tag) const; + Column find_values(const std::string& tag); + bool has_tag(const std::string& tag) const { + return const_cast(this)->find_values(tag).item() != nullptr; + } + bool has_any_value(const std::string& tag) const { + Column c = const_cast(this)->find_values(tag); + return c.item() != nullptr && !std::all_of(c.begin(), c.end(), is_null); + } + Table find(const std::string& prefix, + const std::vector& tags); + Table find(const std::vector& tags) { return find({}, tags); } + Table find_any(const std::string& prefix, + const std::vector& tags); + Table find_or_add(const std::string& prefix, std::vector tags) { + Table t = find(prefix, tags); + if (!t.ok()) { + for (int i = 0; i != (int) tags.size(); ++i) + t.positions.push_back(i); + Table tab = find_any(prefix, tags); + t.loop_item = &setup_loop_item(std::move(tab), prefix, std::move(tags)); + } + return t; + } + Block* find_frame(std::string name); + Table item_as_table(Item& item); + + size_t get_index(const std::string& tag) const; + + // modifying functions + void set_pair(const std::string& tag, const std::string& value); + + Loop& init_loop(const std::string& prefix, std::vector tags) { + Table tab = find_any(prefix, tags); + return setup_loop(std::move(tab), prefix, std::move(tags)); + } + + void move_item(int old_pos, int new_pos); + + // mmCIF specific functions + std::vector get_mmcif_category_names() const; + Table find_mmcif_category(std::string cat); + bool has_mmcif_category(std::string cat) const; + + Loop& init_mmcif_loop(std::string cat, std::vector tags) { + ensure_mmcif_category(cat); // modifies cat + return setup_loop(find_mmcif_category(cat), cat, std::move(tags)); + } + +private: + Item& setup_loop_item(Table&& tab, const std::string& prefix, + std::vector&& tags); + Loop& setup_loop(Table&& tab, const std::string& prefix, + std::vector&& tags); +}; + + +struct Item { + ItemType type; + int line_number = -1; + union { + Pair pair; + Loop loop; + Block frame; + }; + + Item() : type(ItemType::Erased) {} + explicit Item(LoopArg) + : type{ItemType::Loop}, loop{} {} + explicit Item(std::string&& t) + : type{ItemType::Pair}, pair{{std::move(t), std::string()}} {} + Item(const std::string& t, const std::string& v) + : type{ItemType::Pair}, pair{{t, v}} {} + explicit Item(FrameArg&& frame_arg) + : type{ItemType::Frame}, frame(frame_arg.str) {} + explicit Item(CommentArg&& comment) + : type{ItemType::Comment}, pair{{std::string(), std::move(comment.str)}} {} + + Item(Item&& o) noexcept + : type(o.type), line_number(o.line_number) { + move_value(std::move(o)); + } + Item(const Item& o) + : type(o.type), line_number(o.line_number) { + copy_value(o); + } + + Item& operator=(Item o) { set_value(std::move(o)); return *this; } + + ~Item() { destruct(); } + + void erase() { + destruct(); + type = ItemType::Erased; + } + + // case-insensitive, the prefix should be lower-case + bool has_prefix(const std::string& prefix) const { + return (type == ItemType::Pair && gemmi::istarts_with(pair[0], prefix)) || + (type == ItemType::Loop && !loop.tags.empty() && + gemmi::istarts_with(loop.tags[0], prefix)); + } + + void set_value(Item&& o) { + if (type == o.type) { + switch (type) { + case ItemType::Pair: pair = std::move(o.pair); break; + case ItemType::Loop: loop = std::move(o.loop); break; + case ItemType::Frame: frame = std::move(o.frame); break; + case ItemType::Comment: pair = std::move(o.pair); break; + case ItemType::Erased: break; + } + } else { + destruct(); + type = o.type; + move_value(std::move(o)); + } + } + +private: + void destruct() { + switch (type) { + case ItemType::Pair: pair.~Pair(); break; + case ItemType::Loop: loop.~Loop(); break; + case ItemType::Frame: frame.~Block(); break; + case ItemType::Comment: pair.~Pair(); break; + case ItemType::Erased: break; + } + } + + void copy_value(const Item& o) { + if (o.type == ItemType::Pair || o.type == ItemType::Comment) + new (&pair) Pair(o.pair); + else if (o.type == ItemType::Loop) + new (&loop) Loop(o.loop); + else if (o.type == ItemType::Frame) + new (&frame) Block(o.frame); + } + + void move_value(Item&& o) { + if (o.type == ItemType::Pair || o.type == ItemType::Comment) + new (&pair) Pair(std::move(o.pair)); + else if (o.type == ItemType::Loop) + new (&loop) Loop(std::move(o.loop)); + else if (o.type == ItemType::Frame) + new (&frame) Block(std::move(o.frame)); + } +}; + + +// ItemSpan is used to add tag-value pairs next to the same category tags. +struct ItemSpan { + ItemSpan(std::vector& items) + : items_(items), begin_(0), end_(items.size()) {} + ItemSpan(std::vector& items, std::string prefix) + : ItemSpan(items) { + assert_tag(prefix); + prefix = gemmi::to_lower(prefix); + while (begin_ != end_ && !items_[begin_].has_prefix(prefix)) + ++begin_; + if (begin_ != end_) + while (end_-1 != begin_ && !items_[end_-1].has_prefix(prefix)) + --end_; + } + void set_pair(const std::string& tag, const std::string& value) { + assert_tag(tag); + std::string lctag = gemmi::to_lower(tag); + auto end = items_.begin() + end_; + for (auto i = items_.begin() + begin_; i != end; ++i) { + if (i->type == ItemType::Pair && gemmi::iequal(i->pair[0], lctag)) { + i->pair[0] = tag; // if letter case differs, the tag changes + i->pair[1] = value; + return; + } + if (i->type == ItemType::Loop && i->loop.find_tag_lc(lctag) != -1) { + i->set_value(Item(tag, value)); + return; + } + } + items_.emplace(end, tag, value); + ++end_; + } +private: + std::vector& items_; + size_t begin_, end_; // iterators would be invalidated by emplace() +}; + + +inline void Loop::set_all_values(std::vector> columns){ + size_t w = columns.size(); + if (w != width()) + fail(cat("set_all_values(): expected ", width(), " columns, got ", w)); + if (w == 0) + return; + size_t h = columns[0].size(); + for (auto& col : columns) + if (col.size() != h) + fail("set_all_values(): all columns must have the same length"); + values.resize(w * h); + for (size_t i = 0; i != h; ++i) + for (size_t j = 0; j != w; ++j) + values[w * i + j] = std::move(columns[j][i]); +} + +inline std::string* Column::get_tag() { + if (!item_) + return nullptr; + if (Loop* loop = get_loop()) + return &loop->tags.at(col_); + return &item_->pair[0]; +} + +inline void Column::erase() { + if (Loop* loop = get_loop()) + loop->remove_column_at(col_); + else if (item_) + item_->erase(); +} + +inline Loop* Column::get_loop() const { + return item_ && item_->type == ItemType::Loop ? &item_->loop : nullptr; +} +inline Column::iterator Column::begin() { + if (Loop* loop = get_loop()) + return iterator({loop->values.data(), col_, (unsigned) loop->width()}); + if (item_ && item_->type == ItemType::Pair) + return iterator({&item_->pair[1], 0, 1}); + return iterator(); +} + +inline Column::iterator Column::end() { + if (Loop* loop = get_loop()) + return iterator({loop->values.data() + loop->values.size(), + col_, (unsigned) loop->width()}); + if (item_ && item_->type == ItemType::Pair) + return iterator({&item_->pair[1] + 1, 0, 1}); + return iterator(); +} + +inline std::string& Column::operator[](int n) { + if (Loop* loop = get_loop()) + return loop->values[n * loop->width() + col_]; + return item_->pair[1]; +} + +inline std::string& Table::Row::operator[](size_t n) { + int pos = tab.positions[n]; + if (Loop* loop = tab.get_loop()) { + if (row_index == -1) // tags + return loop->tags[pos]; + return loop->values[loop->width() * row_index + pos]; + } + return tab.bloc.items[pos].pair[row_index == -1 ? 0 : 1]; +} + +inline std::string& Table::Row::value_at_unsafe(int pos) { + Loop* loop = tab.get_loop(); + if (row_index == -1) { // tags + if (loop) + return loop->tags.at(pos); + return tab.bloc.items[pos].pair[0]; + } + if (loop) + return loop->values.at(loop->width() * row_index + pos); + return tab.bloc.items[pos].pair[1]; +} + +inline Loop* Table::get_loop() { // NOLINT(readability-make-member-function-const) + return loop_item ? &loop_item->loop : nullptr; +} + +inline size_t Table::length() const { + return loop_item ? loop_item->loop.length() : (positions.empty() ? 0 : 1); +} + +inline Table::Row Table::find_row(const std::string& s) { + int pos = positions.at(0); + if (const Loop* loop = get_loop()) { + for (size_t i = 0; i < loop->values.size(); i += loop->width()) + if (as_string(loop->values[i + pos]) == s) + return Row{*this, static_cast(i / loop->width())}; + } else if (as_string(bloc.items[pos].pair[1]) == s) { + return Row{*this, 0}; + } + fail("Not found in " + *column_at_pos(pos).get_tag() + ": " + s); +} + +template void Table::append_row(const T& new_values) { + if (!ok()) + fail("append_row(): table not found"); + if (new_values.size() != width()) + fail("append_row(): wrong row length"); + if (!loop_item) + fail("append_row(): data is not in loop, call ensure_loop() first"); + Loop& loop = loop_item->loop; + size_t cur_size = loop.values.size(); + loop.values.resize(cur_size + loop.width(), "."); + int n = 0; + for (const auto& value : new_values) + loop.values[cur_size + positions[n++]] = value; +} + +inline void Table::remove_rows(int start, int end) { + if (!ok()) + // this function is used mostly through remove_row() + fail("remove_row(): table not found"); + ensure_loop(); // should we fail instead if we have pairs? + Loop& loop = loop_item->loop; + size_t start_pos = start * loop.width(); + size_t end_pos = end * loop.width(); + if (start_pos >= end_pos || end_pos > loop.values.size()) + throw std::out_of_range("remove_row(): invalid index"); + loop.values.erase(loop.values.begin() + start_pos, + loop.values.begin() + end_pos); +} + +inline Column Table::column_at_pos(int pos) { + if (loop_item) + return Column(loop_item, pos); + return Column(&bloc.items[pos], 0); +} + +inline void Table::erase() { + if (loop_item) { + loop_item->erase(); + loop_item = nullptr; + } else { + for (int pos : positions) + if (pos >= 0) + bloc.items[pos].erase(); + } + positions.clear(); +} + +inline void Table::ensure_loop() { + if (loop_item) + return; + Item new_item(LoopArg{}); + new_item.loop.tags.resize(positions.size()); + new_item.loop.values.resize(positions.size()); + loop_item = &bloc.items.at(positions[0]); + for (size_t i = 0; i != positions.size(); ++i) { + Item& item = bloc.items[positions[i]]; + new_item.loop.tags[i].swap(item.pair[0]); + new_item.loop.values[i].swap(item.pair[1]); + item.erase(); + positions[i] = i; + } + loop_item->set_value(std::move(new_item)); +} + +inline Block::Block(const std::string& name_) : name(name_) {} +inline Block::Block() {} + +inline const Item* Block::find_pair_item(const std::string& tag) const { + std::string lctag = gemmi::to_lower(tag); + for (const Item& i : items) + if (i.type == ItemType::Pair && gemmi::iequal(i.pair[0], lctag)) + return &i; + return nullptr; +} + +inline const Pair* Block::find_pair(const std::string& tag) const { + const Item* item = find_pair_item(tag); + return item ? &item->pair : nullptr; +} + +inline Column Block::find_loop(const std::string& tag) { + Column c = find_values(tag); + return c.item() && c.item()->type == ItemType::Loop ? c : Column(); +} + +inline const Item* Block::find_loop_item(const std::string& tag) const { + for (const Item& i : items) + if (i.type == ItemType::Loop && i.loop.find_tag_lc(tag) != -1) + return &i; + return nullptr; +} + +inline const std::string* Block::find_value(const std::string& tag) const { + std::string lctag = gemmi::to_lower(tag); + for (const Item& i : items) + if (i.type == ItemType::Pair && gemmi::iequal(i.pair[0], lctag)) + return &i.pair[1]; + for (const Item& i : items) + if (i.type == ItemType::Loop) { + int pos = i.loop.find_tag_lc(lctag); + if (pos != -1 && i.loop.tags.size() == i.loop.values.size()) + return &i.loop.values[pos]; + } + return nullptr; +} + +inline Column Block::find_values(const std::string& tag) { + std::string lctag = gemmi::to_lower(tag); + for (Item& i : items) + if (i.type == ItemType::Loop) { + int pos = i.loop.find_tag_lc(lctag); + if (pos != -1) + return Column{&i, static_cast(pos)}; + } else if (i.type == ItemType::Pair) { + if (gemmi::iequal(i.pair[0], lctag)) + return Column{&i, 0}; + } + return Column{nullptr, 0}; +} + +inline Block* Block::find_frame(std::string frame_name) { + frame_name = gemmi::to_lower(frame_name); + for (Item& i : items) + if (i.type == ItemType::Frame && gemmi::iequal(i.frame.name, frame_name)) + return &i.frame; + return nullptr; +} + +inline Table Block::item_as_table(Item& item) { + if (item.type != ItemType::Loop) + fail("item_as_table: item is not Loop"); + std::vector indices(item.loop.tags.size()); + for (size_t j = 0; j != indices.size(); ++j) + indices[j] = (int) j; + return Table{&item, *this, indices, 0}; +} + +inline size_t Block::get_index(const std::string& tag) const { + std::string lctag = gemmi::to_lower(tag); + for (size_t i = 0; i != items.size(); ++i) { + const Item& item = items[i]; + if ((item.type == ItemType::Pair && gemmi::iequal(item.pair[0], lctag)) || + (item.type == ItemType::Loop && item.loop.find_tag_lc(lctag) != -1)) + return i; + } + fail(tag + " not found in block"); +} + +inline void Block::set_pair(const std::string& tag, const std::string& value) { + ItemSpan(items).set_pair(tag, value); +} + +inline void Block::move_item(int old_pos, int new_pos) { + if (old_pos < 0) + old_pos += items.size(); + if ((size_t) old_pos >= items.size()) + fail("move_item: old_pos out of range"); + if (new_pos < 0) + new_pos += items.size(); + if ((size_t) new_pos >= items.size()) + fail("move_item: new_pos out of range"); + auto src = items.begin() + old_pos; + auto dst = items.begin() + new_pos; + if (src < dst) + std::rotate(src, src+1, dst+1); + else + std::rotate(dst, src, src+1); +} + +inline std::vector Block::get_mmcif_category_names() const { + std::vector cats; + for (const Item& item : items) { + const std::string* tag = nullptr; + if (item.type == ItemType::Pair) + tag = &item.pair[0]; + else if (item.type == ItemType::Loop && !item.loop.tags.empty()) + tag = &item.loop.tags[0]; + if (tag) + for (auto j = cats.rbegin(); j != cats.rend(); ++j) + if (gemmi::starts_with(*tag, *j)) { + tag = nullptr; + break; + } + if (tag) { + size_t dot = tag->find('.'); + if (dot != std::string::npos) + cats.emplace_back(*tag, 0, dot + 1); + } + } + return cats; +} + +inline Item& Block::setup_loop_item(Table&& tab, const std::string& prefix, + std::vector&& tags) { + Item *item; + if (tab.loop_item) { + item = tab.loop_item; + item->loop.clear(); + } else if (tab.ok()) { + item = &tab.bloc.items.at(tab.positions[0]); + tab.erase(); + item->set_value(Item(LoopArg{})); + } else { + items.emplace_back(LoopArg{}); + item = &items.back(); + } + for (std::string& tag : tags) { + tag.insert(0, prefix); + assert_tag(tag); + } + item->loop.tags = std::move(tags); + return *item; +} + +inline Loop& Block::setup_loop(Table&& tab, const std::string& prefix, + std::vector&& tags) { + return setup_loop_item(std::move(tab), prefix, std::move(tags)).loop; +} + +inline Table Block::find(const std::string& prefix, + const std::vector& tags) { + Item* loop_item = nullptr; + if (!tags.empty()) { + if (tags[0][0] == '?') + fail("The first tag in find() cannot be ?optional."); + loop_item = find_loop(prefix + tags[0]).item(); + } + + std::vector indices; + indices.reserve(tags.size()); + if (loop_item) { + for (const std::string& tag : tags) { + std::string full_tag = prefix + (tag[0] != '?' ? tag : tag.substr(1)); + int idx = loop_item->loop.find_tag(full_tag); + if (idx == -1 && tag[0] != '?') { + loop_item = nullptr; + indices.clear(); + break; + } + indices.push_back(idx); + } + } else { + for (const std::string& tag : tags) { + std::string full_tag = prefix + (tag[0] != '?' ? tag : tag.substr(1)); + if (const Item* p = find_pair_item(full_tag)) { + indices.push_back(p - items.data()); + } else if (tag[0] == '?') { + indices.push_back(-1); + } else { + indices.clear(); + break; + } + } + } + return Table{loop_item, *this, indices, prefix.length()}; +} + +inline Table Block::find_any(const std::string& prefix, + const std::vector& tags) { + std::vector indices; + for (auto tag = tags.begin(); tag != tags.end(); ++tag) { + Column column = find_values(prefix + *tag); + if (Item* item = column.item()) { + if (item->type == ItemType::Loop) { + indices.push_back(column.col()); + while (++tag != tags.end()) { + int idx = item->loop.find_tag(prefix + *tag); + if (idx != -1) + indices.push_back(idx); + } + return Table{item, *this, indices, prefix.length()}; + } + indices.push_back(item - items.data()); + while (++tag != tags.end()) + if (const Item* p = find_pair_item(prefix + *tag)) + indices.push_back(p - items.data()); + break; + } + } + return Table{nullptr, *this, indices, prefix.length()}; +} + +inline Table Block::find_mmcif_category(std::string cat) { + ensure_mmcif_category(cat); + cat = gemmi::to_lower(cat); + std::vector indices; + for (Item& i : items) + if (i.has_prefix(cat)) { + if (i.type == ItemType::Loop) { + indices.resize(i.loop.tags.size()); + for (size_t j = 0; j != indices.size(); ++j) { + indices[j] = j; + const std::string& tag = i.loop.tags[j]; + if (!istarts_with(tag, cat)) + fail("Tag ", tag, " in loop with ", cat); + } + return Table{&i, *this, indices, cat.length()}; + } + indices.push_back(&i - items.data()); + } + return Table{nullptr, *this, indices, cat.length()}; +} + +inline bool Block::has_mmcif_category(std::string cat) const { + ensure_mmcif_category(cat); + cat = gemmi::to_lower(cat); + for (const Item& i : items) + if (i.has_prefix(cat)) + return true; + return false; +} + +struct Document { + std::string source; + std::vector blocks; + + // implementation detail: items of the currently parsed block or frame + std::vector* items_ = nullptr; + + Block& add_new_block(const std::string& name, int pos=-1) { + if (find_block(name)) + fail("Block with such name already exists: " + name); + if (pos > 0 && static_cast(pos) > blocks.size()) + throw std::out_of_range("add_new_block(): invalid position"); + return *blocks.emplace(pos < 0 ? blocks.end() : blocks.begin() + pos, name); + } + + void clear() noexcept { + source.clear(); + blocks.clear(); + items_ = nullptr; + } + + // returns blocks[0] if the document has exactly one block (like mmCIF) + Block& sole_block() { + if (blocks.size() > 1) + fail("single data block expected, got " + std::to_string(blocks.size())); + return blocks.at(0); + } + const Block& sole_block() const { + return const_cast(this)->sole_block(); + } + + Block* find_block(const std::string& name) { + for (Block& b : blocks) + if (b.name == name) + return &b; + return nullptr; + } + const Block* find_block(const std::string& name) const { + return const_cast(this)->find_block(name); + } +}; + + +[[noreturn]] +inline void cif_fail(const std::string& source, const Block& b, + const Item& item, const std::string& s) { + fail(cat(source, ':', item.line_number, " in data_", b.name, ": ", s)); +} + +inline void check_for_missing_values_in_block(const Block& block, + const std::string& source) { + for (const Item& item : block.items) { + if (item.type == ItemType::Pair) { + if (item.pair[1].empty()) + cif_fail(source, block, item, item.pair[0] + " has no value"); + } else if (item.type == ItemType::Frame) { + check_for_missing_values_in_block(item.frame, source); + } + } +} + +// Throw an error if any item (pair) value is missing +inline void check_for_missing_values(const Document& d) { + for (const Block& block : d.blocks) + check_for_missing_values_in_block(block, d.source); +} + +// Throw an error if any block name, frame name or tag is duplicated. +inline void check_for_duplicates(const Document& d) { + // check for duplicate block names (except empty "" which is global_) + std::unordered_set names; + for (const Block& block : d.blocks) { + bool ok = names.insert(gemmi::to_lower(block.name)).second; + if (!ok && !block.name.empty()) + fail(d.source + ": duplicate block name: ", block.name); + } + // check for dups inside each block + std::unordered_set frame_names; + for (const Block& block : d.blocks) { + names.clear(); + frame_names.clear(); + for (const Item& item : block.items) { + if (item.type == ItemType::Pair) { + bool ok = names.insert(gemmi::to_lower(item.pair[0])).second; + if (!ok) + cif_fail(d.source, block, item, "duplicate tag " + item.pair[0]); + } else if (item.type == ItemType::Loop) { + for (const std::string& t : item.loop.tags) { + bool ok = names.insert(gemmi::to_lower(t)).second; + if (!ok) + cif_fail(d.source, block, item, "duplicate tag " + t); + } + } else if (item.type == ItemType::Frame) { + bool ok = frame_names.insert(gemmi::to_lower(item.frame.name)).second; + if (!ok) + cif_fail(d.source, block, item, "duplicate save_" + item.frame.name); + } + } + } +} + +// Empty loop is not a valid CIF syntax, but we parse it to accommodate +// some broken CIF files. Only check_level>=2 shows an error. +inline void check_empty_loops(const cif::Block& block, const std::string& source) { + for (const cif::Item& item : block.items) { + if (item.type == cif::ItemType::Loop) { + if (item.loop.values.empty() && !item.loop.tags.empty()) + cif_fail(source, block, item, "empty loop with " + item.loop.tags[0]); + } else if (item.type == cif::ItemType::Frame) { + check_empty_loops(item.frame, source); + } + } +} + +inline bool is_text_field(const std::string& val) { + size_t len = val.size(); + return len > 2 && val[0] == ';' && (val[len-2] == '\n' || val[len-2] == '\r'); +} + +inline std::string quote(std::string v) { + if (std::all_of(v.begin(), v.end(), [](char c) { return char_table(c) == 1; }) + && !v.empty() && !is_null(v)) + return v; + char q = ';'; + if (std::memchr(v.c_str(), '\n', v.size()) == nullptr) { + if (std::memchr(v.c_str(), '\'', v.size()) == nullptr) + q = '\''; + else if (std::memchr(v.c_str(), '"', v.size()) == nullptr) + q = '"'; + } + v.insert(v.begin(), q); + if (q == ';') + v += '\n'; + v += q; + return v; +} + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +} // namespace cif +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/contact.hpp b/gemmi_gph/gemmi/contact.hpp new file mode 100644 index 00000000..ed439f46 --- /dev/null +++ b/gemmi_gph/gemmi/contact.hpp @@ -0,0 +1,135 @@ +// Copyright 2020 Global Phasing Ltd. +// +// Contact search, based on NeighborSearch from neighbor.hpp. + +#ifndef GEMMI_CONTACT_HPP_ +#define GEMMI_CONTACT_HPP_ + +#include "model.hpp" +#include "neighbor.hpp" +#include "polyheur.hpp" // for check_polymer_type, are_connected + +namespace gemmi { + +struct ContactSearch { + enum class Ignore { + Nothing=0, SameResidue, AdjacentResidues, SameChain, SameAsu + }; + // parameters used to configure the search + double search_radius; + Ignore ignore = Ignore::SameResidue; + bool twice = false; // report both A-B and B-A + float min_occupancy = 0.f; + double special_pos_cutoff_sq = 0.8 * 0.8; + std::vector radii; + + ContactSearch(double radius) noexcept : search_radius(radius) {} + + // a helper function that sets per-atom radii basing on covalent_radius() + void setup_atomic_radii(double multiplier, double tolerance) { + radii.resize((size_t)El::END); + for (int i = 0; i != (int) El::END; ++i) + radii[i] = float(multiplier * Element(i).covalent_r() + tolerance / 2); + } + float get_radius(El el) const { return radii.empty() ? 0.f : radii[(int)el]; } + void set_radius(El el, float r) { + if (!radii.empty()) + radii[(int)el] = r; + } + + template + void for_each_contact(NeighborSearch& ns, const Func& func); + + struct Result { + CRA partner1, partner2; + int image_idx; + double dist_sq; + }; + std::vector find_contacts(NeighborSearch& ns) { + std::vector out; + for_each_contact(ns, [&out](const CRA& cra1, const CRA& cra2, + int image_idx, double dist_sq) { + out.push_back({cra1, cra2, image_idx, dist_sq}); + }); + return out; + } +}; + +template +void ContactSearch::for_each_contact(NeighborSearch& ns, const Func& func) { + if (!ns.model) + fail(ns.small_structure ? "ContactSearch does not work with SmallStructure" + : "NeighborSearch not initialized"); + for (int n_ch = 0; n_ch != (int) ns.model->chains.size(); ++n_ch) { + Chain& chain = ns.model->chains[n_ch]; + PolymerType pt = PolymerType::Unknown; + if (ignore == Ignore::AdjacentResidues) + pt = check_polymer_type(chain.get_polymer()); + for (int n_res = 0; n_res != (int) chain.residues.size(); ++n_res) { + Residue& res = chain.residues[n_res]; + for (int n_atom = 0; n_atom != (int) res.atoms.size(); ++n_atom) { + Atom& atom = res.atoms[n_atom]; + if (!ns.include_h && is_hydrogen(atom.element)) + continue; + if (atom.occ < min_occupancy) + continue; + ns.for_each(atom.pos, atom.altloc, search_radius, + [&](NeighborSearch::Mark& m, double dist_sq) { + // do not consider connections inside a residue + if (ignore != Ignore::Nothing && m.image_idx == 0 && + m.chain_idx == n_ch && m.residue_idx == n_res) + return; + switch (ignore) { + case Ignore::Nothing: + break; + case Ignore::SameResidue: + if (m.image_idx == 0 && m.chain_idx == n_ch) + if (m.residue_idx == n_res) + return; + break; + case Ignore::AdjacentResidues: + if (m.image_idx == 0 && m.chain_idx == n_ch) + if (m.residue_idx == n_res || + are_connected(res, chain.residues[m.residue_idx], pt) || + are_connected(chain.residues[m.residue_idx], res, pt)) + return; + break; + case Ignore::SameChain: + if (m.image_idx == 0 && m.chain_idx == n_ch) + return; + break; + case Ignore::SameAsu: + if (m.image_idx == 0) + return; + break; + } + // additionally, we may have per-element distances + if (!radii.empty()) { + double d = radii[atom.element.ordinal()] + radii[m.element.ordinal()]; + if (d < 0 || dist_sq > d * d) + return; + } + // avoid reporting connections twice (A-B and B-A) + if (!twice) + if (m.chain_idx < n_ch || (m.chain_idx == n_ch && + (m.residue_idx < n_res || (m.residue_idx == n_res && + m.atom_idx < n_atom)))) + return; + // atom can be linked with its image, but if the image + // is too close the atom is likely on special position. + if (m.chain_idx == n_ch && m.residue_idx == n_res && + m.atom_idx == n_atom && dist_sq < special_pos_cutoff_sq) + return; + CRA cra2 = m.to_cra(*ns.model); + // ignore atoms with occupancy below the specified value + if (cra2.atom->occ < min_occupancy) + return; + func(CRA{&chain, &res, &atom}, cra2, m.image_idx, dist_sq); + }, ns.sufficient_k(search_radius)); + } + } + } +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/crd.hpp b/gemmi_gph/gemmi/crd.hpp new file mode 100644 index 00000000..5d4ceed8 --- /dev/null +++ b/gemmi_gph/gemmi/crd.hpp @@ -0,0 +1,24 @@ +// Copyright 2022 Global Phasing Ltd. +// +// Generate Refmac intermediate (prepared) files crd and rst + +#ifndef GEMMI_CRD_HPP_ +#define GEMMI_CRD_HPP_ + +#include "topo.hpp" // for Topo + +namespace gemmi { + +GEMMI_DLL void setup_for_crd(Structure& st); + +GEMMI_DLL void add_automatic_links(Model& model, Structure& st, const MonLib& monlib); + +GEMMI_DLL void add_dictionary_blocks(cif::Document& doc, const std::vector& resnames, + const Topo& topo, const MonLib& monlib); + +// Structure is not const b/c this function calls shorten_ccd_codes(st) +GEMMI_DLL cif::Document prepare_refmac_crd(Structure& st, const Topo& topo, + const MonLib& monlib, HydrogenChange h_change); + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/ddl.hpp b/gemmi_gph/gemmi/ddl.hpp new file mode 100644 index 00000000..47c70a21 --- /dev/null +++ b/gemmi_gph/gemmi/ddl.hpp @@ -0,0 +1,85 @@ +// Copyright Global Phasing Ltd. +// +// Using DDL1/DDL2 dictionaries to validate CIF/mmCIF files. + +#ifndef GEMMI_DDL_HPP_ +#define GEMMI_DDL_HPP_ + +#include +#include // for unique_ptr +#include +#include "cifdoc.hpp" // for cif::Document +#include "logger.hpp" // for Logger + +namespace gemmi { namespace cif { + +/// Represents DDL1 or DDL2 dictionary (ontology). +struct GEMMI_DLL Ddl { + /// member functions use logger's callback and threshold for output + Logger logger; + // configuration - some of these flag must be set before read_ddl() + bool print_unknown_tags = true; + // these flags below are relevant to DDL2 only + bool use_regex = true; + bool use_context = false; + bool use_parents = false; + bool use_mandatory = true; + bool use_unique_keys = true; + // instead of _item_type.code, _pdbx_item_enumeration.value, and _item_range + // use _pdbx-prefixed equivalents (_pdbx_item_type.code, etc). + bool use_deposition_checks = false; + + // variables set when reading DLL; normally, no need to change them + int major_version = 0; // currently 1 and 2 are supported + std::string dict_name; // _dictionary_name or _dictionary.title + std::string dict_version; // _dictionary_version or _dictionary.version + + Ddl() = default; + // MSVC with dllexport attempts to export all non-deleted member functions, + // failing with Error C2280 (because of ddl_docs_) if we don't delete these: + Ddl(Ddl const&) = delete; + Ddl& operator=(Ddl const&) = delete; + + /// it moves doc to ddl_docs_ to control lifetime and prevent modifications + void read_ddl(cif::Document&& doc); + + bool validate_cif(const cif::Document& doc) const; + bool validate_block(const cif::Block& b, const std::string& source) const; + + void check_audit_conform(const cif::Document& doc) const; + + const std::map& regexes() const { return regexes_; } + +private: + // items from DDL2 _pdbx_item_linked_group[_list] + struct ParentLink { + std::string group; + std::vector child_tags; + std::vector parent_tags; + }; + + std::vector> ddl_docs_; + std::map name_index_; + std::map regexes_; + std::vector parents_; + // storage for DDL2 _item_linked.child_name -> _item_linked.parent_name + std::map item_parents_; + + cif::Block* find_rules(const std::string& name) const { + auto iter = name_index_.find(to_lower(name)); + return iter != name_index_.end() ? iter->second : nullptr; + } + void check_mandatory_items(const cif::Block& b) const; + void check_unique_keys_in_loop(const cif::Loop& loop, const cif::Block& block) const; + void check_parents(const cif::Block& b) const; + void check_parent_link(const ParentLink& link, const cif::Block& b) const; + void read_ddl1_block(cif::Block& block); + void read_ddl2_block(cif::Block& block); + + template void warn(const cif::Block& b, Args const&... args) const { + logger.level<3>('[', b.name, "] ", args...); + } +}; + +}} // namespace gemmi::cif +#endif diff --git a/gemmi_gph/gemmi/dencalc.hpp b/gemmi_gph/gemmi/dencalc.hpp new file mode 100644 index 00000000..f5d9217b --- /dev/null +++ b/gemmi_gph/gemmi/dencalc.hpp @@ -0,0 +1,209 @@ +// Copyright 2019 Global Phasing Ltd. +// +// Tools to prepare a grid with values of electron density of a model. + +#ifndef GEMMI_DENCALC_HPP_ +#define GEMMI_DENCALC_HPP_ + +#include "addends.hpp" // for Addends +#include "formfact.hpp" // for ExpSum +#include "grid.hpp" // for Grid +#include "model.hpp" // for Structure, ... +#include "calculate.hpp" // for calculate_b_aniso_range + +namespace gemmi { + +template +Real determine_cutoff_radius(Real x1, const ExpSum& precal, Real cutoff_level) { + Real y1, dy; + std::tie(y1, dy) = precal.calculate_with_derivative(x1); + // Generally, density is supposed to decrease with radius. + // But if we have addends (in particular -Z for Mott-Bothe), + // it can first rise, then decrease. We want to be after the maximum. + while (std::copysign(dy, y1 * dy) > 0) { // unlikely + x1 += 1.0f; + std::tie(y1, dy) = precal.calculate_with_derivative(x1); + } + Real x2 = x1; + Real y2 = y1; + if (std::fabs(y1) < cutoff_level) { + while (std::fabs(y1) < cutoff_level) { + x2 = x1; + y2 = y1; + x1 -= 0.5f; + std::tie(y1, dy) = precal.calculate_with_derivative(x1); + // with addends it's possible to land on the left side of the maximum + if (std::copysign(dy, y1 * dy) > 0) { // unlikely + while (std::copysign(dy, y1 * dy) > 0 && x1 + 0.1f < x2) { + x1 += 0.1f; + std::tie(y1, dy) = precal.calculate_with_derivative(x1); + } + if (std::fabs(y1) < cutoff_level) + return x1; + break; + } + if (x1 < 0) { // unlikely + x1 = 0; + y1 = precal.calculate(x1 * x1); + break; + } + } + } else { + while (std::fabs(y2) > cutoff_level) { + x1 = x2; + y1 = y2; + x2 += 0.5f; + y2 = precal.calculate(x2 * x2); + } + } + + return x1 + (x1 - x2) / (y1 - y2) * (cutoff_level - y1); +} + +// approximated radius of electron density (IT92) above cutoff=1e-5 for C +template +Real it92_radius_approx(Real b) { + return (8.5f + 0.075f * b) / (2.4f + 0.0045f * b); +} + +// Usual usage: +// - set d_min and optionally also other parameters, +// - set addends to f' values for your wavelength (see fprime.hpp) +// - use grid.setup_from() to set grid's unit cell and space group +// - check that Table has SF coefficients for all elements that are to be used +// - call put_model_density_on_grid() +// - do FFT using transform_map_to_f_phi() +// - if blur is used, multiply the SF by reciprocal_space_multiplier() +template +struct DensityCalculator { + // GReal = type of grid; CReal = type of coefficients in Table + using CReal = typename Table::Coef::coef_type; + Grid grid; + double d_min = 0.; + double rate = 1.5; + double blur = 0.; + float cutoff = 1e-5f; +#if GEMMI_COUNT_DC + size_t atoms_added = 0; + size_t density_computations = 0; +#endif + Addends addends; + + double requested_grid_spacing() const { return d_min / (2 * rate); } + + void set_refmac_compatible_blur(const Model& model, bool allow_negative=false) { + double spacing = requested_grid_spacing(); + if (spacing <= 0) + spacing = std::min(std::min(grid.spacing[0], grid.spacing[1]), grid.spacing[2]); + double b_min = calculate_b_aniso_range(model).first; + blur = u_to_b() / 1.1 * sq(spacing) - b_min; + if (!allow_negative && blur < 0) + blur = 0.; + } + + // pre: check if Table::has(atom.element) + void add_atom_density_to_grid(const Atom& atom) { + Element el = atom.element; + const auto& coef = Table::get(el, atom.charge, atom.serial); + do_add_atom_density_to_grid(atom, coef, addends.get(el)); + } + + // Parameter c is a constant factor and has the same meaning as either addend + // or c in scattering factor coefficients (a1, b1, ..., c). + void add_c_contribution_to_grid(const Atom& atom, float c) { + do_add_atom_density_to_grid(atom, GaussianCoef<0, 1, CReal>{0}, c); + } + + template + CReal estimate_radius(const ExpSum& precal, CReal b) const { + if (N == 1) + return std::sqrt(std::log(cutoff / std::abs(precal.a[0])) / precal.b[0]); + CReal x1 = it92_radius_approx(b); + return determine_cutoff_radius(x1, precal, (CReal)cutoff); + } + + template + void do_add_atom_density_to_grid(const Atom& atom, const Coef& coef, float addend) { +#if GEMMI_COUNT_DC + ++atoms_added; +#endif + Fractional fpos = grid.unit_cell.fractionalize(atom.pos); + if (!atom.aniso.nonzero()) { + // isotropic + CReal b = static_cast(atom.b_iso + blur); + auto precal = coef.precalculate_density_iso(b, addend); + CReal radius = estimate_radius(precal, b); + grid.template use_points_around(fpos, radius, [&](GReal& point, double r2) { + point += GReal(atom.occ * precal.calculate((CReal)r2)); +#if GEMMI_COUNT_DC + ++density_computations; +#endif + }, /*fail_on_too_large_radius=*/false); + } else { + // anisotropic + auto aniso_b = atom.aniso.scaled(CReal(u_to_b())).added_kI(CReal(blur)); + // rough estimate, so we don't calculate eigenvalues + CReal b_max = std::max(std::max(aniso_b.u11, aniso_b.u22), aniso_b.u33); + auto precal_iso = coef.precalculate_density_iso(b_max, addend); + double radius = estimate_radius(precal_iso, b_max); + auto precal = coef.precalculate_density_aniso_b(aniso_b, addend); + int du = (int) std::ceil(radius / grid.spacing[0]); + int dv = (int) std::ceil(radius / grid.spacing[1]); + int dw = (int) std::ceil(radius / grid.spacing[2]); + grid.template use_points_in_box( + fpos, du, dv, dw, + [&](GReal& point, double, const Position& delta, int, int, int) { + point += GReal(atom.occ * precal.calculate(delta)); +#if GEMMI_COUNT_DC + ++density_computations; +#endif + }, + false, radius); + } + } + + void initialize_grid() { + grid.data.clear(); + double spacing = requested_grid_spacing(); + if (spacing > 0) + grid.set_size_from_spacing(spacing, GridSizeRounding::Up); + else if (grid.point_count() > 0) + // d_min not set, but a custom grid has been setup by the user + grid.fill(0.); + else + fail("initialize_grid(): d_min is not set"); + } + + void add_model_density_to_grid(const Model& model) { + grid.check_not_empty(); + for (const Chain& chain : model.chains) + for (const Residue& res : chain.residues) + for (const Atom& atom : res.atoms) + add_atom_density_to_grid(atom); + } + + void put_model_density_on_grid(const Model& model) { + initialize_grid(); + add_model_density_to_grid(model); + grid.symmetrize_sum(); + } + + // deprecated, use directly grid.setup_from(st) + void set_grid_cell_and_spacegroup(const Structure& st) { + grid.setup_from(st); + } + + // The argument is 1/d^2 - as outputted by unit_cell.calculate_1_d2(hkl). + double reciprocal_space_multiplier(double inv_d2) const { + return std::exp(blur * 0.25 * inv_d2); + } + + double mott_bethe_factor(const Miller& hkl) const { + double inv_d2 = grid.unit_cell.calculate_1_d2(hkl); + double factor = -mott_bethe_const() / inv_d2; + return blur == 0 ? factor : factor * reciprocal_space_multiplier(inv_d2); + } +}; + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/dirwalk.hpp b/gemmi_gph/gemmi/dirwalk.hpp new file mode 100644 index 00000000..b1b2213c --- /dev/null +++ b/gemmi_gph/gemmi/dirwalk.hpp @@ -0,0 +1,232 @@ +// Copyright 2018 Global Phasing Ltd. +// +// Classes for iterating over files in a directory tree, top-down, +// in alphabetical order. Wraps the tinydir library (as we cannot yet +// depend on C++17 ). + +// DirWalk<> iterates through all files and directories. +// CifWalk yields only cif files (either files that end with .cif or .cif.gz, +// or files that look like SF mmCIF files from wwPDB, e.g. r3aaasf.ent.gz). +// It's good for traversing a local copy of the wwPDB archive. +// PdbWalk: .pdb or .ent (optionally with .gz) except r????sf.ent +// CoorFileWalk: .cif, .pdb or .ent (optionally with .gz) +// except r????sf.ent and *-sf.cif +// +// Usage: +// for (const std::string& file : gemmi::DirWalk<>(top_dir)) +// do_something(file); +// or +// for (const std::string& file : gemmi::CifWalk(top_dir)) +// do_something(file); +// You should also catch std::runtime_error. + +#ifndef GEMMI_DIRWALK_HPP_ +#define GEMMI_DIRWALK_HPP_ + +#include +#include +#include +#if defined(_MSC_VER) && !defined(NOMINMAX) +# define NOMINMAX +#endif +#include "third_party/tinydir.h" + +#include "util.hpp" // for giends_with +#include "fail.hpp" // for sys_fail +#include "pdb_id.hpp" // for is_pdb_code, expand_pdb_code_to_path +#include "glob.hpp" // for glob_match +#if defined(_WIN32) && defined(_UNICODE) + #include "utf.hpp" +#endif + +namespace gemmi { + +inline std::string as_utf8(const _tinydir_char_t* path) { +#if defined(_WIN32) && defined(_UNICODE) + return wchar_to_UTF8(path); +#else + return path; +#endif +} + + +namespace impl { +// the SF mmCIF files from PDB have names such as +// divided/structure_factors/aa/r3aaasf.ent.gz +inline bool is_rxsf_ent_filename(const std::string& filename) { + return filename[0] == 'r' && giends_with(filename, "sf.ent") + && filename.find('.') >= 4; +} + +struct IsMmCifFile { // actually we don't know what kind of cif file it is + static bool check(const std::string& filename) { + return giends_with(filename, ".cif") || giends_with(filename, ".mmcif"); + } +}; + +struct IsCifFile { + static bool check(const std::string& filename) { + return giends_with(filename, ".cif") || is_rxsf_ent_filename(filename); + } +}; + +struct IsPdbFile { + static bool check(const std::string& filename) { + return giends_with(filename, ".pdb") || + (giends_with(filename, ".ent") && !is_rxsf_ent_filename(filename)); + } +}; + +struct IsCoordinateFile { + static bool check(const std::string& filename) { + // the SF mmCIF files from RCSB website have names such as 3AAA-sf.cif + return IsPdbFile::check(filename) || + (IsMmCifFile::check(filename) && !giends_with(filename, "-sf.cif")); + } +}; + +struct IsAnyFile { + static bool check(const std::string&) { return true; } +}; + +struct IsMatchingFile { + bool check(const std::string& filename) const { + return glob_match(pattern, filename); + } + std::string pattern; +}; + +inline int utf8_tinydir_file_open(tinydir_file* file, const char* path) { +#if defined(_WIN32) && defined(_UNICODE) + return tinydir_file_open(file, UTF8_to_wchar(path).c_str()); +#else + return tinydir_file_open(file, path); +#endif +} + +} // namespace impl + + +template +class DirWalk { +public: + explicit DirWalk(const char* path, char try_pdbid='\0') { + if (impl::utf8_tinydir_file_open(&top_, path) != -1) + return; + if (try_pdbid != '\0' && is_pdb_code(path)) { + std::string epath = expand_pdb_code_to_path(path, try_pdbid, true); + if (impl::utf8_tinydir_file_open(&top_, epath.c_str()) != -1) + return; + sys_fail("Cannot open " + epath); + } + sys_fail("Cannot open " + std::string(path)); + } + explicit DirWalk(const std::string& path, char try_pdbid='\0') + : DirWalk(path.c_str(), try_pdbid) {} + ~DirWalk() { + for (auto& d : dirs_) + tinydir_close(&d.second); + } + void push_dir(size_t cur_pos, const _tinydir_char_t* path) { + dirs_.emplace_back(); + dirs_.back().first = cur_pos; + if (tinydir_open_sorted(&dirs_.back().second, path) == -1) + sys_fail("Cannot open directory " + as_utf8(path)); + } + size_t pop_dir() { + assert(!dirs_.empty()); + size_t old_pos = dirs_.back().first; + tinydir_close(&dirs_.back().second); + dirs_.pop_back(); + return old_pos; + } + + struct Iter { + DirWalk& walk; + size_t cur; + + const tinydir_dir& get_dir() const { return walk.dirs_.back().second; } + + const tinydir_file& get() const { + if (walk.dirs_.empty()) + return walk.top_; + assert(cur < get_dir().n_files); + return get_dir()._files[cur]; + } + + std::string operator*() const { return as_utf8(get().path); } + + // checks for "." and ".." + bool is_special(const _tinydir_char_t* name) const { + return name[0] == '.' && (name[1] == '\0' || + (name[1] == '.' && name[2] == '\0')); + } + + size_t depth() const { return walk.dirs_.size(); } + + void next() { // depth first + const tinydir_file& tf = get(); + if (tf.is_dir) { + walk.push_dir(cur, tf.path); + cur = 0; + } else { + cur++; + } + while (!walk.dirs_.empty()) { + if (cur == get_dir().n_files) + cur = walk.pop_dir() + 1; + else if (is_special(get_dir()._files[cur].name)) + cur++; + else + break; + } + } + + void operator++() { + for (;;) { + next(); + const tinydir_file& f = get(); + if ((!FileOnly && f.is_dir) + || (!f.is_dir && walk.filter.check(as_utf8(f.name))) + || walk.is_single_file() + || (depth() == 0 && cur == 1)) + break; + } + } + + // == and != is used only to compare with end() + bool operator==(const Iter& o) const { return depth()==0 && cur == o.cur; } + bool operator!=(const Iter& o) const { return !operator==(o); } + }; + + Iter begin() { + Iter it{*this, 0}; + if (FileOnly && !is_single_file()) // i.e. the top item is a directory + ++it; + return it; + } + + Iter end() { return Iter{*this, 1}; } + bool is_single_file() { return !top_.is_dir; } + +private: + friend struct Iter; + tinydir_file top_; + std::vector> dirs_; +protected: + Filter filter; +}; + +using CifWalk = DirWalk; +using MmCifWalk = DirWalk; +using PdbWalk = DirWalk; +using CoorFileWalk = DirWalk; + +struct GlobWalk : public DirWalk { + GlobWalk(const std::string& path, const std::string& glob) : DirWalk(path) { + filter.pattern = glob; + } +}; + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/dssp.hpp b/gemmi_gph/gemmi/dssp.hpp new file mode 100644 index 00000000..ddcaef76 --- /dev/null +++ b/gemmi_gph/gemmi/dssp.hpp @@ -0,0 +1,167 @@ +// Copyright Global Phasing Ltd. +// +// DSSP (Define Secondary Structure of Proteins) implementation. + +#ifndef GEMMI_DSSP_HPP_ +#define GEMMI_DSSP_HPP_ + +#include "topo.hpp" +#include "neighbor.hpp" +#include +#include + +namespace gemmi { + +// Secondary structure types as defined in DSSP +enum class SecondaryStructure : char { + Loop = '~', // Loop/coil + Break = '=', // Break + Bend = 'S', // Bend + Turn = 'T', // Turn + Helix_PP = 'P', // Polyproline helix + Helix_5 = 'I', // Pi helix (5-turn) + Helix_3 = 'G', // 3-10 helix + Strand = 'E', // Extended strand + Bridge = 'B', // Beta bridge + Helix_4 = 'H' // Alpha helix (4-turn) +}; + +// Turn types +enum class TurnType { + Turn_3 = 3, + Turn_4 = 4, + Turn_5 = 5, + Turn_PP = 6 +}; + +// Helix positions +enum class HelixPosition { + None = 0, + Start, + Middle, + End, + StartAndEnd +}; + +// Bridge types +enum class BridgeType { + None = 0, + Parallel, + AntiParallel +}; + +struct Bridge { + size_t partner1; + size_t partner2; + BridgeType type; +}; + +// Hydrogen bond modes +enum class HydrogenMode { + Existing = 0, // Use existing hydrogen atoms from structure + Calculate // Calculate hydrogen positions (original DSSP method) +}; + +// Hydrogen bond definition +enum class HBondDefinition { + Energy = 0, // Energy-based (original DSSP) + Geometry // Geometry-based (distance + angle) +}; + +struct GEMMI_DLL HBond { + Topo::ResInfo *donor = nullptr, *acceptor = nullptr; + char alt1 = '\0'; + char alt2 = '\0'; + double energy = 0; + bool is_valid = false; +}; + +// Per-residue secondary structure information +struct GEMMI_DLL SecondaryStructureInfo { + SecondaryStructure ss_type = SecondaryStructure::Loop; + std::vector parallel_bridges; + std::vector antiparallel_bridges; + std::vector break_partners; + std::array helix_positions = {HelixPosition::None, HelixPosition::None, + HelixPosition::None, HelixPosition::None}; + bool has_break = false; + bool nturn_acceptor = false; + + void set_helix_position(TurnType turn, HelixPosition pos) { + helix_positions[static_cast(turn) - 3] = pos; + } + + HelixPosition get_helix_position(TurnType turn) const { + return helix_positions[static_cast(turn) - 3]; + } + + void add_bridge(size_t partner_idx, BridgeType type) { + if (type == BridgeType::Parallel) { + parallel_bridges.push_back(partner_idx); + } else if (type == BridgeType::AntiParallel) { + antiparallel_bridges.push_back(partner_idx); + } + } + + bool has_bridges(BridgeType type) const { + return type == BridgeType::Parallel ? !parallel_bridges.empty() + : !antiparallel_bridges.empty(); + } +}; + +// DSSP options/parameters +struct GEMMI_DLL DsspOptions { + HydrogenMode hydrogen_mode = HydrogenMode::Calculate; + HBondDefinition hbond_definition = HBondDefinition::Energy; + double cutoff = 0.9; // nm + bool pi_helix_preference = true; + bool search_polyproline = true; + bool shortened_pp_stretch = false; + double hbond_energy_cutoff = -0.5; // kcal/mol + double min_ca_distance = 9.0; // Angstrom + double bend_angle_min = 70.0; // degrees + double max_peptide_bond_distance = 2.5; // Angstrom +}; + +// Main DSSP calculator class +struct GEMMI_DLL DsspCalculator { + explicit DsspCalculator(const DsspOptions& opts = DsspOptions{}) : options(opts) {} + + // Calculate secondary structure for a chain + std::string calculate_secondary_structure(NeighborSearch& ns, Topo::ChainInfo& chain_info); + + // Get detailed secondary structure information + const std::vector& get_detailed_info() const { return ss_info; } + + DsspOptions options; + std::vector ss_info; + std::vector bridges_; + + // Calculate hydrogen bonds + void calculate_hydrogen_bonds(NeighborSearch& ns, Topo::ChainInfo& chain_info); + void calculate_hbond_energy(Topo::ResInfo* donor, Topo::ResInfo* acceptor); + void calculate_hbond_geometry(Topo::ResInfo* donor, Topo::ResInfo* acceptor); + + /* + // Pattern recognition functions + void find_bridges_and_strands(Topo::ChainInfo& chain_info); + void find_turns_and_helices(Topo::ChainInfo& chain_info); + void find_bends_and_breaks(Topo::ChainInfo& chain_info); + void find_polyproline_helices(Topo::ChainInfo& chain_info); + */ + + // Utility functions + bool has_hbond_between(Topo::ResInfo* donor, Topo::ResInfo* acceptor) const; + bool no_chain_breaks_between(Topo::ChainInfo& chain_info, size_t res1_idx, size_t res2_idx) const; + BridgeType calculate_bridge_type(Topo::ChainInfo& chain_info, size_t res1_idx, size_t res2_idx) const; + + // Generate final secondary structure string + //std::string generate_ss_string() const; +}; + +// Convenience function for simple use cases +GEMMI_DLL std::string calculate_dssp(NeighborSearch& ns, Topo::ChainInfo& cinfo, + const DsspOptions& opts = DsspOptions{}); + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/ecalc.hpp b/gemmi_gph/gemmi/ecalc.hpp new file mode 100644 index 00000000..9da667d9 --- /dev/null +++ b/gemmi_gph/gemmi/ecalc.hpp @@ -0,0 +1,97 @@ +// Copyright 2023 Global Phasing Ltd. +// +// Normalization of amplitudes F->E ("Karle" approach, similar to CCP4 ECALC). + +#ifndef GEMMI_ECALC_HPP_ +#define GEMMI_ECALC_HPP_ + +#include +#include "binner.hpp" + +namespace gemmi { + +template +std::vector calculate_amplitude_normalizers(const DataProxy& data, int fcol_idx, + const Binner& binner) { + struct CountAndSum { + int n = 0; + double sum = 0.; + }; + int nreflections = data.size() / data.stride(); + std::vector multipliers(nreflections, NAN); + if (data.spacegroup() == nullptr) + gemmi::fail("unknown space group in the data file"); + GroupOps gops = data.spacegroup()->operations(); + std::vector inv_d2(multipliers.size()); + for (size_t i = 0, n = 0; n < data.size(); n += data.stride(), ++i) + inv_d2[i] = data.unit_cell().calculate_1_d2(data.get_hkl(n)); + std::vector bin_index = binner.get_bins_from_1_d2(inv_d2); + std::vector stats(binner.size()); + for (size_t i = 0, n = 0; n < data.size(); n += data.stride(), i++) { + Miller hkl = data.get_hkl(n); + double f = data.get_num(n + fcol_idx); + if (!std::isnan(f)) { + int epsilon = gops.epsilon_factor(hkl); + double inv_epsilon = 1.0 / epsilon; + double f2 = f * f * inv_epsilon; + multipliers[i] = std::sqrt(inv_epsilon); + CountAndSum& cs = stats[bin_index[i]]; + cs.n++; + cs.sum += f2; + } + } + + // simple smoothing with kernel [0.75 1 0.75] + std::vector smoothed(stats.size()); + { + const double k = 0.75; + smoothed[0] = (stats[0].sum + k * stats[1].sum) / (stats[0].n + k * stats[1].n); + size_t n = stats.size() - 1; + for (size_t i = 1; i < n; ++i) + smoothed[i] = (stats[i].sum + k * (stats[i-1].sum + stats[i+1].sum)) + / (stats[i].n + k * (stats[i-1].n + stats[i+1].n)); + smoothed[n] = (stats[n].sum + k * stats[n-1].sum) / (stats[n].n + k * stats[n-1].n); + } + +#if 0 + { + // print shell statistics + std::vector refl_counts(binner.size()); + printf(" shell\t #F\t d\t \tsmoothd\t #refl\t mid d\n"); + for (int idx : bin_index) + ++refl_counts[idx]; + for (size_t i = 0; i < binner.size(); ++i) { + double d = 1 / std::sqrt(binner.limits[i]); + double mid_d = 1 / std::sqrt(binner.mids[i]); + double avg_f2 = stats[i].sum / stats[i].n; + printf("%6zu\t%6d\t%7.3f\t%7.0f\t%7.0f\t%6d\t%7.3f\n", + i+1, stats[i].n, d, avg_f2, smoothed[i], refl_counts[i], mid_d); + } + printf("\n"); + } +#endif + + for (double& x : smoothed) + x = std::sqrt(x); + for (size_t i = 0; i < multipliers.size(); ++i) { + double x = inv_d2[i]; + int bin = bin_index[i]; + double rms = smoothed[bin]; + if (x > binner.mids.front() && x < binner.mids.back()) { + // linear interpolation in 1/d^2 + if (x > binner.mids[bin]) + ++bin; + double x0 = binner.mids[bin - 1]; + double x1 = binner.mids[bin]; + double y0 = smoothed[bin - 1]; + double y1 = smoothed[bin]; + assert(x0 <= x && x <= x1); + rms = y0 + (x - x0) * (y1 - y0) / (x1 - x0); + } + multipliers[i] /= rms; + } + return multipliers; +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/eig3.hpp b/gemmi_gph/gemmi/eig3.hpp new file mode 100644 index 00000000..17df0774 --- /dev/null +++ b/gemmi_gph/gemmi/eig3.hpp @@ -0,0 +1,15 @@ +// Eigen decomposition code for symmetric 3x3 matrices. + +#ifndef GEMMI_EIG3_HPP_ +#define GEMMI_EIG3_HPP_ + +#include "math.hpp" // for SMat33, Mat33 +#include "fail.hpp" // for GEMMI_DLL + +namespace gemmi { + +GEMMI_DLL Mat33 eigen_decomposition(const SMat33& A, double (&d)[3]); + +} // namespace gemmi + +#endif diff --git a/gemmi_gph/gemmi/elem.hpp b/gemmi_gph/gemmi/elem.hpp new file mode 100644 index 00000000..114d136f --- /dev/null +++ b/gemmi_gph/gemmi/elem.hpp @@ -0,0 +1,391 @@ +// Copyright 2017 Global Phasing Ltd. +// +// Elements from the periodic table. + +#ifndef GEMMI_ELEM_HPP_ +#define GEMMI_ELEM_HPP_ + +#include +#include + +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(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(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(El::END) + 1, "Hmm"); + return table[static_cast(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(El::D)], 2.0141), "Hmm"); + static_assert(sizeof(weights) / sizeof(weights[0]) == + static_cast(El::END) + 1, "Hmm"); + return weights[static_cast(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(El::D)], 0.31f), "Hmm"); + static_assert(sizeof(radii) / sizeof(radii[0]) == + static_cast(El::END) + 1, "Hmm"); + return radii[static_cast(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(El::D)], 1.2f), "Hmm"); + static_assert(sizeof(radii) / sizeof(radii[0]) == + static_cast(El::END) + 1, "Hmm"); + return radii[static_cast(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(El::Og) == 118, "Hmm"); + static_assert(names[118][0] == 'O', "Hmm"); + static_assert(sizeof(names) / sizeof(names[0]) == + static_cast(El::END) + 1, "Hmm"); + return names[static_cast(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(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(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(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(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 diff --git a/gemmi_gph/gemmi/enumstr.hpp b/gemmi_gph/gemmi/enumstr.hpp new file mode 100644 index 00000000..e804895e --- /dev/null +++ b/gemmi_gph/gemmi/enumstr.hpp @@ -0,0 +1,110 @@ +// Copyright 2018 Global Phasing Ltd. +// +// Converts between enums (EntityType, PolymerType, Connection::Type, +// SoftwareItem::Classification) and mmCIF strings. + +#ifndef GEMMI_ENUMSTR_HPP_ +#define GEMMI_ENUMSTR_HPP_ + +#include "metadata.hpp" // for EntityType, PolymerType, SoftwareItem +#include "util.hpp" // for iequal + +namespace gemmi { + +inline const char* entity_type_to_string(EntityType entity_type) { + switch (entity_type) { + case EntityType::Polymer: return "polymer"; + case EntityType::Branched: return "branched"; + case EntityType::NonPolymer: return "non-polymer"; + case EntityType::Water: return "water"; + default /*EntityType::Unknown*/: return "?"; + } +} + +inline EntityType entity_type_from_string(const std::string& t) { + if (t == "polymer") return EntityType::Polymer; + if (t == "branched") return EntityType::Branched; + if (t == "non-polymer") return EntityType::NonPolymer; + if (t == "water") return EntityType::Water; + return EntityType::Unknown; +} + + +inline const char* polymer_type_to_string(PolymerType polymer_type) { + switch (polymer_type) { + case PolymerType::PeptideL: return "polypeptide(L)"; + case PolymerType::PeptideD: return "polypeptide(D)"; + case PolymerType::Dna: return "polydeoxyribonucleotide"; + case PolymerType::Rna: return "polyribonucleotide"; + case PolymerType::DnaRnaHybrid: + return "'polydeoxyribonucleotide/polyribonucleotide hybrid'"; + case PolymerType::SaccharideD: return "polysaccharide(D)"; + case PolymerType::SaccharideL: return "polysaccharide(L)"; + case PolymerType::Other: return "other"; + case PolymerType::Pna: return "'peptide nucleic acid'"; + case PolymerType::CyclicPseudoPeptide: return "cyclic-pseudo-peptide"; + default /*PolymerType::Unknown*/: return "?"; + } +} + +inline PolymerType polymer_type_from_string(const std::string& t) { + if (t == "polypeptide(L)") return PolymerType::PeptideL; + if (t == "polydeoxyribonucleotide") return PolymerType::Dna; + if (t == "polyribonucleotide") return PolymerType::Rna; + if (t == "polydeoxyribonucleotide/polyribonucleotide hybrid") + return PolymerType::DnaRnaHybrid; + if (t == "polypeptide(D)") return PolymerType::PeptideD; + if (t == "polysaccharide(D)") return PolymerType::SaccharideD; + if (t == "other") return PolymerType::Other; + if (t == "peptide nucleic acid") return PolymerType::Pna; + if (t == "cyclic-pseudo-peptide") return PolymerType::CyclicPseudoPeptide; + if (t == "polysaccharide(L)") return PolymerType::SaccharideL; + return PolymerType::Unknown; +} + + +inline const char* connection_type_to_string(Connection::Type t) { + static constexpr const char* type_ids[] = { + "covale", "disulf", "hydrog", "metalc", "." + }; + return type_ids[t]; +} + +inline Connection::Type connection_type_from_string(const std::string& t) { + for (int i = 0; i != Connection::Unknown; ++i) + if (connection_type_to_string(Connection::Type(i)) == t) + return Connection::Type(i); + return Connection::Unknown; +} + +inline +std::string software_classification_to_string(SoftwareItem::Classification c) { + switch (c) { + case SoftwareItem::DataCollection: return "data collection"; + case SoftwareItem::DataExtraction: return "data extraction"; + case SoftwareItem::DataProcessing: return "data processing"; + case SoftwareItem::DataReduction: return "data reduction"; + case SoftwareItem::DataScaling: return "data scaling"; + case SoftwareItem::ModelBuilding: return "model building"; + case SoftwareItem::Phasing: return "phasing"; + case SoftwareItem::Refinement: return "refinement"; + case SoftwareItem::Unspecified: return ""; + } + unreachable(); +} + +inline SoftwareItem::Classification +software_classification_from_string(const std::string& str) { + if (iequal(str, "data collection")) return SoftwareItem::DataCollection; + if (iequal(str, "data extraction")) return SoftwareItem::DataExtraction; + if (iequal(str, "data processing")) return SoftwareItem::DataProcessing; + if (iequal(str, "data reduction")) return SoftwareItem::DataReduction; + if (iequal(str, "data scaling")) return SoftwareItem::DataScaling; + if (iequal(str, "model building")) return SoftwareItem::ModelBuilding; + if (iequal(str, "phasing")) return SoftwareItem::Phasing; + if (iequal(str, "refinement")) return SoftwareItem::Refinement; + return SoftwareItem::Unspecified; +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/flat.hpp b/gemmi_gph/gemmi/flat.hpp new file mode 100644 index 00000000..ec1e49ed --- /dev/null +++ b/gemmi_gph/gemmi/flat.hpp @@ -0,0 +1,50 @@ +// Copyright Global Phasing Ltd. +// +// FlatStructure, FlatAtom + +#ifndef GEMMI_FLAT_HPP_ +#define GEMMI_FLAT_HPP_ + +#include +#include "model.hpp" + +namespace gemmi { + +struct FlatAtom { + char atom_name[8] {}; + char residue_name[8] = {}; + char chain_id[8] = {}; + char subchain[8] = {}; + char entity_id[8] = {}; + SeqId seq_id; + Position pos; + float occ = 1.0f; + float b_iso = 20.0f; // arbitrary default value + char altloc = '\0'; // 0 if not set + char het_flag = '\0'; // 'A' = ATOM, 'H' = HETATM, 0 = unspecified + EntityType entity_type = EntityType::Unknown; + Element element = El::X; + signed char charge = 0; // [-8, +8] + SMat33 aniso = {0, 0, 0, 0, 0, 0}; + int model_num; + int serial = 0; + bool selected = false; + + std::string atom_str() const { + ResidueId resid{seq_id, "", residue_name}; + return gemmi::atom_str(chain_id, resid, atom_name, altloc); + } +}; +struct GEMMI_DLL FlatStructure { + Structure empty_st; + std::vector table; + bool strings_as_numbers = true; // when accessed as NumPy arrays (Python bindings) + + // Structure <-> FlatStructure + FlatStructure(const Structure& st); + Structure generate_structure(); +}; + +} // namespace gemmi + +#endif diff --git a/gemmi_gph/gemmi/floodfill.hpp b/gemmi_gph/gemmi/floodfill.hpp new file mode 100644 index 00000000..8594f40c --- /dev/null +++ b/gemmi_gph/gemmi/floodfill.hpp @@ -0,0 +1,162 @@ +// Copyright 2020 Global Phasing Ltd. +// +// The flood fill (scanline fill) algorithm for Grid. +// Assumes periodic boundary conditions in the grid and 6-way connectivity. + +#ifndef GEMMI_FLOODFILL_HPP_ +#define GEMMI_FLOODFILL_HPP_ + +#include // for int8_t +#include "grid.hpp" // for Grid + +namespace gemmi { + +// Land is either 0 (when 1=sea, 0=land) or 1 (when 0=sea, 1=land) +template +struct FloodFill { + Grid& mask; + + struct Line { + int u, v, w, ulen; + T* ptr; + }; + + struct Result { + std::vector lines; + + size_t point_count() const { + size_t n = 0; + for (const Line& line : lines) + n += line.ulen; + return n; + } + }; + + static constexpr T this_island() { return T(Land|2); } + + void set_line_values(Line& line, T value) const { + for (int i = 0; i < std::min(line.ulen, mask.nu - line.u); ++i) { + assert(line.ptr[i] != value); + line.ptr[i] = value; + } + for (int i = -line.u; i < line.ulen - mask.nu; ++i) { + assert(line.ptr[i] != value); + line.ptr[i] = value; + } + } + + void set_volume_values(Result& r, T value) const { + for (Line& line : r.lines) + set_line_values(line, value); + } + + // Find all connected points with value Land. Change them to this_island(). + Result find_all_connected_points(int u, int v, int w) { + Result r; + T* ptr = &mask.data[mask.index_q(u, v, w)]; + r.lines.push_back(line_from_point(u, v, w, ptr)); + set_line_values(r.lines.back(), this_island()); + for (size_t i = 0; i < r.lines.size()/*increasing!*/; ++i) { + int u_ = r.lines[i].u; + int v_ = r.lines[i].v; + int w_ = r.lines[i].w; + int ustart = u_ != 0 ? u_ - 1 : mask.nu - 1; + int ulen = std::min(mask.nu, r.lines[i].ulen + 2); + // add adjacent lines + int vl = v_ != 0 ? v_ - 1 : mask.nv - 1; + int vh = v_ + 1 != mask.nv ? v_ + 1 : 0; + int wl = w_ != 0 ? w_ - 1 : mask.nw - 1; + int wh = w_ + 1 != mask.nw ? w_ + 1 : 0; + add_lines(ustart, vl, wl, ulen, r); + add_lines(ustart, vl, w_, ulen, r); + add_lines(ustart, vl, wh, ulen, r); + add_lines(ustart, v_, wl, ulen, r); + add_lines(ustart, v_, wh, ulen, r); + add_lines(ustart, vh, wl, ulen, r); + add_lines(ustart, vh, w_, ulen, r); + add_lines(ustart, vh, wh, ulen, r); + } + return r; + } + + template + void for_each_islands(Func func) { + size_t idx = 0; + for (int w = 0; w != mask.nw; ++w) + for (int v = 0; v != mask.nv; ++v) + for (int u = 0; u != mask.nu; ++u, ++idx) { + assert(idx == mask.index_q(u, v, w)); + if (mask.data[idx] == Land) { + // it temporarily marks current island as this_island() + Result r = find_all_connected_points(u, v, w); + func(r); + } + } + // set big islands (continents) back to Land + for (T& p : mask.data) + p = T((int)p & 1); + } + +private: + void add_lines(int u, int v, int w, int ulen, Result& r) { + T* ptr = &mask.data[mask.index_q(u, v, w)]; + for (int i = 0; i < std::min(ulen, mask.nu - u); ++i) + if (ptr[i] == Land) { + r.lines.push_back(line_from_point(u + i, v, w, ptr + i)); + set_line_values(r.lines.back(), this_island()); + } + for (int i = -u; i < ulen - mask.nu; ++i) + if (ptr[i] == Land) { + r.lines.push_back(line_from_point(u + i, v, w, ptr + i)); + set_line_values(r.lines.back(), this_island()); + } + } + + Line line_from_point(int u, int v, int w, T* ptr) const { + int len = 1; + while (u + len < mask.nu && ptr[len] == Land) + ++len; + if (u + len == mask.nu) + while (len < mask.nu && ptr[len - mask.nu] == Land) + ++len; + for (int i = 0; i > -u; --i) + if (ptr[i-1] != Land) + return {u + i, v, w, len - i, ptr + i}; + if (ptr[mask.nu-1-u] != Land) + return {0, v, w, len + u, ptr - u}; + for (int i = mask.nu - 1 - u; i > 1; --i) + if (ptr[i-1] != Land) + return {u + i, v, w, len + mask.nu - 2 - i, ptr + i}; + return {u, v, w, mask.nu, ptr}; + } +}; + +inline void mask_nodes_above_threshold(Grid& mask, const Grid& grid, + double threshold, bool negate=false) { + mask.copy_metadata_from(grid); + mask.data.resize(grid.data.size()); + size_t n = 0; + for (float d : grid.data) + mask.data[n++] = std::int8_t((negate ? -d : d) > threshold); +} + +inline Grid flood_fill_above(const Grid& grid, + const std::vector& seeds, + double threshold, + bool negate=false) { + grid.check_not_empty(); + Grid mask; + mask_nodes_above_threshold(mask, grid, threshold, negate); + FloodFill flood_fill{mask}; + for (const Position& pos : seeds) { + auto point = mask.get_nearest_point(pos); + if (*point.value == 1) + flood_fill.find_all_connected_points(point.u, point.v, point.w); + } + for (std::int8_t& d : mask.data) + d = std::int8_t(d == flood_fill.this_island()); + return mask; +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/formfact.hpp b/gemmi_gph/gemmi/formfact.hpp new file mode 100644 index 00000000..b4f15481 --- /dev/null +++ b/gemmi_gph/gemmi/formfact.hpp @@ -0,0 +1,209 @@ +// Copyright 2019 Global Phasing Ltd. + +// Calculation of atomic form factors approximated by a sum of Gaussians. +// Tables with numerical coefficients are in it92.hpp and c4322.hpp. + +#ifndef GEMMI_FORMFACT_HPP_ +#define GEMMI_FORMFACT_HPP_ + +#include // for exp, sqrt +#include // for int32_t +#include // for memcpy +#include // for numeric_limits +#include // for pair +#include "math.hpp" // for pi() + +namespace gemmi { + +// NOTE: the argument x must be between -88 and 88. +// It is based on expapprox() from +// https://github.com/jhjourdan/SIMD-math-prims/blob/master/simd_math_prims.h +// Relative error is below 1e-5. +inline float unsafe_expapprox(float x) { + static_assert(std::numeric_limits::is_iec559, "float is not IEEE 754?"); + //static float zero = 0.f; // non-const to disable optimization + float val = 12102203.1615614f * x + 1065353216.f; + //val = std::max(val, zero); // check if x < -88.02969 + std::int32_t vali = static_cast(val); + std::int32_t xu1 = vali & 0x7F800000; + std::int32_t xu2 = (vali & 0x7FFFFF) | 0x3F800000; + float a, b; + std::memcpy(&a, &xu1, 4); + std::memcpy(&b, &xu2, 4); + return a * (0.509871020f + b * (0.312146713f + b * (0.166617139f + b * + (-2.190619930e-3f + b * 1.3555747234e-2f)))); +} + +// precalculated density of an isotropic atom +template +struct ExpSum { + Real a[N], b[N]; + + Real calculate(Real r2) const { + Real density = 0; + for (int i = 0; i < N; ++i) + density += a[i] * std::exp(b[i] * r2); + return density; + } + + std::pair calculate_with_derivative(Real r) const { + Real density = 0; + Real derivative = 0; + for (int i = 0; i < N; ++i) { + Real y = a[i] * std::exp(b[i] * (r * r)); + density += y; + derivative += 2 * b[i] * r * y; + } + return std::make_pair(density, derivative); + } +}; + +template +struct ExpSum { + float a[N], b[N]; + float calculate(float r2) const { + float density = 0; + float tmp[N]; + for (int i = 0; i < N; ++i) + tmp[i] = std::max(b[i] * r2, -88.f); + for (int i = 0; i < N; ++i) + density += a[i] * unsafe_expapprox(tmp[i]); + return density; + } + + std::pair calculate_with_derivative(float r) const { + float density = 0; + float derivative = 0; + float tmp[N]; + for (int i = 0; i < N; ++i) + tmp[i] = std::max(b[i] * (r * r), -88.f); + for (int i = 0; i < N; ++i) { + float y = a[i] * unsafe_expapprox(tmp[i]); + density += y; + derivative += y * b[i] * (2 * r); + } + return std::make_pair(density, derivative); + } +}; + +// precalculated density of an anisotropic atom +template +struct ExpAnisoSum { + Real a[N]; + SMat33 b[N]; + + Real calculate(const Vec3& r) const { + Real density = 0; + for (int i = 0; i < N; ++i) + density += a[i] * std::exp(b[i].r_u_r(r)); + return density; + } +}; + +template +struct ExpAnisoSum { + float a[N]; + SMat33 b[N]; + + float calculate(const Vec3& r_) const { + Vec3f r((float)r_.x, (float)r_.y, (float)r_.z); + float density = 0; + float tmp[N]; + for (int i = 0; i < N; ++i) + tmp[i] = std::max(b[i].r_u_r(r), -88.f); + for (int i = 0; i < N; ++i) + density += a[i] * unsafe_expapprox(tmp[i]); + return density; + } +}; + +template +Real pow15(Real x) { return x * std::sqrt(x); } + +// Gaussian coefficients with functions to calculate sf and density. +template +struct GaussianCoef { + using coef_type = Real; + static const int ncoeffs = N; + std::array coefs; + Real a(int n) const { return coefs[n]; } + Real b(int n) const { return coefs[N+n]; } + Real c() const { return WithC ? coefs[2*N] : 0; } + + void set_coefs(const std::array& c) { coefs = c; } + + // argument: (sin(theta)/lambda)^2 + Real calculate_sf(Real stol2) const { + Real sf = c(); + for (int i = 0; i < N; ++i) + sf += a(i) * std::exp(-b(i)*stol2); + return sf; + } + + Real calculate_density_iso(Real r2, Real B) const { + constexpr Real _4pi = Real(4 * pi()); + Real r2pi = Real(r2 * pi()); + Real density = c() * pow15(_4pi / B) * std::exp(-(_4pi / B) * r2pi); + for (int i = 0; i < N; ++i) { + Real t = _4pi / (b(i)+B); + density += a(i) * pow15(t) * std::exp(-t*r2pi); + } + return density; + } + + // note: addend is considered only if WithC (addend is usually dispersion f') + ExpSum precalculate_density_iso(Real B, Real addend=0) const { + ExpSum prec; + constexpr Real _4pi = Real(4 * pi()); + for (int i = 0; i < N; ++i) { + Real t = _4pi / (b(i)+B); + prec.a[i] = a(i) * pow15(t); + prec.b[i] = -t * Real(pi()); + } + if (WithC) { + Real t = _4pi / B; + prec.a[N] = (c() + addend) * pow15(t); + prec.b[N] = -t * Real(pi()); + } + return prec; + } + + Real calculate_density_aniso(const Vec3& r, const SMat33& U) const { + constexpr Real pi2 = sq(pi()); + const SMat33 B = U.scaled(8 * pi2); + Real density = c() * pow15(4 * pi()) / std::sqrt(B.determinant()) * + std::exp(-4 * pi2 * B.inverse().r_u_r(r)); + for (int i = 0; i < N; ++i) { + SMat33 Bb = B.added_kI(b(i)); + density += a(i) * pow15(4 * pi()) / std::sqrt(Bb.determinant()) * + std::exp(-4 * pi2 * Bb.inverse().r_u_r(r)); + } + return density; + } + + ExpAnisoSum precalculate_density_aniso_b(const SMat33& B, + Real addend=0) const { + constexpr Real m4pi2 = Real(-4 * sq(pi())); + constexpr Real pow_4pi_15 = (Real) 44.546623974653663; // pow15(4 * pi()) + ExpAnisoSum prec; + for (int i = 0; i < N; ++i) { + SMat33 Bb = B.added_kI(b(i)); + prec.a[i] = a(i) * pow_4pi_15 / std::sqrt(Bb.determinant()); + prec.b[i] = Bb.inverse().scaled(m4pi2); + } + if (WithC) { + prec.a[N] = (c() + addend) * pow_4pi_15 / std::sqrt(B.determinant()); + prec.b[N] = B.inverse().scaled(m4pi2); + } + return prec; + } + + ExpAnisoSum precalculate_density_aniso_u(const SMat33& U, + Real addend=0) const { + constexpr Real UtoB = 8 * sq(pi()); + return precalculate_density_aniso_b(U.scaled(UtoB), addend); + } +}; + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/fourier.hpp b/gemmi_gph/gemmi/fourier.hpp new file mode 100644 index 00000000..31a44105 --- /dev/null +++ b/gemmi_gph/gemmi/fourier.hpp @@ -0,0 +1,357 @@ +// Copyright 2019 Global Phasing Ltd. +// +// Fourier transform applied to map coefficients. + +#ifndef GEMMI_FOURIER_HPP_ +#define GEMMI_FOURIER_HPP_ + +#include +#include // for std::conj +#include "recgrid.hpp" // for ReciprocalGrid +#include "math.hpp" // for rad +#include "symmetry.hpp" // for GroupOps, Op +#include "fail.hpp" // for fail + +#ifdef __MINGW32__ // MinGW may have problem with std::mutex etc +# define POCKETFFT_CACHE_SIZE 0 +#endif +#define POCKETFFT_NO_MULTITHREADING +#include "third_party/pocketfft_hdronly.h" + +namespace gemmi { + +// Returns angle in [0, 360). When it's negative but approximately zero, +// printing 0.00 than 360.00 might be better, hence the second arg. +// The default value is for similar precision as float around 360: +// std::nextafter(360.f, 361.f) - 360.f -> 3.052e-5 +template +double phase_in_angles(const std::complex& v, double eps=2e-5) { + double angle = gemmi::deg(std::arg(v)); + if (angle < -eps) + angle += 360.; + return std::max(0., angle); // the order matters when angle is -0.0 +} + +// the first arg is usually Mtz::data +inline void add_asu_f_phi_to_float_vector(std::vector& float_data, + const AsuData>& asu_data) { + float_data.reserve(float_data.size() + asu_data.v.size() * 5); + for (const auto& item : asu_data.v) { + for (int i = 0; i != 3; ++i) + float_data.push_back((float) item.hkl[i]); + float_data.push_back(std::abs(item.value)); + float_data.push_back((float) gemmi::phase_in_angles(item.value)); + } +} + +template +std::array get_size_for_hkl(const DataProxy& data, + std::array min_size, + double sample_rate) { + // adjust min_size by checking Miller indices in the data + for (size_t i = 0; i < data.size(); i += data.stride()) { + Miller hkl = data.get_hkl(i); + for (int j = 0; j != 3; ++j) { + int v = 2 * std::abs(hkl[j]) + 1; + if (v > min_size[j]) + min_size[j] = v; + } + } + std::array dsize{{(double)min_size[0], + (double)min_size[1], + (double)min_size[2]}}; + if (sample_rate > 0) { + const UnitCell& cell = data.unit_cell(); + double max_1_d2 = 0; + for (size_t i = 0; i < data.size(); i += data.stride()) + max_1_d2 = std::max(max_1_d2, cell.calculate_1_d2(data.get_hkl(i))); + double inv_d_min = std::sqrt(max_1_d2); + std::array cellr = {{cell.ar, cell.br, cell.cr}}; + for (int i = 0; i < 3; ++i) + dsize[i] = std::max(dsize[i], sample_rate * inv_d_min / cellr[i]); + } + return good_grid_size(dsize, GridSizeRounding::Up, data.spacegroup()); +} + +template +bool data_fits_into(const DataProxy& data, std::array size) { + for (size_t i = 0; i < data.size(); i += data.stride()) { + Miller hkl = data.get_hkl(i); + for (int j = 0; j != 3; ++j) + if (2 * std::abs(hkl[j]) >= size[j]) + return false; + } + return true; +} + +template +void add_friedel_mates(ReciprocalGrid& grid) { + const T default_val = T(); // initialized to 0 or 0+0i + if (grid.axis_order == AxisOrder::XYZ) { + for (int w = 0; w != (grid.half_l ? 1 : grid.nw); ++w) { + int w_ = w == 0 ? 0 : grid.nw - w; + for (int v = 0; v != grid.nv; ++v) { + int v_ = v == 0 ? 0 : grid.nv - v; + for (int u = 0; u != grid.nu; ++u) { + size_t idx = grid.index_q(u, v, w); + if (grid.data[idx] == default_val) { + int u_ = u == 0 ? 0 : grid.nu - u; + size_t inv_idx = grid.index_q(u_, v_, w_); + grid.data[idx] = friedel_mate_value(grid.data[inv_idx]); + } + } + } + } + } else { // grid.axis_order == AxisOrder::ZYX + for (int w = 0; w != grid.nw; ++w) { + int w_ = w == 0 ? 0 : grid.nw - w; + for (int v = 0; v != grid.nv; ++v) { + int v_ = v == 0 ? 0 : grid.nv - v; + if (grid.half_l) { + size_t idx = grid.index_q(0, v, w); + if (grid.data[idx] == default_val) { + size_t inv_idx = grid.index_q(0, v_, w_); + grid.data[idx] = friedel_mate_value(grid.data[inv_idx]); + } + } else { + for (int u = 0; u != grid.nu; ++u) { + size_t idx = grid.index_q(u, v, w); + if (grid.data[idx] == default_val) { + int u_ = u == 0 ? 0 : grid.nu - u; + size_t inv_idx = grid.index_q(u_, v_, w_); + grid.data[idx] = friedel_mate_value(grid.data[inv_idx]); + } + } + } + } + } + } +} + +template +void initialize_hkl_grid(ReciprocalGrid& grid, const DataProxy& data, + std::array size, bool half_l, + AxisOrder axis_order) { + if (data.size() == 0) + fail("No data."); + if (!data.spacegroup()) + fail("No spacegroup."); + check_grid_factors(data.spacegroup(), size); + grid.unit_cell = data.unit_cell(); + grid.half_l = half_l; + grid.axis_order = axis_order; + grid.spacegroup = data.spacegroup(); + if (half_l) + size[2] = size[2] / 2 + 1; + if (axis_order == AxisOrder::ZYX) + std::swap(size[0], size[2]); + grid.set_size_without_checking(size[0], size[1], size[2]); +} + +template +struct FPhiProxy : DataProxy { + FPhiProxy(const DataProxy& data_proxy, size_t f_col, size_t phi_col) + : DataProxy(data_proxy), f_col_(f_col), phi_col_(phi_col) { + if (f_col >= data_proxy.stride() || (phi_col >= data_proxy.stride() && phi_col != size_t(-1))) + fail("Map coefficients not found."); + } + using real = typename DataProxy::num_type; + real get_f(size_t offset) const { return this->get_num(offset + f_col_); } + double get_phi(size_t offset) const { + return phi_col_ != (size_t)-1 ? rad(this->get_num(offset + phi_col_)) : 0; + } +private: + size_t f_col_, phi_col_; +}; + +// If half_l is true, grid has only data with l>=0. +// Parameter size can be obtained from get_size_for_hkl(). +template +FPhiGrid get_f_phi_on_grid(const FPhi& fphi, + std::array size, bool half_l, + AxisOrder axis_order=AxisOrder::XYZ) { + FPhiGrid grid; + initialize_hkl_grid(grid, fphi, size, half_l, axis_order); + const std::complex default_val; // initialized to 0+0i + GroupOps ops = grid.spacegroup->operations(); + for (size_t i = 0; i < fphi.size(); i += fphi.stride()) { + Miller hkl = fphi.get_hkl(i); + T f = (T) fphi.get_f(i); + if (f == 0.f) // is there enough of F=0 to justify this 'if'? + continue; + double phi = fphi.get_phi(i); + for (const Op& op : ops.sym_ops) { + auto hklp = op.apply_to_hkl(hkl); + int lp = hklp[2]; + if (axis_order == AxisOrder::ZYX) + std::swap(hklp[0], hklp[2]); + if (!grid.has_index(hklp[0], hklp[1], hklp[2])) + continue; + int sign = (!half_l || lp >= 0 ? 1 : -1); + size_t idx = grid.index_n(sign * hklp[0], sign * hklp[1], sign * hklp[2]); + if (grid.data[idx] == default_val) { + T theta = sign * T(phi + op.phase_shift(hkl)); + grid.data[idx] = std::complex(f * std::cos(theta), f * std::sin(theta)); + } + } + } + if (!ops.is_centrosymmetric()) + add_friedel_mates(grid); + return grid; +} + +template +ReciprocalGrid get_value_on_grid(const DataProxy& data, size_t column, + std::array size, bool half_l, + AxisOrder axis_order=AxisOrder::XYZ) { + ReciprocalGrid grid; + initialize_hkl_grid(grid, data, size, half_l, axis_order); + + if (column >= data.stride()) + fail("Map coefficients not found."); + GroupOps ops = grid.spacegroup->operations(); + for (size_t i = 0; i < data.size(); i += data.stride()) { + Miller hkl = data.get_hkl(i); + T val = (T) data.get_num(i + column); + if (val != 0.) { + for (const Op& op : ops.sym_ops) { + auto hklp = op.apply_to_hkl(hkl); + int lp = hklp[2]; + if (axis_order == AxisOrder::ZYX) + std::swap(hklp[0], hklp[2]); + if (!grid.has_index(hklp[0], hklp[1], hklp[2])) + continue; + int sign = (!half_l || lp >= 0 ? 1 : -1); + size_t idx = grid.index_n(sign * hklp[0], sign * hklp[1], sign * hklp[2]); + if (grid.data[idx] == 0.) // 0 is the default value + grid.data[idx] = val; + } + } + } + if (!ops.is_centrosymmetric()) + add_friedel_mates(grid); + return grid; +} + + +template +void transform_f_phi_grid_to_map_(FPhiGrid&& hkl, Grid& map) { + // NaNs are not good for FFT, so we change them to 0. + // x -> conj(x) is equivalent to changing axis direction before FFT. + for (std::complex& x : hkl.data) + if (std::isnan(x.imag())) + x = 0; + else + x.imag(-x.imag()); + map.spacegroup = hkl.spacegroup; + map.unit_cell = hkl.unit_cell; + map.axis_order = hkl.axis_order; + if (hkl.axis_order == AxisOrder::XYZ) { + int nw = hkl.half_l ? 2 * (hkl.nw - 1) : hkl.nw; + map.set_size(hkl.nu, hkl.nv, nw); + } else { // hkl.axis_order == AxisOrder::ZYX + int nu = hkl.half_l ? 2 * (hkl.nu - 1) : hkl.nu; + check_grid_factors(map.spacegroup, {{hkl.nw, hkl.nv, nu}}); + map.set_size_without_checking(nu, hkl.nv, hkl.nw); + } + // FIXME set_size_without_checking is changing axis_order - bad + map.axis_order = hkl.axis_order; + pocketfft::shape_t shape{(size_t)hkl.nw, (size_t)hkl.nv, (size_t)hkl.nu}; + std::ptrdiff_t s = sizeof(T); + pocketfft::stride_t stride{2*s * hkl.nv * hkl.nu, 2*s * hkl.nu, 2*s}; + pocketfft::shape_t axes{2, 1, 0}; + if (hkl.axis_order == AxisOrder::ZYX) + std::swap(axes[0], axes[2]); + T norm = T(1.0 / hkl.unit_cell.volume); + if (hkl.half_l) { + size_t last_axis = axes.back(); + axes.pop_back(); + pocketfft::c2c(shape, stride, stride, axes, pocketfft::BACKWARD, + &hkl.data[0], &hkl.data[0], norm); + pocketfft::stride_t stride_out{s * map.nu * map.nv, s * map.nu, s}; + shape[0] = (size_t) map.nw; + shape[2] = (size_t) map.nu; + pocketfft::c2r(shape, stride, stride_out, last_axis, pocketfft::BACKWARD, + &hkl.data[0], &map.data[0], 1.0f); + } else { + pocketfft::c2c(shape, stride, stride, axes, pocketfft::BACKWARD, + &hkl.data[0], &hkl.data[0], norm); + assert(map.data.size() == hkl.data.size()); + for (size_t i = 0; i != map.data.size(); ++i) + map.data[i] = hkl.data[i].real(); + } +} + +template +Grid transform_f_phi_grid_to_map(FPhiGrid&& hkl) { + Grid map; + transform_f_phi_grid_to_map_(std::forward>(hkl), map); + return map; +} + +template +Grid transform_f_phi_to_map(const FPhi& fphi, + std::array size, + double sample_rate, + bool exact_size=false, + AxisOrder order=AxisOrder::XYZ) { + if (exact_size) { + gemmi::check_grid_factors(fphi.spacegroup(), size); + } else { + size = get_size_for_hkl(fphi, size, sample_rate); + } + return transform_f_phi_grid_to_map(get_f_phi_on_grid(fphi, size, true, order)); +} + +template +Grid transform_f_phi_to_map2(const FPhi& fphi, + std::array min_size, + double sample_rate, + std::array exact_size, + AxisOrder order=AxisOrder::XYZ) { + bool exact = (exact_size[0] != 0 || exact_size[1] != 0 || exact_size[2] != 0); + return transform_f_phi_to_map(fphi, exact ? exact_size : min_size, + sample_rate, exact, order); +} + +template +FPhiGrid transform_map_to_f_phi(const Grid& map, bool half_l, bool use_scale=true) { + if (half_l && map.axis_order == AxisOrder::ZYX) + fail("transform_map_to_f_phi(): half_l + ZYX order are not supported yet"); + FPhiGrid hkl; + hkl.unit_cell = map.unit_cell; + hkl.spacegroup = map.spacegroup; + hkl.axis_order = map.axis_order; + hkl.half_l = half_l; + int half_nw = map.nw / 2 + 1; + hkl.set_size_without_checking(map.nu, map.nv, half_l ? half_nw : map.nw); + T norm = use_scale ? T(map.unit_cell.volume / map.point_count()) : 1; + pocketfft::shape_t shape{(size_t)map.nw, (size_t)map.nv, (size_t)map.nu}; + std::ptrdiff_t s = sizeof(T); + pocketfft::stride_t stride_in{s * hkl.nv * hkl.nu, s * hkl.nu, s}; + pocketfft::stride_t stride{2*s * hkl.nv * hkl.nu, 2*s * hkl.nu, 2*s}; + pocketfft::r2c(shape, stride_in, stride, /*axis=*/0, pocketfft::FORWARD, + &map.data[0], &hkl.data[0], norm); + shape[0] = half_nw; + pocketfft::c2c(shape, stride, stride, {1, 2}, pocketfft::FORWARD, + &hkl.data[0], &hkl.data[0], 1.0f); + if (!half_l) // add Friedel pairs + for (int w = half_nw; w != hkl.nw; ++w) { + int w_ = hkl.nw - w; + for (int v = 0; v != hkl.nv; ++v) { + int v_ = v == 0 ? 0 : hkl.nv - v; + for (int u = 0; u != hkl.nu; ++u) { + int u_ = u == 0 ? 0 : hkl.nu - u; + size_t idx = hkl.index_q(u, v, w); + size_t inv_idx = hkl.index_q(u_, v_, w_); + hkl.data[idx] = hkl.data[inv_idx]; // conj() is called later + } + } + } + for (int i = 0; i != hkl.nu * hkl.nv * half_nw; ++i) + hkl.data[i].imag(-hkl.data[i].imag()); + return hkl; +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/fprime.hpp b/gemmi_gph/gemmi/fprime.hpp new file mode 100644 index 00000000..da74ac98 --- /dev/null +++ b/gemmi_gph/gemmi/fprime.hpp @@ -0,0 +1,34 @@ +// Cromer-Liberman calculation of anomalous scattering factors, +// with corrections from Kissel & Pratt. + +// Read the header comment in src/fprime.cpp for the details. + +#ifndef GEMMI_FPRIME_HPP_ +#define GEMMI_FPRIME_HPP_ + +#include "fail.hpp" + +namespace gemmi { + +/// Cromer-Liberman calculation of anomalous scattering factors. +/// input: +/// z - atomic number +/// npts - array length +/// energy - energies in eV +/// output: +/// fp - f' (real part of anomalous scattering) +/// fpp - f" (imaginary part of anomalous scattering) +GEMMI_DLL void cromer_liberman_for_array(int z, int npts, const double* energy, + double* fp, double* fpp); + +/// returns fp, fpp is returned through the last argument. +inline double cromer_liberman(int z, double energy, double* fpp) { + double fp = 0., fpp_ = 0.; + cromer_liberman_for_array(z, 1, &energy, &fp, &fpp_); + if (fpp) + *fpp = fpp_; + return fp; +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/fstream.hpp b/gemmi_gph/gemmi/fstream.hpp new file mode 100644 index 00000000..a145aa9f --- /dev/null +++ b/gemmi_gph/gemmi/fstream.hpp @@ -0,0 +1,88 @@ +// Copyright 2019 Global Phasing Ltd. +// +// Ofstream and Ifstream: wrappers around std::ofstream and std::ifstream. +// +// They offer two extra features: +// - on MSVC supports Unicode filenames (the filename is passed in UTF-8), +// - optionally, filename "-" can be interpreted as stdout or stderr. + +#ifndef GEMMI_OFSTREAM_HPP_ +#define GEMMI_OFSTREAM_HPP_ + +#if defined(_MSC_VER) +# include "utf.hpp" +#elif defined(_WIN32) && defined(__has_include) +// including in MinGW 8 gives error (in bits/fs_path.h:237:47), +// which was fixed in GCC 9 +# if __has_include() && !(defined(__MINGW32__) && __GNUC__ < 9) +# include +# include "utf.hpp" +# endif +#endif +#include +#include +#include "fail.hpp" + +namespace gemmi { + +template +inline void open_stream_from_utf8_path(T& ptr, const std::string& filename) { +#if defined(_MSC_VER) + std::wstring wfilename = UTF8_to_wchar(filename.c_str()); + ptr->open(wfilename.c_str()); +#elif defined(_WIN32) && defined(__cpp_lib_filesystem) + std::wstring wfilename = UTF8_to_wchar(filename.c_str()); + ptr->open(std::filesystem::path(wfilename)); +#else + ptr->open(filename); +#endif +} + +// note: move of std::ofstream doesn't work in GCC 4.8. + +struct Ofstream { + Ofstream(const std::string& filename, std::ostream* dash=nullptr) { + if (filename.size() == 1 && filename[0] == '-' && dash) { + ptr_ = dash; + return; + } + keeper_.reset(new std::ofstream); + open_stream_from_utf8_path(keeper_, filename); + if (!*keeper_) + sys_fail("Failed to open " + filename + " for writing"); + ptr_ = keeper_.get(); + } + + std::ostream* operator->() { return ptr_; } + std::ostream& ref() { return *ptr_; } + +private: + std::unique_ptr keeper_; + std::ostream* ptr_; +}; + +struct Ifstream { + Ifstream(const std::string& filename, std::istream* dash=nullptr) { + if (filename.size() == 1 && filename[0] == '-' && dash) { + ptr_ = dash; + return; + } + keeper_.reset(new std::ifstream); + open_stream_from_utf8_path(keeper_, filename); + if (!*keeper_) + sys_fail("Failed to open " + filename); + ptr_ = keeper_.get(); + } + + std::istream* operator->() { return ptr_; } + std::istream& ref() { return *ptr_; } + +private: + std::unique_ptr keeper_; + std::istream* ptr_; +}; + + + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/glob.hpp b/gemmi_gph/gemmi/glob.hpp new file mode 100644 index 00000000..58d90aa3 --- /dev/null +++ b/gemmi_gph/gemmi/glob.hpp @@ -0,0 +1,43 @@ +// Copyright Global Phasing Ltd. +// +// Glob pattern matching + +#ifndef GEMMI_GLOB_HPP_ +#define GEMMI_GLOB_HPP_ + +#include + +namespace gemmi { + +// linear-time glob matching: https://research.swtch.com/glob +inline bool glob_match(const std::string& pattern, const std::string& str) { + size_t pat_next = 0; + size_t str_next = std::string::npos; + size_t pat_pos = 0; + size_t str_pos = 0; + while (pat_pos < pattern.size() || str_pos < str.size()) { + if (pat_pos < pattern.size()) { + char c = pattern[pat_pos]; + if (c == '*') { + pat_next = pat_pos; + str_next = str_pos + 1; + pat_pos++; + continue; + } + if (str_pos < str.size() && (c == '?' || c == str[str_pos])) { + pat_pos++; + str_pos++; + continue; + } + } + if (str_next > str.size()) + return false; + pat_pos = pat_next; + str_pos = str_next; + } + return true; +} + +} // namespace gemmi + +#endif diff --git a/gemmi_gph/gemmi/grid.hpp b/gemmi_gph/gemmi/grid.hpp new file mode 100644 index 00000000..38fe3d73 --- /dev/null +++ b/gemmi_gph/gemmi/grid.hpp @@ -0,0 +1,819 @@ +// Copyright 2017 Global Phasing Ltd. +// +// 3d grids used by CCP4 maps, cell-method search and hkl data. + +#ifndef GEMMI_GRID_HPP_ +#define GEMMI_GRID_HPP_ + +#include +#include // for ptrdiff_t +#include +#include // for fill +#include // for accumulate +#include +#include +#include "unitcell.hpp" +#include "symmetry.hpp" +#include "stats.hpp" // for DataStats +#include "fail.hpp" // for fail + +namespace gemmi { + +/// Order of grid axis. Some Grid functionality works only with the XYZ order. +/// The values XYZ and ZYX are used only when the grid covers whole unit cell. +enum class AxisOrder : unsigned char { + Unknown, + XYZ, // default, corresponds to CCP4 map with axis order XYZ, + // i.e. index X (H in reciprocal space) is fast and Z (or L) is slow + ZYX // fast Z (or L), may not be fully supported everywhere +}; + +enum class GridSizeRounding { + Nearest, + Up, + Down +}; + +inline int modulo(int a, int n) { + if (a >= n) + a %= n; + else if (a < 0) + a = (a + 1) % n + n - 1; + return a; +} + +inline bool has_small_factorization(int n) { + while (n % 2 == 0) + n /= 2; + for (int k : {3, 5}) + while (n % k == 0) + n /= k; + return n == 1 || n == -1; +} + +inline int round_with_small_factorization(double exact, GridSizeRounding rounding) { + int n; + if (rounding == GridSizeRounding::Up) { + n = int(std::ceil(exact)); + while (!has_small_factorization(n)) + ++n; + } else if (rounding == GridSizeRounding::Down) { + n = std::max(int(std::floor(exact)), 1); + while (!has_small_factorization(n)) + --n; + } else { // GridSizeRounding::Nearest + n = int(std::round(exact)); + int sign = n > exact ? -1 : 1; + int k = 1; + while (n <= 0 || !has_small_factorization(n)) { + // for sign=1 we want sequence n+1, n-1, n+2, n-2, n+3, ... + n += sign * k; + sign = -sign; + ++k; + } + } + return n; +} + +inline std::array good_grid_size(std::array limit, + GridSizeRounding rounding, + const SpaceGroup* sg) { + std::array m = {{0, 0, 0}}; + GroupOps gops; + if (sg) + gops = sg->operations(); + std::array sg_fac = gops.find_grid_factors(); + + // If two dimensions are symmetry-related, they must have the same size. + // Otherwise, if two dimensions are almost equal, the same grid size + // looks better and is preferred. + for (int i = 1; i < 3; ++i) + for (int j = 0; j < i; ++j) + if (gops.are_directions_symmetry_related(i, j) || + (std::fabs(limit[i] - limit[j]) < 0.5 && sg_fac[i] == sg_fac[j])) { + if (rounding == GridSizeRounding::Up) + limit[j] = std::max(limit[i], limit[j]); + else if (rounding == GridSizeRounding::Down) + limit[j] = std::min(limit[i], limit[j]); + else // GridSizeRounding::Nearest + limit[j] = 0.5 * (limit[i] + limit[j]); + sg_fac[i] = -j; // mark the dimension with higher index + } + + for (int i = 0; i < 3; ++i) { + int f = sg_fac[i]; + if (f > 0) { // need to calculate the size + if (f % 2 != 0) + f *= 2; // always use even sizes - it simplifies things + m[i] = f * round_with_small_factorization(limit[i] / f, rounding); + } else { // the same size was already calculated + m[i] = m[-f]; + } + } + return m; +} + +struct GridOp { + Op scaled_op; + + std::array apply(int u, int v, int w) const { + std::array t; + const Op::Rot& rot = scaled_op.rot; + for (int i = 0; i != 3; ++i) + t[i] = rot[i][0] * u + rot[i][1] * v + rot[i][2] * w + scaled_op.tran[i]; + return t; + } +}; + +inline void check_grid_factors(const SpaceGroup* sg, std::array size) { + if (sg) { + GroupOps gops = sg->operations(); + auto factors = gops.find_grid_factors(); + for (int i = 0; i != 3; ++i) + if (size[i] % factors[i] != 0) + fail("Grid not compatible with the space group " + sg->xhm()); + for (int i = 1; i != 3; ++i) + for (int j = 0; j != i; ++j) + if (gops.are_directions_symmetry_related(i, j) && size[i] != size[j]) + fail("Grid must have the same size in symmetry-related directions"); + } +} + +inline double lerp_(double a, double b, double t) { + return a + (b - a) * t; +} +template +std::complex lerp_(std::complex a, std::complex b, double t) { + return a + (b - a) * (T) t; +} + +/// Catmull–Rom spline interpolation. CINT_u from: +/// https://en.wikipedia.org/wiki/Cubic_Hermite_spline +/// The same as (24) in https://journals.iucr.org/d/issues/2018/06/00/ic5103/ +inline double cubic_interpolation(double u, double a, double b, double c, double d) { + //return 0.5 * u * (u * (u * (3*b - 3*c + d - a) + (2*a - 5*b + 4*c - d)) + (c - a)) + b; + // equivalent form that is faster on my computer: + return -0.5 * (a * u * ((u-2)*u + 1) - b * ((3*u - 5) * u*u + 2) + + u * (c * ((3*u - 4) * u - 1) - d * (u-1) * u)); +} + +/// df/du (from Wolfram Alpha) +inline double cubic_interpolation_der(double u, double a, double b, double c, double d) { + return a * (-1.5*u*u + 2*u - 0.5) + c * (-4.5*u*u + 4*u + 0.5) + + u * (4.5*b*u - 5*b + 1.5*d*u - d); +} + + +/// The base of Grid classes that does not depend on stored data type. +struct GridMeta { + UnitCell unit_cell; + const SpaceGroup* spacegroup = nullptr; + int nu = 0, nv = 0, nw = 0; + AxisOrder axis_order = AxisOrder::Unknown; + + size_t point_count() const { return (size_t)nu * nv * nw; } + /// u,v,w are not normalized here + Fractional get_fractional(int u, int v, int w) const { + return {u * (1.0 / nu), v * (1.0 / nv), w * (1.0 / nw)}; + } + Position get_position(int u, int v, int w) const { + return unit_cell.orthogonalize(get_fractional(u, v, w)); + } + + // operations re-scaled for faster later calculations; identity not included + std::vector get_scaled_ops_except_id() const { + std::vector grid_ops; + if (!spacegroup || spacegroup->number == 1) + return grid_ops; + if (axis_order != AxisOrder::XYZ) + fail("grid can use symmetries only if it is setup in the XYZ order"); + GroupOps gops = spacegroup->operations(); + grid_ops.reserve(gops.order()); + for (const Op& so : gops.sym_ops) + for (const Op::Tran& co : gops.cen_ops) { + Op op = so.add_centering(co); + if (op != Op::identity()) { + // Rescale. Rotations are expected to be integral. + op.tran[0] = op.tran[0] * nu / Op::DEN; + op.tran[1] = op.tran[1] * nv / Op::DEN; + op.tran[2] = op.tran[2] * nw / Op::DEN; + for (int i = 0; i != 3; ++i) + for (int j = 0; j != 3; ++j) + op.rot[i][j] /= Op::DEN; + grid_ops.push_back({op}); + } + } + return grid_ops; + } + + /// Quick(est) index function, but works only if `0 <= u < nu`, etc. + size_t index_q(int u, int v, int w) const { + return size_t(w * nv + v) * nu + u; + } + size_t index_q(size_t u, size_t v, size_t w) const { + return (w * nv + v) * nu + u; + } + + /// Faster than index_s(), but works only if `-nu <= u < 2*nu`, etc. + size_t index_n(int u, int v, int w) const { return index_n_ref(u, v, w); } + + /// The same as index_n(), but modifies arguments. + size_t index_n_ref(int& u, int& v, int& w) const { + if (u >= nu) u -= nu; else if (u < 0) u += nu; + if (v >= nv) v -= nv; else if (v < 0) v += nv; + if (w >= nw) w -= nw; else if (w < 0) w += nw; + return this->index_q(u, v, w); + } + + /// Faster than index_n(), but works only if -nu <= u < nu, etc. + size_t index_near_zero(int u, int v, int w) const { + return this->index_q(u >= 0 ? u : u + nu, + v >= 0 ? v : v + nv, + w >= 0 ? w : w + nw); + } +}; + +/// A common subset of Grid and ReciprocalGrid. +template +struct GridBase : GridMeta { + /// grid coordinates (modulo size) and a pointer to value + struct Point { + int u, v, w; + T* value; + }; + + std::vector data; + + void check_not_empty() const { + if (data.empty()) + fail("grid is empty"); + } + + void set_size_without_checking(int nu_, int nv_, int nw_) { + nu = nu_, nv = nv_, nw = nw_; + data.resize((size_t)nu_ * nv_ * nw_); + } + + T get_value_q(int u, int v, int w) const { return data[index_q(u, v, w)]; } + + size_t point_to_index(const Point& p) const { return p.value - data.data(); } + + Point index_to_point(size_t idx) { + auto d1 = std::div((ptrdiff_t)idx, (ptrdiff_t)nu); + auto d2 = std::div(d1.quot, (ptrdiff_t)nv); + int u = (int) d1.rem; + int v = (int) d2.rem; + int w = (int) d2.quot; + assert(index_q(u, v, w) == idx); + return {u, v, w, &data.at(idx)}; + } + + void fill(T value) { + data.resize(point_count()); + std::fill(data.begin(), data.end(), value); + } + + using Tsum = typename std::conditional::value, + std::ptrdiff_t, T>::type; + Tsum sum() const { return std::accumulate(data.begin(), data.end(), Tsum()); } + + + struct iterator { + GridBase& parent; + size_t index; + int u = 0, v = 0, w = 0; + iterator(GridBase& parent_, size_t index_) + : parent(parent_), index(index_) {} + iterator& operator++() { + ++index; + if (++u == parent.nu) { + u = 0; + if (++v == parent.nv) { + v = 0; + ++w; + } + } + return *this; + } + typename GridBase::Point operator*() { + return {u, v, w, &parent.data[index]}; + } + bool operator==(const iterator &o) const { return index == o.index; } + bool operator!=(const iterator &o) const { return index != o.index; } + }; + iterator begin() { return {*this, 0}; } + iterator end() { return {*this, data.size()}; } +}; + +/// Real-space grid. +/// For simplicity, some operations work only if the grid covers whole unit cell +/// and axes u,v,w correspond to a,b,c in the unit cell. +template +struct Grid : GridBase { + using Point = typename GridBase::Point; + using GridBase::nu; + using GridBase::nv; + using GridBase::nw; + using GridBase::unit_cell; + using GridBase::spacegroup; + using GridBase::data; + + /// spacing between virtual planes, not between points + double spacing[3] = {0., 0., 0.}; + /// unit_cell.orth.mat columns divided by nu, nv, nw + UpperTriangularMat33 orth_n; + + /// copy unit_cell, spacegroup, nu, nv, nw, axis_order and set spacing + void copy_metadata_from(const GridMeta& g) { + unit_cell = g.unit_cell; + spacegroup = g.spacegroup; + nu = g.nu; + nv = g.nv; + nw = g.nw; + this->axis_order = g.axis_order; + calculate_spacing(); + } + + /// set #spacing and #orth_n + void calculate_spacing() { + spacing[0] = 1.0 / (nu * unit_cell.ar); + spacing[1] = 1.0 / (nv * unit_cell.br); + spacing[2] = 1.0 / (nw * unit_cell.cr); + Vec3 inv_n(1.0 / nu, 1.0 / nv, 1.0 / nw); + orth_n = unit_cell.orth.mat.multiply_by_diagonal(inv_n); + if (!unit_cell.orth.mat.is_upper_triangular()) + fail("Grids work only with the standard orientation of crystal frame (SCALEn)"); + } + + void set_size_without_checking(int nu_, int nv_, int nw_) { + GridBase::set_size_without_checking(nu_, nv_, nw_); + calculate_spacing(); + this->axis_order = AxisOrder::XYZ; + } + + void set_size(int nu_, int nv_, int nw_) { + check_grid_factors(spacegroup, {{nu_, nv_, nw_}}); + set_size_without_checking(nu_, nv_, nw_); + } + + void set_size_from_spacing(double approx_spacing, GridSizeRounding rounding) { + std::array limit = {{unit_cell.a / approx_spacing, + unit_cell.b / approx_spacing, + unit_cell.c / approx_spacing}}; + auto m = good_grid_size(limit, rounding, spacegroup); + set_size_without_checking(m[0], m[1], m[2]); + } + + void set_unit_cell(double a, double b, double c, + double alpha, double beta, double gamma) { + unit_cell.set(a, b, c, alpha, beta, gamma); + calculate_spacing(); + } + + void set_unit_cell(const UnitCell& cell) { + unit_cell = cell; + calculate_spacing(); + } + + template + void setup_from(const S& st, double approx_spacing=0) { + spacegroup = st.find_spacegroup(); + unit_cell = st.cell; + if (approx_spacing > 0) + set_size_from_spacing(approx_spacing, GridSizeRounding::Up); + } + + /// Returns index in data array for (u,v,w). Safe but slower than index_q(). + size_t index_s(int u, int v, int w) const { + this->check_not_empty(); + return this->index_q(modulo(u, nu), modulo(v, nv), modulo(w, nw)); + } + + /// returns `data[index_s(u, v, w)]` + T get_value(int u, int v, int w) const { + return data[index_s(u, v, w)]; + } + + void set_value(int u, int v, int w, T x) { + data[index_s(u, v, w)] = x; + } + + /// Point stores normalizes indices (not the original u,v,w). + Point get_point(int u, int v, int w) { + u = modulo(u, nu); + v = modulo(v, nv); + w = modulo(w, nw); + return {u, v, w, &data[this->index_q(u, v, w)]}; + } + + Point get_nearest_point(const Fractional& f) { + if (this->axis_order != AxisOrder::XYZ) + fail("grid is not fully setup"); + return get_point(iround(f.x * nu), iround(f.y * nv), iround(f.z * nw)); + } + Point get_nearest_point(const Position& pos) { + return get_nearest_point(unit_cell.fractionalize(pos)); + } + + size_t get_nearest_index(const Fractional& f) { + return index_s(iround(f.x * nu), iround(f.y * nv), iround(f.z * nw)); + } + + /// Point stores normalized indices, so fractional coordinates are in [0,1). + Fractional point_to_fractional(const Point& p) const { + return this->get_fractional(p.u, p.v, p.w); + } + Position point_to_position(const Point& p) const { + return this->get_position(p.u, p.v, p.w); + } + + static double grid_modulo(double x, int n, int* iptr) { + double f = std::floor(x); + *iptr = modulo((int)f, n); + return x - f; + } + + /// https://en.wikipedia.org/wiki/Trilinear_interpolation + /// x,y,z are grid coordinates (x=1.5 is between 2nd and 3rd grid point). + T trilinear_interpolation(double x, double y, double z) const { + this->check_not_empty(); + int u, v, w; + double xd = grid_modulo(x, nu, &u); + double yd = grid_modulo(y, nv, &v); + double zd = grid_modulo(z, nw, &w); + assert(u >= 0 && v >= 0 && w >= 0); + assert(u < nu && v < nv && w < nw); + T avg[2]; + for (int i = 0; i < 2; ++i) { + int wi = (i == 0 || w + 1 != nw ? w + i : 0); + size_t idx1 = this->index_q(u, v, wi); + int v2 = v + 1 != nv ? v + 1 : 0; + size_t idx2 = this->index_q(u, v2, wi); + int u_add = u + 1 != nu ? 1 : -u; + avg[i] = (T) lerp_(lerp_(data[idx1], data[idx1 + u_add], xd), + lerp_(data[idx2], data[idx2 + u_add], xd), + yd); + } + return (T) lerp_(avg[0], avg[1], zd); + } + T trilinear_interpolation(const Fractional& fctr) const { + return trilinear_interpolation(fctr.x * nu, fctr.y * nv, fctr.z * nw); + } + T trilinear_interpolation(const Position& ctr) const { + return trilinear_interpolation(unit_cell.fractionalize(ctr)); + } + + /// https://en.wikipedia.org/wiki/Tricubic_interpolation + /// x,y,z are grid coordinates (x=1.5 is between 2nd and 3rd grid point). + double tricubic_interpolation(double x, double y, double z) const { + std::array,4>,4> copy; + copy_4x4x4(x, y, z, copy); + auto s = [©](int i, int j, int k) { return copy[i][j][k]; }; + double a[4], b[4]; + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < 4; ++j) + a[j] = cubic_interpolation(z, s(i,j,0), s(i,j,1), s(i,j,2), s(i,j,3)); + b[i] = cubic_interpolation(y, a[0], a[1], a[2], a[3]); + } + return cubic_interpolation(x, b[0], b[1], b[2], b[3]); + } + double tricubic_interpolation(const Fractional& fctr) const { + return tricubic_interpolation(fctr.x * nu, fctr.y * nv, fctr.z * nw); + } + double tricubic_interpolation(const Position& ctr) const { + return tricubic_interpolation(unit_cell.fractionalize(ctr)); + } + /// returns the same as above + derivatives df/dx, df/dy, df/dz + std::array tricubic_interpolation_der(double x, double y, double z) const { + std::array,4>,4> copy; + copy_4x4x4(x, y, z, copy); + auto s = [©](int i, int j, int k) { return copy[i][j][k]; }; + double a[4][4]; + double b[4]; + for (int i = 0; i < 4; ++i) + for (int j = 0; j < 4; ++j) + a[i][j] = cubic_interpolation(z, s(i,j,0), s(i,j,1), s(i,j,2), s(i,j,3)); + for (int i = 0; i < 4; ++i) + b[i] = cubic_interpolation(y, a[i][0], a[i][1], a[i][2], a[i][3]); + std::array ret; + ret[0] = cubic_interpolation(x, b[0], b[1], b[2], b[3]); + ret[1] = cubic_interpolation_der(x, b[0], b[1], b[2], b[3]); + for (int i = 0; i < 4; ++i) + b[i] = cubic_interpolation(x, a[0][i], a[1][i], a[2][i], a[3][i]); + ret[2] = cubic_interpolation_der(y, b[0], b[1], b[2], b[3]); + for (int i = 0; i < 4; ++i) + for (int j = 0; j < 4; ++j) + a[i][j] = cubic_interpolation(y, s(i,0,j), s(i,1,j), s(i,2,j), s(i,3,j)); + for (int i = 0; i < 4; ++i) + b[i] = cubic_interpolation(x, a[0][i], a[1][i], a[2][i], a[3][i]); + ret[3] = cubic_interpolation_der(z, b[0], b[1], b[2], b[3]); + return ret; + } + std::array tricubic_interpolation_der(const Fractional& fctr) const { + auto r = tricubic_interpolation_der(fctr.x * nu, fctr.y * nv, fctr.z * nw); + return {r[0], r[1] * nu, r[2] * nv, r[3] * nw}; + } + /// @private + void copy_4x4x4(double& x, double& y, double& z, + std::array,4>,4>& copy) const { + this->check_not_empty(); + auto prepare_indices = [](double& r, int nt, int (&indices)[4]) { + int t; + r = grid_modulo(r, nt, &t); + indices[0] = (t != 0 ? t : nt) - 1; + indices[1] = t; + if (t + 2 < nt) { + indices[2] = t + 1; + indices[3] = t + 2; + } else { + indices[2] = t + 2 == nt ? t + 1 : 0; + indices[3] = t + 2 == nt ? 0 : 1; + } + }; + int u_indices[4], v_indices[4], w_indices[4]; + prepare_indices(x, nu, u_indices); + prepare_indices(y, nv, v_indices); + prepare_indices(z, nw, w_indices); + for (int i = 0; i < 4; ++i) + for (int j = 0; j < 4; ++j) + for (int k = 0; k < 4; ++k) + copy[i][j][k] = this->get_value_q(u_indices[i], v_indices[j], w_indices[k]); + } + + /// @param order 0=nearest, 1=linear, 3=cubic interpolation + T interpolate_value(const Fractional& f, int order=1) const { + switch (order) { + case 0: return *const_cast*>(this)->get_nearest_point(f).value; + case 1: return trilinear_interpolation(f); + case 3: return (T) tricubic_interpolation(f); + } + throw std::invalid_argument("interpolation \"order\" must 0, 1 or 3"); + } + T interpolate_value(const Position& ctr, int order=1) const { + return interpolate_value(unit_cell.fractionalize(ctr), order); + } + + void get_subarray(T* dest, std::array start, std::array shape) const { + this->check_not_empty(); + if (this->axis_order != AxisOrder::XYZ) + fail("get_subarray() is for Grids in XYZ order"); + const int u_start0 = modulo(start[0], nu); + for (int w = 0; w < shape[2]; w++) { + const int w0 = modulo(start[2] + w, nw); + for (int v = 0; v < shape[1]; v++) { + const int v0 = modulo(start[1] + v, nv); + int u_start = u_start0; + const T* src0 = &data[this->index_q(u_start, v0, w0)]; + int len = shape[0]; + while (len > nu - u_start) { + int elem = nu - u_start; + std::copy(src0, src0 + elem, dest); + src0 -= u_start; + dest += elem; + len -= elem; + u_start = 0; + } + std::copy(src0, src0 + len, dest); + dest += len; + } + } + } + + void set_subarray(const T* src, std::array start, std::array shape) { + this->check_not_empty(); + if (this->axis_order != AxisOrder::XYZ) + fail("set_subarray() is for Grids in XYZ order"); + const int u_start0 = modulo(start[0], nu); + for (int w = 0; w < shape[2]; w++) { + const int w0 = modulo(start[2] + w, nw); + for (int v = 0; v < shape[1]; v++) { + const int v0 = modulo(start[1] + v, nv); + int u_start = u_start0; + T* dst0 = &data[this->index_q(u_start, v0, w0)]; + int len = shape[0]; + while (len > nu - u_start) { + int elem = nu - u_start; + std::copy(src, src + elem, dst0); + dst0 -= u_start; + src += elem; + len -= elem; + u_start = 0; + } + std::copy(src, src + len, dst0); + src += len; + } + } + } + + template + void check_size_for_points_in_box(int& du, int& dv, int& dw, + bool fail_on_too_large_radius) const { + if (UsePbc) { + if (fail_on_too_large_radius) { + if (2 * du >= nu || 2 * dv >= nv || 2 * dw >= nw) + fail("grid operation failed: radius bigger than half the unit cell?"); + } else { + // If we'd use the minimum image convention the max would be (nu-1)/2. + // The limits set here are necessary for index_n() that is used below. + du = std::min(du, nu - 1); + dv = std::min(dv, nv - 1); + dw = std::min(dw, nw - 1); + } + } + } + + template + void do_use_points_in_box(const Fractional& fctr, int du, int dv, int dw, Func&& func, + double radius=INFINITY) { + double max_dist_sq = radius * radius; + const Fractional nctr(fctr.x * nu, fctr.y * nv, fctr.z * nw); + int u0 = iround(nctr.x); + int v0 = iround(nctr.y); + int w0 = iround(nctr.z); + int u_lo = u0 - du; + int u_hi = u0 + du; + int v_lo = v0 - dv; + int v_hi = v0 + dv; + int w_lo = w0 - dw; + int w_hi = w0 + dw; + if (!UsePbc) { + u_lo = std::max(u_lo, 0); + u_hi = std::min(u_hi, nu - 1); + v_lo = std::max(v_lo, 0); + v_hi = std::min(v_hi, nv - 1); + w_lo = std::max(w_lo, 0); + w_hi = std::min(w_hi, nw - 1); + } + int u_0 = UsePbc ? modulo(u_lo, nu) : u_lo; + int v_0 = UsePbc ? modulo(v_lo, nv) : v_lo; + int w_0 = UsePbc ? modulo(w_lo, nw) : w_lo; + auto wrap = [](int& q, int nq) { if (UsePbc && q == nq) q = 0; }; + Fractional fdelta(nctr.x - u_lo, 0, 0); + for (int w = w_lo, w_ = w_0; w <= w_hi; ++w, wrap(++w_, nw)) { + fdelta.z = nctr.z - w; + for (int v = v_lo, v_ = v_0; v <= v_hi; ++v, wrap(++v_, nv)) { + fdelta.y = nctr.y - v; + Position delta(orth_n.multiply(fdelta)); + T* t = &data[this->index_q(u_0, v_, w_)]; + double dist_sq0 = sq(delta.y) + sq(delta.z); + if (dist_sq0 > max_dist_sq) + continue; + for (int u = u_lo, u_ = u_0;;) { + double dist_sq = dist_sq0 + sq(delta.x); + if (!(dist_sq > max_dist_sq)) + func(*t, dist_sq, delta, u, v, w); + if (u >= u_hi) + break; + ++u; + ++u_; + ++t; + if (UsePbc && u_ == nu) { + u_ = 0; + t -= nu; + } + delta.x -= orth_n.a11; + } + } + } + } + + template + void use_points_in_box(const Fractional& fctr, int du, int dv, int dw, + Func&& func, bool fail_on_too_large_radius=true, + double radius=INFINITY) { + check_size_for_points_in_box(du, dv, dw, fail_on_too_large_radius); + do_use_points_in_box(fctr, du, dv, dw, func, radius); + } + + template + void use_points_around(const Fractional& fctr, double radius, Func&& func, + bool fail_on_too_large_radius=true) { + int du = (int) std::ceil(radius / spacing[0]); + int dv = (int) std::ceil(radius / spacing[1]); + int dw = (int) std::ceil(radius / spacing[2]); + use_points_in_box( + fctr, du, dv, dw, + [&](T& ref, double d2, const Position&, int, int, int) { func(ref, d2); }, + fail_on_too_large_radius, + radius); + } + + void set_points_around(const Position& ctr, double radius, T value, bool use_pbc=true) { + Fractional fctr = unit_cell.fractionalize(ctr); + if (use_pbc) + use_points_around(fctr, radius, [&](T& ref, double) { ref = value; }, false); + else + use_points_around(fctr, radius, [&](T& ref, double) { ref = value; }, false); + } + + /// Change all occurrences of old_value to new_value + void change_values(T old_value, T new_value) { + for (auto& d : data) + if (impl::is_same(d, old_value)) + d = new_value; + } + + /// Use \par func to reduce values of all symmetry mates of each + /// grid point, then assign the result to all the points. + /// \par func takes two values and returns a value. + template + void symmetrize(Func func) { + symmetrize_using_ops(this->get_scaled_ops_except_id(), func); + } + + template + void symmetrize_using_ops(const std::vector& ops, Func func) { + if (ops.empty()) + return; + std::vector mates(ops.size(), 0); + std::vector visited(data.size(), 0); // faster than vector + size_t idx = 0; + for (int w = 0; w != nw; ++w) + for (int v = 0; v != nv; ++v) + for (int u = 0; u != nu; ++u, ++idx) { + assert(idx == this->index_q(u, v, w)); + if (visited[idx]) + continue; + for (size_t k = 0; k < ops.size(); ++k) { + std::array t = ops[k].apply(u, v, w); + mates[k] = this->index_n(t[0], t[1], t[2]); + } + T value = data[idx]; + for (size_t k : mates) { + if (visited[k]) + fail("grid size is not compatible with space group"); + value = func(value, data[k]); + } + data[idx] = value; + visited[idx] = 1; + for (size_t k : mates) { + data[k] = value; + visited[k] = 1; + } + } + assert(idx == data.size()); + } + + // most common symmetrize functions + void symmetrize_min() { + symmetrize([](T a, T b) { return (a < b || !(b == b)) ? a : b; }); + } + void symmetrize_max() { + symmetrize([](T a, T b) { return (a > b || !(b == b)) ? a : b; }); + } + void symmetrize_abs_max() { + symmetrize([](T a, T b) { return (std::abs(a) > std::abs(b) || !(b == b)) ? a : b; }); + } + /// multiplies grid points on special position + void symmetrize_sum() { + symmetrize([](T a, T b) { return a + b; }); + } + void symmetrize_nondefault(T default_) { + symmetrize([default_](T a, T b) { return impl::is_same(a, default_) ? b : a; }); + } + void symmetrize_avg() { + symmetrize_sum(); + if (spacegroup && spacegroup->number != 1) { + int n_ops = spacegroup->operations().order(); + for (T& x : data) + x /= n_ops; + } + } + + /// scale the data to get mean == 0 and rmsd == 1 (doesn't work for T=complex) + void normalize() { + DataStats stats = calculate_data_statistics(data); + for (T& x : data) + x = static_cast((x - stats.dmean) / stats.rms); + } +}; + +// TODO: add argument Box src_extent +// cf. interpolate_grid_around_model() in solmask.hpp +// cf interpolate_values in python/grid.cpp +template +void interpolate_grid(Grid& dest, const Grid& src, const Transform& tr, int order=1) { + FTransform frac_tr = src.unit_cell.frac.combine(tr).combine(dest.unit_cell.orth); + size_t idx = 0; + for (int w = 0; w != dest.nw; ++w) + for (int v = 0; v != dest.nv; ++v) + for (int u = 0; u != dest.nu; ++u, ++idx) { + Fractional dest_fr = dest.get_fractional(u, v, w); + Fractional src_fr = frac_tr.apply(dest_fr); + dest.data[idx] = src.interpolate_value(src_fr, order); + } +} + +template +Correlation calculate_correlation(const GridBase& a, const GridBase& b) { + if (a.data.size() != b.data.size() || a.nu != b.nu || a.nv != b.nv || a.nw != b.nw) + fail("calculate_correlation(): grids have different sizes"); + Correlation corr; + for (size_t i = 0; i != a.data.size(); ++i) + if (!std::isnan(a.data[i]) && !std::isnan(b.data[i])) + corr.add_point(a.data[i], b.data[i]); + return corr; +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/intensit.hpp b/gemmi_gph/gemmi/intensit.hpp new file mode 100644 index 00000000..d47ad6bb --- /dev/null +++ b/gemmi_gph/gemmi/intensit.hpp @@ -0,0 +1,238 @@ +// Copyright 2020 Global Phasing Ltd. +// +// Class Intensities that reads multi-record data from MTZ, mmCIF or XDS_ASCII +// and merges it into mean or anomalous intensities. +// It can also read merged data. + +#ifndef GEMMI_INTENSIT_HPP_ +#define GEMMI_INTENSIT_HPP_ + +#include // for int8_t +#include +#include "symmetry.hpp" +#include "unitcell.hpp" +#include "util.hpp" // for vector_remove_if +#include "stats.hpp" // for Correlation + +namespace gemmi { + +struct Binner; +struct Mtz; +struct XdsAscii; +struct ReflnBlock; +namespace cif { struct Block; } +using std::int8_t; + +// If used to request a particular data type: +// MergedMA = Mean if available, otherwise Anomalous, +// MergedAM = Anomalous if available, otherwise Mean. +// UAM = Unmerged if available, otherwise MergedAM +enum class DataType { Unknown, Unmerged, Mean, Anomalous, + MergedMA, MergedAM, UAM }; + +struct GEMMI_DLL MergingStats { + int all_refl = 0; // all reflections, sometimes called observations + int unique_refl = 0; + int stats_refl = 0; // unique reflections with 2+ observations (used for statistics) + double r_merge_num = 0; // numerator for R-merge + double r_meas_num = 0; // numerator for R-meas + double r_pim_num = 0; // numerator for R-pim + double r_denom = 0; // denominator for R-* + // sums for CC1/2 + double sum_ibar = 0; + double sum_ibar2 = 0; + double sum_sig2_eps = 0; + + /// This class is additive. Adding two MergingStats gives the same result + /// as calculating statistics in these two resolution shells from the start. + void add_other(const MergingStats& o) { + all_refl += o.all_refl; + unique_refl += o.unique_refl; + stats_refl += o.stats_refl; + r_merge_num += o.r_merge_num; + r_meas_num += o.r_meas_num; + r_pim_num += o.r_pim_num; + r_denom += o.r_denom; + sum_ibar += o.sum_ibar; + sum_ibar2 += o.sum_ibar2; + sum_sig2_eps += o.sum_sig2_eps; + } + + double r_merge() const { return r_merge_num / r_denom; } + double r_meas() const { return r_meas_num / r_denom; } + double r_pim() const { return r_pim_num / r_denom; } + double cc_half() const; // calculated using sigma-tau method + /// split-half reliability using the Spearman-Brown prediction formula :-) + double cc_full() const { + double cc = cc_half(); + return 2 * cc / (1 + cc); + } + double cc_star() const { return std::sqrt(cc_full()); } +}; + +/// Returns STARANISO version or empty string. +GEMMI_DLL std::string read_staraniso_b_from_mtz(const Mtz& mtz, SMat33& output); + +struct GEMMI_DLL Intensities { + struct Refl { + Miller hkl; + int8_t isign; // 1 for I(+), -1 for I(-), 0 for mean or unmerged + int8_t isym; // for unmerged data: encodes symmetry op like M/ISYM in MTZ + short nobs; + double value; + double sigma; + + bool operator<(const Refl& o) const { + return std::tie(hkl[0], hkl[1], hkl[2], isign) < + std::tie(o.hkl[0], o.hkl[1], o.hkl[2], o.isign); + } + // for merged data + const char* intensity_label() const { + if (isign == 0) + return ""; + return isign > 0 ? "I(+)" : "I(-)"; + } + std::string hkl_label() const { + return cat(intensity_label(), " (", hkl[0], ' ', hkl[1], ' ', hkl[2], ')'); + } + }; + + struct AnisoScaling { + SMat33 b = {0., 0., 0., 0., 0., 0.}; + + bool ok() const { return !b.all_zero(); } + double scale(const Miller& hkl, const UnitCell& cell) const { + Vec3 s = cell.frac.mat.left_multiply(Vec3(hkl[0], hkl[1], hkl[2])); + return std::exp(0.5 * b.r_u_r(s)); + } + }; + + std::vector data; + const SpaceGroup* spacegroup = nullptr; + UnitCell unit_cell; + double unit_cell_rmsd[6] = {0., 0., 0., 0., 0., 0.}; + double wavelength; + DataType type = DataType::Unknown; + std::vector isym_ops; + AnisoScaling staraniso_b; + + static const char* type_str(DataType data_type) { + switch (data_type) { + case DataType::Unmerged: return "I"; + case DataType::Mean: return ""; + case DataType::Anomalous: return "I+/I-"; + case DataType::MergedAM: + case DataType::MergedMA: + case DataType::UAM: + case DataType::Unknown: return "n/a"; + } + unreachable(); + } + + const char* type_str() const { return Intensities::type_str(type); } + + std::string spacegroup_str() const { return spacegroup ? spacegroup->xhm() : "none"; } + + // returns (d_max, d_min) + std::array resolution_range() const; + + // pre: both are sorted + Correlation calculate_correlation(const Intensities& other) const; + + void add_if_valid(const Miller& hkl, int8_t isign, int8_t isym, double value, double sigma) { + // XDS marks rejected reflections with negative sigma. + // Sigma 0.0 rarely happens (e.g. 5tkn), but is also problematic. + if (!std::isnan(value) && sigma > 0) + data.push_back({hkl, isign, isym, /*nobs=*/0, value, sigma}); + } + + void remove_systematic_absences() { + if (!spacegroup) + return; + GroupOps gops = spacegroup->operations(); + vector_remove_if(data, [&](Refl& x) { return gops.is_systematically_absent(x.hkl); }); + } + + void sort() { std::sort(data.begin(), data.end()); } + + void merge_in_place(DataType new_type); + + Intensities merged(DataType new_type) { + Intensities m(*this); + m.merge_in_place(new_type); + return m; + } + + /// use_weights can be 'Y' (yes, like Aimless), 'U' (unweighted), 'X' (yes, like XDS) + std::vector calculate_merging_stats(const Binner* binner, + char use_weights='Y') const; + + // call with DataType::Anomalous before calculate_merging_stats() to get I+/I- stats + DataType prepare_for_merging(DataType new_type); + + void switch_to_asu_indices(); + + void import_unmerged_intensities_from_mtz(const Mtz& mtz); + void import_mean_intensities_from_mtz(const Mtz& mtz); + // with check_complete=true, throw if anomalous data is null where it shouldn't be + void import_anomalous_intensities_from_mtz(const Mtz& mtz, bool check_complete=false); + + void import_mtz(const Mtz& mtz, DataType data_type=DataType::Unknown); + + void import_unmerged_intensities_from_mmcif(const ReflnBlock& rb); + void import_mean_intensities_from_mmcif(const ReflnBlock& rb); + void import_anomalous_intensities_from_mmcif(const ReflnBlock& rb, bool check_complete=false); + + void import_f_squared_from_mmcif(const ReflnBlock& rb); + + void import_refln_block(const ReflnBlock& rb, DataType data_type=DataType::Unknown); + + void import_xds(const XdsAscii& xds); + + // returns STARANISO version or empty string + std::string take_staraniso_b_from_mtz(const Mtz& mtz); + + bool take_staraniso_b_from_mmcif(const cif::Block& block); + + Mtz prepare_merged_mtz(bool with_nobs); +}; + +// Minimal compatibility with MtzDataProxy and ReflnDataProxy. +struct IntensitiesDataProxy { + const Intensities& intensities_; + size_t stride() const { return 1; } + size_t size() const { return intensities_.data.size(); } + const SpaceGroup* spacegroup() const { return intensities_.spacegroup; } + const UnitCell& unit_cell() const { return intensities_.unit_cell; } + Miller get_hkl(size_t offset) const { return intensities_.data[offset].hkl; } + double get_num(size_t n) const { return intensities_.data[n].value; } +}; + +template +std::pair check_data_type_under_symmetry(const DataProxy& proxy) { + const SpaceGroup* sg = proxy.spacegroup(); + if (!sg) + return {DataType::Unknown, 0}; + std::unordered_map seen; + ReciprocalAsu asu(sg); + GroupOps gops = sg->operations(); + bool centric = gops.is_centrosymmetric(); + DataType data_type = DataType::Mean; + for (size_t i = 0; i < proxy.size(); i += proxy.stride()) { + auto hkl_sign = asu.to_asu_sign(proxy.get_hkl(i), gops); + int sign = hkl_sign.second ? 2 : 1; // 2=positive, 1=negative + auto r = seen.emplace(hkl_sign.first, sign); + if (data_type != DataType::Unmerged && !r.second) { + if ((r.first->second & sign) != 0 || centric) { + data_type = DataType::Unmerged; + } else { + r.first->second |= sign; + data_type = DataType::Anomalous; + } + } + } + return {data_type, seen.size()}; +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/interop.hpp b/gemmi_gph/gemmi/interop.hpp new file mode 100644 index 00000000..07e5c30b --- /dev/null +++ b/gemmi_gph/gemmi/interop.hpp @@ -0,0 +1,59 @@ +// Copyright 2020 Global Phasing Ltd. +// +// Interoperability between Model (MX) and SmallStructure (SX). + +#ifndef GEMMI_INTEROP_HPP_ +#define GEMMI_INTEROP_HPP_ + +#include "model.hpp" +#include "small.hpp" + +namespace gemmi { + +inline SmallStructure::Site atom_to_site(const Atom& atom, const UnitCell& cell) { + SmallStructure::Site site; + site.label = atom.name; + site.type_symbol = atom.element.name(); + site.fract = cell.fractionalize(atom.pos); + site.occ = atom.occ; + // occupancy may need to be adjusted if the atom is on special position + if (atom.occ <= 0.5) { + int n_mates = cell.is_special_position(atom.pos); + if (n_mates > 0 && atom.occ * (n_mates + 1) <= 1.0) + site.occ = atom.occ * (n_mates + 1); + } + site.u_iso = atom.b_iso / u_to_b(); + if (atom.aniso.nonzero()) { + if (cell.alpha == 90. || cell.beta == 90. || cell.gamma == 90.) { + site.aniso = atom.aniso.scaled(1.0); + } else { + SMat33 t = atom.aniso.transformed_by<>(cell.frac.mat); + Vec3 v = {1.0 / cell.ar, 1.0 / cell.br, 1.0 / cell.cr}; + site.aniso = {t.u11 * v.x * v.x, + t.u22 * v.y * v.y, + t.u33 * v.z * v.z, + t.u12 * v.x * v.y, + t.u13 * v.x * v.z, + t.u23 * v.y * v.z}; + } + } + site.element = atom.element; + site.charge = atom.charge; + return site; +} + +inline SmallStructure mx_to_sx_structure(const Structure& st, int n=0) { + const Model& model = st.models.at(n); + SmallStructure small_st; + small_st.name = st.name; + small_st.cell = st.cell; + small_st.spacegroup_hm = st.spacegroup_hm; + for (const Chain& chain : model.chains) + for (const Residue& residue : chain.residues) + for (const Atom& atom : residue.atoms) + small_st.sites.push_back(atom_to_site(atom, st.cell)); + return small_st; +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/it92.hpp b/gemmi_gph/gemmi/it92.hpp new file mode 100644 index 00000000..a7d21978 --- /dev/null +++ b/gemmi_gph/gemmi/it92.hpp @@ -0,0 +1,426 @@ +// Copyright 2019 Global Phasing Ltd. + +// X-ray scattering factor coefficients from International Tables +// for Crystallography Volume C, edition from 1992 or later. +// +// The same data is in CCP4 (file atomsf.lib) and in cctbx (table="it1992"). +// But here we have only one entry per element. The original table also +// lists ions, for example Fe2+ and Fe3+ in addition to Fe. +// Scattering factors from ITC are approximated by four Gaussians +// and a constant value - together 9 coefficients known as Cromer-Mann +// coefficients. The approximation holds for sin(theta)/lambda < 2 A^-1. + +#ifndef GEMMI_IT92_HPP_ +#define GEMMI_IT92_HPP_ + +#include "formfact.hpp" // for GaussianCoef +#include "elem.hpp" // for El + +namespace gemmi { + +#if defined(__GNUC__) && __GNUC__-0 > 4 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-conversion" +#endif + +template +struct IT92 { + using Coef = GaussianCoef<4, 1, Real>; + static Coef data[99+112]; + static std::pair ion_list[112]; + static bool ignore_charge; + + static bool has(El el) { + return el <= El::Cf || el == El::D; + } + + static Coef& get(El el, signed char charge, int /*serial*/=0) { + // ordinal for X, H, ... Cf; H=1 for D; X=0 for Es, ... Og + int pos = el <= El::Cf ? (int)el : (int)(el == El::D); + if (charge != 0 && !ignore_charge) { + std::pair p{el, charge}; + int start = std::max(0, (int)el - 8); // optimization based on plot of index(El) + for (int i = start; i < 112; ++i) { + if (ion_list[i] == p) { + pos = 99 + i; + break; + } + if (ion_list[i] > p) + break; + } + } + return data[pos]; + } + + static Coef* get_exact(El el, signed char charge) { + if (has(el)) { + Coef* coef = &get(el, charge); + if (charge == 0 || coef - data > int(El::Cf)) + return coef; + } + return nullptr; + } + + // Make a1+a2+a3+a4+c equal exactly to the number of electrons. + // It changes the values from ITC only slightly (by not more than 0.12%). + static void normalize_one(Coef& f, int n) { + double factor = n / (f.a(0) + f.a(1) + f.a(2) + f.a(3) + f.c()); + for (int j = 0; j < 4; ++j) + f.coefs[j] *= factor; + f.coefs[8] *= factor; + } + static void normalize() { + for (int i = 1; i < 99; ++i) + normalize_one(data[i], i); + for (int i = 0; i < 112; ++i) { + int n = (int)ion_list[i].first - ion_list[i].second; + normalize_one(data[99+i], n); + } + } +}; + +template +typename IT92::Coef IT92::data[99+112] = { + // a1, a2, a3, a4, b1, b2, b3, b4, c + {3.0485, 2.2868, 1.5463, 0.867, 13.2771, 5.7011, 0.3239, 32.9089, 0.2508}, // X=O + {0.493002, 0.322912, 0.140191, 0.04081, 10.5109, 26.1257, 3.14236, 57.7997, 0.003038}, // H + {0.8734, 0.6309, 0.3112, 0.178, 9.1037, 3.3568, 22.9276, 0.9821, 0.0064}, // He + {1.1282, 0.7508, 0.6175, 0.4653, 3.9546, 1.0524, 85.3905, 168.261, 0.0377}, // Li + {1.5919, 1.1278, 0.5391, 0.7029, 43.6427, 1.8623, 103.483, 0.542, 0.0385}, // Be + {2.0545, 1.3326, 1.0979, 0.7068, 23.2185, 1.021, 60.3498, 0.1403, -0.1932}, // B + {2.31, 1.02, 1.5886, 0.865, 20.8439, 10.2075, 0.5687, 51.6512, 0.2156}, // C + {12.2126, 3.1322, 2.0125, 1.1663, 0.0057, 9.8933, 28.9975, 0.5826, -11.529}, // N + {3.0485, 2.2868, 1.5463, 0.867, 13.2771, 5.7011, 0.3239, 32.9089, 0.2508}, // O + {3.5392, 2.6412, 1.517, 1.0243, 10.2825, 4.2944, 0.2615, 26.1476, 0.2776}, // F + {3.9553, 3.1125, 1.4546, 1.1251, 8.4042, 3.4262, 0.2306, 21.7184, 0.3515}, // Ne + {4.7626, 3.1736, 1.2674, 1.1128, 3.285, 8.8422, 0.3136, 129.424, 0.676}, // Na + {5.4204, 2.1735, 1.2269, 2.3073, 2.8275, 79.2611, 0.3808, 7.1937, 0.8584}, // Mg + {6.4202, 1.9002, 1.5936, 1.9646, 3.0387, 0.7426, 31.5472, 85.0886, 1.1151}, // Al + {6.2915, 3.0353, 1.9891, 1.541, 2.4386, 32.3337, 0.6785, 81.6937, 1.1407}, // Si + {6.4345, 4.1791, 1.78, 1.4908, 1.9067, 27.157, 0.526, 68.1645, 1.1149}, // P + {6.9053, 5.2034, 1.4379, 1.5863, 1.4679, 22.2151, 0.2536, 56.172, 0.8669}, // S + {11.4604, 7.1964, 6.2556, 1.6455, 0.0104, 1.1662, 18.5194, 47.7784, -9.5574}, // Cl + {7.4845, 6.7723, 0.6539, 1.6442, 0.9072, 14.8407, 43.8983, 33.3929, 1.4445}, // Ar + {8.2186, 7.4398, 1.0519, 0.8659, 12.7949, 0.7748, 213.187, 41.6841, 1.4228}, // K + {8.6266, 7.3873, 1.5899, 1.0211, 10.4421, 0.6599, 85.7484, 178.437, 1.3751}, // Ca + {9.189, 7.3679, 1.6409, 1.468, 9.0213, 0.5729, 136.108, 51.3531, 1.3329}, // Sc + {9.7595, 7.3558, 1.6991, 1.9021, 7.8508, 0.5, 35.6338, 116.105, 1.2807}, // Ti + {10.2971, 7.3511, 2.0703, 2.0571, 6.8657, 0.4385, 26.8938, 102.478, 1.2199}, // V + {10.6406, 7.3537, 3.324, 1.4922, 6.1038, 0.392, 20.2626, 98.7399, 1.1832}, // Cr + {11.2819, 7.3573, 3.0193, 2.2441, 5.3409, 0.3432, 17.8674, 83.7543, 1.0896}, // Mn + {11.7695, 7.3573, 3.5222, 2.3045, 4.7611, 0.3072, 15.3535, 76.8805, 1.0369}, // Fe + {12.2841, 7.3409, 4.0034, 2.3488, 4.2791, 0.2784, 13.5359, 71.1692, 1.0118}, // Co + {12.8376, 7.292, 4.4438, 2.38, 3.8785, 0.2565, 12.1763, 66.3421, 1.0341}, // Ni + {13.338, 7.1676, 5.6158, 1.6735, 3.5828, 0.247, 11.3966, 64.8126, 1.191}, // Cu + {14.0743, 7.0318, 5.1652, 2.41, 3.2655, 0.2333, 10.3163, 58.7097, 1.3041}, // Zn + {15.2354, 6.7006, 4.3591, 2.9623, 3.0669, 0.2412, 10.7805, 61.4135, 1.7189}, // Ga + {16.0816, 6.3747, 3.7068, 3.683, 2.8509, 0.2516, 11.4468, 54.7625, 2.1313}, // Ge + {16.6723, 6.0701, 3.4313, 4.2779, 2.6345, 0.2647, 12.9479, 47.7972, 2.531}, // As + {17.0006, 5.8196, 3.9731, 4.3543, 2.4098, 0.2726, 15.2372, 43.8163, 2.8409}, // Se + {17.1789, 5.2358, 5.6377, 3.9851, 2.1723, 16.5796, 0.2609, 41.4328, 2.9557}, // Br + {17.3555, 6.7286, 5.5493, 3.5375, 1.9384, 16.5623, 0.2261, 39.3972, 2.825}, // Kr + {17.1784, 9.6435, 5.1399, 1.5292, 1.7888, 17.3151, 0.2748, 164.934, 3.4873}, // Rb + {17.5663, 9.8184, 5.422, 2.6694, 1.5564, 14.0988, 0.1664, 132.376, 2.5064}, // Sr + {17.776, 10.2946, 5.72629, 3.26588, 1.4029, 12.8006, 0.125599, 104.354, 1.91213}, // Y + {17.8765, 10.948, 5.41732, 3.65721, 1.27618, 11.916, 0.117622, 87.6627, 2.06929}, // Zr + {17.6142, 12.0144, 4.04183, 3.53346, 1.18865, 11.766, 0.204785, 69.7957, 3.75591}, // Nb + {3.7025, 17.2356, 12.8876, 3.7429, 0.2772, 1.0958, 11.004, 61.6584, 4.3875}, // Mo + {19.1301, 11.0948, 4.64901, 2.71263, 0.864132, 8.14487, 21.5707, 86.8472, 5.40428}, // Tc + {19.2674, 12.9182, 4.86337, 1.56756, 0.80852, 8.43467, 24.7997, 94.2928, 5.37874}, // Ru + {19.2957, 14.3501, 4.73425, 1.28918, 0.751536, 8.21758, 25.8749, 98.6062, 5.328}, // Rh + {19.3319, 15.5017, 5.29537, 0.605844, 0.698655, 7.98929, 25.2052, 76.8986, 5.26593}, // Pd + {19.2808, 16.6885, 4.8045, 1.0463, 0.6446, 7.4726, 24.6605, 99.8156, 5.179}, // Ag + {19.2214, 17.6444, 4.461, 1.6029, 0.5946, 6.9089, 24.7008, 87.4825, 5.0694}, // Cd + {19.1624, 18.5596, 4.2948, 2.0396, 0.5476, 6.3776, 25.8499, 92.8029, 4.9391}, // In + {19.1889, 19.1005, 4.4585, 2.4663, 5.8303, 0.5031, 26.8909, 83.9571, 4.7821}, // Sn + {19.6418, 19.0455, 5.0371, 2.6827, 5.3034, 0.4607, 27.9074, 75.2825, 4.5909}, // Sb + {19.9644, 19.0138, 6.14487, 2.5239, 4.81742, 0.420885, 28.5284, 70.8403, 4.352}, // Te + {20.1472, 18.9949, 7.5138, 2.2735, 4.347, 0.3814, 27.766, 66.8776, 4.0712}, // I + {20.2933, 19.0298, 8.9767, 1.99, 3.9282, 0.344, 26.4659, 64.2658, 3.7118}, // Xe + {20.3892, 19.1062, 10.662, 1.4953, 3.569, 0.3107, 24.3879, 213.904, 3.3352}, // Cs + {20.3361, 19.297, 10.888, 2.6959, 3.216, 0.2756, 20.2073, 167.202, 2.7731}, // Ba + {20.578, 19.599, 11.3727, 3.28719, 2.94817, 0.244475, 18.7726, 133.124, 2.14678}, // La + {21.1671, 19.7695, 11.8513, 3.33049, 2.81219, 0.226836, 17.6083, 127.113, 1.86264}, // Ce + {22.044, 19.6697, 12.3856, 2.82428, 2.77393, 0.222087, 16.7669, 143.644, 2.0583}, // Pr + {22.6845, 19.6847, 12.774, 2.85137, 2.66248, 0.210628, 15.885, 137.903, 1.98486}, // Nd + {23.3405, 19.6095, 13.1235, 2.87516, 2.5627, 0.202088, 15.1009, 132.721, 2.02876}, // Pm + {24.0042, 19.4258, 13.4396, 2.89604, 2.47274, 0.196451, 14.3996, 128.007, 2.20963}, // Sm + {24.6274, 19.0886, 13.7603, 2.9227, 2.3879, 0.1942, 13.7546, 123.174, 2.5745}, // Eu + {25.0709, 19.0798, 13.8518, 3.54545, 2.25341, 0.181951, 12.9331, 101.398, 2.4196}, // Gd + {25.8976, 18.2185, 14.3167, 2.95354, 2.24256, 0.196143, 12.6648, 115.362, 3.58324}, // Tb + {26.507, 17.6383, 14.5596, 2.96577, 2.1802, 0.202172, 12.1899, 111.874, 4.29728}, // Dy + {26.9049, 17.294, 14.5583, 3.63837, 2.07051, 0.19794, 11.4407, 92.6566, 4.56796}, // Ho + {27.6563, 16.4285, 14.9779, 2.98233, 2.07356, 0.223545, 11.3604, 105.703, 5.92046}, // Er + {28.1819, 15.8851, 15.1542, 2.98706, 2.02859, 0.238849, 10.9975, 102.961, 6.75621}, // Tm + {28.6641, 15.4345, 15.3087, 2.98963, 1.9889, 0.257119, 10.6647, 100.417, 7.56672}, // Yb + {28.9476, 15.2208, 15.1, 3.71601, 1.90182, 9.98519, 0.261033, 84.3298, 7.97628}, // Lu + {29.144, 15.1726, 14.7586, 4.30013, 1.83262, 9.5999, 0.275116, 72.029, 8.58154}, // Hf + {29.2024, 15.2293, 14.5135, 4.76492, 1.77333, 9.37046, 0.295977, 63.3644, 9.24354}, // Ta + {29.0818, 15.43, 14.4327, 5.11982, 1.72029, 9.2259, 0.321703, 57.056, 9.8875}, // W + {28.7621, 15.7189, 14.5564, 5.44174, 1.67191, 9.09227, 0.3505, 52.0861, 10.472}, // Re + {28.1894, 16.155, 14.9305, 5.67589, 1.62903, 8.97948, 0.382661, 48.1647, 11.0005}, // Os + {27.3049, 16.7296, 15.6115, 5.83377, 1.59279, 8.86553, 0.417916, 45.0011, 11.4722}, // Ir + {27.0059, 17.7639, 15.7131, 5.7837, 1.51293, 8.81174, 0.424593, 38.6103, 11.6883}, // Pt + {16.8819, 18.5913, 25.5582, 5.86, 0.4611, 8.6216, 1.4826, 36.3956, 12.0658}, // Au + {20.6809, 19.0417, 21.6575, 5.9676, 0.545, 8.4484, 1.5729, 38.3246, 12.6089}, // Hg + {27.5446, 19.1584, 15.538, 5.52593, 0.65515, 8.70751, 1.96347, 45.8149, 13.1746}, // Tl + {31.0617, 13.0637, 18.442, 5.9696, 0.6902, 2.3576, 8.618, 47.2579, 13.4118}, // Pb + {33.3689, 12.951, 16.5877, 6.4692, 0.704, 2.9238, 8.7937, 48.0093, 13.5782}, // Bi + {34.6726, 15.4733, 13.1138, 7.02588, 0.700999, 3.55078, 9.55642, 47.0045, 13.677}, // Po + {35.3163, 19.0211, 9.49887, 7.42518, 0.68587, 3.97458, 11.3824, 45.4715, 13.7108}, // At + {35.5631, 21.2816, 8.0037, 7.4433, 0.6631, 4.0691, 14.0422, 44.2473, 13.6905}, // Rn + {35.9299, 23.0547, 12.1439, 2.11253, 0.646453, 4.17619, 23.1052, 150.645, 13.7247}, // Fr + {35.763, 22.9064, 12.4739, 3.21097, 0.616341, 3.87135, 19.9887, 142.325, 13.6211}, // Ra + {35.6597, 23.1032, 12.5977, 4.08655, 0.589092, 3.65155, 18.599, 117.02, 13.5266}, // Ac + {35.5645, 23.4219, 12.7473, 4.80703, 0.563359, 3.46204, 17.8309, 99.1722, 13.4314}, // Th + {35.8847, 23.2948, 14.1891, 4.17287, 0.547751, 3.41519, 16.9235, 105.251, 13.4287}, // Pa + {36.0228, 23.4128, 14.9491, 4.188, 0.5293, 3.3253, 16.0927, 100.613, 13.3966}, // U + {36.1874, 23.5964, 15.6402, 4.1855, 0.511929, 3.25396, 15.3622, 97.4908, 13.3573}, // Np + {36.5254, 23.8083, 16.7707, 3.47947, 0.499384, 3.26371, 14.9455, 105.98, 13.3812}, // Pu + {36.6706, 24.0992, 17.3415, 3.49331, 0.483629, 3.20647, 14.3136, 102.273, 13.3592}, // Am + {36.6488, 24.4096, 17.399, 4.21665, 0.465154, 3.08997, 13.4346, 88.4834, 13.2887}, // Cm + {36.7881, 24.7736, 17.8919, 4.23284, 0.451018, 3.04619, 12.8946, 86.003, 13.2754}, // Bk + {36.9185, 25.1995, 18.3317, 4.24391, 0.437533, 3.00775, 12.4044, 83.7881, 13.2674}, // Cf + + // Atoms with charge, in the same order as in ion_list[]. + // For compatibility with cctbx, use O2- from Hovestreydt, Acta Cryst. (1983) A39, 268 + // and Tl3+ a2 = 18.3841. + {0.897661, 0.565616, 0.415815, 0.116973, 53.1368, 15.187, 186.576, 3.56709, 0.002389},// H1- + {0.6968, 0.7888, 0.3414, 0.1563, 4.6237, 1.9557, 0.6316, 10.0953, 0.016700}, // Li1+ + {6.2603, 0.8849, 0.7993, 0.1647, 0.0027, 0.8313, 2.2758, 5.1146, -6.1092}, // Be2+ + {3.7504, 2.84294, 1.54298, 1.62091, 16.5151, 6.59203, 0.319201, 43.3486, 0.242060}, // O2- + {4.1916, 1.63969, 1.52673, -20.307, 12.8573, 4.17236, 47.0179, -0.01404, 21.9412}, // O1- + {3.6322, 3.51057, 1.26064, 0.940706, 5.27756, 14.7353, 0.442258, 47.3437, 0.653396}, // F1- + {3.2565, 3.9362, 1.3998, 1.0032, 2.6671, 6.1153, 0.2001, 14.039, 0.404000}, // Na1+ + {3.4988, 3.8378, 1.3284, 0.8497, 2.1676, 4.7542, 0.185, 10.1411, 0.485300}, // Mg2+ + {4.17448, 3.3876, 1.20296, 0.528137, 1.93816, 4.14553, 0.228753, 8.28524, 0.706786}, // Al3+ + {4.43918, 3.20345, 1.19453, 0.41653, 1.64167, 3.43757, 0.2149, 6.65365, 0.746297}, // Si4+ + {18.2915, 7.2084, 6.5337, 2.3386, 0.0066, 1.1717, 19.5424, 60.4486, -16.378}, // Cl1- + {7.9578, 7.4917, 6.359, 1.1915, 12.6331, 0.7674, -0.002, 31.9128, -4.9978}, // K1+ + {15.6348, 7.9518, 8.4372, 0.8537, -0.0074, 0.6089, 10.3116, 25.9905, -14.875}, // Ca2+ + {13.4008, 8.0273, 1.65943, 1.57936, 0.29854, 7.9629, -0.28604, 16.0662, -6.6667}, // Sc3+ + {9.11423, 7.62174, 2.2793, 0.087899, 7.5243, 0.457585, 19.5361, 61.6558, 0.897155}, // Ti2+ + {17.7344, 8.73816, 5.25691, 1.92134, 0.22061, 7.04716, -0.15762, 15.9768, -14.652}, // Ti3+ + {19.5114, 8.23473, 2.01341, 1.5208, 0.178847, 6.67018, -0.29263, 12.9464, -13.280}, // Ti4+ + {10.106, 7.3541, 2.2884, 0.0223, 6.8818, 0.4409, 20.3004, 115.122, 1.22980}, // V2+ + {9.43141, 7.7419, 2.15343, 0.016865, 6.39535, 0.383349, 15.1908, 63.969, 0.656565}, // V3+ + {15.6887, 8.14208, 2.03081, -9.576, 0.679003, 5.40135, 9.97278, 0.940464, 1.71430}, // V5+ + {9.54034, 7.7509, 3.58274, 0.509107, 5.66078, 0.344261, 13.3075, 32.4224, 0.616898}, // Cr2+ + {9.6809, 7.81136, 2.87603, 0.113575, 5.59463, 0.334393, 12.8288, 32.8761, 0.518275}, // Cr3+ + {10.8061, 7.362, 3.5268, 0.2184, 5.2796, 0.3435, 14.343, 41.3235, 1.08740}, // Mn2+ + {9.84521, 7.87194, 3.56531, 0.323613, 4.91797, 0.294393, 10.8171, 24.1281, 0.393974},// Mn3+ + {9.96253, 7.97057, 2.76067, 0.054447, 4.8485, 0.283303, 10.4852, 27.573, 0.251877}, // Mn4+ + {11.0424, 7.374, 4.1346, 0.4399, 4.6538, 0.3053, 12.0546, 31.2809, 1.00970}, // Fe2+ + {11.1764, 7.3863, 3.3948, 0.0724, 4.6147, 0.3005, 11.6729, 38.5566, 0.970700}, // Fe3+ + {11.2296, 7.3883, 4.7393, 0.7108, 4.1231, 0.2726, 10.2443, 25.6466, 0.932400}, // Co2+ + {10.338, 7.88173, 4.76795, 0.725591, 3.90969, 0.238668, 8.35583, 18.3491, 0.286667}, // Co3+ + {11.4166, 7.4005, 5.3442, 0.9773, 3.6766, 0.2449, 8.873, 22.1626, 0.861400}, // Ni2+ + {10.7806, 7.75868, 5.22746, 0.847114, 3.5477, 0.22314, 7.64468, 16.9673, 0.386044}, // Ni3+ + {11.9475, 7.3573, 6.2455, 1.5578, 3.3669, 0.2274, 8.6625, 25.8487, 0.89000}, // Cu1+ + {11.8168, 7.11181, 5.78135, 1.14523, 3.37484, 0.244078, 7.9876, 19.897, 1.14431}, // Cu2+ + {11.9719, 7.3862, 6.4668, 1.394, 2.9946, 0.2031, 7.0826, 18.0995, 0.780700}, // Zn2+ + {12.692, 6.69883, 6.06692, 1.0066, 2.81262, 0.22789, 6.36441, 14.4122, 1.53545}, // Ga3+ + {12.9172, 6.70003, 6.06791, 0.859041, 2.53718, 0.205855, 5.47913, 11.603, 1.45572}, // Ge4+ + {17.1718, 6.3338, 5.5754, 3.7272, 2.2059, 19.3345, 0.2871, 58.1535, 3.17760}, // Br1- + {17.5816, 7.6598, 5.8981, 2.7817, 1.7139, 14.7957, 0.1603, 31.2087, 2.07820}, // Rb1+ + {18.0874, 8.1373, 2.5654, -34.193, 1.4907, 12.6963, 24.5651, -0.0138, 41.4025}, // Sr2+ + {17.9268, 9.1531, 1.76795, -33.108, 1.35417, 11.2145, 22.6599, -0.01319, 40.2602}, // Y3+ + {18.1668, 10.0562, 1.01118, -2.6479, 1.2148, 10.1483, 21.6054, -0.10276, 9.41454}, // Zr4+ + {19.8812, 18.0653, 11.0177, 1.94715, 0.019175, 1.13305, 10.1621, 28.3389, -12.912}, // Nb3+ + {17.9163, 13.3417, 10.799, 0.337905, 1.12446, 0.028781, 9.28206, 25.7228, -6.3934}, // Nb5+ + {21.1664, 18.2017, 11.7423, 2.30951, 0.014734, 1.03031, 9.53659, 26.6307, -14.421}, // Mo3+ + {21.0149, 18.0992, 11.4632, 0.740625, 0.014345, 1.02238, 8.78809, 23.3452, -14.316}, // Mo5+ + {17.8871, 11.175, 6.57891, 0., 1.03649, 8.48061, 0.058881, 0., 0.344941}, // Mo6+ + {18.5638, 13.2885, 9.32602, 3.00964, 0.847329, 8.37164, 0.017662, 22.887, -3.1892}, // Ru3+ + {18.5003, 13.1787, 4.71304, 2.18535, 0.844582, 8.12534, 0.36495, 20.8504, 1.42357}, // Ru4+ + {18.8785, 14.1259, 3.32515, -6.1989, 0.764252, 7.84438, 21.2487, -0.01036, 11.8678}, // Rh3+ + {18.8545, 13.9806, 2.53464, -5.6526, 0.760825, 7.62436, 19.3317, -0.0102, 11.2835}, // Rh4+ + {19.1701, 15.2096, 4.32234, 0., 0.696219, 7.55573, 22.5057, 0., 5.29160}, // Pd2+ + {19.2493, 14.79, 2.89289, -7.9492, 0.683839, 7.14833, 17.9144, 0.005127, 13.0174}, // Pd4+ + {19.1812, 15.9719, 5.27475, 0.357534, 0.646179, 7.19123, 21.7326, 66.1147, 5.21572}, // Ag1+ + {19.1643, 16.2456, 4.3709, 0., 0.645643, 7.18544, 21.4072, 0., 5.21404}, // Ag2+ + {19.1514, 17.2535, 4.47128, 0., 0.597922, 6.80639, 20.2521, 0., 5.11937}, // Cd2+ + {19.1045, 18.1108, 3.78897, 0., 0.551522, 6.3247, 17.3595, 0., 4.99635}, // In3+ + {19.1094, 19.0548, 4.5648, 0.487, 0.5036, 5.8378, 23.3752, 62.2061, 4.78610}, // Sn2+ + {18.9333, 19.7131, 3.4182, 0.0193, 5.764, 0.4655, 14.0049, -0.7583, 3.91820}, // Sn4+ + {18.9755, 18.933, 5.10789, 0.288753, 0.467196, 5.22126, 19.5902, 55.5113, 4.69626}, // Sb3+ + {19.8685, 19.0302, 2.41253, 0., 5.44853, 0.467973, 14.1259, 0., 4.69263}, // Sb5+ + {20.2332, 18.997, 7.8069, 2.8868, 4.3579, 0.3815, 29.5259, 84.9304, 4.07140}, // I1- + {20.3524, 19.1278, 10.2821, 0.9615, 3.552, 0.3086, 23.7128, 59.4565, 3.27910}, // Cs1+ + {20.1807, 19.1136, 10.9054, 0.77634, 3.21367, 0.28331, 20.0558, 51.746, 3.02902}, // Ba2+ + {20.2489, 19.3763, 11.6323, 0.336048, 2.9207, 0.250698, 17.8211, 54.9453, 2.40860}, // La3+ + {20.8036, 19.559, 11.9369, 0.612376, 2.77691, 0.23154, 16.5408, 43.1692, 2.09013}, // Ce3+ + {20.3235, 19.8186, 12.1233, 0.144583, 2.65941, 0.21885, 15.7992, 62.2355, 1.59180}, // Ce4+ + {21.3727, 19.7491, 12.1329, 0.97518, 2.6452, 0.214299, 15.323, 36.4065, 1.77132}, // Pr3+ + {20.9413, 20.0539, 12.4668, 0.296689, 2.54467, 0.202481, 14.8137, 45.4643, 1.24285}, // Pr4+ + {21.961, 19.9339, 12.12, 1.51031, 2.52722, 0.199237, 14.1783, 30.8717, 1.47588}, // Nd3+ + {22.5527, 20.1108, 12.0671, 2.07492, 2.4174, 0.185769, 13.1275, 27.4491, 1.19499}, // Pm3+ + {23.1504, 20.2599, 11.9202, 2.71488, 2.31641, 0.174081, 12.1571, 24.8242, 0.954586}, // Sm3+ + {24.0063, 19.9504, 11.8034, 3.87243, 2.27783, 0.17353, 11.6096, 26.5156, 1.36389}, // Eu2+ + {23.7497, 20.3745, 11.8509, 3.26503, 2.22258, 0.16394, 11.311, 22.9966, 0.759344}, // Eu3+ + {24.3466, 20.4208, 11.8708, 3.7149, 2.13553, 0.155525, 10.5782, 21.7029, 0.645089}, // Gd3+ + {24.9559, 20.3271, 12.2471, 3.773, 2.05601, 0.149525, 10.0499, 21.2773, 0.691967}, // Tb3+ + {25.5395, 20.2861, 11.9812, 4.50073, 1.9804, 0.143384, 9.34972, 19.581, 0.689690}, // Dy3+ + {26.1296, 20.0994, 11.9788, 4.93676, 1.91072, 0.139358, 8.80018, 18.5908, 0.852795}, // Ho3+ + {26.722, 19.7748, 12.1506, 5.17379, 1.84659, 0.13729, 8.36225, 17.8974, 1.17613}, // Er3+ + {27.3083, 19.332, 12.3339, 5.38348, 1.78711, 0.136974, 7.96778, 17.2922, 1.63929}, // Tm3+ + {28.1209, 17.6817, 13.3335, 5.14657, 1.78503, 0.15997, 8.18304, 20.39, 3.70983}, // Yb2+ + {27.8917, 18.7614, 12.6072, 5.47647, 1.73272, 0.13879, 7.64412, 16.8153, 2.26001}, // Yb3+ + {28.4628, 18.121, 12.8429, 5.59415, 1.68216, 0.142292, 7.33727, 16.3535, 2.97573}, // Lu3+ + {28.8131, 18.4601, 12.7285, 5.59927, 1.59136, 0.128903, 6.76232, 14.0366, 2.39699}, // Hf4+ + {29.1587, 18.8407, 12.8268, 5.38695, 1.50711, 0.116741, 6.31524, 12.4244, 1.78555}, // Ta5+ + {29.4936, 19.3763, 13.0544, 5.06412, 1.42755, 0.104621, 5.93667, 11.1972, 1.01074}, // W6+ + {30.419, 15.2637, 14.7458, 5.06795, 1.37113, 6.84706, 0.165191, 18.003, 6.49804}, // Os4+ + {30.4156, 15.862, 13.6145, 5.82008, 1.34323, 7.10909, 0.204633, 20.3254, 8.27903}, // Ir3+ + {30.7058, 15.5512, 14.2326, 5.53672, 1.30923, 6.71983, 0.167252, 17.4911, 6.96824}, // Ir4+ + {29.8429, 16.7224, 13.2153, 6.35234, 1.32927, 7.38979, 0.263297, 22.9426, 9.85329}, // Pt2+ + {30.9612, 15.9829, 13.7348, 5.92034, 1.24813, 6.60834, 0.16864, 16.9392, 7.39534}, // Pt4+ + {28.0109, 17.8204, 14.3359, 6.58077, 1.35321, 7.7395, 0.356752, 26.4043, 11.2299}, // Au1+ + {30.6886, 16.9029, 12.7801, 6.52354, 1.2199, 6.82872, 0.212867, 18.659, 9.09680}, // Au3+ + {25.0853, 18.4973, 16.8883, 6.48216, 1.39507, 7.65105, 0.443378, 28.2262, 12.0205}, // Hg1+ + {29.5641, 18.06, 12.8374, 6.89912, 1.21152, 7.05639, 0.284738, 20.7482, 10.6268}, // Hg2+ + {21.3985, 20.4723, 18.7478, 6.82847, 1.4711, 0.517394, 7.43463, 28.8482, 12.5258}, // Tl1+ + {30.8695, 18.3841, 11.9328, 7.00574, 1.1008, 6.53852, 0.219074, 17.2114, 9.80270}, // Tl3+ + {21.7886, 19.5682, 19.1406, 7.01107, 1.3366, 0.488383, 6.7727, 23.8132, 12.4734}, // Pb2+ + {32.1244, 18.8003, 12.0175, 6.96886, 1.00566, 6.10926, 0.147041, 14.714, 8.08428}, // Pb4+ + {21.8053, 19.5026, 19.1053, 7.10295, 1.2356, 6.24149, 0.469999, 20.3185, 12.4711}, // Bi3+ + {33.5364, 25.0946, 19.2497, 6.91555, 0.91654, 0.39042, 5.71414, 12.8285, -6.7994}, // Bi5+ + {35.215, 21.67, 7.91342, 7.65078, 0.604909, 3.5767, 12.601, 29.8436, 13.5431}, // Ra2+ + {35.1736, 22.1112, 8.19216, 7.05545, 0.579689, 3.41437, 12.9187, 25.9443, 13.4637}, // Ac3+ + {35.1007, 22.4418, 9.78554, 5.29444, 0.555054, 3.24498, 13.4661, 23.9533, 13.3760}, // Th4+ + {35.5747, 22.5259, 12.2165, 5.37073, 0.52048, 3.12293, 12.7148, 26.3394, 13.3092}, // U3+ + {35.3715, 22.5326, 12.0291, 4.7984, 0.516598, 3.05053, 12.5723, 23.4582, 13.2671}, // U4+ + {34.8509, 22.7584, 14.0099, 1.21457, 0.507079, 2.8903, 13.1767, 25.2017, 13.1665}, // U6+ + {35.7074, 22.613, 12.9898, 5.43227, 0.502322, 3.03807, 12.1449, 25.4928, 13.2544}, // Np3+ + {35.5103, 22.5787, 12.7766, 4.92159, 0.498626, 2.96627, 11.9484, 22.7502, 13.2116}, // Np4+ + {35.0136, 22.7286, 14.3884, 1.75669, 0.48981, 2.81099, 12.33, 22.6581, 13.1130}, // Np6+ + {35.84, 22.7169, 13.5807, 5.66016, 0.484938, 2.96118, 11.5331, 24.3992, 13.1991}, // Pu3+ + {35.6493, 22.646, 13.3595, 5.18831, 0.481422, 2.8902, 11.316, 21.8301, 13.1555}, // Pu4+ + {35.1736, 22.7181, 14.7635, 2.28678, 0.473204, 2.73848, 11.553, 20.9303, 13.0582}, // Pu6+ +}; + +template +std::pair IT92::ion_list[112] = { + {El::H , -1}, + {El::Li, +1}, + {El::Be, +2}, + {El::O , -2}, // O2- is before O1- so that the pairs are sorted + {El::O , -1}, + {El::F , -1}, + {El::Na, +1}, + {El::Mg, +2}, + {El::Al, +3}, + {El::Si, +4}, + {El::Cl, -1}, + {El::K , +1}, + {El::Ca, +2}, + {El::Sc, +3}, + {El::Ti, +2}, + {El::Ti, +3}, + {El::Ti, +4}, + {El::V , +2}, + {El::V , +3}, + {El::V , +5}, + {El::Cr, +2}, + {El::Cr, +3}, + {El::Mn, +2}, + {El::Mn, +3}, + {El::Mn, +4}, + {El::Fe, +2}, + {El::Fe, +3}, + {El::Co, +2}, + {El::Co, +3}, + {El::Ni, +2}, + {El::Ni, +3}, + {El::Cu, +1}, + {El::Cu, +2}, + {El::Zn, +2}, + {El::Ga, +3}, + {El::Ge, +4}, + {El::Br, -1}, + {El::Rb, +1}, + {El::Sr, +2}, + {El::Y , +3}, + {El::Zr, +4}, + {El::Nb, +3}, + {El::Nb, +5}, + {El::Mo, +3}, + {El::Mo, +5}, + {El::Mo, +6}, + {El::Ru, +3}, + {El::Ru, +4}, + {El::Rh, +3}, + {El::Rh, +4}, + {El::Pd, +2}, + {El::Pd, +4}, + {El::Ag, +1}, + {El::Ag, +2}, + {El::Cd, +2}, + {El::In, +3}, + {El::Sn, +2}, + {El::Sn, +4}, + {El::Sb, +3}, + {El::Sb, +5}, + {El::I , -1}, + {El::Cs, +1}, + {El::Ba, +2}, + {El::La, +3}, + {El::Ce, +3}, + {El::Ce, +4}, + {El::Pr, +3}, + {El::Pr, +4}, + {El::Nd, +3}, + {El::Pm, +3}, + {El::Sm, +3}, + {El::Eu, +2}, + {El::Eu, +3}, + {El::Gd, +3}, + {El::Tb, +3}, + {El::Dy, +3}, + {El::Ho, +3}, + {El::Er, +3}, + {El::Tm, +3}, + {El::Yb, +2}, + {El::Yb, +3}, + {El::Lu, +3}, + {El::Hf, +4}, + {El::Ta, +5}, + {El::W , +6}, + {El::Os, +4}, + {El::Ir, +3}, + {El::Ir, +4}, + {El::Pt, +2}, + {El::Pt, +4}, + {El::Au, +1}, + {El::Au, +3}, + {El::Hg, +1}, + {El::Hg, +2}, + {El::Tl, +1}, + {El::Tl, +3}, + {El::Pb, +2}, + {El::Pb, +4}, + {El::Bi, +3}, + {El::Bi, +5}, + {El::Ra, +2}, + {El::Ac, +3}, + {El::Th, +4}, + {El::U , +3}, + {El::U , +4}, + {El::U , +6}, + {El::Np, +3}, + {El::Np, +4}, + {El::Np, +6}, + {El::Pu, +3}, + {El::Pu, +4}, + {El::Pu, +6}, +}; + +template +bool IT92::ignore_charge = true; + +#if defined(__GNUC__) && __GNUC__-0 > 4 +#pragma GCC diagnostic pop +#endif + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/json.hpp b/gemmi_gph/gemmi/json.hpp new file mode 100644 index 00000000..4e20606f --- /dev/null +++ b/gemmi_gph/gemmi/json.hpp @@ -0,0 +1,33 @@ +// Copyright 2017 Global Phasing Ltd. +// +// Reading CIF-JSON (COMCIFS) and mmJSON (PDBj) formats into cif::Document. + +#ifndef GEMMI_JSON_HPP_ +#define GEMMI_JSON_HPP_ + +#include // for forward +#include "cifdoc.hpp" // for Document, etc +#include "fileutil.hpp" // for read_file_into_buffer + +namespace gemmi { +namespace cif { + +// reads mmJSON file mutating the input buffer as a side effect +GEMMI_DLL Document read_mmjson_insitu(char* buffer, std::size_t size, + const std::string& name="mmJSON"); + +inline Document read_mmjson_file(const std::string& path) { + CharArray buffer = read_file_into_buffer(path); + return read_mmjson_insitu(buffer.data(), buffer.size(), path); +} + +template +Document read_mmjson(T&& input) { + std::string name = input.is_stdin() ? "stdin" : input.path(); + CharArray buffer = read_into_buffer(std::forward(input)); + return read_mmjson_insitu(buffer.data(), buffer.size(), name); +} + +} // namespace cif +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/levmar.hpp b/gemmi_gph/gemmi/levmar.hpp new file mode 100644 index 00000000..88b23181 --- /dev/null +++ b/gemmi_gph/gemmi/levmar.hpp @@ -0,0 +1,267 @@ +// Copyright 2020 Global Phasing Ltd. +// +// Least-squares fitting - Levenberg-Marquardt method. +// +// Based on the code from fityk (but here it's under MPL 2.0). + +#ifndef GEMMI_LEVMAR_HPP_ +#define GEMMI_LEVMAR_HPP_ + +#include +#include // for fabs +#include // for min +#include +#include "fail.hpp" // for fail +#include "math.hpp" // for sq + +//#define GEMMI_DEBUG_LEVMAR + +namespace gemmi { + +/// This function solves a set of linear algebraic equations using +/// Gauss-Jordan elimination with partial pivoting. +/// +/// A * x = b +/// +/// a is n x n matrix (in vector) +/// b is vector of length n, +/// This function returns vector x[] in b[], and 1-matrix in a[]. +inline void jordan_solve(double* a, double* b, int n) { + for (int i = 0; i < n; i++) { + // looking for a pivot element + int maxnr = -1; + double amax = 0; + for (int j = i; j < n; j++) { + double aji = std::fabs(a[n * j + i]); + if (aji > amax) { + maxnr = j; + amax = aji; + } + } + // handle singular matrix + if (maxnr == -1) { + // i-th column has only zeros. + // If it's the same about i-th row, and b[i]==0, let x[i]==0. + for (int j = i; j < n; j++) + if (a[n * i + j] != 0. || b[i] != 0.) + fail("Trying to reverse singular matrix. Column ", std::to_string(i), " is zeroed."); + continue; // x[i]=b[i], b[i]==0 + } + // interchanging rows + if (maxnr != i) { + for (int j = i; j < n; j++) + std::swap(a[n * maxnr + j], a[n * i + j]); + std::swap(b[i], b[maxnr]); + } + // divide by a_ii -- to get a_ii=1 + double c = 1.0 / a[i * n + i]; + for (int j = i; j < n; j++) + a[i * n + j] *= c; + b[i] *= c; + // subtract -- to zero all remaining elements of this row + for (int k = 0; k < n; k++) + if (k != i) { + double d = a[k * n + i]; + for (int j = i; j < n; j++) + a[k * n + j] -= a[i * n + j] * d; + b[k] -= b[i] * d; + } + } +} + +inline void jordan_solve(std::vector& a, std::vector& b) { + assert(a.size() == b.size() * b.size()); + jordan_solve(a.data(), b.data(), (int)b.size()); +} + +inline void print_parameters(const std::string& name, std::vector &a) { + fprintf(stderr, " %s:", name.c_str()); + for (double& x : a) + fprintf(stderr, " %g", x); + fprintf(stderr, "\n"); +} + +template +double compute_wssr(const Target& target) { + long double wssr = 0; // long double here notably increases the accuracy + for (const auto& p : target.points) + wssr += sq(p.get_weight() * (p.get_y() - target.compute_value(p))); + return (double) wssr; +} + +template +double compute_gradients(const Target& target, unsigned n, double* grad) { + double wssr = 0; + for (unsigned i = 0; i < n; ++i) + grad[i] = 0; + std::vector dy_da(n); + for (const auto& p : target.points) { + double y = target.compute_value_and_derivatives(p, dy_da); + double dy = p.get_weight() * (p.get_y() - y); + wssr += sq(dy); + for (unsigned i = 0; i < n; ++i) + grad[i] += -2 * dy * dy_da[i]; + } +#if 0 + // calculate numerical derivatives to check analytical formulas + fprintf(stderr, ">> y=%g\n", wssr); + std::vector x = target.get_parameters(); + assert(x.size() == n); + for (unsigned i = 0; i < n; ++i) { + double x_orig = x[i]; + double h = std::max(std::fabs(x[i]), 1e-6) * 1e-3; + x[i] = x_orig - h; + const_cast(target).set_parameters(x); + double y_left = compute_wssr(target); + x[i] = x_orig + h; + const_cast(target).set_parameters(x); + double y_right = compute_wssr(target); + double numeric = (y_right - y_left) / (2 * h); + x[i] = x_orig; + double m = std::max(std::fabs(grad[i]), std::fabs(numeric)); + if (m > 1e-3 && std::fabs(grad[i] - numeric) > 0.02 * m) + fprintf(stderr, "!! grad[%u]: %g vs %g (value: %g)\n", i, grad[i], numeric, x[i]); + } + const_cast(target).set_parameters(x); +#endif + return wssr; +} + +// alpha and beta are matrices outputted for the Levenberg-Marquardt algorithm. +// Ignoring weights, alpha is a squared Jacobian J^T J (which approximates the +// Hessian, as discussed in Numerical Recipes, chapter 15.5), not "damped" yet. +// The return value is the same as from compute_wssr(). +template +double compute_lm_matrices(const Target& target, + std::vector& alpha, + std::vector& beta) { + assert(!beta.empty()); + assert(alpha.size() == beta.size() * beta.size()); + long double wssr = 0; // long double here notably increases the accuracy + size_t na = beta.size(); + std::fill(alpha.begin(), alpha.end(), 0.0); + std::fill(beta.begin(), beta.end(), 0.0); + std::vector dy_da(na); + for (const auto& p : target.points) { + double y = target.compute_value_and_derivatives(p, dy_da); + double weight = p.get_weight(); + double dy_sig = weight * (p.get_y() - y); + for (size_t j = 0; j != na; ++j) { + if (dy_da[j] != 0) { + dy_da[j] *= weight; + for (size_t k = j+1; k-- != 0;) + alpha[na * j + k] += dy_da[j] * dy_da[k]; + beta[j] += dy_sig * dy_da[j]; + } + } + wssr += sq(dy_sig); + } + + // Only half of the alpha matrix was filled above. Fill the rest. + for (size_t j = 1; j < na; j++) + for (size_t k = 0; k < j; k++) + alpha[na * k + j] = alpha[na * j + k]; + return (double) wssr; +} + +struct LevMar { + // termination criteria + int eval_limit = 100; + double lambda_limit = 1e+15; + double stop_rel_change = 1e-5; + + // adjustable parameters (normally the default values work fine) + double lambda_up_factor = 10; + double lambda_down_factor = 0.1; + double lambda_start = 0.001; + + // values set in fit() that can be inspected later + double initial_wssr = NAN; + int eval_count = 0; // number of function evaluations + + // arrays used during refinement + std::vector alpha; // matrix + std::vector beta; // vector + std::vector temp_alpha, temp_beta; // working arrays + + + template + double fit(Target& target) { + std::vector initial_a = target.get_parameters(); +#ifdef GEMMI_DEBUG_LEVMAR + print_parameters("ini", initial_a); +#endif + std::vector best_a = initial_a; + size_t na = initial_a.size(); + + double lambda = lambda_start; + alpha.resize(na * na); + beta.resize(na); + + initial_wssr = compute_lm_matrices(target, alpha, beta); + double wssr = initial_wssr; + + int small_change_counter = 0; + eval_count = 1; // number of function evaluations so far + for (int iter = 0; ; iter++) { + if (eval_limit > 0 && eval_count >= eval_limit) + break; + + // prepare next parameters -> temp_beta + temp_alpha = alpha; + // Using '*=' not '+=' below applies the dampling factor as: + // J^T J + lambda * diag(J^T J); not ... + lambda * I. + for (size_t j = 0; j < na; j++) + temp_alpha[na * j + j] *= (1.0 + lambda); + temp_beta = beta; + + // Matrix solution (Ax=b) temp_alpha * da == temp_beta + jordan_solve(temp_alpha, temp_beta); + + for (size_t i = 0; i < na; i++) + // put new a[] into temp_beta[] + temp_beta[i] += best_a[i]; + + target.set_parameters(temp_beta); + double new_wssr = compute_wssr(target); + ++eval_count; +#ifdef GEMMI_DEBUG_LEVMAR + fprintf(stderr, " #%d WSSR=%.8g %+g%% (%+.4g%%) lambda=%g\n", + iter, new_wssr, 100. * (new_wssr / initial_wssr - 1.), + 100. * (new_wssr / wssr - 1.), lambda); + if (new_wssr < wssr) + print_parameters("", temp_beta); +#else + (void) iter; +#endif + if (new_wssr < wssr) { + double rel_change = (wssr - new_wssr) / wssr; + wssr = new_wssr; + best_a = temp_beta; + + if (wssr == 0) + break; + // termination criterion: negligible change of wssr + if (rel_change < stop_rel_change) { + if (++small_change_counter >= 2) + break; + } else { + small_change_counter = 0; + } + compute_lm_matrices(target, alpha, beta); + ++eval_count; + lambda *= lambda_down_factor; + } else { // worse fitting + if (lambda > lambda_limit) // termination criterion: large lambda + break; + lambda *= lambda_up_factor; + } + } + + target.set_parameters(wssr < initial_wssr ? best_a : initial_a); + return wssr; + } +}; + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/linkhunt.hpp b/gemmi_gph/gemmi/linkhunt.hpp new file mode 100644 index 00000000..f4458a29 --- /dev/null +++ b/gemmi_gph/gemmi/linkhunt.hpp @@ -0,0 +1,179 @@ +// Copyright 2019 Global Phasing Ltd. +// +// Searching for links based on the _chem_link table from monomer dictionary. + +#ifndef GEMMI_LINKHUNT_HPP_ +#define GEMMI_LINKHUNT_HPP_ + +#include +#include "elem.hpp" +#include "model.hpp" +#include "monlib.hpp" +#include "neighbor.hpp" +#include "contact.hpp" + +namespace gemmi { + +struct LinkHunt { + struct Match { + const ChemLink* chem_link = nullptr; + int chem_link_count = 0; + int score = -1000; + CRA cra1; + CRA cra2; + bool same_image; + double bond_length = 0; + Connection* conn = nullptr; + }; + + double global_max_dist = 2.34; // ZN-CYS + const MonLib* monlib_ptr = nullptr; + std::multimap links; + + void index_chem_links(const MonLib& monlib, bool use_alias=true) { + std::map>> aliases; + if (use_alias) + for (const auto& iter : monlib.monomers) + for (const ChemComp::Aliasing& a : iter.second.aliases) + for (const std::pair& r : a.related) { + const ChemComp::Group& gr = ChemComp::is_nucleotide_group(a.group) ? ChemComp::Group::DnaRna : a.group; + aliases[gr][r.second].push_back(r.first); + } + + for (const auto& iter : monlib.links) { + const ChemLink& link = iter.second; + if (link.rt.bonds.empty()) + continue; + if (link.rt.bonds.size() > 1) + fprintf(stderr, "Note: considering only the first bond in %s\n", + link.id.c_str()); + if (link.side1.comp.empty() && link.side2.comp.empty()) + if (link.side1.group == ChemComp::Group::Null || + link.side2.group == ChemComp::Group::Null || + link.id == "SS") + continue; + const Restraints::Bond& bond = link.rt.bonds[0]; + if (bond.value > global_max_dist) + global_max_dist = bond.value; + links.emplace(bond.lexicographic_str(), &link); + + if (!use_alias || (!link.side1.comp.empty() && !link.side2.comp.empty())) + continue; + std::vector *names1 = nullptr, *names2 = nullptr; + if (link.side1.comp.empty()) { + auto i = aliases.find(link.side1.group); + if (i != aliases.end()) { + auto j = i->second.find(bond.id1.atom); + if (j != i->second.end()) + names1 = &j->second; + } + } + if (link.side2.comp.empty()) { + auto i = aliases.find(link.side2.group); + if (i != aliases.end()) { + auto j = i->second.find(bond.id2.atom); + if (j != i->second.end()) + names2 = &j->second; + } + } + if (names1 && names2) + for (const std::string& n1 : *names1) + for (const std::string& n2 : *names2) + links.emplace(Restraints::lexicographic_str(n1, n2), &link); + else if (names1 || names2) { + const std::string& n1 = names1 ? bond.id2.atom : bond.id1.atom; + for (const std::string& n2 : (names1 ? *names1 : *names2)) + links.emplace(Restraints::lexicographic_str(n1, n2), &link); + } + } + monlib_ptr = &monlib; + } + + std::vector find_possible_links(Structure& st, + double bond_margin, + double radius_margin, + ContactSearch::Ignore ignore) { + std::vector results; + Model& model = st.first_model(); + double search_radius = std::max(global_max_dist * bond_margin, + /*max r1+r2 ~=*/3.0 * radius_margin); + NeighborSearch ns(model, st.cell, std::max(5.0, search_radius)); + ns.populate(); + + ContactSearch contacts((float) search_radius); + contacts.ignore = ignore; + contacts.for_each_contact(ns, [&](const CRA& cra1, const CRA& cra2, + int image_idx, double dist_sq) { + Match match; + + // search for a match in chem_links + if (bond_margin > 0) { + auto range = links.equal_range(Restraints::lexicographic_str( + cra1.atom->name, cra2.atom->name)); + // similar to MonLib::match_link() + for (auto iter = range.first; iter != range.second; ++iter) { + const ChemLink& link = *iter->second; + const Restraints::Bond& bond = link.rt.bonds[0]; + if (dist_sq > sq(bond.value * bond_margin)) + continue; + const ChemComp::Aliasing* aliasing1 = nullptr; + const ChemComp::Aliasing* aliasing2 = nullptr; + bool order1; + if (monlib_ptr->link_side_matches_residue(link.side1, cra1.residue->name, &aliasing1) && + monlib_ptr->link_side_matches_residue(link.side2, cra2.residue->name, &aliasing2) && + atom_match_with_alias(bond.id1.atom, cra1.atom->name, aliasing1)) + order1 = true; + else if (monlib_ptr->link_side_matches_residue(link.side2, cra1.residue->name, &aliasing1) && + monlib_ptr->link_side_matches_residue(link.side1, cra2.residue->name, &aliasing2) && + atom_match_with_alias(bond.id2.atom, cra1.atom->name, aliasing1)) + order1 = false; + else + continue; + int link_score = link.calculate_score( + order1 ? *cra1.residue : *cra2.residue, + order1 ? cra2.residue : cra1.residue, + order1 ? cra1.atom->altloc : cra2.atom->altloc, + order1 ? cra2.atom->altloc : cra1.atom->altloc, + order1 ? aliasing1 : aliasing2, + order1 ? aliasing2 : aliasing1); + match.chem_link_count++; + if (link_score > match.score) { + match.chem_link = &link; + match.score = link_score; + if (order1) { + match.cra1 = cra1; + match.cra2 = cra2; + } else { + match.cra1 = cra2; + match.cra2 = cra1; + } + } + } + } + + // potential other links according to covalent radii + if (!match.chem_link) { + float r1 = cra1.atom->element.covalent_r(); + float r2 = cra2.atom->element.covalent_r(); + if (dist_sq > sq((r1 + r2) * radius_margin)) + return; + match.cra1 = cra1; + match.cra2 = cra2; + } + + // finalize + match.same_image = !image_idx; + match.bond_length = std::sqrt(dist_sq); + results.push_back(match); + }); + + // add references to st.connections + for (Match& match : results) + match.conn = st.find_connection_by_cra(match.cra1, match.cra2); + + return results; + } +}; + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/metadata.hpp b/gemmi_gph/gemmi/metadata.hpp new file mode 100644 index 00000000..6d92c7a5 --- /dev/null +++ b/gemmi_gph/gemmi/metadata.hpp @@ -0,0 +1,390 @@ +// Copyright 2019 Global Phasing Ltd. +// +// Metadata from coordinate files. + +#ifndef GEMMI_METADATA_HPP_ +#define GEMMI_METADATA_HPP_ + +#include // for uint8_t, uint16_t +#include // for any_of +#include +#include +#include "math.hpp" // for Mat33 +#include "unitcell.hpp" // for Position, Asu +#include "seqid.hpp" // for SeqId + +namespace gemmi { + +// corresponds to the mmCIF _software category +struct SoftwareItem { + enum Classification { + DataCollection, DataExtraction, DataProcessing, DataReduction, + DataScaling, ModelBuilding, Phasing, Refinement, Unspecified + }; + std::string name; + std::string version; + std::string date; + std::string description; + std::string contact_author; + std::string contact_author_email; + Classification classification = Unspecified; +}; + +// Information from REMARK 200/230 is significantly expanded in PDBx/mmCIF. +// These remarks corresponds to data across 12 mmCIF categories +// including categories _exptl, _reflns, _exptl_crystal, _diffrn and others. +// _exptl and _reflns seem to be 1:1. Usually we have one experiment (_exptl), +// except for a joint refinement (e.g. X-ray + neutron data). +// Both crystal (_exptl_crystal) and reflection statistics (_reflns) can +// be associated with multiple diffraction sets (_diffrn). +// But if we use the PDB format, only one diffraction set per method +// can be described. + +struct ReflectionsInfo { + double resolution_high = NAN; // _reflns.d_resolution_high + // (or _reflns_shell.d_res_high) + double resolution_low = NAN; // _reflns.d_resolution_low + double completeness = NAN; // _reflns.percent_possible_obs + double redundancy = NAN; // _reflns.pdbx_redundancy + double r_merge = NAN; // _reflns.pdbx_Rmerge_I_obs + double r_sym = NAN; // _reflns.pdbx_Rsym_value + double mean_I_over_sigma = NAN; // _reflns.pdbx_netI_over_sigmaI +}; + +// _exptl has no id, _exptl.method is key item and must be unique +struct ExperimentInfo { + std::string method; // _exptl.method + int number_of_crystals = -1; // _exptl.crystals_number + int unique_reflections = -1; // _reflns.number_obs + ReflectionsInfo reflections; + double b_wilson = NAN; // _reflns.B_iso_Wilson_estimate + std::vector shells; + std::vector diffraction_ids; +}; + +struct DiffractionInfo { + std::string id; // _diffrn.id + double temperature = NAN; // _diffrn.ambient_temp + std::string source; // _diffrn_source.source + std::string source_type; // _diffrn_source.type + std::string synchrotron; // _diffrn_source.pdbx_synchrotron_site + std::string beamline; // _diffrn_source.pdbx_synchrotron_beamline + std::string wavelengths; // _diffrn_source.pdbx_wavelength + std::string scattering_type; // _diffrn_radiation.pdbx_scattering_type + char mono_or_laue = '\0'; // _diffrn_radiation.pdbx_monochromatic_or_laue_m_l + std::string monochromator; // _diffrn_radiation.monochromator + std::string collection_date; // _diffrn_detector.pdbx_collection_date + std::string optics; // _diffrn_detector.details + std::string detector; // _diffrn_detector.detector + std::string detector_make; // _diffrn_detector.type +}; + +struct CrystalInfo { + std::string id; // _exptl_crystal.id + std::string description; // _exptl_crystal.description + double ph = NAN; // _exptl_crystal_grow.pH + std::string ph_range; // _exptl_crystal_grow.pdbx_pH_range + std::vector diffractions; +}; + + +struct TlsGroup { + struct Selection { + std::string chain; + SeqId res_begin; + SeqId res_end; + std::string details; // _pdbx_refine_tls_group.selection_details + }; + short num_id = -1; // id stored as number (optimization) + std::string id; // _pdbx_refine_tls.id + std::vector selections; + Position origin; // _pdbx_refine_tls.origin_x/y/z + SMat33 T = {NAN, NAN, NAN, NAN, NAN, NAN}; // _pdbx_refine_tls.T[][] + SMat33 L = {NAN, NAN, NAN, NAN, NAN, NAN}; // _pdbx_refine_tls.L[][] + Mat33 S = Mat33{NAN}; // _pdbx_refine_tls.S[][] +}; + +// RefinementInfo corresponds to REMARK 3. +// BasicRefinementInfo is used for both total and per-bin statistics. +// For per-bin data, each values corresponds to one _refine_ls_shell.* tag. +struct BasicRefinementInfo { + double resolution_high = NAN; // _refine.ls_d_res_high, _refine_ls_shell.d_res_high + double resolution_low = NAN; // _refine.ls_d_res_low, _refine_ls_shell.d_res_low + double completeness = NAN; // _refine.ls_percent_reflns_obs, _refine_ls_shell.percent... + int reflection_count = -1; // _refine.ls_number_reflns_obs, _refine_ls_shell.number... + int work_set_count = -1; // _refine.ls_number_reflns_R_work, _refine_ls_shell.number... + int rfree_set_count = -1; // _refine.ls_number_reflns_R_free, _refine_ls_shell.number... + double r_all = NAN; // _refine.ls_R_factor_obs, _refine_ls_shell.R_factor_obs + double r_work = NAN; // _refine.ls_R_factor_R_work, _refine_ls_shell.R_factor_R_work + double r_free = NAN; // _refine.ls_R_factor_R_free, _refine_ls_shell.R_factor_R_free + double cc_fo_fc_work = NAN; // _refine.correlation_coeff_Fo_to_Fc, _refine_ls_shell.corr... + double cc_fo_fc_free = NAN; // _refine.correlation_coeff_Fo_to_Fc_free, _refine_ls_shell.c... + double fsc_work = NAN; // _refine.pdbx_average_fsc_work, _refine_ls_shell.pdbx_fsc_work + double fsc_free = NAN; // _refine.pdbx_average_fsc_free, _refine_ls_shell.pdbx_fsc_free + double cc_intensity_work = NAN; // _refine.correlation_coeff_I_to_Fcsqd_work, ... + double cc_intensity_free = NAN; // _refine.correlation_coeff_I_to_Fcsqd_free, ... +}; + +struct RefinementInfo : BasicRefinementInfo { + struct Restr { + std::string name; + int count = -1; + double weight = NAN; + std::string function; + double dev_ideal = NAN; + + Restr() = default; + explicit Restr(const std::string& name_) : name(name_) {} + }; + std::string id; + std::string cross_validation_method; // _refine.pdbx_ls_cross_valid_method + std::string rfree_selection_method; // _refine.pdbx_R_Free_selection_details + int bin_count = -1; // _refine_ls_shell.pdbx_total_number_of_bins_used + std::vector bins; + double mean_b = NAN; // _refine.B_iso_mean + SMat33 aniso_b{NAN, NAN, NAN, NAN, NAN, NAN}; // _refine.aniso_B[][] + double luzzati_error = NAN; // _refine_analyze.Luzzati_coordinate_error_obs + double dpi_blow_r = NAN; // _refine.pdbx_overall_SU_R_Blow_DPI + double dpi_blow_rfree = NAN; // _refine.pdbx_overall_SU_R_free_Blow_DPI + double dpi_cruickshank_r = NAN; // _refine.overall_SU_R_Cruickshank_DPI + double dpi_cruickshank_rfree = NAN; // _refine.pdbx_overall_SU_R_free_Cruickshank_DPI + std::vector restr_stats; // _refine_ls_restr + std::vector tls_groups; // _pdbx_refine_tls + std::string remarks; +}; + + +struct Metadata { + std::vector authors; // _audit_author.name + std::vector experiments; + std::vector crystals; + std::vector refinement; + std::vector software; + std::string solved_by; // _refine.pdbx_method_to_determine_struct + std::string starting_model; // _refine.pdbx_starting_model + std::string remark_300_detail; // _struct_biol.details + + bool has(double RefinementInfo::*field) const { + return std::any_of(refinement.begin(), refinement.end(), + [&](const RefinementInfo& r) { return !std::isnan(r.*field); }); + } + bool has(int RefinementInfo::*field) const { + return std::any_of(refinement.begin(), refinement.end(), + [&](const RefinementInfo& r) { return r.*field != -1; }); + } + bool has(std::string RefinementInfo::*field) const { + return std::any_of(refinement.begin(), refinement.end(), + [&](const RefinementInfo& r) { return !(r.*field).empty(); }); + } + bool has(SMat33 RefinementInfo::*field) const { + return std::any_of(refinement.begin(), refinement.end(), + [&](const RefinementInfo& r) { return !std::isnan((r.*field).u11); }); + } + bool has_restr() const { + return std::any_of(refinement.begin(), refinement.end(), + [&](const RefinementInfo& r) { return !r.restr_stats.empty(); }); + } + + // TLS constraint are not specific to refinement in joint refinement, + // so they are expected to be present only in a single RefinementInfo. + // As of 2025, two PDB entries have TLS + joint refinement: 6N3U and 5NKU. + std::vector* get_tls_groups() { + for (gemmi::RefinementInfo& ref : refinement) + if (!ref.tls_groups.empty()) + return &ref.tls_groups; + return nullptr; + } + const std::vector* get_tls_groups() const { + return const_cast(this)->get_tls_groups(); + } +}; + + +// Entity description. +// +// values corresponding to mmCIF _entity.type +enum class EntityType : unsigned char { + Unknown, + Polymer, + NonPolymer, + Branched, // introduced in 2020 + // _entity.type macrolide is in PDBx/mmCIF, but no PDB entry uses it + //Macrolide, + Water +}; + +// values corresponding to mmCIF _entity_poly.type +enum class PolymerType : unsigned char { + Unknown, // unknown or not applicable + PeptideL, // polypeptide(L) in mmCIF (168923 values in the PDB in 2017) + PeptideD, // polypeptide(D) (57 values) + Dna, // polydeoxyribonucleotide (9905) + Rna, // polyribonucleotide (4559) + DnaRnaHybrid, // polydeoxyribonucleotide/polyribonucleotide hybrid (156) + SaccharideD, // polysaccharide(D) (18) + SaccharideL, // polysaccharide(L) (0) + Pna, // peptide nucleic acid (2) + CyclicPseudoPeptide, // cyclic-pseudo-peptide (1) + Other, // other (4) +}; + +inline bool is_polypeptide(PolymerType pt) { + return pt == PolymerType::PeptideL || pt == PolymerType::PeptideD; +} + +inline bool is_polynucleotide(PolymerType pt) { + return pt == PolymerType::Dna || pt == PolymerType::Rna || + pt == PolymerType::DnaRnaHybrid; +} + +struct Entity { + struct DbRef { + std::string db_name; + std::string accession_code; + std::string id_code; + std::string isoform; // pdbx_db_isoform + SeqId seq_begin, seq_end; + SeqId db_begin, db_end; + SeqId::OptionalNum label_seq_begin, label_seq_end; + }; + std::string name; + std::vector subchains; + EntityType entity_type = EntityType::Unknown; + PolymerType polymer_type = PolymerType::Unknown; + // In case of microheterogeneity, PDB SEQRES has only the first residue name. + bool reflects_microhetero = false; + std::vector dbrefs; + /// List of SIFTS Uniprot ACs referenced by SiftsUnpResidue::acc_index + std::vector sifts_unp_acc; + /// SEQRES or entity_poly_seq with microheterogeneity as comma-separated names + std::vector full_sequence; + + Entity() = default; + explicit Entity(const std::string& name_) noexcept : name(name_) {} + static std::string first_mon(const std::string& mon_list) { + return mon_list.substr(0, mon_list.find(',')); + } +}; + +/// Reference to UniProt residue, based on _pdbx_sifts_xref_db. +/// Used in Residue::sifts_unp. res==0 <=> unset. +struct SiftsUnpResidue { + char res = '\0'; // _pdbx_sifts_xref_db.unp_res + std::uint8_t acc_index = 0; // index of Entity::sifts_unp_acc + std::uint16_t num = 0; // _pdbx_sifts_xref_db.unp_num +}; + +// A connection. Corresponds to _struct_conn. +// Symmetry operators are not trusted and not stored. +// We assume that the nearest symmetry mate is connected. +struct Connection { + // in write_struct_conn() we assume that Unknown is at the end + enum Type : unsigned char { Covale=0, Disulf, Hydrog, MetalC, Unknown }; + std::string name; + std::string link_id; // _struct_conn.ccp4_link_id (== _chem_link.id) + Type type = Unknown; + Asu asu = Asu::Any; + AtomAddress partner1, partner2; + double reported_distance = 0.0; + short reported_sym[4] = {}; // don't rely on it, for internal use only +}; + +// Corresponds to CISPEP or _struct_mon_prot_cis +struct CisPep { + AtomAddress partner_c, partner_n; + int model_num = 0; + // mmCIF has (unused by the PDB) tag _struct_mon_prot_cis.label_alt_id + // that enables defining CIS link per conformation. + char only_altloc = '\0'; + double reported_angle = NAN; +}; + +struct ModRes { + std::string chain_name; + ResidueId res_id; + std::string parent_comp_id; + std::string mod_id; // non-standard extension used in Refmac + std::string details; +}; + +// Secondary structure. PDBx/mmCIF stores helices and sheets separately. + +// mmCIF spec defines 32 possible values for _struct_conf.conf_type_id - +// "the type of the conformation of the backbone of the polymer (whether +// protein or nucleic acid)". But as of 2019 only HELX_P is used (not counting +// TURN_P that occurs in only 6 entries). The actual helix type is given +// by numeric value of _struct_conf.pdbx_PDB_helix_class, which corresponds +// to helixClass from the PDB HELIX record. These values are in the range 1-10. +// As of 2019 it's almost only type 1 and 5: +// 3116566 of 1 - right-handed alpha +// 16 of 2 - right-handed omega +// 84 of 3 - right-handed pi +// 79 of 4 - right-handed gamma +// 1063337 of 5 - right-handed 3-10 +// 27 of 6 - left-handed alpha +// 5 of 7 - left-handed omega +// 2 of 8 - left-handed gamma +// 8 of 9 - 2-7 ribbon/helix +// 46 of 10 - polyproline +struct Helix { + enum HelixClass { + UnknownHelix, RAlpha, ROmega, RPi, RGamma, R310, + LAlpha, LOmega, LGamma, Helix27, HelixPolyProlineNone + }; + AtomAddress start, end; + HelixClass pdb_helix_class = UnknownHelix; + int length = -1; + void set_helix_class_as_int(int n) { + if (n >= 1 && n <= 10) + pdb_helix_class = static_cast(n); + } +}; + +struct Sheet { + struct Strand { + AtomAddress start, end; + AtomAddress hbond_atom2, hbond_atom1; + int sense; // 0 = first strand, 1 = parallel, -1 = anti-parallel. + std::string name; // optional, _struct_sheet_range.id if from mmCIF + }; + std::string name; + std::vector strands; + + Sheet() = default; + explicit Sheet(const std::string& sheet_id) noexcept : name(sheet_id) {} +}; + + +// bioassembly / biomolecule +struct Assembly { + struct Operator { + std::string name; // optional + std::string type; // optional (from mmCIF only) + Transform transform; + }; + struct Gen { + std::vector chains; + std::vector subchains; + std::vector operators; + }; + enum class SpecialKind : unsigned char { + NA, CompleteIcosahedral, RepresentativeHelical, CompletePoint + }; + std::string name; + bool author_determined = false; + bool software_determined = false; + SpecialKind special_kind = SpecialKind::NA; + int oligomeric_count = 0; + std::string oligomeric_details; + std::string software_name; + double absa = NAN; // TOTAL BURIED SURFACE AREA: ... ANGSTROM**2 + double ssa = NAN; // SURFACE AREA OF THE COMPLEX: ... ANGSTROM**2 + double more = NAN; // CHANGE IN SOLVENT FREE ENERGY: ... KCAL/MOL + std::vector generators; + + Assembly() = default; + explicit Assembly(const std::string& name_) : name(name_) {} +}; + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/mmcif.hpp b/gemmi_gph/gemmi/mmcif.hpp new file mode 100644 index 00000000..988b4396 --- /dev/null +++ b/gemmi_gph/gemmi/mmcif.hpp @@ -0,0 +1,112 @@ +// Copyright 2017 Global Phasing Ltd. +// +// Read mmCIF (PDBx/mmCIF) file into a Structure from model.hpp. + +#ifndef GEMMI_MMCIF_HPP_ +#define GEMMI_MMCIF_HPP_ + +#include +#include "cifdoc.hpp" // for Block, etc +#include "fail.hpp" // for fail +#include "model.hpp" // for Structure + +namespace gemmi { + +/// structure from a coordinate mmCIF block +GEMMI_DLL void populate_structure_from_block(const cif::Block& block_, Structure& st); + +inline Structure make_structure_from_block(const cif::Block& block_) { + gemmi::Structure st; + populate_structure_from_block(block_, st); + return st; +} + +/// structure from a coordinate mmCIF document +inline Structure make_structure(cif::Document&& doc, cif::Document* save_doc=nullptr) { + // mmCIF files for deposition may have more than one block: + // coordinates in the first block and restraints in the others. + for (size_t i = 1; i < doc.blocks.size(); ++i) + if (doc.blocks[i].has_tag("_atom_site.id")) + fail("2+ blocks are ok if only the first one has coordinates;\n" + "_atom_site in block #" + std::to_string(i+1) + ": " + doc.source); + Structure st = make_structure_from_block(doc.blocks.at(0)); + if (save_doc) + *save_doc = std::move(doc); + return st; +} + +// Reading chemical component as a coordinate file. +enum class ChemCompModel { + Xyz = 1, // _chem_comp_atom.x, etc + Example = 2, // _chem_comp_atom.model_Cartn_x + Ideal = 4, // _chem_comp_atom.pdbx_model_Cartn_x_ideal + First = 8 // whatever comes first in the input file +}; + +constexpr int operator|(ChemCompModel a, ChemCompModel b) { return (int)a | (int)b; } + +/// make_residue_from_chemcomp_block +GEMMI_DLL Residue make_residue_from_chemcomp_block(const cif::Block& block, ChemCompModel kind); + +inline Model make_model_from_chemcomp_block(const cif::Block& block, ChemCompModel kind) { + Model model; + model.chains.emplace_back(""); + model.chains[0].residues.push_back(make_residue_from_chemcomp_block(block, kind)); + return model; +} + +// For CCD input - returns a structure with two single-residue models: +// example (model_Cartn_x) and ideal (pdbx_model_Cartn_x_ideal). +// For Refmac dictionary (monomer library) files returns structure with +// a single model. +inline Structure make_structure_from_chemcomp_block(const cif::Block& block, int which=7) { + Structure st; + st.input_format = CoorFormat::ChemComp; + if (const std::string* name = block.find_value("_chem_comp.id")) + st.name = *name; + auto ok = [which](ChemCompModel x) { return which & static_cast(x); }; + if (ok(ChemCompModel::Xyz) && block.has_any_value("_chem_comp_atom.x")) + st.models.push_back(make_model_from_chemcomp_block(block, ChemCompModel::Xyz)); + if (ok(ChemCompModel::Example) && block.has_any_value("_chem_comp_atom.model_Cartn_x")) + st.models.push_back(make_model_from_chemcomp_block(block, ChemCompModel::Example)); + if (ok(ChemCompModel::Ideal) && block.has_any_value("_chem_comp_atom.pdbx_model_Cartn_x_ideal")) + st.models.push_back(make_model_from_chemcomp_block(block, ChemCompModel::Ideal)); + st.renumber_models(); + return st; +} + +// a helper function for use with make_structure_from_chemcomp_block(): +// int n = check_chemcomp_block_number(doc); +// if (n != -1) +// Structure st = make_structure_from_chemcomp_block(doc.blocks[n]); +inline int check_chemcomp_block_number(const cif::Document& doc) { + // monomer library file without global_ + if (doc.blocks.size() == 2 && doc.blocks[0].name == "comp_list") + return 1; + // monomer library file with global_ + if (doc.blocks.size() == 3 && doc.blocks[0].name.empty() && + doc.blocks[1].name == "comp_list") + return 2; + // CCD file + if (doc.blocks.size() == 1 && + !doc.blocks[0].has_tag("_atom_site.id") && + !doc.blocks[0].has_tag("_cell.length_a") && + doc.blocks[0].has_tag("_chem_comp_atom.atom_id")) + return 0; + return -1; +} + +inline Structure make_structure_from_chemcomp_doc(const cif::Document& doc, + cif::Document* save_doc=nullptr, + int which=7) { + int n = check_chemcomp_block_number(doc); + if (n == -1) + fail("Not a chem_comp format."); + Structure st = make_structure_from_chemcomp_block(doc.blocks[n], which); + if (save_doc) + *save_doc = std::move(doc); + return st; +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/mmcif_impl.hpp b/gemmi_gph/gemmi/mmcif_impl.hpp new file mode 100644 index 00000000..505a32c2 --- /dev/null +++ b/gemmi_gph/gemmi/mmcif_impl.hpp @@ -0,0 +1,37 @@ +// Copyright 2019 Global Phasing Ltd. +// +// Functions used in both mmcif.hpp and refln.hpp (for coordinate and +// reflection mmCIF files). + +#ifndef GEMMI_MMCIF_IMPL_HPP_ +#define GEMMI_MMCIF_IMPL_HPP_ + +#include "cifdoc.hpp" // for cif::Block +#include "numb.hpp" // for cif::as_number +#include "unitcell.hpp" // for UnitCell + +namespace gemmi { +namespace impl { + +inline void set_cell_from_mmcif(cif::Block& block, UnitCell& cell, + bool mmcif=true) { + cif::Table tab = block.find((mmcif ? "_cell." : "_cell_"), + {"length_a", "length_b", "length_c", + "angle_alpha", "angle_beta", "angle_gamma"}); + if (tab.ok()) { + auto c = tab.one(); + if (!cif::is_null(c[0]) && !cif::is_null(c[1]) && !cif::is_null(c[2])) + cell.set(cif::as_number(c[0]), cif::as_number(c[1]), cif::as_number(c[2]), + cif::as_number(c[3]), cif::as_number(c[4]), cif::as_number(c[5])); + } +} + +inline const std::string* find_spacegroup_hm_value(const cif::Block& block) { + const char* hm_tag = "_symmetry.space_group_name_H-M"; + return block.find_value(hm_tag); +} + +} // namespace impl +} // namespace gemmi + +#endif diff --git a/gemmi_gph/gemmi/mmdb.hpp b/gemmi_gph/gemmi/mmdb.hpp new file mode 100644 index 00000000..3060be92 --- /dev/null +++ b/gemmi_gph/gemmi/mmdb.hpp @@ -0,0 +1,501 @@ +// Copyright 2020-2022 Global Phasing Ltd. +// +// Converts between gemmi::Structure and mmdb::Manager. + +#ifndef GEMMI_MMDB_HPP_ +#define GEMMI_MMDB_HPP_ + +#include // for atoi +#include // for memcpy +#include "model.hpp" +#include "util.hpp" // for rtrim_str +#include "polyheur.hpp" // for assign_subchains +#include + +namespace gemmi { + +inline void copy_transform_to_mmdb(const Transform& tr, + mmdb::mat33& mat, mmdb::vect3& vec) { + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) + mat[i][j] = tr.mat[i][j]; + vec[i] = tr.vec.at(i); + } +} + +template +void strcpy_to_mmdb(char (&dest)[N], const std::string& src) { + if (src.size() + 1 >= N) + fail("This string is too long: " + src); + std::memcpy(dest, src.c_str(), src.size() + 1); +} + +inline void set_seqid_in_mmdb(int* seqnum, mmdb::InsCode& icode, SeqId seqid) { + *seqnum = *seqid.num; + icode[0] = icode[1] = '\0'; + if (seqid.has_icode()) + icode[0] = seqid.icode; +} + +inline SeqId seqid_from_mmdb(int seqnum, const mmdb::InsCode& inscode) { + return SeqId(seqnum, inscode[0] ? inscode[0] : ' '); +} + +inline mmdb::CisPep* cispep_to_mmdb(const CisPep& g, int ser_num, int model_num) { + mmdb::CisPep* m = new mmdb::CisPep(); + m->serNum = ser_num; + strcpy_to_mmdb(m->pep1, g.partner_c.res_id.name); + strcpy_to_mmdb(m->chainID1, g.partner_c.chain_name); + set_seqid_in_mmdb(&m->seqNum1, m->icode1, g.partner_c.res_id.seqid); + strcpy_to_mmdb(m->pep2, g.partner_n.res_id.name); + strcpy_to_mmdb(m->chainID2, g.partner_n.chain_name); + set_seqid_in_mmdb(&m->seqNum2, m->icode2, g.partner_n.res_id.seqid); + m->modNum = model_num; + m->measure = g.reported_angle; + return m; +} + +inline CisPep cispep_from_mmdb(const mmdb::CisPep& m, int model_num) { + CisPep g; + g.partner_c.res_id.name = m.pep1; + g.partner_c.res_id.seqid = seqid_from_mmdb(m.seqNum1, m.icode1); + g.partner_c.chain_name = m.chainID1; + g.partner_n.res_id.name = m.pep2; + g.partner_n.res_id.seqid = seqid_from_mmdb(m.seqNum2, m.icode2); + g.partner_n.chain_name = m.chainID2; + g.model_num = model_num; + g.reported_angle = m.measure; + return g; +} + +inline void transfer_links_to_mmdb(const Structure& st, mmdb::Manager* mol) { + // based on code provided by Paul Emsley + for (const Connection& con : st.connections) { + if (!con.partner1.res_id.seqid.num || !con.partner2.res_id.seqid.num) + continue; + mmdb::Link link{}; + // partner1 + strcpy_to_mmdb(link.atName1, con.partner1.atom_name); + link.aloc1[0] = con.partner1.altloc; + set_seqid_in_mmdb(&link.seqNum1, link.insCode1, con.partner1.res_id.seqid); + strcpy_to_mmdb(link.resName1, con.partner1.res_id.name); + strcpy_to_mmdb(link.chainID1, con.partner1.chain_name); + // partner2 + strcpy_to_mmdb(link.atName2, con.partner2.atom_name); + link.aloc2[0] = con.partner2.altloc; + set_seqid_in_mmdb(&link.seqNum2, link.insCode2, con.partner2.res_id.seqid); + strcpy_to_mmdb(link.resName2, con.partner2.res_id.name); + strcpy_to_mmdb(link.chainID2, con.partner2.chain_name); + if (con.reported_distance > 0) + link.dist = con.reported_distance; + if (con.asu == Asu::Different) { + link.s2 = con.reported_sym[0]; + link.i2 = link.i1 + con.reported_sym[1]; + link.j2 = link.j1 + con.reported_sym[2]; + link.k2 = link.k1 + con.reported_sym[3]; + } + // add links to models + for (int imod = 1; imod <= mol->GetNumberOfModels(); imod++) + if (mmdb::Model* model_p = mol->GetModel(imod)) + model_p->AddLink(new mmdb::Link(link)); + } +} + +// ignoring LinkR's here +inline void transfer_links_from_mmdb(mmdb::LinkContainer& mmdb_links, Structure& st) { + for (int i = 0; i < mmdb_links.Length(); ++i) { + mmdb::Link& link = *static_cast(mmdb_links.GetContainerClass(i)); + Connection con; + // partner1 + con.partner1.atom_name = link.atName1; + con.partner1.altloc = link.aloc1[0]; + con.partner1.res_id.seqid = seqid_from_mmdb(link.seqNum1, link.insCode1); + con.partner1.res_id.name = link.resName1; + con.partner1.chain_name = link.chainID1; + // partner2 + con.partner2.atom_name = link.atName2; + con.partner2.altloc = link.aloc2[0]; + con.partner2.res_id.seqid = seqid_from_mmdb(link.seqNum2, link.insCode2); + con.partner2.res_id.name = link.resName2; + con.partner2.chain_name = link.chainID2; + con.reported_distance = link.dist; + if (link.s1 == link.s2 && link.i1 == link.i2 && link.j1 == link.j2 && link.k1 == link.k2) { + con.asu = Asu::Same; + } else { + con.asu = Asu::Different; + if (link.s1 == 1) + con.reported_sym[0] = link.s2; + con.reported_sym[1] = link.i2 - link.i1; + con.reported_sym[2] = link.j2 - link.j1; + con.reported_sym[3] = link.k2 - link.k1; + } + // for LinkR we'd also have: + // con.link_id = link.linkRID; + st.connections.push_back(con); + } +} + +// Helper function to convert gemmi sequence to mmdb ResName array +inline void set_mmdb_seqres(const std::vector& sequence, + mmdb::SeqRes& seqres) { + // Free existing sequence data + delete[] seqres.resName; + if (seqres.resName) + seqres.resName = nullptr; + + if (sequence.empty()) { + seqres.numRes = 0; + return; + } + + // Allocate new array + seqres.numRes = static_cast(sequence.size()); + seqres.resName = new mmdb::ResName[seqres.numRes]; + + // Copy sequence data, taking first monomer from each item + for (int i = 0; i < seqres.numRes; ++i) { + // Handle microheterogeneity: use gemmi's utility function + std::string mon = Entity::first_mon(sequence[i]); + + // Copy residue name (max 3 characters for most cases, but ResName allows up to 19) + strcpy_to_mmdb(seqres.resName[i], mon); + } +} + +inline bool has_sequences(const Structure& st) { + // If there are no entities with sequences, try to infer sequences from chains + for (const Entity& entity : st.entities) { + if (entity.entity_type == EntityType::Polymer && !entity.full_sequence.empty()) + return true; + } + return false; +} + +// Transfer SEQRES from gemmi entities to mmdb chains +inline void transfer_seqres_to_mmdb(const Structure& st, mmdb::Manager* manager) { + if (!has_sequences(st)) + return; + + // Create a map of chain ID -> entity for faster lookup + std::map chain_to_entity; + for (const Model& model : st.models) + for (const Chain& ch : model.chains) { + const Entity* entity = st.get_entity_of(ch.get_polymer()); + chain_to_entity[ch.name] = entity; + } + + for (int imodel = 1; imodel <= manager->GetNumberOfModels(); ++imodel) { + if (mmdb::Model* model = manager->GetModel(imodel)) { + for (int ichain = 0; ichain < model->GetNumberOfChains(); ++ichain) { + if (mmdb::Chain* chain = model->GetChain(ichain)) { + std::string chain_id = chain->GetChainID(); + // MMDB uses empty string (not space) for blank chain IDs + + // Find matching entity + auto it = chain_to_entity.find(chain_id); + if (it != chain_to_entity.end()) { + if (const Entity* entity = it->second) + set_mmdb_seqres(entity->full_sequence, chain->seqRes); + // Set chain association to ensure consistency + chain->seqRes.SetChain(chain); + } + } + } + } + } +} + +inline void copy_to_mmdb(const Structure& st, mmdb::Manager* manager) { + for (const std::string& s : st.raw_remarks) { + std::string line = rtrim_str(s); + manager->PutPDBString(line.c_str()); + } + + for (int imodel = 0; imodel < (int) st.models.size(); ++imodel) { + const Model& model = st.models[imodel]; + mmdb::Model* model2 = mmdb::newModel(); + manager->AddModel(model2); + + for (const Chain& chain : model.chains) { + mmdb::Chain* chain2 = model2->CreateChain(chain.name.c_str()); + for (const Residue& res : chain.residues) { + char icode[2] = {}; + if (res.seqid.has_icode()) + icode[0] = res.seqid.icode; + mmdb::Residue* res2 = chain2->GetResidueCreate(res.name.c_str(), + *res.seqid.num, + icode, + true); + for (const Atom& atom : res.atoms) { + mmdb::Atom* atom2 = mmdb::newAtom(); + const char altloc[2] = {atom.altloc, '\0'}; + std::string padded_name = atom.padded_name(); + // padded_name() is padding from the left; MMDB from both sides + if (padded_name.size() < 4) + padded_name.resize(4, ' '); + atom2->SetAtomName(0, atom.serial, padded_name.c_str(), + altloc, res.segment.c_str(), atom.element.uname()); + atom2->Het = res.het_flag == 'H'; + atom2->SetCharge(atom.charge); + atom2->SetCoordinates(atom.pos.x, atom.pos.y, atom.pos.z, + atom.occ, atom.b_iso); + if (atom.aniso.nonzero()) { + atom2->u11 = atom.aniso.u11; + atom2->u22 = atom.aniso.u22; + atom2->u33 = atom.aniso.u33; + atom2->u12 = atom.aniso.u12; + atom2->u13 = atom.aniso.u13; + atom2->u23 = atom.aniso.u23; + atom2->WhatIsSet |= mmdb::ASET_Anis_tFSigma; + } + res2->AddAtom(atom2); + } + // TER + if (res.entity_type == EntityType::Polymer && + (&res == &chain.residues.back() || + (&res + 1)->entity_type != EntityType::Polymer)) { + mmdb::Atom* atom2 = mmdb::newAtom(); + atom2->MakeTer(); + atom2->serNum = res.atoms.back().serial + 1; + res2->AddAtom(atom2); + } + } + } + manager->PutCell(st.cell.a, st.cell.b, st.cell.c, + st.cell.alpha, st.cell.beta, st.cell.gamma, 1); + manager->SetSpaceGroup(st.spacegroup_hm.c_str()); + mmdb::Cryst* cryst = manager->GetCrystData(); + auto z = st.info.find("_cell.Z_PDB"); + if (z != st.info.end() && !z->second.empty()) { + cryst->Z = std::atoi(z->second.c_str()); + cryst->WhatIsSet |= mmdb::CSET_ZValue; + } + if (st.has_origx && !st.origx.is_identity()) { + copy_transform_to_mmdb(st.origx, cryst->o, cryst->t); + cryst->WhatIsSet |= mmdb::CSET_OrigMatrix; + } + if (st.cell.explicit_matrices) { + copy_transform_to_mmdb(st.cell.frac, cryst->s, cryst->u); + cryst->WhatIsSet |= mmdb::CSET_ScaleMatrix; + } + if (!st.ncs.empty()) { + mmdb::mat33 m; + mmdb::vect3 v; + if (st.info.find("_struct_ncs_oper.id") != st.info.end()) { + copy_transform_to_mmdb(Transform{}, m, v); + cryst->AddNCSMatrix(m, v, 1); + } + for (const NcsOp& op : st.ncs) { + copy_transform_to_mmdb(op.tr, m, v); + cryst->AddNCSMatrix(m, v, op.given); + } + } + } + if (!st.cispeps.empty() && !st.models.empty()) { + int ser_num = 0; + for (const CisPep& cispep : st.cispeps) { + // In the PDB, CISPEP records have modNum=0 if there is only one model + int modnum = st.models.size() > 1 ? cispep.model_num : 0; + int model_no = std::max(1, modnum); // GetModel() takes 1-based index + if (mmdb::Model* m_model = manager->GetModel(model_no)) + m_model->AddCisPep(cispep_to_mmdb(cispep, ++ser_num, modnum)); + } + } + transfer_seqres_to_mmdb(st, manager); + transfer_links_to_mmdb(st, manager); +} + + +// don't use const for mmdb objects - MMDB doesn't have const method +inline Atom copy_atom_from_mmdb(mmdb::Atom& m_atom) { + Atom atom; + atom.name = m_atom.label_atom_id; + atom.altloc = m_atom.altLoc[0]; + atom.charge = (signed char) m_atom.charge; + atom.element = Element(m_atom.element); + atom.serial = m_atom.serNum; + atom.pos = Position(m_atom.x, m_atom.y, m_atom.z); + atom.occ = (float) m_atom.occupancy; + atom.b_iso = (float) m_atom.tempFactor; + if (m_atom.WhatIsSet & mmdb::ASET_Anis_tFSigma) { + atom.aniso.u11 = (float) m_atom.u11; + atom.aniso.u22 = (float) m_atom.u22; + atom.aniso.u33 = (float) m_atom.u33; + atom.aniso.u12 = (float) m_atom.u12; + atom.aniso.u13 = (float) m_atom.u13; + atom.aniso.u23 = (float) m_atom.u23; + } + return atom; +} + +inline Residue copy_residue_from_mmdb(mmdb::Residue& m_res) { + Residue res; + res.name = m_res.name; + res.seqid = seqid_from_mmdb(m_res.seqNum, m_res.insCode); + int n = m_res.GetNumberOfAtoms(); + res.atoms.reserve(n); + bool first = true; + for (int i = 0; i < n; ++i) + if (mmdb::Atom* m_atom = m_res.GetAtom(i)) { + if (m_atom->isTer()) { + res.entity_type = EntityType::Polymer; + } else { + res.atoms.push_back(copy_atom_from_mmdb(*m_atom)); + if (first) { + res.het_flag = m_atom->Het ? 'H' : 'A'; + res.segment = m_atom->segID; + first = false; + } + } + } + return res; +} + +// Helper function to convert mmdb ResName array to gemmi sequence +inline std::vector get_gemmi_sequence(const mmdb::SeqRes& seqres) { + std::vector sequence; + if (seqres.numRes > 0 && seqres.resName) { + sequence.reserve(seqres.numRes); + for (int i = 0; i < seqres.numRes; ++i) { + const char* s = seqres.resName[i]; + std::string resname(s, rtrim_cstr(s)); + if (!resname.empty()) + sequence.push_back(resname); + } + } + return sequence; +} + +inline Chain copy_chain_from_mmdb(Structure& st, mmdb::Chain& m_chain) { + Chain chain(m_chain.GetChainID()); + int n = m_chain.GetNumberOfResidues(); + chain.residues.reserve(n); + for (int i = 0; i < n; ++i) + if (mmdb::Residue* m_res = m_chain.GetResidue(i)) + chain.residues.push_back(copy_residue_from_mmdb(*m_res)); + // in MMDB we may have pseudo-atom TER that marks polymer end + for (auto i = chain.residues.begin(); i != chain.residues.end(); ++i) + if (i->entity_type == EntityType::Polymer) { // residue before TER + for (auto j = chain.residues.begin(); j != i; ++j) + j->entity_type = EntityType::Polymer; + for (auto j = i + 1; j != chain.residues.end(); ++j) + j->entity_type = j->is_water() ? EntityType::Water + : EntityType::NonPolymer; + break; + } + // Get sequence from SEQRES if available + std::vector sequence = get_gemmi_sequence(m_chain.seqRes); + if (!sequence.empty()) { + Entity& ent = impl::find_or_add(st.entities, chain.name); + ent.entity_type = EntityType::Polymer; + ent.full_sequence = sequence; + } + return chain; +} + +inline Model copy_model_from_mmdb(Structure& st, mmdb::Model& m_model) { + Model model(m_model.GetSerNum()); + int n = m_model.GetNumberOfChains(); + model.chains.reserve(n); + for (int i = 0; i < n; ++i) + if (mmdb::Chain* m_chain = m_model.GetChain(i)) + model.chains.push_back(copy_chain_from_mmdb(st, *m_chain)); + ensure_entities(st); + for (Model& m : st.models) + for (Chain& chain : m.chains) { + Entity& ent = impl::find_or_add(st.entities, chain.name); + ent.subchains.push_back(chain.get_polymer().subchain_id()); + printf("Dodajemy Entity %s - %s\n", + chain.name.c_str(), chain.get_polymer().subchain_id().c_str()); + } + deduplicate_entities(st); + return model; +} + +inline Structure copy_from_mmdb(mmdb::Manager* manager) { + Structure st; + const mmdb::Cryst& cryst = *manager->GetCrystData(); + st.cell.set(cryst.a, cryst.b, cryst.c, cryst.alpha, cryst.beta, cryst.gamma); + if (cryst.WhatIsSet & mmdb::CSET_ZValue) + st.info.emplace("_cell.Z_PDB", std::to_string(cryst.Z)); + st.spacegroup_hm = cryst.spaceGroup; + int n = manager->GetNumberOfModels(); + st.models.reserve(n); + for (int i = 1; i <= n; ++i) + if (mmdb::Model* m_model = manager->GetModel(i)) { + st.models.push_back(copy_model_from_mmdb(st, *m_model)); + int model_num = st.models.back().num; + for (int j = 1; j <= m_model->GetNumberOfCisPeps(); ++j) + if (const mmdb::CisPep* m_cispep = m_model->GetCisPep(j)) + st.cispeps.push_back(cispep_from_mmdb(*m_cispep, model_num)); + } + if (n > 0) { + mmdb::Model* mmdb_model = manager->GetModel(1); + transfer_links_from_mmdb(*mmdb_model->GetLinks(), st); + } + st.input_format = CoorFormat::Pdb; + return st; +} + +} // namespace gemmi +#endif + + +/* +// Example 1. +// Read a coordinate file using gemmi and write it to pdb using mmdb. + +#include +#include + +// two arguments expected: input and output paths. +int main (int argc, char** argv) { + if (argc != 3) + return 1; + + mmdb::InitMatType(); + mmdb::Manager* manager = new mmdb::Manager(); + try { + gemmi::Structure st = gemmi::read_structure_gz(argv[1]); + st.merge_chain_parts(); + gemmi::copy_to_mmdb(st, manager); + } catch(std::runtime_error& e) { + printf("File reading failed: %s\n", e.what()); + return 1; + } + + mmdb::ERROR_CODE rc = manager->WritePDBASCII(argv[2]); + if (rc) + printf(" ***** ERROR #%i WRITE:\n\n %s\n\n", + rc, mmdb::GetErrorDescription(rc)); + return 0; +} + +// Example 2. +// Read a coordinate file using mmdb and write it to PDB using gemmi. +#include +#include +#include + +// two arguments expected: input and output paths. +int main (int argc, char** argv) { + if (argc != 3) + return 1; + mmdb::InitMatType(); + mmdb::Manager manager; + manager.SetFlag(mmdb::MMDBF_PrintCIFWarnings | + mmdb::MMDBF_FixSpaceGroup | + mmdb::MMDBF_IgnoreHash | + mmdb::MMDBF_IgnoreNonCoorPDBErrors| + mmdb::MMDBF_DoNotProcessSpaceGroup); + mmdb::ERROR_CODE rc = manager.ReadCoorFile(argv[1]); + if (rc != mmdb::Error_NoError) { + printf(" ***** ERROR reading the file\n"); + return 1; + } + gemmi::Structure st = gemmi::copy_from_mmdb(&manager); + std::ofstream os(argv[2]); + gemmi::write_pdb(st, os); + return 0; +} +*/ diff --git a/gemmi_gph/gemmi/mmread.hpp b/gemmi_gph/gemmi/mmread.hpp new file mode 100644 index 00000000..e367a67a --- /dev/null +++ b/gemmi_gph/gemmi/mmread.hpp @@ -0,0 +1,122 @@ +// Copyright 2017 Global Phasing Ltd. +// +// Read any supported coordinate file. Usually, mmread_gz.hpp is preferred. + +#ifndef GEMMI_MMREAD_HPP_ +#define GEMMI_MMREAD_HPP_ + +#include "cif.hpp" // for cif::read +#include "fail.hpp" // for fail +#include "input.hpp" // for BasicInput +#include "json.hpp" // for read_mmjson +#include "mmcif.hpp" // for make_structure_from_block, ... +#include "model.hpp" // for Structure +#include "pdb.hpp" // for read_pdb +#include "util.hpp" // for iends_with + +namespace gemmi { + +inline CoorFormat coor_format_from_ext(const std::string& path) { + if (iends_with(path, ".pdb") || iends_with(path, ".ent")) + return CoorFormat::Pdb; + if (iends_with(path, ".cif") || iends_with(path, ".mmcif")) + return CoorFormat::Mmcif; + if (iends_with(path, ".json")) + return CoorFormat::Mmjson; + return CoorFormat::Unknown; +} + +// If it's neither CIF nor JSON nor almost empty - we assume PDB. +inline CoorFormat coor_format_from_content(const char* buf, const char* end) { + while (buf < end - 8) { + if (std::isspace(*buf)) { + ++buf; + } else if (*buf == '#') { + while (buf < end - 8 && *buf != '\n') + ++buf; + } else if (*buf == '{') { + return CoorFormat::Mmjson; + } else if (ialpha4_id(buf) == ialpha4_id("data") && buf[4] == '_') { + return CoorFormat::Mmcif; + } else { + return CoorFormat::Pdb; + } + } + return CoorFormat::Unknown; +} + +inline Structure make_structure_from_doc(cif::Document&& doc, bool possible_chemcomp, + cif::Document* save_doc=nullptr) { + if (possible_chemcomp) { + // check for special case - refmac dictionary or CCD file + int n = check_chemcomp_block_number(doc); + if (n != -1) + return make_structure_from_chemcomp_block(doc.blocks[n]); + } + return make_structure(std::move(doc), save_doc); +} + +// when reading JSON, the input buffer is changed (as an optimization) +inline Structure read_structure_from_memory(char* data, size_t size, + const std::string& path, + CoorFormat format=CoorFormat::Unknown, + cif::Document* save_doc=nullptr) { + if (save_doc) + save_doc->clear(); + if (format == CoorFormat::Unknown || format == CoorFormat::Detect) + format = coor_format_from_content(data, data + size); + if (format == CoorFormat::Pdb) + return read_pdb_from_memory(data, size, path); + if (format == CoorFormat::Mmcif) + return make_structure_from_doc(cif::read_memory(data, size, path.c_str()), + true, save_doc); + if (format == CoorFormat::Mmjson) + return make_structure(cif::read_mmjson_insitu(data, size, path), save_doc); + fail("wrong format of coordinate file " + path); +} + +// deprecated +inline Structure read_structure_from_char_array(char* data, size_t size, + const std::string& path, + cif::Document* save_doc=nullptr) { + return read_structure_from_memory(data, size, path, CoorFormat::Unknown, save_doc); +} + +template +Structure read_structure(T&& input, CoorFormat format=CoorFormat::Unknown, + cif::Document* save_doc=nullptr) { + if (format == CoorFormat::Detect) { + CharArray mem = read_into_buffer(input); + return read_structure_from_memory(mem.data(), mem.size(), input.path(), format, save_doc); + } + if (save_doc) + save_doc->clear(); + if (format == CoorFormat::Unknown) + format = coor_format_from_ext(input.basepath()); + switch (format) { + case CoorFormat::Pdb: + return read_pdb(input); + case CoorFormat::Mmcif: + return make_structure(cif::read(input), save_doc); + case CoorFormat::Mmjson: { + Structure st = make_structure(cif::read_mmjson(input), save_doc); + st.input_format = CoorFormat::Mmjson; + return st; + } + case CoorFormat::ChemComp: + return make_structure_from_chemcomp_doc(cif::read(input), save_doc); + case CoorFormat::Unknown: + case CoorFormat::Detect: + fail("Unknown format of " + + (input.path().empty() ? "coordinate file" : input.path()) + "."); + } + unreachable(); +} + +inline Structure read_structure_file(const std::string& path, + CoorFormat format=CoorFormat::Unknown) { + return read_structure(BasicInput(path), format); +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/mmread_gz.hpp b/gemmi_gph/gemmi/mmread_gz.hpp new file mode 100644 index 00000000..c6acad2c --- /dev/null +++ b/gemmi_gph/gemmi/mmread_gz.hpp @@ -0,0 +1,29 @@ +// Copyright 2021 Global Phasing Ltd. +// +// Functions for reading possibly gzipped coordinate files. + +#ifndef GEMMI_MMREAD_GZ_HPP_ +#define GEMMI_MMREAD_GZ_HPP_ + +#include "model.hpp" // for Structure + +namespace gemmi { + +namespace cif { struct Document; } + +GEMMI_DLL Structure read_structure_gz(const std::string& path, + CoorFormat format=CoorFormat::Unknown, + cif::Document* save_doc=nullptr); + +GEMMI_DLL Structure read_pdb_gz(const std::string& path, + PdbReadOptions options=PdbReadOptions()); + +GEMMI_DLL Structure read_structure_from_chemcomp_gz(const std::string& path, + cif::Document* save_doc=nullptr, + int which=7); + +GEMMI_DLL CoorFormat coor_format_from_ext_gz(const std::string& path); + +} // namespace gemmi + +#endif diff --git a/gemmi_gph/gemmi/model.hpp b/gemmi_gph/gemmi/model.hpp new file mode 100644 index 00000000..58ed6103 --- /dev/null +++ b/gemmi_gph/gemmi/model.hpp @@ -0,0 +1,1107 @@ +// Copyright 2017 Global Phasing Ltd. +// +// Data structures to store macromolecular structure models. + +#ifndef GEMMI_MODEL_HPP_ +#define GEMMI_MODEL_HPP_ + +#include // for find_if, count_if, lower_bound +#include +#include +#include // for back_inserter +#include // for map +#include // for out_of_range +#include +#include + +#include "elem.hpp" +#include "fail.hpp" // for fail +#include "unitcell.hpp" +#include "symmetry.hpp" +#include "metadata.hpp" +#include "iterator.hpp" +#include "span.hpp" // for Span, MutableVectorSpan +#include "seqid.hpp" +#include "util.hpp" // for join_str, vector_move_extend, in_vector + +namespace gemmi { + +namespace impl { + +template +auto get_id(const T& m) -> decltype(m.name) { return m.name; } +template +auto get_id(const T& m) -> decltype(m.num) { return m.num; } + + +template +auto find_iter_(Vec& vec, const S& name) { + return std::find_if(vec.begin(), vec.end(), [&name](const auto& m) { return get_id(m) == name; }); +} + +template +T* find_or_null(std::vector& vec, const S& name) { + auto it = find_iter_(vec, name); + return it != vec.end() ? &*it : nullptr; +} + +template +T& find_or_add(std::vector& vec, const S& name) { + if (T* ret = find_or_null(vec, name)) + return *ret; + vec.emplace_back(name); + return vec.back(); +} + +template +typename Span::iterator find_iter(Span& span, const S& name) { + using T = typename Span::value_type; + auto it = find_iter_(span, name); + if (it == span.end()) + throw std::invalid_argument(cat( + T::what(), ' ', name, " not found (only [", + join_str(span.begin(), span.end(), ' ', [](const T& item) { return cat(get_id(item)); }), + "])")); + return it; +} + +template +typename Group::element_type& get_by_altloc(Group& group, char alt) { + for (auto& atom : group) + if (atom.altloc == alt) + return atom; + fail("No such altloc"); +} + +template std::vector model_subchains(M* model) { + std::vector v; + for (auto& chain : model->chains) + vector_move_extend(v, chain.subchains()); + return v; +} +} // namespace impl + + +/// File format of a macromolecular model. When passed to read_structure(): +/// Unknown = guess format from the extension, +/// Detect = guess format from the content. +enum class CoorFormat : unsigned char { + Unknown, Detect, Pdb, Mmcif, Mmjson, ChemComp +}; + +/// corresponds to _atom_site.calc_flag in mmCIF +enum class CalcFlag : signed char { + // NoHydrogen is the same as NotSet; it's used internally to mark atoms + // which should not have riding hydrogens. + // NB: add_cif_atoms() relies on this order. + NotSet=0, NoHydrogen, Determined, Calculated, Dummy +}; + +/// options affecting how pdb file is read +struct PdbReadOptions { + int max_line_length = 0; + bool check_non_ascii = false; + bool ignore_ter = false; // ignores TER records completely + bool split_chain_on_ter = false; + bool skip_remarks = false; +}; +// end of PdbReadOptions for mol.rst + +// remove empty residues from chain, empty chains from model, etc +template void remove_empty_children(T& obj) { + using Item = typename T::child_type; + vector_remove_if(obj.children(), [](const Item& x) { return x.children().empty(); }); +} + +inline bool is_same_conformer(char altloc1, char altloc2) { + return altloc1 == '\0' || altloc2 == '\0' || altloc1 == altloc2; +} + +/// Represents atom site in macromolecular structure (~100 bytes). +struct Atom { + static const char* what() { return "Atom"; } + std::string name; + char altloc = '\0'; // 0 if not set + signed char charge = 0; // [-8, +8] + Element element = El::X; + CalcFlag calc_flag = CalcFlag::NotSet; // mmCIF _atom_site.calc_flag + char flag = '\0'; // a custom flag + short tls_group_id = -1; + int serial = 0; + float fraction = 0.f; // custom value, one use is Refmac's ccp4_deuterium_fraction + Position pos; + float occ = 1.0f; + // ADP - in MX it's usual to give isotropic ADP as B and anisotropic as U + float b_iso = 20.0f; // arbitrary default value + SMat33 aniso = {0, 0, 0, 0, 0, 0}; + + char altloc_or(char null_char) const { return altloc ? altloc : null_char; } + bool same_conformer(const Atom& other) const { + return is_same_conformer(altloc, other.altloc); + } + bool altloc_matches(char request) const { + return request == '*' || altloc == '\0' || altloc == request; + } + // group_key() is used in UniqIter and similar tools + const std::string& group_key() const { return name; } + bool has_altloc() const { return altloc != '\0'; } + double b_eq() const { return u_to_b() / 3. * aniso.trace(); } + bool is_hydrogen() const { return gemmi::is_hydrogen(element); } + + // Name as a string left-padded like in the PDB format: + // the first two characters make the element name. + std::string padded_name() const { + std::string s; + const char* el = element.uname(); + if (el[1] == '\0' && + (el[0] == alpha_up(name[0]) || (is_hydrogen() && alpha_up(name[0]) == 'H')) && + name.size() < 4) + s += ' '; + s += name; + return s; + } + + // a method present in Atom, Residue, ... Structure - used in templates + Atom empty_copy() const { return Atom(*this); } +}; + +template +struct AtomGroup_ : ItemGroup { + using ItemGroup::ItemGroup; + std::string name() const { return !this->empty() ? this->front().name : ""; } + AtomType& by_altloc(char alt) { + for (int i = 0; i != this->extent(); ++i) { + AtomType* a = &this->front() + i; + if (a->altloc == alt && (a->name == this->front().name)) + return *a; + } + fail("No such altloc"); + } +}; + +using AtomGroup = AtomGroup_; +using ConstAtomGroup = AtomGroup_; + +struct Residue : public ResidueId { + using OptionalNum = SeqId::OptionalNum; + static const char* what() { return "Residue"; } + + std::string subchain; // mmCIF _atom_site.label_asym_id + std::string entity_id; // mmCIF _atom_site.label_entity_id + OptionalNum label_seq; // mmCIF _atom_site.label_seq_id + EntityType entity_type = EntityType::Unknown; + char het_flag = '\0'; // 'A' = ATOM, 'H' = HETATM, 0 = unspecified + char flag = '\0'; // custom flag + SiftsUnpResidue sifts_unp; // UniProt reference from SIFTS + short group_idx = 0; // ignore - internal variable + std::vector atoms; + + Residue() = default; + explicit Residue(const ResidueId& rid) noexcept : ResidueId(rid) {} + + // copy all but atoms (children) - for use in templates + Residue empty_copy() const { + Residue res((ResidueId&)*this); + res.subchain = subchain; + res.entity_id = entity_id; + res.label_seq = label_seq; + res.entity_type = entity_type; + res.het_flag = het_flag; + res.flag = flag; + res.sifts_unp = sifts_unp; + return res; + } + using child_type = Atom; + std::vector& children() { return atoms; } + const std::vector& children() const { return atoms; } + + const Atom* find_by_element(El el) const { + for (const Atom& a : atoms) + if (a.element == el) + return &a; + return nullptr; + } + + /// El::X means anything; + /// in strict_altloc mode, '*' = any altloc, otherwise it's \0 + Atom* find_atom(const std::string& atom_name, char altloc, El el=El::X, + bool strict_altloc=true) { + if (!strict_altloc && altloc == '\0') + altloc = '*'; + for (Atom& a : atoms) + if (a.name == atom_name && a.altloc_matches(altloc) && (el == El::X || a.element == el)) + return &a; + return nullptr; + } + const Atom* find_atom(const std::string& atom_name, char altloc, El el=El::X, + bool strict_altloc=true) const { + return const_cast(this)->find_atom(atom_name, altloc, el, strict_altloc); + } + + std::vector::iterator find_atom_iter(const std::string& atom_name, + char altloc, El el=El::X) { + if (Atom* a = find_atom(atom_name, altloc, el)) + return atoms.begin() + (a - atoms.data()); + fail("Atom not found."); + } + + AtomGroup get(const std::string& atom_name) { + for (Atom& atom : atoms) + if (atom.name == atom_name) + return AtomGroup(&atom, atoms.data() + atoms.size()); + fail("No such atom: " + atom_name); + } + + Atom& sole_atom(const std::string& atom_name) { + AtomGroup aa = get(atom_name); + if (aa.size() != 1) + fail("Multiple alternative atoms " + atom_name); + return aa.front(); + } + + // short-cuts to access peptide backbone atoms + const Atom* get_ca() const { return find_atom("CA", '*', El::C); } + const Atom* get_c() const { return find_atom("C", '*', El::C); } + const Atom* get_n() const { return find_atom("N", '*', El::N); } + const Atom* get_o() const { return find_atom("O", '*', El::O); } + // short-cuts to access nucleic acid atoms + const Atom* get_p() const { return find_atom("P", '*', El::P); } + const Atom* get_o3prim() const { return find_atom("O3'", '*', El::O); } + + bool same_conformer(const Residue& other) const { + return atoms.empty() || other.atoms.empty() || + atoms[0].same_conformer(other.atoms[0]) || + other.find_atom(other.atoms[0].name, atoms[0].altloc) != nullptr; + } + + /// Convenience function that duplicates functionality from resinfo.hpp. + /// Returns true for HOH and DOD (and old alternative names of HOH), + /// but not for OH and H3O/D3O. + bool is_water() const { + if (name.length() != 3) + return false; + int id = ialpha4_id(name.c_str()); + return id == ialpha4_id("HOH") || id == ialpha4_id("DOD") || + id == ialpha4_id("WAT") || id == ialpha4_id("H2O"); + } + + // Iterators that in case of multiple conformations (alt. locations) + // skip all but the first conformation. + UniqProxy first_conformer() { return {atoms}; } + ConstUniqProxy first_conformer() const { return {atoms}; } +}; + +inline void add_distinct_altlocs(const Residue& res, std::string& altlocs) { + for (const Atom& atom : res.atoms) + if (atom.altloc && altlocs.find(atom.altloc) == std::string::npos) + altlocs += atom.altloc; +} + +struct ResidueGroup; +struct ConstResidueGroup; + +struct ConstResidueSpan : Span { + using Parent = Span; + using Parent::Span; + ConstResidueSpan(Parent&& span) : Parent(std::move(span)) {} + + int length() const { + int length = (int) size(); + for (int n = length - 1; n > 0; --n) + if ((begin() + n)->group_key() == (begin() + n - 1)->group_key()) + --length; + return length; + } + + // sign=-1 for min, sign=1 for max + SeqId::OptionalNum extreme_num(bool label, int sign) const { + SeqId::OptionalNum result; + for (const Residue& r : *this) { + if (auto num = label ? r.label_seq : r.seqid.num) + if (!result || sign * int(num) > sign * int(result)) + result = num; + } + return result; + } + + ConstUniqProxy first_conformer() const { + return {*this}; + } + + const std::string& subchain_id() const { + if (this->empty()) + throw std::out_of_range("subchain_id(): empty span"); + if (this->size() > 1 && this->front().subchain != this->back().subchain) + fail("subchain id varies in a residue span: ", this->front().subchain, + " vs ", this->back().subchain); + return this->begin()->subchain; + } + + ConstResidueGroup find_residue_group(SeqId id) const; + + std::vector extract_sequence() const { + std::vector seq; + for (const Residue& res : first_conformer()) + seq.push_back(res.name); + return seq; + } + + // We assume residues are ordered. It works (approximately) also with + // missing numbers which can be present in DBREF. + SeqId label_seq_id_to_auth(SeqId::OptionalNum label_seq_id) const { + if (empty()) + throw std::out_of_range("label_seq_id_to_auth(): empty span"); + const auto* it = std::lower_bound(begin(), end(), label_seq_id, + [](const Residue& r, SeqId::OptionalNum v){ return r.label_seq < v; }); + if (it == end()) + --it; + else if (it->label_seq == label_seq_id) + return it->seqid; + else if (it != begin() && + label_seq_id - (it-1)->label_seq < it->label_seq - label_seq_id) + --it; + return {it->seqid.num + (label_seq_id - it->label_seq), ' '}; + } + // The residue numbers (auth) are sometimes not ordered. + // That is why we use this multi-step heuristic. + SeqId::OptionalNum auth_seq_id_to_label(SeqId auth_seq_id) const { + if (empty()) + throw std::out_of_range("auth_seq_id_to_label(): empty span"); + for (const Residue& r : *this) + if (r.seqid == auth_seq_id) + return r.label_seq; + const_iterator it; + if (auth_seq_id.num < front().seqid.num) { + it = begin(); + } else if (back().seqid.num < auth_seq_id.num) { + it = end() - 1; + } else { + it = std::lower_bound(begin(), end(), auth_seq_id.num, + [](const Residue& r, SeqId::OptionalNum v){ return r.seqid.num < v; }); + while (it != end() && it->seqid.num == auth_seq_id.num && + it->seqid.icode != auth_seq_id.icode) + ++it; + if (it == end()) + --it; + } + return it->label_seq + (auth_seq_id.num - it->seqid.num); + } +}; + +// ResidueSpan represents consecutive residues within the same chain. +// It's used as return value of get_polymer(), get_ligands(), get_waters() +// and get_subchain(). +struct ResidueSpan : MutableVectorSpan { + using Parent = MutableVectorSpan; + struct GroupingProxy; + ResidueSpan() = default; + ResidueSpan(Parent&& span) : Parent(std::move(span)) {} + ResidueSpan(vector_type& v, iterator begin, std::size_t n) + : Parent(v, begin, n) {} + int length() const { return const_().length(); } + SeqId::OptionalNum extreme_num(bool label, int sign) const { + return const_().extreme_num(label, sign); + } + UniqProxy first_conformer() { return {*this}; } + ConstUniqProxy first_conformer() const { return {*this}; } + GroupingProxy residue_groups(); + const std::string& subchain_id() const { return const_().subchain_id(); } + ResidueGroup find_residue_group(SeqId id); + std::vector extract_sequence() const { return const_().extract_sequence(); } + ConstResidueGroup find_residue_group(SeqId id) const; + SeqId label_seq_id_to_auth(SeqId::OptionalNum label_seq_id) const { + return const_().label_seq_id_to_auth(label_seq_id); + } + SeqId::OptionalNum auth_seq_id_to_label(SeqId auth_seq_id) const { + return const_().auth_seq_id_to_label(auth_seq_id); + } +private: + ConstResidueSpan const_() const { return ConstResidueSpan(begin(), size()); } +}; + +// ResidueGroup represents residues with the same sequence number and insertion +// code, but different residue names. I.e. microheterogeneity. +// Usually, there is only one residue in the group. +// The residues must be consecutive. +struct ResidueGroup : ResidueSpan { + ResidueGroup() = default; + ResidueGroup(ResidueSpan&& span) : ResidueSpan(std::move(span)) {} + Residue& by_resname(const std::string& name) { + return *impl::find_iter(*this, name); + } + void remove_residue(const std::string& name) { + erase(impl::find_iter(*this, name)); + } +}; + +struct ConstResidueGroup : ConstResidueSpan { + ConstResidueGroup() = default; + ConstResidueGroup(ConstResidueSpan&& sp) : ConstResidueSpan(std::move(sp)) {} + const Residue& by_resname(const std::string& name) { + return *impl::find_iter(*this, name); + } +}; + +inline ResidueGroup ResidueSpan::find_residue_group(SeqId id) { + return ResidueSpan(subspan([&](const Residue& r) { return r.seqid == id; })); +} +inline ConstResidueGroup ResidueSpan::find_residue_group(SeqId id) const { + return const_().find_residue_group(id); +} +inline ConstResidueGroup ConstResidueSpan::find_residue_group(SeqId id) const { + return ConstResidueSpan(subspan([&](const Residue& r) { return r.seqid == id; })); +} + +struct ResidueSpan::GroupingProxy { + ResidueSpan& span; + using iterator = GroupingIter; + iterator begin() { + return ++iterator{ResidueGroup(span.sub(span.begin(), span.begin()))}; + } + iterator end() { + return iterator{ResidueGroup(span.sub(span.end(), span.end()))}; + } +}; + +inline ResidueSpan::GroupingProxy ResidueSpan::residue_groups() { + return {*this}; +} + + +namespace impl { +template std::vector chain_subchains(Ch* ch) { + std::vector v; + for (auto start = ch->residues.begin(); start != ch->residues.end(); ) { + auto end = start + 1; + while (end != ch->residues.end() && end->subchain == start->subchain) + ++end; + v.push_back(ch->whole().sub(start, end)); + start = end; + } + return v; +} +} // namespace impl + +struct Chain { + static const char* what() { return "Chain"; } + std::string name; + std::vector residues; + + Chain() = default; + explicit Chain(const std::string& name_) noexcept : name(name_) {} + + ResidueSpan whole() { + Residue* begin = residues.empty() ? nullptr : &residues[0]; + return ResidueSpan(residues, begin, residues.size()); + } + ConstResidueSpan whole() const { + const Residue* begin = residues.empty() ? nullptr : &residues[0]; + return ConstResidueSpan(begin, residues.size()); + } + + template ResidueSpan get_residue_span(F&& func) { + return whole().subspan(func); + } + template ConstResidueSpan get_residue_span(F&& func) const { + return whole().subspan(func); + } + + ResidueSpan get_polymer() { + auto begin = residues.begin(); + while (begin != residues.end() && begin->entity_type != EntityType::Polymer) + ++begin; + auto end = begin; + while (end != residues.end() && end->entity_type == EntityType::Polymer + && end->subchain == begin->subchain) + ++end; + return ResidueSpan(residues, &*begin, end - begin); + } + ConstResidueSpan get_polymer() const { + return const_cast(this)->get_polymer(); + } + + ResidueSpan get_ligands() { + return get_residue_span([](const Residue& r) { + return r.entity_type == EntityType::NonPolymer || + r.entity_type == EntityType::Branched; + }); + } + ConstResidueSpan get_ligands() const { + return const_cast(this)->get_ligands(); + } + + ResidueSpan get_waters() { + return get_residue_span([](const Residue& r) { + return r.entity_type == EntityType::Water; + }); + } + ConstResidueSpan get_waters() const { + return const_cast(this)->get_waters(); + } + + ResidueSpan get_subchain(const std::string& s) { + return get_residue_span([&](const Residue& r) { return r.subchain == s; }); + } + ConstResidueSpan get_subchain(const std::string& s) const { + return const_cast(this)->get_subchain(s); + } + + std::vector subchains() { + return impl::chain_subchains(this); + } + std::vector subchains() const { + return impl::chain_subchains(this); + } + + ResidueGroup find_residue_group(SeqId id) { + return whole().find_residue_group(id); + } + ConstResidueGroup find_residue_group(SeqId id) const { + return whole().find_residue_group(id); + } + + Residue* find_residue(const ResidueId& rid); + const Residue* find_residue(const ResidueId& rid) const { + return const_cast(this)->find_residue(rid); + } + + Residue* find_or_add_residue(const ResidueId& rid); + void append_residues(std::vector new_resi, int min_sep=0); + + // methods present in Structure, Model, ... - used in templates + Chain empty_copy() const { return Chain(name); } + using child_type = Residue; + std::vector& children() { return residues; } + const std::vector& children() const { return residues; } + + // Returns false only for alternative conformation (microheterogeneity). + bool is_first_in_group(const Residue& res) const { + return &res == residues.data() || (&res - 1)->group_key() != res.group_key(); + } + + // Returns the previous residue or nullptr. + // Got complicated by handling of multi-conformations / microheterogeneity. + const Residue* previous_residue(const Residue& res) const { + const Residue* start = residues.data(); + for (const Residue* p = &res; p-- != start; ) + if (res.group_key() != p->group_key()) { + while (p != start && p->group_key() == (p-1)->group_key() && + (res.atoms.at(0).altloc == '\0' || !res.same_conformer(*p))) + --p; + return p; + } + return nullptr; + } + + // Returns the next residue or nullptr. + const Residue* next_residue(const Residue& res) const { + const Residue* end = residues.data() + residues.size(); + for (const Residue* p = &res + 1; p != end; ++p) + if (res.group_key() != p->group_key()) { + while (p+1 != end && p->group_key() == (p+1)->group_key() && + !res.same_conformer(*p)) + ++p; + return p; + } + return nullptr; + } + + // Iterators that in case of multiple conformations (microheterogeneity) + // skip all but the first conformation. + UniqProxy first_conformer() { return {residues}; } + ConstUniqProxy first_conformer() const { return {residues}; } +}; + +inline std::string atom_str(const Chain& chain, + const ResidueId& res_id, + const Atom& atom, + bool as_cid=false) { + return atom_str(chain.name, res_id, atom.name, atom.altloc, as_cid); +} + +struct const_CRA { + const Chain* chain; + const Residue* residue; + const Atom* atom; +}; + +struct CRA { + Chain* chain; + Residue* residue; + Atom* atom; + operator const_CRA() const { return const_CRA{chain, residue, atom}; } +}; + +inline std::string atom_str(const const_CRA& cra, bool as_cif=false) { + static const ResidueId null_residue_id = {}; + return atom_str(cra.chain ? cra.chain->name : "null", + cra.residue ? *cra.residue : null_residue_id, + cra.atom ? cra.atom->name : "null", + cra.atom ? cra.atom->altloc : '\0', + as_cif); +} + +inline bool atom_matches(const const_CRA& cra, const AtomAddress& addr, bool ignore_segment=false) { + return cra.chain && cra.chain->name == addr.chain_name && + cra.residue && cra.residue->matches_noseg(addr.res_id) && + (ignore_segment || cra.residue->segment == addr.res_id.segment) && + cra.atom && cra.atom->name == addr.atom_name && + cra.atom->altloc == addr.altloc; +} + +inline AtomAddress make_address(const Chain& ch, const Residue& res, const Atom& at) { + return AtomAddress(ch.name, res, at.name, at.altloc); +} + + +template +class CraIterPolicy { +public: + using value_type = CraT; + using reference = const CraT; + CraIterPolicy() : chains_end(nullptr), cra{nullptr, nullptr, nullptr} {} + CraIterPolicy(const Chain* end, CraT cra_) : chains_end(end), cra(cra_) {} + void increment() { + if (cra.atom == nullptr) + return; + if (++cra.atom == vector_end_ptr(cra.residue->atoms)) { + do { + if (++cra.residue == vector_end_ptr(cra.chain->residues)) { + do { + if (++cra.chain == chains_end) { + cra.atom = nullptr; + return; + } + } while (cra.chain->residues.empty()); + cra.residue = &cra.chain->residues[0]; + } + } while (cra.residue->atoms.empty()); + cra.atom = &cra.residue->atoms[0]; + } + } + void decrement() { + while (cra.atom == nullptr || cra.atom == cra.residue->atoms.data()) { + while (cra.residue == nullptr || cra.residue == cra.chain->residues.data()) { + // iterating backward beyond begin() will have undefined effects + while ((--cra.chain)->residues.empty()) {} + cra.residue = vector_end_ptr(cra.chain->residues); + } + --cra.residue; + cra.atom = vector_end_ptr(cra.residue->atoms); + } + --cra.atom; + } + bool equal(const CraIterPolicy& o) const { return cra.atom == o.cra.atom; } + CraT dereference() { return cra; } // make copy b/c increment() modifies cra + using const_policy = CraIterPolicy; + operator const_policy() const { return const_policy(chains_end, cra); } +private: + const Chain* chains_end; + CraT cra; +}; + +template +struct CraProxy_ { + ChainsRefT chains; + using iterator = BidirIterator>; + iterator begin() { + for (auto& chain : chains) + for (auto& residue : chain.residues) + for (auto& atom : residue.atoms) + return CraIterPolicy{vector_end_ptr(chains), CraT{&chain, &residue, &atom}}; + return {}; + } + iterator end() { + auto* chains_end = vector_end_ptr(chains); + return CraIterPolicy{chains_end, CraT{chains_end, nullptr, nullptr}}; + } +}; + +using CraProxy = CraProxy_&>; +using ConstCraProxy = CraProxy_&>; + +struct Model { + static const char* what() { return "Model"; } + int num = 0; + std::vector chains; + + Model() = default; + explicit Model(int num_) noexcept : num(num_) {} + + // Returns the first chain with given name, or nullptr. + Chain* find_chain(const std::string& chain_name) { + return impl::find_or_null(chains, chain_name); + } + const Chain* find_chain(const std::string& chain_name) const { + return const_cast(this)->find_chain(chain_name); + } + + // Returns the last chain with given name, or nullptr. + Chain* find_last_chain(const std::string& chain_name) { + auto it = std::find_if(chains.rbegin(), chains.rend(), + [&](const Chain& c) { return c.name == chain_name; }); + return it != chains.rend() ? &*it : nullptr; + } + + + void remove_chain(const std::string& chain_name) { + vector_remove_if(chains, + [&](const Chain& c) { return c.name == chain_name; }); + } + + void merge_chain_parts(int min_sep=0) { + for (auto i = chains.begin(); i != chains.end(); ++i) + for (auto j = i + 1; j != chains.end(); ++j) + if (i->name == j->name) { + i->append_residues(j->residues, min_sep); + chains.erase(j--); + } + } + + ResidueSpan get_subchain(const std::string& sub_name) { + for (Chain& chain : chains) + if (ResidueSpan sub = chain.get_subchain(sub_name)) + return sub; + return ResidueSpan(); + } + ConstResidueSpan get_subchain(const std::string& sub_name) const { + return const_cast(this)->get_subchain(sub_name); + } + + std::vector subchains() { + return impl::model_subchains(this); + } + std::vector subchains() const { + return impl::model_subchains(this); + } + + std::map subchain_to_chain() const { + std::map mapping; + for (const Chain& chain : chains) { + std::string prev; + for (const Residue& res : chain.residues) + if (!res.subchain.empty() && res.subchain != prev) { + prev = res.subchain; + mapping[res.subchain] = chain.name; + } + } + return mapping; + } + + Residue* find_residue(const std::string& chain_name, const ResidueId& rid) { + for (Chain& chain : chains) + if (chain.name == chain_name) + if (Residue* residue = chain.find_residue(rid)) + return residue; + return nullptr; + } + const Residue* find_residue(const std::string& chain_name, const ResidueId& rid) const { + return const_cast(this)->find_residue(chain_name, rid); + } + + + ResidueGroup find_residue_group(const std::string& chain_name, SeqId seqid) { + for (Chain& chain : chains) + if (chain.name == chain_name) + if (ResidueGroup res_group = chain.find_residue_group(seqid)) + return res_group; + fail("No such chain or residue: " + chain_name + " " + seqid.str()); + } + + Residue& sole_residue(const std::string& chain_name, SeqId seqid) { + ResidueSpan rr = find_residue_group(chain_name, seqid); + if (rr.size() != 1) + fail("Multiple residues " + chain_name + " " + seqid.str()); + return rr[0]; + } + + std::vector get_all_residue_names() const { + std::vector names; + for (const Chain& chain : chains) + for (const Residue& res : chain.residues) + if (!in_vector(res.name, names)) + names.push_back(res.name); + return names; + } + + CRA find_cra(const AtomAddress& address, bool ignore_segment=false) { + for (Chain& chain : chains) + if (chain.name == address.chain_name) { + for (Residue& res : chain.residues) + if (address.res_id.matches_noseg(res) && + (ignore_segment || address.res_id.segment == res.segment)) { + Atom *at = nullptr; + if (!address.atom_name.empty()) + at = res.find_atom(address.atom_name, address.altloc); + return {&chain, &res, at}; + } + } + return {nullptr, nullptr, nullptr}; + } + + const_CRA find_cra(const AtomAddress& address, bool ignore_segment=false) const { + return const_cast(this)->find_cra(address, ignore_segment); + } + + CraProxy all() { return {chains}; } + ConstCraProxy all() const { return {chains}; } + + Atom* find_atom(const AtomAddress& address) { return find_cra(address).atom; } + const Atom* find_atom(const AtomAddress& address) const { return find_cra(address).atom; } + + std::array get_indices(const Chain* c, const Residue* r, + const Atom* a) const { + return {{ c ? static_cast(c - chains.data()) : -1, + c && r ? static_cast(r - c->residues.data()) : -1, + r && a ? static_cast(a - r->atoms.data()) : -1 }}; + } + + std::bitset<(size_t)El::END> present_elements() const { + std::bitset<(size_t)El::END> table; + for (const Chain& chain : chains) + for (const Residue& res : chain.residues) + for (const Atom& a : res.atoms) + table.set(a.element.ordinal()); + return table; + } + + // methods present in Structure, Model, ... - used in templates + Model empty_copy() const { return Model(num); } + using child_type = Chain; + std::vector& children() { return chains; } + const std::vector& children() const { return chains; } +}; + +inline Entity* find_entity_of_subchain(const std::string& subchain_id, + std::vector& entities) { + if (!subchain_id.empty()) + for (Entity& ent : entities) + if (in_vector(subchain_id, ent.subchains)) + return &ent; + return nullptr; +} +inline const Entity* find_entity_of_subchain(const std::string& subchain_id, + const std::vector& entities) { + return find_entity_of_subchain(subchain_id, const_cast&>(entities)); +} + +struct Structure { + static const char* what() { return "Structure"; } + std::string name; + UnitCell cell; + std::string spacegroup_hm; // usually pdb symbol, cf. SpaceGroup::pdb_name() + std::vector models; + std::vector ncs; + std::vector entities; + std::vector connections; + std::vector cispeps; + std::vector mod_residues; + std::vector helices; + std::vector sheets; + std::vector assemblies; + std::map> conect_map; + Metadata meta; + + CoorFormat input_format = CoorFormat::Unknown; + bool has_d_fraction = false; // uses Refmac's ccp4_deuterium_fraction + int non_ascii_line = 0; // first PDB line with non-ASCII bytes, or 0 + /// in input PDB file: y = TER records were read, e = errors were detected + char ter_status = '\0'; + + /// Store ORIGXn / _database_PDB_matrix.origx* + bool has_origx = false; + Transform origx; + + /// Minimal metadata with keys being mmcif tags: _entry.id, _cell.Z_PDB, ... + std::map info; + /// Mapping of long (4+) CCD codes (residue names) to PDB-compatible ones + std::vector> shortened_ccd_codes; + /// original REMARK records stored if the file was read from the PDB format + std::vector raw_remarks; + /// simplistic resolution value from/for REMARK 2 + double resolution = 0; + + const SpaceGroup* find_spacegroup() const { + if (!cell.is_crystal()) + return nullptr; + return find_spacegroup_by_name(spacegroup_hm, cell.alpha, cell.gamma); + } + + const std::string& get_info(const std::string& tag) const { + static const std::string empty; + auto it = info.find(tag); + return it != info.end() ? it->second : empty; + } + + Model& first_model() { + if (models.empty()) + fail("no structural models"); + return models[0]; + } + const Model& first_model() const { + return const_cast(this)->first_model(); + } + + Model* find_model(int model_num) { + return impl::find_or_null(models, model_num); + } + const Model* find_model(int model_num) const { + return const_cast(this)->find_model(model_num); + } + Model& find_or_add_model(int model_num) { + return impl::find_or_add(models, model_num); + } + + void renumber_models() { + for (size_t i = 0; i != models.size(); ++i) + models[i].num = i + 1; + } + + Entity* get_entity(const std::string& ent_id) { + return impl::find_or_null(entities, ent_id); + } + const Entity* get_entity(const std::string& ent_id) const { + return const_cast(this)->get_entity(ent_id); + } + + Entity* get_entity_of(const ConstResidueSpan& sub) { + return sub ? find_entity_of_subchain(sub.subchain_id(), entities) : nullptr; + } + const Entity* get_entity_of(const ConstResidueSpan& sub) const { + return const_cast(this)->get_entity_of(sub); + } + + Assembly* find_assembly(const std::string& assembly_id) { + return impl::find_or_null(assemblies, assembly_id); + } + + Connection* find_connection_by_name(const std::string& conn_name) { + return impl::find_or_null(connections, conn_name); + } + const Connection* find_connection_by_name(const std::string& conn_name) const { + return const_cast(this)->find_connection_by_name(conn_name); + } + + Connection* find_connection_by_cra(const const_CRA& cra1, + const const_CRA& cra2, + bool ignore_segment=false) { + for (Connection& c : connections) + if ((atom_matches(cra1, c.partner1, ignore_segment) && + atom_matches(cra2, c.partner2, ignore_segment)) || + (atom_matches(cra1, c.partner2, ignore_segment) && + atom_matches(cra2, c.partner1, ignore_segment))) + return &c; + return nullptr; + } + + Connection* find_connection(const AtomAddress& a1, const AtomAddress& a2) { + for (Connection& c : connections) + if ((a1 == c.partner1 && a2 == c.partner2) || + (a1 == c.partner2 && a2 == c.partner1)) + return &c; + return nullptr; + } + + size_t ncs_given_count() const { + return std::count_if(ncs.begin(), ncs.end(), [](const NcsOp& o) { return o.given; }); + } + double get_ncs_multiplier() const { + return (ncs.size() + 1.0) / (ncs_given_count() + 1.0); // +1 b/c identity not included + } + bool ncs_not_expanded() const { + return std::any_of(ncs.begin(), ncs.end(), [](const NcsOp& o) { return !o.given; }); + } + + void add_conect_one_way(int serial_a, int serial_b, int order) { + auto& vec = conect_map[serial_a]; + for (int i = 0; i < order; ++i) + vec.insert(std::upper_bound(vec.begin(), vec.end(), serial_b), serial_b); + } + void add_conect(int serial1, int serial2, int order) { + add_conect_one_way(serial1, serial2, order); + add_conect_one_way(serial2, serial1, order); + } + + void merge_chain_parts(int min_sep=0) { + for (Model& model : models) + model.merge_chain_parts(min_sep); + } + + void remove_empty_chains() { + for (Model& model : models) + remove_empty_children(model); + } + + // copy all but models (in general, empty_copy copies all but children) + Structure empty_copy() const { + Structure st; + st.name = name; + st.cell = cell; + st.spacegroup_hm = spacegroup_hm; + st.ncs = ncs; + st.entities = entities; + st.connections = connections; + st.cispeps = cispeps; + st.mod_residues = mod_residues; + st.helices = helices; + st.sheets = sheets; + st.assemblies = assemblies; + st.meta = meta; + st.input_format = input_format; + st.has_origx = has_origx; + st.origx = origx; + st.info = info; + st.raw_remarks = raw_remarks; + st.resolution = resolution; + return st; + } + + using child_type = Model; + std::vector& children() { return models; } + const std::vector& children() const { return models; } + + void setup_cell_images(); +}; + +inline Residue* Chain::find_residue(const ResidueId& rid) { + auto it = std::find_if(residues.begin(), residues.end(), + [&](const Residue& r) { return r.matches(rid); }); + return it != residues.end() ? &*it : nullptr; +} + +inline Residue* Chain::find_or_add_residue(const ResidueId& rid) { + Residue* r = find_residue(rid); + if (r) + return r; + residues.emplace_back(rid); + return &residues.back(); +} + +inline void Chain::append_residues(std::vector new_resi, int min_sep) { + if (new_resi.empty()) + return; + if (min_sep > 0) { + ConstResidueSpan new_span(&new_resi[0], new_resi.size()); + // adjust sequence numbers if necessary + auto diff = new_span.extreme_num(false, -1) - whole().extreme_num(false, 1); + if (diff && int(diff) < min_sep) + for (Residue& res : new_resi) + res.seqid.num += min_sep - int(diff); + // adjust label_seq_id if necessary + diff = new_span.extreme_num(true, -1) - whole().extreme_num(true, 1); + if (diff && int(diff) < min_sep) + for (Residue& res : new_resi) + res.label_seq += min_sep - int(diff); + } + std::move(new_resi.begin(), new_resi.end(), std::back_inserter(residues)); +} + +inline void Structure::setup_cell_images() { + const SpaceGroup* sg = find_spacegroup(); + cell.set_cell_images_from_spacegroup(sg); + cell.add_ncs_images_to_cs_images(ncs); +} + +} // namespace gemmi + +#endif diff --git a/gemmi_gph/gemmi/modify.hpp b/gemmi_gph/gemmi/modify.hpp new file mode 100644 index 00000000..c797c59c --- /dev/null +++ b/gemmi_gph/gemmi/modify.hpp @@ -0,0 +1,335 @@ +// Copyright 2017-2021 Global Phasing Ltd. +// +// Modify various properties of the model. + +// For modifications that depend on entities or connectivity see polyheur.hpp. + +#ifndef GEMMI_MODIFY_HPP_ +#define GEMMI_MODIFY_HPP_ + +#include "model.hpp" +#include "util.hpp" // for vector_remove_if +#include + +namespace gemmi { + +/// Remove alternative conformations. +template void remove_alternative_conformations(T& obj) { + for (auto& child : obj.children()) + remove_alternative_conformations(child); +} +template<> inline void remove_alternative_conformations(Chain& chain) { + std::set seqids; + for (size_t i = 0; i < chain.residues.size(); ) { + if (seqids.insert(chain.residues[i].seqid).second) + ++i; + else + chain.residues.erase(chain.residues.begin() + i); + } + for (Residue& residue : chain.residues) { + std::set names; + for (size_t i = 0; i < residue.atoms.size(); ) { + Atom& atom = residue.atoms[i]; + atom.altloc = '\0'; + if (names.insert(atom.name).second) + ++i; + else + residue.atoms.erase(residue.atoms.begin() + i); + } + } +} + +/// Remove hydrogens. +template void remove_hydrogens(T& obj) { + for (auto& child : obj.children()) + remove_hydrogens(child); +} +template<> inline void remove_hydrogens(Residue& res) { + vector_remove_if(res.atoms, [](const Atom& a) { + return a.element == El::H || a.element == El::D; + }); +} + +/// Set isotropic ADP to the range (b_min, b_max). Values smaller than +/// b_min are changed to b_min, values larger than b_max to b_max. +/// Anisotropic ADP is left unchanged. +template void assign_b_iso(T& obj, float b_min, float b_max) { + for (auto& child : obj.children()) + assign_b_iso(child, b_min, b_max); +} +template<> inline void assign_b_iso(Atom& atom, float b_min, float b_max) { + atom.b_iso = clamp(atom.b_iso, b_min, b_max); +} + +/// Remove anisotropic ADP +template void remove_anisou(T& obj) { + for (auto& child : obj.children()) + remove_anisou(child); +} +template<> inline void remove_anisou(Atom& atom) { + atom.aniso = {0, 0, 0, 0, 0, 0}; +} + +/// Set absent ANISOU to value from B_iso +template void ensure_anisou(T& obj) { + for (auto& child : obj.children()) + ensure_anisou(child); +} +template<> inline void ensure_anisou(Atom& atom) { + if (!atom.aniso.nonzero()) { + float u = float(1. / gemmi::u_to_b() * atom.b_iso); + atom.aniso = {u, u, u, 0.f, 0.f, 0.f}; + } +} + +/// apply Transform to both atom's position and ADP +template void transform_pos_and_adp(T& obj, const Transform& tr) { + for (auto& child : obj.children()) + transform_pos_and_adp(child, tr); +} +template<> inline void transform_pos_and_adp(Atom& atom, const Transform& tr) { + atom.pos = Position(tr.apply(atom.pos)); + if (atom.aniso.nonzero()) + atom.aniso = atom.aniso.transformed_by(tr.mat); +} + +/// set atom site serial numbers to 1, 2, ..., optionally leaving gaps for TERs +inline void assign_serial_numbers(Model& model, bool numbered_ter=false) { + int serial = 0; + for (Chain& chain : model.chains) + for (Residue& res : chain.residues) { + for (Atom& atom : res.atoms) + atom.serial = ++serial; + if (numbered_ter && res.entity_type == EntityType::Polymer && + (&res == &chain.residues.back() || (&res + 1)->entity_type != EntityType::Polymer)) + ++serial; + } +} +inline void assign_serial_numbers(Structure& st, bool numbered_ter=false) { + for (Model& model : st.models) + assign_serial_numbers(model, numbered_ter); +} + + +/// Helper function for processing (usually: changing) names and numbers +/// in AtomAddress instances in metadata: +/// Connection, CisPep, Helix, Sheet::Strand. +/// Other fields are not updated here, in particular: ModRes, Entity::DbRef, +/// Entity::full_sequence, TlsGroup::Selection. +template +void process_addresses(Structure& st, Func func) { + for (Connection& con : st.connections) { + func(con.partner1); + func(con.partner2); + } + for (CisPep& cispep : st.cispeps) { + func(cispep.partner_c); + func(cispep.partner_n); + } + for (Helix& helix : st.helices) { + func(helix.start); + func(helix.end); + } + for (Sheet& sheet : st.sheets) + for (Sheet::Strand& strand : sheet.strands) { + func(strand.start); + func(strand.end); + func(strand.hbond_atom2); + func(strand.hbond_atom1); + } +} + +/// Takes func(const std::string& chain_name, gemmi::SeqId& seqid). +/// It doesn't process Entity::DbRef::seq_begin/seq_end (b/c there is no +/// single corresponding chain name). +template +void process_sequence_ids(Structure& st, Func func) { + process_addresses(st, [&](AtomAddress& aa) { func(aa.chain_name, aa.res_id.seqid); }); + for (ModRes& modres : st.mod_residues) + func(modres.chain_name, modres.res_id.seqid); + for (RefinementInfo& ri : st.meta.refinement) + for (TlsGroup& tls : ri.tls_groups) + for (TlsGroup::Selection& sel : tls.selections) { + func(sel.chain, sel.res_begin); + func(sel.chain, sel.res_end); + } +} + +inline void rename_chain(Structure& st, const std::string& old_name, + const std::string& new_name) { + auto update = [&](std::string& name) { + if (name == old_name) + name = new_name; + }; + process_addresses(st, [&](AtomAddress& aa) { update(aa.chain_name); }); + for (ModRes& modres : st.mod_residues) + update(modres.chain_name); + for (RefinementInfo& ri : st.meta.refinement) + for (TlsGroup& tls : ri.tls_groups) + for (TlsGroup::Selection& sel : tls.selections) + update(sel.chain); + for (Model& model : st.models) + for (Chain& chain : model.chains) + update(chain.name); +} + +inline void rename_residues(Structure& st, const std::string& old_name, + const std::string& new_name) { + auto update = [&](ResidueId& rid) { + if (rid.name == old_name) + rid.name = new_name; + }; + process_addresses(st, [&](AtomAddress& aa) { update(aa.res_id); }); + for (ModRes& modres : st.mod_residues) + update(modres.res_id); + for (Entity& ent : st.entities) + for (std::string& mon_ids : ent.full_sequence) + for (size_t start = 0;;) { + size_t end = mon_ids.find(',', start); + if (mon_ids.compare(start, end-start, old_name) == 0) { + mon_ids.replace(start, end-start, new_name); + if (end != std::string::npos) + end = start + new_name.size(); + } + if (end == std::string::npos) + break; + start = end + 1; + } + for (Model& model : st.models) + for (Chain& chain : model.chains) + for (Residue& res : chain.residues) + update(res); +} + + +inline void rename_atom_names(Structure& st, const std::string& res_name, + const std::map& old_new) { + auto update = [&old_new](std::string& name) { + auto it = old_new.find(name); + if (it != old_new.end()) + name = it->second; + }; + process_addresses(st, [&](AtomAddress& aa) { + if (aa.res_id.name == res_name) + update(aa.atom_name); + }); + for (Model& model : st.models) + for (Chain& chain : model.chains) + for (Residue& res : chain.residues) + if (res.name == res_name) { + for (Atom& atom : res.atoms) + update(atom.name); + } +} + + +inline void replace_d_fraction_with_altlocs(Residue& res) { + for (size_t i = res.atoms.size(); i-- != 0; ) { + Atom& atom = res.atoms[i]; + float d_fraction = atom.fraction; + if (atom.element == El::H && d_fraction > 0) { + if (d_fraction >= 1) { + atom.element = El::D; + if (atom.name[0] == 'H') + atom.name[0] = 'D'; + } else { + int alt_offset = atom.altloc; + if (alt_offset) { + alt_offset -= 'A'; + // we don't expect 4+ altlocs - ignore such cases + if (alt_offset < 0 || alt_offset >= 3) + continue; + } + atom.altloc = 'A' + alt_offset; + float d_occ = atom.occ * d_fraction; + atom.occ *= (1 - d_fraction); + auto deut = res.atoms.insert(res.atoms.begin() + i + 1, atom); + deut->altloc = 'D' + alt_offset; + deut->element = El::D; + deut->occ = d_occ; + if (deut->name[0] == 'H') + deut->name[0] = 'D'; + } + } + } +} + +inline bool replace_deuterium_with_fraction(Residue& res) { + bool found = false; + for (auto d = res.atoms.end(); d-- != res.atoms.begin(); ) + if (d->element == El::D) { + found = true; + auto h = res.atoms.begin(); + for (; h != res.atoms.end(); ++h) + if (h->element == El::H && h->pos.approx(d->pos, 1e-9)) + break; + if (h != res.atoms.end()) { + h->occ += d->occ; + h->fraction = h->occ > 0.f ? d->occ / h->occ : 0.f; + if (h->altloc) { + bool keep_altloc = false; + for (auto i = res.atoms.begin(); i != res.atoms.end(); ++i) + if (i != d && i != h && (i->name == h->name || i->name == d->name)) + keep_altloc = true; + if (!keep_altloc) + h->altloc = '\0'; + } + res.atoms.erase(d); + } else { + d->element = El::H; + d->fraction = 1; + // Atom name is left unchanged. prepare_topology() first calls this + // function and then conditionally changes the name (Dxx -> Hxx). + } + } + return found; +} + +/// Hydrogens modelled as H/D mixture (altlocs H and D with the same position +/// and ADP, but with refined fraction of D), it can be stored in mmCIF either +/// as two atoms (H and D) or, using CCP4/Refmac extension, as H atoms with +/// the ccp4_deuterium_fraction parameter. +/// This function switches fraction <-> altlocs +inline void store_deuterium_as_fraction(Structure& st, bool store_fraction) { + if (st.has_d_fraction == store_fraction) + return; + st.has_d_fraction = false; + for (Model& model : st.models) + for (Chain& chain : model.chains) + for (Residue& res : chain.residues) + if (store_fraction) { + if (replace_deuterium_with_fraction(res)) + st.has_d_fraction = true; + } else { + replace_d_fraction_with_altlocs(res); + } +} + +inline void set_deuterium_fraction_of_hydrogens(Structure& st, float d_fract) { + st.has_d_fraction = true; + for (Model& model : st.models) + for (Chain& chain : model.chains) + for (Residue& res : chain.residues) + for (Atom& atom : res.atoms) + if (atom.is_hydrogen()) + atom.fraction = d_fract; +} + +/// Convert coordinates to the standard coordinate system for the unit cell. +inline void standardize_crystal_frame(Structure& st) { + if (!st.cell.explicit_matrices || !st.cell.is_crystal()) + return; + Transform orig_frac = st.cell.frac; + st.cell.explicit_matrices = false; + st.cell.calculate_properties(); + Transform tr = st.cell.orth.combine(orig_frac); + Transform tr_inv = tr.inverse(); + st.has_origx = true; + st.origx = tr_inv.combine(st.origx); + for (NcsOp& ncsop : st.ncs) + ncsop.tr = tr.combine(ncsop.tr).combine(tr_inv); + transform_pos_and_adp(st, tr); +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/monlib.hpp b/gemmi_gph/gemmi/monlib.hpp new file mode 100644 index 00000000..c31cd14a --- /dev/null +++ b/gemmi_gph/gemmi/monlib.hpp @@ -0,0 +1,256 @@ +// Copyright 2018 Global Phasing Ltd. +// +// Monomer library - (Refmac) restraints dictionary, +// which consists of monomers (chemical components), links, and modifications. + +#ifndef GEMMI_MONLIB_HPP_ +#define GEMMI_MONLIB_HPP_ + +#include +#include +#include +#include "cifdoc.hpp" +#include "elem.hpp" // for Element +#include "fail.hpp" // for fail, unreachable +#include "model.hpp" // for Residue, Atom +#include "chemcomp.hpp" // for ChemComp +#include "logger.hpp" // for Logger + +namespace gemmi { + +inline bool atom_match_with_alias(const std::string& atom_id, const std::string& atom, + const ChemComp::Aliasing* aliasing) { + if (aliasing) + if (const std::string* real_id = aliasing->name_from_alias(atom_id)) + return *real_id == atom; + return atom_id == atom; +} + +struct GEMMI_DLL ChemLink { + struct Side { + using Group = ChemComp::Group; + std::string comp; + std::string mod; + Group group = Group::Null; + bool matches_group(Group res) const { + if (group == Group::Null) + return false; + return res == group || (group == Group::Peptide && ChemComp::is_peptide_group(res)) + || (group == Group::DnaRna && ChemComp::is_nucleotide_group(res)); + } + int specificity() const { + if (!comp.empty()) + return 3; + return group == Group::PPeptide || group == Group::MPeptide ? 1 : 0; + } + }; + std::string id; + std::string name; + Side side1; + Side side2; + Restraints rt; + cif::Block block; // temporary, until we have ChemLink->Block function + + /// If multiple ChemLinks match a bond, the one with highest scores should be used. + int calculate_score(const Residue& res1, const Residue* res2, + char alt, char alt2, + const ChemComp::Aliasing* aliasing1, + const ChemComp::Aliasing* aliasing2) const; +}; + +struct GEMMI_DLL ChemMod { + struct AtomMod { + int func; + std::string old_id; + std::string new_id; + Element el; + float charge; + std::string chem_type; + }; + + std::string id; + std::string name; + std::string comp_id; + std::string group_id; + std::vector atom_mods; + Restraints rt; + cif::Block block; // temporary, until we have ChemMod->Block function + + void apply_to(ChemComp& chemcomp, ChemComp::Group alias_group) const; +}; + +struct EnerLib { + enum class RadiusType {Vdw, Vdwh, Ion}; + struct Atom { + Element element; + char hb_type; + double vdw_radius; + double vdwh_radius; + double ion_radius; + int valency; + int sp; + }; + struct Bond { + std::string atom_type_2; + BondType type; + double length; + double value_esd; + }; + + EnerLib() {} + void read(const cif::Document& doc); + std::map atoms; // type->Atom + std::multimap bonds; // atom_type_1->Bond +}; + +struct GEMMI_DLL MonLib { + std::string monomer_dir; + std::map monomers; + std::map links; + std::map modifications; + std::map cc_groups; + EnerLib ener_lib; + + const ChemLink* get_link(const std::string& link_id) const { + auto link = links.find(link_id); + return link != links.end() ? &link->second : nullptr; + } + const ChemMod* get_mod(const std::string& name) const { + auto modif = modifications.find(name); + return modif != modifications.end() ? &modif->second : nullptr; + } + + // Returns the most specific link and a flag that is true + // if the order is comp2-comp1 in the link definition. + std::tuple + match_link(const Residue& res1, const std::string& atom1, char alt1, + const Residue& res2, const std::string& atom2, char alt2, + double min_bond_sq=0) const { + const ChemLink* best_link = nullptr; + bool inverted = false; + const ChemComp::Aliasing* aliasing1 = nullptr; + const ChemComp::Aliasing* aliasing2 = nullptr; + const ChemComp::Aliasing* aliasing1_final = nullptr; + const ChemComp::Aliasing* aliasing2_final = nullptr; + int best_score = -1000; + for (const auto& ml : links) { + const ChemLink& link = ml.second; + if (link.rt.bonds.empty() || starts_with(link.name, "auto-")) + continue; + // for now we don't have link definitions with >1 bonds + const Restraints::Bond& bond = link.rt.bonds[0]; + if (sq(bond.value) < min_bond_sq) + continue; + if (link_side_matches_residue(link.side1, res1.name, &aliasing1) && + link_side_matches_residue(link.side2, res2.name, &aliasing2) && + atom_match_with_alias(bond.id1.atom, atom1, aliasing1) && + atom_match_with_alias(bond.id2.atom, atom2, aliasing2)) { + int score = link.calculate_score(res1, &res2, alt1, alt2, aliasing1, aliasing2); + if (score > best_score) { + best_link = &link; + best_score = score; + aliasing1_final = aliasing1; + aliasing2_final = aliasing2; + inverted = false; + } + } + if (link_side_matches_residue(link.side1, res2.name, &aliasing2) && + link_side_matches_residue(link.side2, res1.name, &aliasing1) && + atom_match_with_alias(bond.id1.atom, atom2, aliasing2) && + atom_match_with_alias(bond.id2.atom, atom1, aliasing1)) { + // NOLINTNEXTLINE(readability-suspicious-call-argument) + int score = link.calculate_score(res2, &res1, alt2, alt1, aliasing2, aliasing1); + if (score > best_score) { + best_link = &link; + best_score = score; + aliasing1_final = aliasing1; + aliasing2_final = aliasing2; + inverted = true; + } + } + } + return std::make_tuple(best_link, inverted, aliasing1_final, aliasing2_final); + } + + void add_monomer_if_present(const cif::Block& block) { + if (block.has_tag("_chem_comp_atom.atom_id")) { + ChemComp cc = make_chemcomp_from_block(block); + if (cc.group == ChemComp::Group::Null) { + auto it = cc_groups.find(cc.name); + if (it != cc_groups.end()) + cc.group = it->second; + } + std::string name = cc.name; + monomers.emplace(name, std::move(cc)); + } + } + + bool link_side_matches_residue(const ChemLink::Side& side, + const std::string& res_name, + ChemComp::Aliasing const** aliasing) const { + assert(aliasing); + *aliasing = nullptr; + if (!side.comp.empty()) + return side.comp == res_name; + auto it = monomers.find(res_name); + if (it != monomers.end()) { + if (side.matches_group(it->second.group)) + return true; + for (const ChemComp::Aliasing& a : it->second.aliases) + if (side.matches_group(a.group)) { + *aliasing = &a; + return true; + } + } + return false; + } + + /// Returns path to the monomer cif file (the file may not exist). + std::string path(const std::string& code) const { + return monomer_dir + relative_monomer_path(code); + } + + static std::string relative_monomer_path(const std::string& code); + + void read_monomer_doc(const cif::Document& doc); + + void read_monomer_cif(const std::string& path_); + + void set_monomer_dir(const std::string& monomer_dir_) { + monomer_dir = monomer_dir_; + if (!monomer_dir.empty() && monomer_dir.back() != '/' && monomer_dir.back() != '\\') + monomer_dir += '/'; + } + + /// Read mon_lib_list.cif, ener_lib.cif and required monomers. + /// Returns true if all requested monomers were added. + bool read_monomer_lib(const std::string& monomer_dir_, + const std::vector& resnames, + const Logger& logger); + + double find_ideal_distance(const const_CRA& cra1, const const_CRA& cra2) const; + void update_old_atom_names(Structure& st, const Logger& logger) const; +}; + +// to be deprecated +inline MonLib read_monomer_lib(const std::string& monomer_dir, + const std::vector& resnames, + const std::string& libin="", + bool ignore_missing=false) { + MonLib monlib; + if (!libin.empty()) + monlib.read_monomer_cif(libin); + std::string error; + Logger logger; + if (!ignore_missing) + logger.callback = [&error](const std::string& s) { cat_to(error, s, '\n'); }; + bool ok = monlib.read_monomer_lib(monomer_dir, resnames, logger); + if (!ignore_missing && !ok) { + error += "Please create definitions for missing monomers."; + fail(error); + } + return monlib; +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/mtz2cif.hpp b/gemmi_gph/gemmi/mtz2cif.hpp new file mode 100644 index 00000000..387ceedc --- /dev/null +++ b/gemmi_gph/gemmi/mtz2cif.hpp @@ -0,0 +1,115 @@ +// Copyright 2020 Global Phasing Ltd. +// +// A class for converting MTZ (merged or unmerged) to SF-mmCIF + +// TODO: +// - cell parameters may be different in CELL and DCELL records, check for it +// - check that the FP column is not from Refmac +// - should we allow for repeated column names in MTZ? + +#ifndef GEMMI_MTZ2CIF_HPP_ +#define GEMMI_MTZ2CIF_HPP_ + +#include +#include "mtz.hpp" // for Mtz +#include "xds_ascii.hpp" // for XdsAscii +#include "intensit.hpp" // for Intensities +#include "logger.hpp" // for Logger +#include "metadata.hpp" // for SoftwareItem + +namespace gemmi { + +class GEMMI_DLL MtzToCif { +public: + // options that can be set directly + std::vector spec_lines; // conversion specification (cf. default_spec) + const char* block_name = nullptr; // NAME in data_NAME + std::string entry_id = "xxxx"; // _entry.id + bool with_comments = true; // write comments + bool with_history = true; // write MTZ history in comments + bool skip_empty = false; // skip reflections with no values + bool skip_negative_sigi = false; // skip refl. with sigma(I) < 0 in unmerged + bool enable_UB = false; // write _diffrn_orient_matrix.UB + bool write_staraniso_tensor = true; // write _reflns.pdbx_aniso_B_tensor_* + bool write_special_marker_for_pdb = false; + int less_anomalous = 0; // skip (+)/(-) columns even if in spec + std::string skip_empty_cols; // columns used to determine "emptiness" + double wavelength = NAN; // user-specified wavelength + int trim = 0; // output only reflections -N<=h,k,l<=N + int free_flag_value = -1; // -1 = auto: 0 or (if we have >50% of 0's) 1 + std::string staraniso_version; // for _software.version in "special_marker" + std::string gemmi_run_from; // added to gemmi as _software.description + + static const char** default_spec(bool for_merged) { + static const char* merged[] = { + "H H index_h", + "K H index_k", + "L H index_l", + "? FREE|RFREE|FREER|FreeR_flag|R-free-flags|FreeRflag I status S", + "? IMEAN|I|IOBS|I-obs J intensity_meas", + "& SIG{prev} Q intensity_sigma", + "? I(+)|IOBS(+)|I-obs(+)|Iplus K pdbx_I_plus", + "& SIG{prev} M pdbx_I_plus_sigma", + "? I(-)|IOBS(-)|I-obs(-)|Iminus K pdbx_I_minus", + "& SIG{prev} M pdbx_I_minus_sigma", + // TODO: FP from Refmac should show warning or error + "? F|FP|FOBS|F-obs F F_meas_au", + "& SIG{prev} Q F_meas_sigma_au", + "? F(+)|FOBS(+)|F-obs(+)|Fplus G pdbx_F_plus", + "& SIG{prev} L pdbx_F_plus_sigma", + "? F(-)|FOBS(-)|F-obs(-)|Fminus G pdbx_F_minus", + "& SIG{prev} L pdbx_F_minus_sigma", + "? DP D pdbx_anom_difference", + "& SIGDP Q pdbx_anom_difference_sigma", + "? FC F F_calc", + "? PHIC P phase_calc", + "? FOM W fom", + "? HLA A pdbx_HL_A_iso", + "& HLB A pdbx_HL_B_iso", + "& HLC A pdbx_HL_C_iso", + "& HLD A pdbx_HL_D_iso", + "? FWT|2FOFCWT F pdbx_FWT", + "& PHWT|PH2FOFCWT P pdbx_PHWT .3f", + "? DELFWT|FOFCWT F pdbx_DELFWT", + "& DELPHWT|PHDELWT|PHFOFCWT P pdbx_DELPHWT .3f", + nullptr + }; + static const char* unmerged[] = { + "$dataset diffrn_id", // diffrn_id - sweep id deduced from BATCH + "$counter id", // reflection counter (1, 2, ...) + "H H index_h", + "K H index_k", + "L H index_l", + "? I J intensity_net", + "& SIGI Q intensity_sigma .5g", + // new! https://github.com/wwpdb-dictionaries/mmcif_pdbx/pull/33 + "? ROT R pdbx_scan_angle", + "$image pdbx_image_id", + nullptr + }; + return for_merged ? merged : unmerged; + } + + void write_cif(const Mtz& mtz, const Mtz* mtz2, + SMat33* staraniso_b, std::ostream& os); + void write_cif_from_xds(const XdsAscii& xds, std::ostream& os) const; +}; + +GEMMI_DLL void write_staraniso_b_in_mmcif(const SMat33& b, + const std::string& entry_id, + char* buf, std::ostream& os); + +/// remove '_dataset_name' that can be appended to column names in ccp4i +GEMMI_DLL void remove_appendix_from_column_names(Mtz& mtz, const Logger& logger); + +GEMMI_DLL bool validate_merged_mtz_deposition_columns(const Mtz& mtz, const Logger& logger); + +// note: both mi and ui get modified +GEMMI_DLL bool validate_merged_intensities(Intensities& mi, Intensities& ui, + bool relaxed_check, const Logger& logger); + +GEMMI_DLL std::vector +get_software_from_mtz_history(const std::vector& history); + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/neighbor.hpp b/gemmi_gph/gemmi/neighbor.hpp new file mode 100644 index 00000000..40f5dadb --- /dev/null +++ b/gemmi_gph/gemmi/neighbor.hpp @@ -0,0 +1,376 @@ +// Copyright 2018 Global Phasing Ltd. +// +// Cell-linked lists method for atom searching (a.k.a. grid search, binning, +// bucketing, cell technique for neighbor search, etc). + +#ifndef GEMMI_NEIGHBOR_HPP_ +#define GEMMI_NEIGHBOR_HPP_ + +#include +#include // for INFINITY, sqrt + +#include "fail.hpp" // for fail +#include "grid.hpp" +#include "model.hpp" +#include "small.hpp" + +namespace gemmi { + +struct NeighborSearch { + + struct Mark { + Position pos; + char altloc; + Element element; + short image_idx; + int chain_idx; + int residue_idx; + int atom_idx; + + Mark(const Position& p, char alt, El el, short im, int ch, int res, int atom) + : pos(p), altloc(alt), element(el), + image_idx(im), chain_idx(ch), residue_idx(res), atom_idx(atom) {} + + CRA to_cra(Model& mdl) const { + Chain& c = mdl.chains.at(chain_idx); + Residue& r = c.residues.at(residue_idx); + Atom& a = r.atoms.at(atom_idx); + return {&c, &r, &a}; + } + const_CRA to_cra(const Model& mdl) const { + const Chain& c = mdl.chains.at(chain_idx); + const Residue& r = c.residues.at(residue_idx); + const Atom& a = r.atoms.at(atom_idx); + return {&c, &r, &a}; + } + SmallStructure::Site& to_site(SmallStructure& small_st) const { + return small_st.sites.at(atom_idx); + } + const SmallStructure::Site& to_site(const SmallStructure& small_st) const { + return small_st.sites.at(atom_idx); + } + }; + + Grid> grid; + double radius_specified = 0.; + Model* model = nullptr; + SmallStructure* small_structure = nullptr; + bool use_pbc = true; + bool include_h = true; + + NeighborSearch() = default; + // Model is not const so it can be modified in for_each_contact() + NeighborSearch(Model& model_, const UnitCell& cell, double radius) { + model = &model_; + radius_specified = radius; + set_bounding_cell(cell); + set_grid_size(); + } + NeighborSearch(SmallStructure& small_st, double radius) { + small_structure = &small_st; + radius_specified = radius; + grid.unit_cell = small_st.cell; + set_grid_size(); + } + + NeighborSearch& populate(bool include_h_=true); + void add_chain(const Chain& chain, bool include_h_=true); + void add_chain_n(const Chain& chain, int n_ch); + void add_atom(const Atom& atom, int n_ch, int n_res, int n_atom); + void add_site(const SmallStructure::Site& site, int n); + + // assumes data in [0, 1), but uses index_n to account for numerical errors + std::vector& get_subcell(const Fractional& fr) { + size_t idx = grid.index_n(int(fr.x * grid.nu), + int(fr.y * grid.nv), + int(fr.z * grid.nw)); + if (idx >= grid.data.size()) + fail("NeighborSearch error, probably due to NaN in coordinates"); + return grid.data[idx]; + } + + template + void for_each_cell(const Position& pos, const Func& func, int k=1); + + template + void for_each(const Position& pos, char alt, double radius, const Func& func, int k=1) { + if (radius <= 0) + return; + for_each_cell(pos, [&](std::vector& marks, const Fractional& fr) { + Position p = use_pbc ? grid.unit_cell.orthogonalize(fr) : pos; + for (Mark& m : marks) { + double dist_sq = m.pos.dist_sq(p); + if (dist_sq < sq(radius) && is_same_conformer(alt, m.altloc)) + func(m, dist_sq); + } + }, k); + } + + int sufficient_k(double r) const { + // .00001 is added to account for possible numeric error in r + return r <= radius_specified ? 1 : int(r / radius_specified + 1.00001); + } + + // with radius==0 it uses radius_specified + std::vector find_atoms(const Position& pos, char alt, + double min_dist, double radius) { + int k = sufficient_k(radius); + if (radius == 0) + radius = radius_specified; + std::vector out; + for_each(pos, alt, radius, [&](Mark& a, double dist_sq) { + if (dist_sq >= sq(min_dist)) + out.push_back(&a); + }, k); + return out; + } + + std::vector find_neighbors(const Atom& atom, double min_dist, double max_dist) { + return find_atoms(atom.pos, atom.altloc, min_dist, max_dist); + } + std::vector find_site_neighbors(const SmallStructure::Site& site, + double min_dist, double max_dist) { + Position pos = grid.unit_cell.orthogonalize(site.fract); + return find_atoms(pos, '\0', min_dist, max_dist); + } + + std::pair + find_nearest_atom_within_k(const Position& pos, int k, double radius) { + Mark* mark = nullptr; + double nearest_dist_sq = radius * radius; + for_each_cell(pos, [&](std::vector& marks, const Fractional& fr) { + Position p = use_pbc ? grid.unit_cell.orthogonalize(fr) : pos; + for (Mark& m : marks) { + double dist_sq = m.pos.dist_sq(p); + if (dist_sq < nearest_dist_sq) { + mark = &m; + nearest_dist_sq = dist_sq; + } + } + }, k); + return {mark, nearest_dist_sq}; + } + + // it would be good to return also NearestImage + Mark* find_nearest_atom(const Position& pos, double radius=INFINITY) { + double r_spec = radius_specified; + if (radius == 0.f) + radius = r_spec; + int max_k = std::max(std::max(std::max(grid.nu, grid.nv), grid.nw), 2); + for (int k = 1; k < max_k; k *= 2) { + auto result = find_nearest_atom_within_k(pos, k, radius); + // if Mark was not found, result.second is set to radius^2. + if (result.second < sq(k * r_spec)) + return result.first; + if (result.first != nullptr) { + // We found an atom, but because it was further away than k*r_spec, + // so now it's sufficient to find the nearest atom in dist: + double dist = std::sqrt(result.second); + return find_nearest_atom_within_k(pos, sufficient_k(dist), radius).first; + } + } + if (!use_pbc) + // pos can be outside of bounding box. In such case, although it's slow, + // search in all cells. Using large number that will be clipped. + return find_nearest_atom_within_k(pos, INT_MAX/4, radius).first; + return nullptr; + } + + double dist_sq(const Position& pos1, const Position& pos2) const { + return grid.unit_cell.distance_sq(pos1, pos2); + } + double dist(const Position& pos1, const Position& pos2) const { + return std::sqrt(dist_sq(pos1, pos2)); + } + + FTransform get_image_transformation(int image_idx) const { + // 0 is for identity, other indices are shifted by one. + if (image_idx == 0) + return Transform{}; + if ((size_t)image_idx <= grid.unit_cell.images.size()) + return grid.unit_cell.images[image_idx-1]; + fail("No such image index: " + std::to_string(image_idx)); + } + +private: + void set_grid_size() { + // We don't use set_size_from_spacing() etc because we don't need + // FFT-friendly size nor symmetry. + double inv_radius = 1 / radius_specified; + const UnitCell& uc = grid.unit_cell; + grid.set_size_without_checking(std::max(int(inv_radius / uc.ar), 1), + std::max(int(inv_radius / uc.br), 1), + std::max(int(inv_radius / uc.cr), 1)); + } + + void set_bounding_cell(const UnitCell& cell) { + use_pbc = cell.is_crystal(); + if (use_pbc) { + grid.unit_cell = cell; + } else { + // cf. calculate_box() + Box box; + for (CRA cra : model->all()) + box.extend(cra.atom->pos); + // The box needs to include all NCS images (strict NCS from MTRIXn). + // To avoid additional function parameter that would pass Structure::ncs, + // here we obtain NCS transformations from UnitCell::images. + std::vector ncs = cell.get_ncs_transforms(); + if (!ncs.empty()) { + for (CRA cra : model->all()) + // images store fractional transforms, but for non-crystal + // it should be the same as Cartesian transform. + for (const Transform& tr : ncs) + box.extend(Position(tr.apply(cra.atom->pos))); + } + box.add_margin(0.01); + Position size = box.get_size(); + grid.unit_cell.set(size.x, size.y, size.z, 90, 90, 90); + grid.unit_cell.frac.vec -= grid.unit_cell.fractionalize(box.minimum); + grid.unit_cell.orth.vec += box.minimum; + for (const Transform& tr : ncs) { + UnitCell& c = grid.unit_cell; + // cf. add_ncs_images_to_cs_images() + c.images.push_back(c.frac.combine(tr.combine(c.orth))); + } + } + } +}; + +inline NeighborSearch& NeighborSearch::populate(bool include_h_) { + include_h = include_h_; + if (model) { + for (int n_ch = 0; n_ch != (int) model->chains.size(); ++n_ch) + add_chain_n(model->chains[n_ch], n_ch); + } else if (small_structure) { + for (int n = 0; n != (int) small_structure->sites.size(); ++n) { + SmallStructure::Site& site = small_structure->sites[n]; + if (include_h || !site.element.is_hydrogen()) + add_site(site, n); + } + } else { + fail("NeighborSearch not initialized"); + } + return *this; +} + +inline void NeighborSearch::add_chain(const Chain& chain, bool include_h_) { + if (!model) + fail("NeighborSearch.add_chain(): model not initialized yet"); + // to be safe avoid (&chain - model.chains[0]) which could be UB + for (int n_ch = 0; n_ch != (int) model->chains.size(); ++n_ch) + if (&model->chains[n_ch] == &chain) { + include_h = include_h_; + add_chain_n(chain, n_ch); + return; + } + fail("NeighborSearch.add_chain(): chain not in this model"); +} + +inline void NeighborSearch::add_chain_n(const Chain& chain, int n_ch) { + for (int n_res = 0; n_res != (int) chain.residues.size(); ++n_res) { + const Residue& res = chain.residues[n_res]; + for (int n_atom = 0; n_atom != (int) res.atoms.size(); ++n_atom) { + const Atom& atom = res.atoms[n_atom]; + if (include_h || !atom.is_hydrogen()) + add_atom(atom, n_ch, n_res, n_atom); + } + } +} + +inline void NeighborSearch::add_atom(const Atom& atom, + int n_ch, int n_res, int n_atom) { + const UnitCell& gcell = grid.unit_cell; + Fractional frac0 = gcell.fractionalize(atom.pos); + { + Fractional frac = frac0.wrap_to_unit(); + // for non-crystals, frac==frac0 => pos = atom.pos + Position pos = use_pbc ? gcell.orthogonalize(frac) : atom.pos; + get_subcell(frac).emplace_back(pos, atom.altloc, atom.element.elem, + 0, n_ch, n_res, n_atom); + } + for (int n_im = 0; n_im != (int) gcell.images.size(); ++n_im) { + Fractional frac = gcell.images[n_im].apply(frac0).wrap_to_unit(); + Position pos = gcell.orthogonalize(frac); + get_subcell(frac).emplace_back(pos, atom.altloc, atom.element.elem, + short(n_im + 1), n_ch, n_res, n_atom); + } +} + +// We exclude special position images of atoms here, but not in add_atom. +// This choice is somewhat arbitrary, but it also reflects the fact that +// in MX files occupances of atoms on special positions are (almost always) +// fractional and all images are to be taken into account. +inline void NeighborSearch::add_site(const SmallStructure::Site& site, int n) { + const double SPECIAL_POS_TOL = 0.4; + const UnitCell& gcell = grid.unit_cell; + std::vector others; + others.reserve(gcell.images.size()); + Fractional frac0 = site.fract.wrap_to_unit(); + { + Position pos = gcell.orthogonalize(frac0); + get_subcell(frac0).emplace_back(pos, '\0', site.element.elem, 0, -1, -1, n); + } + for (int n_im = 0; n_im != (int) gcell.images.size(); ++n_im) { + Fractional frac = gcell.images[n_im].apply(site.fract).wrap_to_unit(); + if (gcell.distance_sq(frac, frac0) < sq(SPECIAL_POS_TOL) || + std::any_of(others.begin(), others.end(), [&](const Fractional& f) { + return gcell.distance_sq(frac, f) < sq(SPECIAL_POS_TOL); + })) + continue; + Position pos = gcell.orthogonalize(frac); + get_subcell(frac).emplace_back(pos, '\0', site.element.elem, + short(n_im + 1), -1, -1, n); + others.push_back(frac); + } +} + +template +void NeighborSearch::for_each_cell(const Position& pos, const Func& func, int k) { + Fractional fr = grid.unit_cell.fractionalize(pos); + if (use_pbc) + fr = fr.wrap_to_unit(); + int u0 = int(fr.x * grid.nu) - k; + int v0 = int(fr.y * grid.nv) - k; + int w0 = int(fr.z * grid.nw) - k; + int uend = u0 + 2 * k + 1; + int vend = v0 + 2 * k + 1; + int wend = w0 + 2 * k + 1; + if (use_pbc) { + auto shift = [](int j, int n) { + if (j < 0) + return (j + 1) / n - 1; + if (j >= n) + return j / n; + return 0; + }; + for (int w = w0; w < wend; ++w) { + int dw = shift(w, grid.nw); + for (int v = v0; v < vend; ++v) { + int dv = shift(v, grid.nv); + size_t idx0 = grid.index_q(0, v - dv * grid.nv, w - dw * grid.nw); + for (int u = u0; u < uend; ++u) { + int du = shift(u, grid.nu); + size_t idx = idx0 + (u - du * grid.nu); + func(grid.data[idx], Fractional(fr.x - du, fr.y - dv, fr.z - dw)); + } + } + } + } else { + u0 = std::max(0, u0); + v0 = std::max(0, v0); + w0 = std::max(0, w0); + uend = std::min(uend, grid.nu); + vend = std::min(vend, grid.nv); + wend = std::min(wend, grid.nw); + for (int w = w0; w < wend; ++w) + for (int v = v0; v < vend; ++v) + for (int u = u0; u < uend; ++u) { + size_t idx = grid.index_q(u, v, w); + func(grid.data[idx], fr); + } + } +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/neutron92.hpp b/gemmi_gph/gemmi/neutron92.hpp new file mode 100644 index 00000000..67b96d36 --- /dev/null +++ b/gemmi_gph/gemmi/neutron92.hpp @@ -0,0 +1,87 @@ +// Copyright 2022 Global Phasing Ltd. + +// Neutron coherent scattering lengths of the elements, +// from Neutron News, Vol. 3, No. 3, 1992. +// +// We use the same data as cctbx/eltbx/neutron.h, which is based on +// https://www.ncnr.nist.gov/resources/n-lengths/list.html +// which in turn is based on Neutron News, Vol. 3, No. 3, 1992, pp. 29-37. + +#ifndef GEMMI_NEUTRON92_HPP_ +#define GEMMI_NEUTRON92_HPP_ + +#include "formfact.hpp" // for GaussianCoef +#include "elem.hpp" // for El + +namespace gemmi { + +#if defined(__GNUC__) && __GNUC__-0 > 4 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-conversion" +#endif + +template +struct ZeroCoef { + using Coef = GaussianCoef<0, 1, Real>; + static Real data[121]; + + static Real& get_(El el) { return data[static_cast(el)]; } + static bool has(El el) { return static_cast(el) < sizeof(data) / sizeof(Real); } + static Coef get(El el, signed char /*charge*/=0, int /*serial*/=0) { return Coef{{get_(el)}}; } +}; + +// real part of the bound coherent neutron scattering length (fm) +template +Real ZeroCoef::data[121] = { /*X*/ 0.0 }; + +template +struct Neutron92 { + using Coef = GaussianCoef<0, 1, Real>; + static Real data[121]; + + static Real& get_(El el) { return data[static_cast(el)]; } + static bool has(El el) { return get_(el) != 0; } + static Coef get(El el, signed char /*charge*/=0, int /*serial*/=0) { return Coef{{get_(el)}}; } +}; + +// real part of the bound coherent neutron scattering length (fm) +template +Real Neutron92::data[121] = { + /*X*/ 0.0, + /*H*/ -3.7390, /*He*/ 3.26, + /*Li*/ -1.90, /*Be*/ 7.79, /*B*/ 5.30, /*C*/ 6.646, + /*N*/ 9.36, /*O*/ 5.803, /*F*/ 5.654, /*Ne*/ 4.566, + /*Na*/ 3.63, /*Mg*/ 5.375, /*Al*/ 3.449, /*Si*/ 4.1491, + /*P*/ 5.13, /*S*/ 2.847, /*Cl*/ 9.577, /*Ar*/ 1.909, + /*K*/ 3.67, /*Ca*/ 4.70, /*Sc*/ 12.29, /*Ti*/ -3.438, + /*V*/ -0.3824, /*Cr*/ 3.635, /*Mn*/ -3.73, /*Fe*/ 9.45, + /*Co*/ 2.49, /*Ni*/ 10.3, /*Cu*/ 7.718, /*Zn*/ 5.68, + /*Ga*/ 7.288, /*Ge*/ 8.185, /*As*/ 6.58, /*Se*/ 7.97, + /*Br*/ 6.795, /*Kr*/ 7.81, /*Rb*/ 7.09, /*Sr*/ 7.02, + /*Y*/ 7.75, /*Zr*/ 7.16, /*Nb*/ 7.054, + /*Mo*/ 6.715, /*Tc*/ 6.8, /*Ru*/ 7.03, /*Rh*/ 5.88, /*Pd*/ 5.91, + /*Ag*/ 5.922, /*Cd*/ 4.87, /*In*/ 4.065, /*Sn*/ 6.225, + /*Sb*/ 5.57, /*Te*/ 5.8, /*I*/ 5.28, /*Xe*/ 4.92, + /*Cs*/ 5.42, /*Ba*/ 5.07, /*La*/ 8.24, /*Ce*/ 4.84, + /*Pr*/ 4.58, /*Nd*/ 7.69, /*Pm*/ 12.6, /*Sm*/ 0.8, + /*Eu*/ 7.22, /*Gd*/ 6.5, /*Tb*/ 7.38, /*Dy*/ 16.9, + /*Ho*/ 8.01, /*Er*/ 7.79, /*Tm*/ 7.07, /*Yb*/ 12.43, + /*Lu*/ 7.21, /*Hf*/ 7.7, /*Ta*/ 6.91, /*W*/ 4.86, + /*Re*/ 9.2, /*Os*/ 10.7, /*Ir*/ 10.6, /*Pt*/ 9.6, + /*Au*/ 7.63, /*Hg*/ 12.692, /*Tl*/ 8.776, + /*Pb*/ 9.405, /*Bi*/ 8.532, /*Po*/ 0., /*At*/ 0., /*Rn*/ 0., + /*Fr*/ 0., /*Ra*/ 10.0, /*Ac*/ 0., /*Th*/ 10.31, /*Pa*/ 9.1, + /*U*/ 8.417, /*Np*/ 10.55, /*Pu*/ 0., /*Am*/ 8.3, /*Cm*/ 9.5, + /*Bk*/ 0., /*Cf*/ 0., /*Es*/ 0., /*Fm*/ 0., /*Md*/ 0., + /*No*/ 0., /*Lr*/ 0., /*Rf*/ 0., /*Db*/ 0., /*Sg*/ 0., + /*Bh*/ 0., /*Hs*/ 0., /*Mt*/ 0., /*Ds*/ 0., /*Rg*/ 0., /*Cn*/ 0., + /*Nh*/ 0., /*Fl*/ 0., /*Mc*/ 0., /*Lv*/ 0., /*Ts*/ 0., /*Og*/ 0., + /*D*/ 6.671, /*END*/ 0. +}; + +#if defined(__GNUC__) && __GNUC__-0 > 4 +#pragma GCC diagnostic pop +#endif + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/numb.hpp b/gemmi_gph/gemmi/numb.hpp new file mode 100644 index 00000000..54f09d5b --- /dev/null +++ b/gemmi_gph/gemmi/numb.hpp @@ -0,0 +1,58 @@ +// Copyright 2017 Global Phasing Ltd. +// +// Utilities for parsing CIF numbers (the CIF spec calls them 'numb'). +// +// Numb - the numeric type in CIF - is a number with optional +// standard uncertainty (s.u.) in brackets: 1.23(8). +// Mmcif file do not use s.u. though - they define own numeric categories. + +#ifndef GEMMI_NUMB_HPP_ +#define GEMMI_NUMB_HPP_ + +#include // for NAN +#include +#include "third_party/fast_float.h" + +namespace gemmi { +namespace cif { + +inline double as_number(const std::string& s, double nan=NAN) { + const char* start = s.data(); + const char* end = s.data() + s.size(); + if (*start == '+') + ++start; + // NaN, Inf and -Inf are not allowed in CIF + char f = start[int(*start == '-')] | 0x20; + if (f == 'i' || f == 'n') + return nan; + + double d; + auto result = fast_float::from_chars(start, end, d); + if (result.ec != std::errc()) + return nan; + if (*result.ptr == '(') { + const char* p = result.ptr + 1; + while (*p >= '0' && *p <= '9') + ++p; + if (*p == ')') + result.ptr = p + 1; + } + return result.ptr == end ? d : nan; +} + +inline bool is_numb(const std::string& s) { + return !std::isnan(as_number(s)); +} + + +// for use in templates (see also as_any() functions in cifdoc.hpp) +inline float as_any(const std::string& s, float null) { + return (float) as_number(s, null); +} +inline double as_any(const std::string& s, double null) { + return as_number(s, null); +} + +} // namespace cif +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/pdb.hpp b/gemmi_gph/gemmi/pdb.hpp new file mode 100644 index 00000000..c304fe18 --- /dev/null +++ b/gemmi_gph/gemmi/pdb.hpp @@ -0,0 +1,87 @@ +// Copyright 2017 Global Phasing Ltd. +// +// Read the PDB file format and store it in Structure. +// +// Based on the format spec: +// https://www.wwpdb.org/documentation/file-format-content/format33/v3.3.html +// + support for two-character chain IDs (columns 21 and 22) +// + read segment ID (columns 73-76) +// + read hybrid-36 serial numbers (http://cci.lbl.gov/hybrid_36/) +// + hybrid-36 sequence id for sequences longer than 9999 (no such examples) +// + allow for longer REMARK lines (up to 120 characters) + +#ifndef GEMMI_PDB_HPP_ +#define GEMMI_PDB_HPP_ + +#include "input.hpp" // for FileStream +#include "model.hpp" // for Structure, ... + +namespace gemmi { + +/// Compare the first 4 letters of s, ignoring case, with uppercase record. +/// Both args must have at least 3+1 chars. ' ' and NUL are equivalent in s. +inline bool is_record_type4(const char* s, const char* record) { + return ialpha4_id(s) == ialpha4_id(record); +} +/// for record "TER": "TER ", TER\n, TER\r, TER\t match, TERE, TER1 don't +inline bool is_record_type3(const char* s, const char* record) { + return (ialpha4_id(s) & ~0xf) == ialpha4_id(record); +} + +/// Returns operations corresponding to 1555, 2555, ... N555 +GEMMI_DLL std::vector read_remark_290(const std::vector& raw_remarks); + +GEMMI_DLL void populate_structure_from_pdb_stream(AnyStream& line_reader, + const std::string& source, + Structure& st, + PdbReadOptions options); + +inline void populate_structure_from_pdb_memory(const char* data, size_t size, + const std::string& name, + Structure& st, + PdbReadOptions options={}) { + MemoryStream stream{data, size}; + populate_structure_from_pdb_stream(stream, name, st, options); +} + +inline void populate_structure_from_pdb_string(const std::string& str, + const std::string& name, + Structure& st, + PdbReadOptions options={}) { + populate_structure_from_pdb_memory(str.c_str(), str.length(), name, st, options); +} + +inline Structure read_pdb_from_stream(AnyStream& line_reader, + const std::string& source, + PdbReadOptions options) { + gemmi::Structure st; + populate_structure_from_pdb_stream(line_reader, source, st, options); + return st; +}; + +inline Structure read_pdb_file(const std::string& path, + PdbReadOptions options={}) { + FileStream stream(path.c_str(), "rb"); + return read_pdb_from_stream(stream, path, options); +} + +inline Structure read_pdb_from_memory(const char* data, size_t size, + const std::string& name, + PdbReadOptions options={}) { + MemoryStream stream{data, size}; + return read_pdb_from_stream(stream, name, options); +} + +inline Structure read_pdb_string(const std::string& str, + const std::string& name, + PdbReadOptions options={}) { + return read_pdb_from_memory(str.c_str(), str.length(), name, options); +} + +template +inline Structure read_pdb(T&& input, PdbReadOptions options={}) { + return read_pdb_from_stream(*input.create_stream(), input.path(), options); +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/pdb_id.hpp b/gemmi_gph/gemmi/pdb_id.hpp new file mode 100644 index 00000000..97a23229 --- /dev/null +++ b/gemmi_gph/gemmi/pdb_id.hpp @@ -0,0 +1,71 @@ +// Copyright 2018 Global Phasing Ltd. +// +// Handling PDB ID and $PDB_DIR: is_pdb_code(), expand_pdb_code_to_path(), ... + +#ifndef GEMMI_PDB_ID_HPP_ +#define GEMMI_PDB_ID_HPP_ + +#include // for isdigit, isalnum +#include // getenv +#include +#include "fail.hpp" // for fail +#include "util.hpp" // for to_lower + +namespace gemmi { + +inline bool all_alnums(const char* p) { + for (;;++p) + if (!std::isalnum(*p)) + return *p == '\0'; + unreachable(); +} + +inline bool is_pdb_code(const std::string& str) { + return (str.length() == 4 && std::isdigit(str[0]) && all_alnums(&str[1])) + || (str.length() == 12 && str.compare(0, 4, "pdb_") == 0 + && std::isdigit(str[4]) && all_alnums(&str[5])); +} +inline std::string path_in_pdb_dir(const std::string& code, char type) { + if (code.size() == 12) + fail("extended PDB codes are not supported yet: " + code); + std::string lc = to_lower(code); + int n = 0; + if (type == 'M') + n = 1; + else if (type == 'S') + n = 2; + std::string path = "/structures/divided/"; + const char* dir[] = {"pdb/", "mmCIF/", "structure_factors/"}; + path += dir[n]; + path += lc.substr(1, 2); + const char* prefix[] = {"/pdb", "/", "/r"}; + path += prefix[n]; + path += lc; + const char* suffix[] = {".ent.gz", ".cif.gz", "sf.ent.gz"}; + path += suffix[n]; + return path; +} + +/// Call it after checking the code with gemmi::is_pdb_code(code). +/// The convention for $PDB_DIR is the same as in BioJava, see the docs. +/// \par type is the requested file type: 'M' for mmCIF or 'P' for PDB, 'S' for SF-mmCIF. +inline std::string expand_pdb_code_to_path(const std::string& code, char type, + bool throw_if_unset=false) { + std::string path; + if (const char* pdb_dir = std::getenv("PDB_DIR")) { + path = pdb_dir + path_in_pdb_dir(code, type); + } else if (throw_if_unset) { + fail(code + " is a PDB code, but $PDB_DIR is not set."); + } + return path; +} + +/// \par type is: 'M' for mmCIF or 'P' for PDB, 'S' for SF-mmCIF. +inline std::string expand_if_pdb_code(const std::string& input, char type='M') { + if (is_pdb_code(input)) + return expand_pdb_code_to_path(input, type, true); + return input; +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/pirfasta.hpp b/gemmi_gph/gemmi/pirfasta.hpp new file mode 100644 index 00000000..7b95fd65 --- /dev/null +++ b/gemmi_gph/gemmi/pirfasta.hpp @@ -0,0 +1,86 @@ +// Copyright 2020 Global Phasing Ltd. +// +// Read sequences from PIR or (multi-)FASTA formats. + +#ifndef GEMMI_PIRFASTA_HPP_ +#define GEMMI_PIRFASTA_HPP_ + +#include // for isspace +#include +#include // for min +#include "fail.hpp" + +namespace gemmi { + +struct FastaSeq { + std::string header; + std::string seq; +}; + +// PIR format starts with one of: >P1; >F1; >DL; >DC; >RL; >RC; >XX; +inline bool is_pir_format(const std::string& s) { + return s.length() > 4 && s[0] == '>' && s[3] == ';' && ( + ((s[1] == 'P' || s[1] == 'F') && s[2] == '1') || + ((s[1] == 'D' || s[1] == 'R') && (s[2] == 'L' || s[2] == 'C')) || + (s[1] == 'X' && s[2] == 'X')); +} + +inline std::vector read_pir_or_fasta(const std::string& str) { + if (str[0] != '>') + fail("PIR/FASTA files start with '>'"); + bool pir = is_pir_format(str); + std::vector r; + int blank_lines = 0; + int paren_level = 0; + bool ended = false; + for (size_t pos=0, end=0; end != std::string::npos; pos = end + 1) { + end = str.find('\n', pos); + if (str[pos] == '>') { + ended = false; + if (paren_level != 0) + break; + r.emplace_back(); + if (pir && end != std::string::npos) + end = str.find('\n', end+1); + r.back().header = str.substr(pos+1, end-(pos+1)); + } else { + std::string& seq = r.back().seq; + for (size_t i = pos; i < std::min(end, str.size()); ++i) { + char c = str[i]; + if (std::isspace(c)) { + if (c == '\n') + ++blank_lines; + continue; + } + // handle non-blank characters + if (ended) + fail("'*' is interpreted as sequence terminator, here it is followed by: ", c); + if (blank_lines >= 2) + fail("blank lines can be followed only by a line starting with '>'"); + blank_lines = 0; + if (('a' <= (c | 0x20) && (c | 0x20) <= 'z') || c == '-' || + (paren_level != 0 && '0' <= c && c <= '9')) { + // good character, nothing to be done here + } else if (c == '*') { + ended = true; + continue; + } else if (c == '(') { + if (++paren_level > 1) + fail("nested parentheses are not allowed"); + } else if (c == ')') { + if (--paren_level < 0) + fail("')' without matching '('"); + } else { + fail("unexpected character in sequence: ", c); + } + seq += c; + } + } + } + if (paren_level != 0) + fail("unmatched '('"); + return r; +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/polyheur.hpp b/gemmi_gph/gemmi/polyheur.hpp new file mode 100644 index 00000000..6186e79f --- /dev/null +++ b/gemmi_gph/gemmi/polyheur.hpp @@ -0,0 +1,203 @@ +// Copyright 2017-2018 Global Phasing Ltd. +// +// Heuristic methods for working with chains and polymers. +// Also includes a few well-defined functions, such as removal of waters. + +#ifndef GEMMI_POLYHEUR_HPP_ +#define GEMMI_POLYHEUR_HPP_ + +#include +#include "model.hpp" +#include "util.hpp" // for vector_remove_if + +namespace gemmi { + +// A simplistic classification. It may change in the future. +// It returns PolymerType which corresponds to _entity_poly.type, +// but here we use only PeptideL, Rna, Dna, DnaRnaHybrid and Unknown. +GEMMI_DLL PolymerType check_polymer_type(const ConstResidueSpan& span, + bool ignore_entity_type=false); + +inline PolymerType get_or_check_polymer_type(const Entity* ent, + const ConstResidueSpan& polymer) { + if (ent && ent->polymer_type != PolymerType::Unknown) + return ent->polymer_type; + return check_polymer_type(polymer); +} + +struct AtomNameElement { std::string atom_name; El el; }; + +inline std::vector get_mainchain_atoms(PolymerType ptype) { + if (is_polynucleotide(ptype)) + return {{"P", El::P}, {"O5'", El::O}, {"C5'", El::C}, + {"C4'", El::C}, {"O4'", El::O}, {"C3'", El::C}, {"O3'", El::O}, + {"C2'", El::C}, {"O2'", El::O}, {"C1'", El::C}}; + return {{"N", El::N}, {"CA", El::C}, {"C", El::C}, {"O", El::O}}; +} + +/// distance-based check for peptide bond +inline bool in_peptide_bond_distance(const Atom* a1, const Atom* a2) { + return a1 && a2 && a1->pos.dist_sq(a2->pos) < sq(1.341 * 1.5); +} +inline bool have_peptide_bond(const Residue& r1, const Residue& r2) { + return in_peptide_bond_distance(r1.get_c(), r2.get_n()); +} + +/// distance-based check for phosphodiester bond between nucleotide +inline bool in_nucleotide_bond_distance(const Atom* a1, const Atom* a2) { + return a1 && a2 && a1->pos.dist_sq(a2->pos) < sq(1.6 * 1.5); +} +inline bool have_nucleotide_bond(const Residue& r1, const Residue& r2) { + return in_nucleotide_bond_distance(r1.get_o3prim(), r2.get_p()); +} + +/// check C-N or O3'-P distance +inline bool are_connected(const Residue& r1, const Residue& r2, PolymerType ptype) { + if (is_polypeptide(ptype)) + return have_peptide_bond(r1, r2); + if (is_polynucleotide(ptype)) + return have_nucleotide_bond(r1, r2); + return false; +} + +/// are_connected2() is less exact, but requires only CA (or P) atoms. +inline bool are_connected2(const Residue& r1, const Residue& r2, PolymerType ptype) { + auto this_or_first = [](const Atom* a, const Residue& r, El el) -> const Atom* { + if (a || r.atoms.empty()) + return a; + if (const Atom* b = r.find_by_element(el)) + return b; + return &r.atoms.front(); + }; + if (is_polypeptide(ptype)) { + const Atom* a1 = this_or_first(r1.get_ca(), r1, El::C); + const Atom* a2 = this_or_first(r2.get_ca(), r2, El::C); + return a1 && a2 && a1->pos.dist_sq(a2->pos) < sq(5.0); + } + if (is_polynucleotide(ptype)) { + const Atom* a1 = this_or_first(r1.get_p(), r1, El::P); + const Atom* a2 = this_or_first(r2.get_p(), r2, El::P); + return a1 && a2 && a1->pos.dist_sq(a2->pos) < sq(7.5); + } + return false; +} + +/// are_connected3() = are_connected() + fallback to are_connected2() +inline bool are_connected3(const Residue& r1, const Residue& r2, PolymerType ptype) { + if (is_polypeptide(ptype)) { + if (const Atom* a1 = r1.get_c()) + if (const Atom* a2 = r2.get_n()) + return a1->pos.dist_sq(a2->pos) < sq(1.341 * 1.5); + if (const Atom* a1 = r1.get_ca()) + if (const Atom* a2 = r2.get_ca()) + return a1->pos.dist_sq(a2->pos) < sq(5.0); + } else if (is_polynucleotide(ptype)) { + if (const Atom* a1 = r1.get_o3prim()) + if (const Atom* a2 = r2.get_p()) + return a1->pos.dist_sq(a2->pos) < sq(1.6 * 1.5); + if (const Atom* a1 = r1.get_p()) + if (const Atom* a2 = r2.get_p()) + return a1->pos.dist_sq(a2->pos) < sq(7.5); + } + return false; +} + +GEMMI_DLL std::string make_one_letter_sequence(const ConstResidueSpan& polymer); + +/// Assigns entity_type=Polymer|NonPolymer|Water for each Residue (only +/// for residues with entity_type==Unknown, unless overwrite=true). +/// Determining where the polymer ends and ligands start is sometimes +/// arbitrary -- there can be a non-standard residue at the end that can +/// be regarded as as either the last residue or a linked ligand. +GEMMI_DLL void add_entity_types(Chain& chain, bool overwrite); +GEMMI_DLL void add_entity_types(Structure& st, bool overwrite); + +/// Assigns entity_type=Unknown for all residues. +GEMMI_DLL void remove_entity_types(Structure& st); + +/// Assigns Residue::entity_id based on Residue::subchain and Entity::subchains. +GEMMI_DLL void add_entity_ids(Structure& st, bool overwrite); + +/// The subchain field in the residue is where we store_atom_site.label_asym_id +/// from mmCIF files. As of 2018 wwPDB software splits author's chains +/// (auth_asym_id) into label_asym_id units: +/// * linear polymer, +/// * non-polymers (each residue has different separate label_asym_id), +/// * and waters. +/// Refmac/makecif is doing similar thing but using different naming and +/// somewhat different rules (it was written in 1990's before PDBx/mmCIF). +/// +/// Here we use naming and rules different from both wwPDB and makecif. +/// Note: call add_entity_types() first. +GEMMI_DLL void assign_subchain_names(Chain& chain, int& nonpolymer_counter); + +GEMMI_DLL void assign_subchains(Structure& st, bool force, bool fail_if_unknown=true); + +GEMMI_DLL void ensure_entities(Structure& st); + +GEMMI_DLL void deduplicate_entities(Structure& st); + +inline void setup_entities(Structure& st) { + add_entity_types(st, /*overwrite=*/false); + assign_subchains(st, /*force=*/false); + ensure_entities(st); + deduplicate_entities(st); +} + +/// Determine ATOM/HETATM record type, based on Residue::entity_type +GEMMI_DLL char recommended_het_flag(const Residue& res); +/// R = recommended_het_flag(), other valid values are A, H and '\0' +template void assign_het_flags(T& obj, char flag='R') { + for (auto& child : obj.children()) + assign_het_flags(child, flag); +} +template<> inline void assign_het_flags(Residue& res, char flag) { + flag &= ~0x20; // uppercase letters, ' ' -> \0 + if (flag != 'R' && flag != '\0' && flag != 'A' && flag != 'H') + fail("assign_het_flags(): the only allowed values are A, H, ' ' and R"); + res.het_flag = flag == 'R' ? recommended_het_flag(res) : flag; +} + +// Remove waters. It may leave empty chains. +template void remove_waters(T& obj) { + for (auto& child : obj.children()) + remove_waters(child); +} +template<> inline void remove_waters(Chain& ch) { + vector_remove_if(ch.residues, + [](const Residue& res) { return res.is_water(); }); +} + +// Remove ligands and waters. It may leave empty chains. +template void remove_ligands_and_waters(T& obj) { + for (auto& child : obj.children()) + remove_ligands_and_waters(child); +} +template<> inline void remove_ligands_and_waters(Chain& ch) { + vector_remove_if(ch.residues, [&](const Residue& res) { + if (res.entity_type == EntityType::Unknown) + fail("remove_ligands_and_waters(): missing entity_type in chain ", ch.name); + return res.entity_type != EntityType::Polymer; + }); +} + +// Trim to alanine. Returns true if trimmed, false if it's (likely) not AA. +GEMMI_DLL bool trim_to_alanine(Residue& res); + +inline void trim_to_alanine(Chain& chain) { + for (Residue& res : chain.residues) + trim_to_alanine(res); +} + +// Functions for switching between long (>3 chars) residue names (CCD codes) +// and shortened ones that are compatible with the PDB format. +GEMMI_DLL void shorten_ccd_codes(Structure& st); +GEMMI_DLL void restore_full_ccd_codes(Structure& st); + +/// Modifies Entity::full_sequence. Uses only the first chain for each Entity. +GEMMI_DLL void add_microhetero_to_sequences(Structure& st, bool overwrite=false); + +GEMMI_DLL void add_tls_group_ids(Structure& st); + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/pymol_select.hpp b/gemmi_gph/gemmi/pymol_select.hpp new file mode 100644 index 00000000..6f8f50b1 --- /dev/null +++ b/gemmi_gph/gemmi/pymol_select.hpp @@ -0,0 +1,649 @@ +// Copyright Global Phasing Ltd. +// +// PyMOL Selection syntax + +#ifndef GEMMI_PYMO_SEL_HPP_ +#define GEMMI_PYMO_SEL_HPP_ + +#include "flat.hpp" +#include "glob.hpp" // for glob_match +#include "third_party/tao/pegtl.hpp" // IWYU pragma: keep + +#include +#include +#include +#include +#include + +// Only define this if we are not in C++14 mode +#if __cplusplus < 201402L && !defined(_MSC_VER) +namespace std { + template + std::unique_ptr make_unique(Args&&... args) { + return std::unique_ptr(new T(std::forward(args)...)); + } +} +#endif + +// ============================================================================ +// PHASE 1: Abstract Syntax Tree (AST) +// ============================================================================ + + +namespace gemmi { + +namespace psimpl { + +struct Node { + virtual ~Node() = default; + virtual bool match(const gemmi::FlatAtom& a) const = 0; +}; + +// --- Logic Nodes --- + +struct AndNode : Node { + std::unique_ptr left, right; + bool match(const gemmi::FlatAtom& a) const override { + return left->match(a) && right->match(a); + } +}; + +struct OrNode : Node { + std::unique_ptr left, right; + bool match(const gemmi::FlatAtom& a) const override { + return left->match(a) || right->match(a); + } +}; + +struct NotNode : Node { + std::unique_ptr child; + explicit NotNode(std::unique_ptr c) : child(std::move(c)) {} + bool match(const gemmi::FlatAtom& a) const override { + return !child->match(a); + } +}; + +// --- Property Nodes --- + +struct ChainNode : Node { + std::vector names; + explicit ChainNode(std::vector v) : names(std::move(v)) {} + bool match(const gemmi::FlatAtom& a) const override { + for (const auto& n : names) + if (glob_match(n, a.chain_id)) return true; + return false; + } +}; + +struct ResnNode : Node { + std::vector names; + explicit ResnNode(std::vector v) : names(std::move(v)) {} + bool match(const gemmi::FlatAtom& a) const override { + for (const auto& n : names) + if (glob_match(n, a.residue_name)) return true; + return false; + } +}; + +struct AtomNameNode : Node { + std::vector names; + explicit AtomNameNode(std::vector v) : names(std::move(v)) {} + bool match(const gemmi::FlatAtom& a) const override { + for (const auto& n : names) + if (glob_match(n, a.atom_name)) return true; + return false; + } +}; + +struct AltLocNode : Node { + char alt; + explicit AltLocNode(char c) : alt(c) {} + bool match(const gemmi::FlatAtom& a) const override { + return a.altloc == alt; + } +}; + +struct ResiRangeNode : Node { + int min, max; + ResiRangeNode(int a, int b) : min(a), max(b) {} + bool match(const gemmi::FlatAtom& a) const override { + return *a.seq_id.num >= min && *a.seq_id.num <= max; + } +}; + +struct IndexRangeNode : Node { + int min, max; + IndexRangeNode(int a, int b) : min(a), max(b) {} + bool match(const gemmi::FlatAtom& a) const override { + return a.serial >= min && a.serial <= max; + } +}; + +struct ElementNode : Node { + std::vector elems; + explicit ElementNode(std::vector v) : elems(std::move(v)) {} + bool match(const gemmi::FlatAtom& a) const override { + for (const auto& e : elems) + if (a.element == e) return true; + return false; + } +}; + +struct HetatmNode : Node { + bool hetatm; // true = hetatm, false = not hetatm (i.e., ATOM) + explicit HetatmNode(bool h) : hetatm(h) {} + bool match(const gemmi::FlatAtom& a) const override { + return hetatm ? (a.het_flag == 'H') : (a.het_flag == 'A'); + } +}; + +struct EntityTypeNode : Node { + EntityType etype; + explicit EntityTypeNode(EntityType e) : etype(e) {} + bool match(const gemmi::FlatAtom& a) const override { + return a.entity_type == etype; + } +}; + +struct HydrogenNode : Node { + bool match(const gemmi::FlatAtom& a) const override { + return a.element == El::H || a.element == El::D; + } +}; + +enum class CompareOp { LT, LE, GT, GE, EQ, NE }; + +struct BfactorNode : Node { + CompareOp op; + float value; + BfactorNode(CompareOp o, float v) : op(o), value(v) {} + bool match(const gemmi::FlatAtom& a) const override { + switch (op) { + case CompareOp::LT: return a.b_iso < value; + case CompareOp::LE: return a.b_iso <= value; + case CompareOp::GT: return a.b_iso > value; + case CompareOp::GE: return a.b_iso >= value; + case CompareOp::EQ: return a.b_iso == value; + case CompareOp::NE: return a.b_iso != value; + } + return false; + } +}; + +struct OccupancyNode : Node { + CompareOp op; + float value; + OccupancyNode(CompareOp o, float v) : op(o), value(v) {} + bool match(const gemmi::FlatAtom& a) const override { + switch (op) { + case CompareOp::LT: return a.occ < value; + case CompareOp::LE: return a.occ <= value; + case CompareOp::GT: return a.occ > value; + case CompareOp::GE: return a.occ >= value; + case CompareOp::EQ: return a.occ == value; + case CompareOp::NE: return a.occ != value; + } + return false; + } +}; + +struct BackboneNode : Node { + bool match(const gemmi::FlatAtom& a) const override { + // Standard protein backbone atoms + return std::strcmp(a.atom_name, "CA") == 0 || + std::strcmp(a.atom_name, "C") == 0 || + std::strcmp(a.atom_name, "N") == 0 || + std::strcmp(a.atom_name, "O") == 0; + } +}; + +struct SidechainNode : Node { + bool match(const gemmi::FlatAtom& a) const override { + // Sidechain = not backbone and not hydrogen + return std::strcmp(a.atom_name, "CA") != 0 && + std::strcmp(a.atom_name, "C") != 0 && + std::strcmp(a.atom_name, "N") != 0 && + std::strcmp(a.atom_name, "O") != 0 && + a.element != El::H && a.element != El::D; + } +}; + +struct AllNode : Node { + bool match(const gemmi::FlatAtom&) const override { return true; } +}; + +namespace p = tao::pegtl; + +// --- State --- +struct State { + std::vector> stack; + std::vector string_list; // temp storage for building value lists + CompareOp current_op = CompareOp::EQ; +}; + +// --- Helpers --- +struct ws : p::star {}; +struct sep : p::plus {}; // mandatory separator + +// --- Values --- +struct integer : p::seq>, p::plus> {}; +struct float_num : p::seq>, p::plus, + p::opt, p::star>>> {}; +// Allow wildcards in identifiers and names +struct wildcard_char : p::one<'*', '?'> {}; +struct identifier : p::plus, wildcard_char>> {}; +struct atom_name_str : p::plus> {}; +struct element_str : p::seq> {}; // e.g., C, Ca, Fe (no wildcards for elements) + +// --- Comparison operators --- +struct op_le : p::string<'<','='> {}; +struct op_ge : p::string<'>','='> {}; +struct op_ne : p::sor, p::string<'<','>'>> {}; +struct op_lt : p::one<'<'> {}; +struct op_gt : p::one<'>'> {}; +struct op_eq : p::one<'='> {}; +struct compare_op : p::sor {}; + +// --- Keywords --- +// Using istring for case-insensitive matching +struct kw_chain : p::istring<'c','h','a','i','n'> {}; +struct kw_resn : p::istring<'r','e','s','n'> {}; +struct kw_resi : p::istring<'r','e','s','i'> {}; +struct kw_name : p::istring<'n','a','m','e'> {}; +struct kw_alt : p::istring<'a','l','t'> {}; +struct kw_elem : p::istring<'e','l','e','m'> {}; +struct kw_index : p::istring<'i','n','d','e','x'> {}; +struct kw_id : p::istring<'i','d'> {}; +struct kw_b : p::istring<'b'> {}; +struct kw_q : p::istring<'q'> {}; + +struct kw_and : p::istring<'a','n','d'> {}; +struct kw_or : p::istring<'o','r'> {}; +struct kw_not : p::istring<'n','o','t'> {}; + +// Stand-alone keywords (no arguments) +struct kw_hetatm : p::istring<'h','e','t','a','t','m'> {}; +struct kw_polymer : p::istring<'p','o','l','y','m','e','r'> {}; +struct kw_solvent : p::istring<'s','o','l','v','e','n','t'> {}; +struct kw_water : p::istring<'w','a','t','e','r'> {}; +struct kw_hydrogens : p::istring<'h','y','d','r','o','g','e','n','s'> {}; +struct kw_h_dot : p::istring<'h','.'> {}; +struct kw_backbone : p::istring<'b','a','c','k','b','o','n','e'> {}; +struct kw_sidechain : p::istring<'s','i','d','e','c','h','a','i','n'> {}; +struct kw_all : p::istring<'a','l','l'> {}; + +// --- Property Rules --- + +// Chain: chain A or chain A+B+C +struct val_chain_item : identifier {}; +struct val_chain_list : p::list> {}; +struct rule_chain : p::seq {}; + +// Resn: resn ALA or resn ALA+GLY+VAL +struct val_resn_item : atom_name_str {}; +struct val_resn_list : p::list> {}; +struct rule_resn : p::seq {}; + +// Name: name CA or name CA+CB+N +struct val_name_item : atom_name_str {}; +struct val_name_list : p::list> {}; +struct rule_name : p::seq {}; + +// Alt: alt A +struct val_alt : p::alnum {}; // single char +struct rule_alt : p::seq {}; + +// Resi: resi 100 OR resi 100-200 +struct val_resi_range : p::seq, integer> {}; +struct val_resi_single : integer {}; +struct rule_resi : p::seq> {}; + +// Index/ID: index 5 OR index 5-10 OR id 5 +struct val_index_range : p::seq, integer> {}; +struct val_index_single : integer {}; +struct rule_index : p::seq, sep, p::sor> {}; + +// Elem: elem C or elem C+N+O +struct val_elem_item : element_str {}; +struct val_elem_list : p::list> {}; +struct rule_elem : p::seq {}; + +// B-factor: b > 50, b < 20, b = 0 +struct val_b_compare : float_num {}; +struct rule_b : p::seq {}; + +// Occupancy: q < 1, q > 0.5 +struct val_q_compare : float_num {}; +struct rule_q : p::seq {}; + +// Stand-alone keywords +struct rule_hetatm : kw_hetatm {}; +struct rule_polymer : kw_polymer {}; +struct rule_solvent : kw_solvent {}; +struct rule_water : kw_water {}; +struct rule_hydrogens : p::sor {}; +struct rule_backbone : kw_backbone {}; +struct rule_sidechain : kw_sidechain {}; +struct rule_all : kw_all {}; + +// Combined Property +struct property : p::sor< + rule_chain, + rule_resn, + rule_resi, + rule_index, + rule_name, + rule_alt, + rule_elem, + rule_b, + rule_q, + rule_hetatm, + rule_polymer, + rule_solvent, + rule_water, + rule_hydrogens, + rule_backbone, + rule_sidechain, + rule_all +> {}; + +// --- Boolean Logic Rules --- + +struct expression; // forward decl + +struct parens : p::seq, ws, expression, ws, p::one<')'>> {}; + +// Factor: NOT factor | parens | property +struct not_factor : p::seq> {}; // simplified recursion +// Actually, to handle precedence properly with PEGTL without left-recursion: +// factor = (NOT ws factor) | parens | property +struct factor; +struct rule_not : p::seq {}; +struct factor : p::sor {}; + +// Term (AND) +// To allow easy "reduce" actions, we explicitly name the sequence +struct and_rest : p::seq {}; +struct term : p::seq> {}; + +// Expression (OR) +struct or_rest : p::seq {}; +struct expression : p::seq> {}; + +// Root +struct grammar : p::must {}; + +// ============================================================================ +// PHASE 3: Actions +// ============================================================================ + +template +struct action : p::nothing {}; + +// --- List item actions (accumulate into string_list) --- + +template<> struct action { + template static void apply(const Input& in, State& s) { + s.string_list.push_back(in.string()); + } +}; + +template<> struct action { + template static void apply(const Input& in, State& s) { + s.string_list.push_back(in.string()); + } +}; + +template<> struct action { + template static void apply(const Input& in, State& s) { + s.string_list.push_back(in.string()); + } +}; + +template<> struct action { + template static void apply(const Input& in, State& s) { + s.string_list.push_back(in.string()); + } +}; + +// --- Rule actions (create nodes from accumulated lists) --- + +template<> struct action { + static void apply0(State& s) { + s.stack.push_back(std::make_unique(std::move(s.string_list))); + s.string_list.clear(); + } +}; + +template<> struct action { + static void apply0(State& s) { + s.stack.push_back(std::make_unique(std::move(s.string_list))); + s.string_list.clear(); + } +}; + +template<> struct action { + static void apply0(State& s) { + s.stack.push_back(std::make_unique(std::move(s.string_list))); + s.string_list.clear(); + } +}; + +template<> struct action { + static void apply0(State& s) { + std::vector elems; + for (const auto& str : s.string_list) + elems.push_back(Element(str)); + s.stack.push_back(std::make_unique(std::move(elems))); + s.string_list.clear(); + } +}; + +template<> struct action { + template static void apply(const Input& in, State& s) { + std::string str = in.string(); + char c = str.empty() ? ' ' : str[0]; + s.stack.push_back(std::make_unique(c)); + } +}; + +template<> struct action { + template static void apply(const Input& in, State& s) { + int val = std::stoi(in.string()); + s.stack.push_back(std::make_unique(val, val)); + } +}; + +template<> struct action { + template static void apply(const Input& in, State& s) { + std::string str = in.string(); + size_t split_pos = str.find('-', 1); // Skip potential leading negative sign + int v1 = std::stoi(str.substr(0, split_pos)); + int v2 = std::stoi(str.substr(split_pos + 1)); + s.stack.push_back(std::make_unique(v1, v2)); + } +}; + +template<> struct action { + template static void apply(const Input& in, State& s) { + int val = std::stoi(in.string()); + s.stack.push_back(std::make_unique(val, val)); + } +}; + +template<> struct action { + template static void apply(const Input& in, State& s) { + std::string str = in.string(); + size_t split_pos = str.find('-', 1); // Skip potential leading negative sign + int v1 = std::stoi(str.substr(0, split_pos)); + int v2 = std::stoi(str.substr(split_pos + 1)); + s.stack.push_back(std::make_unique(v1, v2)); + } +}; + +// --- Comparison operator actions --- + +template<> struct action { + static void apply0(State& s) { s.current_op = CompareOp::LT; } +}; +template<> struct action { + static void apply0(State& s) { s.current_op = CompareOp::LE; } +}; +template<> struct action { + static void apply0(State& s) { s.current_op = CompareOp::GT; } +}; +template<> struct action { + static void apply0(State& s) { s.current_op = CompareOp::GE; } +}; +template<> struct action { + static void apply0(State& s) { s.current_op = CompareOp::EQ; } +}; +template<> struct action { + static void apply0(State& s) { s.current_op = CompareOp::NE; } +}; + +template<> struct action { + template static void apply(const Input& in, State& s) { + float val = std::stof(in.string()); + s.stack.push_back(std::make_unique(s.current_op, val)); + } +}; + +template<> struct action { + template static void apply(const Input& in, State& s) { + float val = std::stof(in.string()); + s.stack.push_back(std::make_unique(s.current_op, val)); + } +}; + +// --- Stand-alone keyword actions --- + +template<> struct action { + static void apply0(State& s) { + s.stack.push_back(std::make_unique(true)); + } +}; + +template<> struct action { + static void apply0(State& s) { + s.stack.push_back(std::make_unique(EntityType::Polymer)); + } +}; + +template<> struct action { + static void apply0(State& s) { + // Solvent includes water + s.stack.push_back(std::make_unique(EntityType::Water)); + } +}; + +template<> struct action { + static void apply0(State& s) { + s.stack.push_back(std::make_unique(EntityType::Water)); + } +}; + +template<> struct action { + static void apply0(State& s) { + s.stack.push_back(std::make_unique()); + } +}; + +template<> struct action { + static void apply0(State& s) { + s.stack.push_back(std::make_unique()); + } +}; + +template<> struct action { + static void apply0(State& s) { + s.stack.push_back(std::make_unique()); + } +}; + +template<> struct action { + static void apply0(State& s) { + s.stack.push_back(std::make_unique()); + } +}; + +// --- Logic Actions --- + +// NOT +template<> struct action { + static void apply0(State& s) { + auto child = std::move(s.stack.back()); + s.stack.pop_back(); + s.stack.push_back(std::make_unique(std::move(child))); + } +}; + +// AND +template<> struct action { + static void apply0(State& s) { + auto rhs = std::move(s.stack.back()); s.stack.pop_back(); + auto lhs = std::move(s.stack.back()); s.stack.pop_back(); + + auto node = std::make_unique(); + node->left = std::move(lhs); + node->right = std::move(rhs); + s.stack.push_back(std::move(node)); + } +}; + +// OR +template<> struct action { + static void apply0(State& s) { + auto rhs = std::move(s.stack.back()); s.stack.pop_back(); + auto lhs = std::move(s.stack.back()); s.stack.pop_back(); + + auto node = std::make_unique(); + node->left = std::move(lhs); + node->right = std::move(rhs); + s.stack.push_back(std::move(node)); + } +}; + +} // namespace psimpl + + +// ============================================================================ +// Public API +// ============================================================================ + +// Returns a compiled selection tree +inline std::unique_ptr compile_pymol_selection(const std::string& selector) { + psimpl::State state; + tao::pegtl::memory_input<> in(selector, ""); + try { + tao::pegtl::parse(in, state); + if (state.stack.empty()) return nullptr; + return std::move(state.stack.back()); + } + catch (const tao::pegtl::parse_error& e) { + std::cerr << "Selection Parse Error: " << e.what() << std::endl; + return nullptr; + } +} + +inline std::vector +select_atoms(const gemmi::FlatStructure& fs, const std::string& query) { + auto root = compile_pymol_selection(query); + std::vector result; + + if (root) + for (auto& atom : fs.table) { + if (root->match(atom)) { + result.push_back(&atom); + } + } + return result; +} +inline void remove_not_selected(gemmi::FlatStructure& fs, const std::string& query) { + if (auto root = compile_pymol_selection(query)) + vector_remove_if(fs.table, [&](FlatAtom& atom) { return !root->match(atom); }); +} + +} // namespace gemmi + +#endif diff --git a/gemmi_gph/gemmi/qcp.hpp b/gemmi_gph/gemmi/qcp.hpp new file mode 100644 index 00000000..7e0be7a7 --- /dev/null +++ b/gemmi_gph/gemmi/qcp.hpp @@ -0,0 +1,356 @@ +// Structural superposition, the QCP method. +// +// This is modified code from qcprot.c from https://theobald.brandeis.edu/qcp/ +// The original qcprot.c file startsd with the following info and license: + +/******************************************************************************* + * -/_|:|_|_\- + * + * File: qcprot.c + * Version: 1.5 + * + * Function: Rapid calculation of the least-squares rotation using a + * quaternion-based characteristic polynomial and + * a cofactor matrix + * + * Author(s): Douglas L. Theobald + * Department of Biochemistry + * MS 009 + * Brandeis University + * 415 South St + * Waltham, MA 02453 + * USA + * + * dtheobald@brandeis.edu + * + * Pu Liu + * Johnson & Johnson Pharmaceutical Research and Development, L.L.C. + * 665 Stockton Drive + * Exton, PA 19341 + * USA + * + * pliu24@its.jnj.com + * + * + * If you use this QCP rotation calculation method in a publication, please + * reference: + * + * Douglas L. Theobald (2005) + * "Rapid calculation of RMSD using a quaternion-based characteristic + * polynomial." + * Acta Crystallographica A 61(4):478-480. + * + * Pu Liu, Dmitris K. Agrafiotis, and Douglas L. Theobald (2009) + * "Fast determination of the optimal rotational matrix for macromolecular + * superpositions." + * Journal of Computational Chemistry 31(7):1561-1563. + * + * Copyright (c) 2009-2016 Pu Liu and Douglas L. Theobald + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * * Neither the name of the nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GEMMI_QCP_HPP_ +#define GEMMI_QCP_HPP_ + +#include // for fabs, sqrt +#include // for fprintf (it's temporary) +#include "math.hpp" // for Mat33 +#include "unitcell.hpp" // for Position + +namespace gemmi { + +struct SupResult { + double rmsd; + size_t count; + Position center1, center2; + Transform transform; +}; + +// helper function +inline double qcp_inner_product(Mat33& mat, + const Position* pos1, const Position& ctr1, + const Position* pos2, const Position& ctr2, + size_t len, const double* weight) { + double G1 = 0.0, G2 = 0.0; + for (size_t i = 0; i < len; ++i) { + Position f1 = pos1[i] - ctr1; + Position f2 = pos2[i] - ctr2; + double w = (weight != nullptr ? weight[i] : 1.); + Vec3 v1 = w * f1; + G1 += v1.dot(f1); + G2 += w * f2.length_sq(); + mat[0][0] += v1.x * f2.x; + mat[0][1] += v1.x * f2.y; + mat[0][2] += v1.x * f2.z; + mat[1][0] += v1.y * f2.x; + mat[1][1] += v1.y * f2.y; + mat[1][2] += v1.y * f2.z; + mat[2][0] += v1.z * f2.x; + mat[2][1] += v1.z * f2.y; + mat[2][2] += v1.z * f2.z; + } + return (G1 + G2) * 0.5; +} + +// helper function +inline int fast_calc_rmsd_and_rotation(Mat33* rot, const Mat33& A, double *rmsd, + double E0, double len, double min_score) { + const double evecprec = 1e-6; + const double evalprec = 1e-11; + + double Sxx, Sxy, Sxz, Syx, Syy, Syz, Szx, Szy, Szz; + Sxx = A[0][0]; Sxy = A[0][1]; Sxz = A[0][2]; + Syx = A[1][0]; Syy = A[1][1]; Syz = A[1][2]; + Szx = A[2][0]; Szy = A[2][1]; Szz = A[2][2]; + + double Sxx2 = Sxx * Sxx; + double Syy2 = Syy * Syy; + double Szz2 = Szz * Szz; + + double Sxy2 = Sxy * Sxy; + double Syz2 = Syz * Syz; + double Sxz2 = Sxz * Sxz; + + double Syx2 = Syx * Syx; + double Szy2 = Szy * Szy; + double Szx2 = Szx * Szx; + + double SyzSzymSyySzz2 = 2.0 * (Syz*Szy - Syy*Szz); + double Sxx2Syy2Szz2Syz2Szy2 = Syy2 + Szz2 - Sxx2 + Syz2 + Szy2; + + double C[4]; + C[2] = -2.0 * (Sxx2 + Syy2 + Szz2 + Sxy2 + Syx2 + Sxz2 + Szx2 + Syz2 + Szy2); + C[1] = 8.0 * (Sxx*Syz*Szy + Syy*Szx*Sxz + Szz*Sxy*Syx - Sxx*Syy*Szz - Syz*Szx*Sxy - Szy*Syx*Sxz); + + double SxzpSzx = Sxz + Szx; + double SyzpSzy = Syz + Szy; + double SxypSyx = Sxy + Syx; + double SyzmSzy = Syz - Szy; + double SxzmSzx = Sxz - Szx; + double SxymSyx = Sxy - Syx; + double SxxpSyy = Sxx + Syy; + double SxxmSyy = Sxx - Syy; + double Sxy2Sxz2Syx2Szx2 = Sxy2 + Sxz2 - Syx2 - Szx2; + + C[0] = Sxy2Sxz2Syx2Szx2 * Sxy2Sxz2Syx2Szx2 + + (Sxx2Syy2Szz2Syz2Szy2 + SyzSzymSyySzz2) * (Sxx2Syy2Szz2Syz2Szy2 - SyzSzymSyySzz2) + + (-(SxzpSzx)*(SyzmSzy)+(SxymSyx)*(SxxmSyy-Szz)) * (-(SxzmSzx)*(SyzpSzy)+(SxymSyx)*(SxxmSyy+Szz)) + + (-(SxzpSzx)*(SyzpSzy)-(SxypSyx)*(SxxpSyy-Szz)) * (-(SxzmSzx)*(SyzmSzy)-(SxypSyx)*(SxxpSyy+Szz)) + + (+(SxypSyx)*(SyzpSzy)+(SxzpSzx)*(SxxmSyy+Szz)) * (-(SxymSyx)*(SyzmSzy)+(SxzpSzx)*(SxxpSyy+Szz)) + + (+(SxypSyx)*(SyzmSzy)+(SxzmSzx)*(SxxmSyy-Szz)) * (-(SxymSyx)*(SyzpSzy)+(SxzmSzx)*(SxxpSyy-Szz)); + + /* Newton-Raphson */ + double mxEigenV = E0; + int i; + double oldg = 0.0; + for (i = 0; i < 50; ++i) { + oldg = mxEigenV; + double x2 = mxEigenV * mxEigenV; + double b = (x2 + C[2]) * mxEigenV; + double a = b + C[1]; + double delta = (a * mxEigenV + C[0]) / (2.0 * x2 * mxEigenV + b + a); + mxEigenV -= delta; + // printf("\n diff[%3d]: %16g %16g %16g", i, mxEigenV - oldg, evalprec*mxEigenV, mxEigenV); + if (std::fabs(mxEigenV - oldg) < std::fabs(evalprec * mxEigenV)) + break; + } + if (i == 50) + std::fprintf(stderr,"\nMore than %d iterations needed!\n", i); + + // the fabs() is to guard against extremely small, but *negative* numbers + // due to floating point error + double rms = std::sqrt(std::fabs(2.0 * (E0 - mxEigenV) / len)); + (*rmsd) = rms; + // printf("\n\n %16g %16g %16g \n", rms, E0, 2.0 * (E0 - mxEigenV)/len); + + if (rot == nullptr) + return -1; + if (min_score > 0) + if (rms < min_score) + return -1; // Don't bother with rotation. + + double a11, a12, a13, a14, a21, a22, a23, a24; + a11 = SxxpSyy + Szz-mxEigenV; a12 = SyzmSzy; a13 = - SxzmSzx; a14 = SxymSyx; + a21 = SyzmSzy; a22 = SxxmSyy - Szz-mxEigenV; a23 = SxypSyx; a24= SxzpSzx; + double a31, a32, a33, a34, a41, a42, a43, a44; + a31 = a13; a32 = a23; a33 = Syy-Sxx-Szz - mxEigenV; a34 = SyzpSzy; + a41 = a14; a42 = a24; a43 = a34; a44 = Szz - SxxpSyy - mxEigenV; + double a3344_4334, a3244_4234, a3243_4233, a3143_4133,a3144_4134, a3142_4132; + a3344_4334 = a33 * a44 - a43 * a34; a3244_4234 = a32 * a44-a42*a34; + a3243_4233 = a32 * a43 - a42 * a33; a3143_4133 = a31 * a43-a41*a33; + a3144_4134 = a31 * a44 - a41 * a34; a3142_4132 = a31 * a42-a41*a32; + double q1 = a22*a3344_4334-a23*a3244_4234+a24*a3243_4233; + double q2 = -a21*a3344_4334+a23*a3144_4134-a24*a3143_4133; + double q3 = a21*a3244_4234-a22*a3144_4134+a24*a3142_4132; + double q4 = -a21*a3243_4233+a22*a3143_4133-a23*a3142_4132; + + double qsqr = q1 * q1 + q2 * q2 + q3 * q3 + q4 * q4; + + /* The following code tries to calculate another column in the adjoint matrix + when the norm of the current column is too small. + Usually this block will never be activated. To be absolutely safe this should be + uncommented, but it is most likely unnecessary. + */ + if (qsqr < evecprec) { + q1 = a12*a3344_4334 - a13*a3244_4234 + a14*a3243_4233; + q2 = -a11*a3344_4334 + a13*a3144_4134 - a14*a3143_4133; + q3 = a11*a3244_4234 - a12*a3144_4134 + a14*a3142_4132; + q4 = -a11*a3243_4233 + a12*a3143_4133 - a13*a3142_4132; + qsqr = q1*q1 + q2 *q2 + q3*q3+q4*q4; + + if (qsqr < evecprec) { + double a1324_1423 = a13 * a24 - a14 * a23, a1224_1422 = a12 * a24 - a14 * a22; + double a1223_1322 = a12 * a23 - a13 * a22, a1124_1421 = a11 * a24 - a14 * a21; + double a1123_1321 = a11 * a23 - a13 * a21, a1122_1221 = a11 * a22 - a12 * a21; + + q1 = a42 * a1324_1423 - a43 * a1224_1422 + a44 * a1223_1322; + q2 = -a41 * a1324_1423 + a43 * a1124_1421 - a44 * a1123_1321; + q3 = a41 * a1224_1422 - a42 * a1124_1421 + a44 * a1122_1221; + q4 = -a41 * a1223_1322 + a42 * a1123_1321 - a43 * a1122_1221; + qsqr = q1*q1 + q2 *q2 + q3*q3+q4*q4; + + if (qsqr < evecprec) { + q1 = a32 * a1324_1423 - a33 * a1224_1422 + a34 * a1223_1322; + q2 = -a31 * a1324_1423 + a33 * a1124_1421 - a34 * a1123_1321; + q3 = a31 * a1224_1422 - a32 * a1124_1421 + a34 * a1122_1221; + q4 = -a31 * a1223_1322 + a32 * a1123_1321 - a33 * a1122_1221; + qsqr = q1*q1 + q2 *q2 + q3*q3 + q4*q4; + + if (qsqr < evecprec) { + /* if qsqr is still too small, return the identity matrix. */ + *rot = Mat33(); + return 0; + } + } + } + } + + double normq = std::sqrt(qsqr); + q1 /= normq; + q2 /= normq; + q3 /= normq; + q4 /= normq; + + double a2 = q1 * q1; + double x2 = q2 * q2; + double y2 = q3 * q3; + double z2 = q4 * q4; + + double xy = q2 * q3; + double az = q1 * q4; + double zx = q4 * q2; + double ay = q1 * q3; + double yz = q3 * q4; + double ax = q1 * q2; + + rot->a[0][0] = a2 + x2 - y2 - z2; + rot->a[0][1] = 2 * (xy + az); + rot->a[0][2] = 2 * (zx - ay); + rot->a[1][0] = 2 * (xy - az); + rot->a[1][1] = a2 - x2 + y2 - z2; + rot->a[1][2] = 2 * (yz + ax); + rot->a[2][0] = 2 * (zx + ay); + rot->a[2][1] = 2 * (yz - ax); + rot->a[2][2] = a2 - x2 - y2 + z2; + + return 1; +} + +// helper function +inline Position qcp_calculate_center(const Position* pos, size_t len, const double *weight) { + double wsum = 0.0; + Position ctr; + for (size_t i = 0; i < len; ++i) { + double w = (weight != nullptr ? weight[i] : 1.); + ctr += w * pos[i]; + wsum += w; + } + return ctr / wsum; +} + +// Calculate superposition of pos2 onto pos1 -- pos2 is movable. +// Does not perform the superposition, only returns the operation to be used. +inline SupResult superpose_positions(const Position* pos1, const Position* pos2, + size_t len, const double* weight) { + SupResult result; + result.count = len; + + /* center the structures -- if precentered you can omit this step */ + result.center1 = qcp_calculate_center(pos1, len, weight); + result.center2 = qcp_calculate_center(pos2, len, weight); + + if (len == 0) { + result.rmsd = NAN; + return result; + } + + double wsum = 0.0; + if (weight == nullptr) + wsum = (double) len; + else + for (size_t i = 0; i < len; ++i) + wsum += weight[i]; + + Mat33 A(0); + /* calculate the (weighted) inner product of two structures */ + double E0 = qcp_inner_product(A, pos1, result.center1, pos2, result.center2, len, weight); + + /* calculate the RMSD & rotational matrix */ + fast_calc_rmsd_and_rotation(&result.transform.mat, A, &result.rmsd, E0, wsum, -1); + result.transform.vec = Vec3(result.center1) - result.transform.mat.multiply(result.center2); + + return result; +} + +// Similar to superpose_positions(), but calculates RMSD only. +inline double calculate_rmsd_of_superposed_positions(const Position* pos1, + const Position* pos2, + size_t len, const double* weight) { + double result; + + // center the structures + Position ctr1 = qcp_calculate_center(pos1, len, weight); + Position ctr2 = qcp_calculate_center(pos2, len, weight); + + double wsum = 0.0; + if (weight == nullptr) + wsum = (double) len; + else + for (size_t i = 0; i < len; ++i) + wsum += weight[i]; + + // calculate the (weighted) inner product of two structures + Mat33 A(0); + double E0 = qcp_inner_product(A, pos1, ctr1, pos2, ctr2, len, weight); + + // calculate the RMSD + fast_calc_rmsd_and_rotation(nullptr, A, &result, E0, wsum, -1); + return result; +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/read_cif.hpp b/gemmi_gph/gemmi/read_cif.hpp new file mode 100644 index 00000000..8a488884 --- /dev/null +++ b/gemmi_gph/gemmi/read_cif.hpp @@ -0,0 +1,36 @@ +// Copyright 2021 Global Phasing Ltd. +// +// Functions for reading possibly gzipped CIF files. + +#ifndef GEMMI_READ_CIF_HPP_ +#define GEMMI_READ_CIF_HPP_ + +#include "cifdoc.hpp" // for Document +#include "fileutil.hpp" // for CharArray + +namespace gemmi { + +GEMMI_DLL cif::Document read_cif_gz(const std::string& path, int check_level=1); +GEMMI_DLL bool check_cif_syntax_gz(const std::string& path, std::string* msg); +GEMMI_DLL cif::Document read_mmjson_gz(const std::string& path); +GEMMI_DLL CharArray read_into_buffer_gz(const std::string& path); +GEMMI_DLL cif::Document read_cif_from_memory(const char* data, size_t size, const char* name, + int check_level=1); +GEMMI_DLL cif::Document read_first_block_gz(const std::string& path, size_t limit); + +// cif::read_string() was moved here from cif.hpp to speed up compilation +namespace cif { +inline Document read_string(const std::string& data, int check_level=1) { + return read_cif_from_memory(data.data(), data.size(), "string", check_level); +} +} // namespace cif + +inline cif::Document read_cif_or_mmjson_gz(const std::string& path) { + if (giends_with(path, "json") || giends_with(path, "js")) + return read_mmjson_gz(path); + return read_cif_gz(path); +} + +} // namespace gemmi + +#endif diff --git a/gemmi_gph/gemmi/recgrid.hpp b/gemmi_gph/gemmi/recgrid.hpp new file mode 100644 index 00000000..9caf7721 --- /dev/null +++ b/gemmi_gph/gemmi/recgrid.hpp @@ -0,0 +1,175 @@ +// Copyright 2020 Global Phasing Ltd. +// +// ReciprocalGrid -- grid for reciprocal space data. + +#ifndef GEMMI_RECGRID_HPP_ +#define GEMMI_RECGRID_HPP_ + +#include // for unique_ptr +#include "asudata.hpp" +#include "grid.hpp" + +namespace gemmi { + +template T friedel_mate_value(T v) { return v; } +template +std::complex friedel_mate_value(const std::complex& v) { + return std::conj(v); +} + +template +struct ReciprocalGrid : GridBase { + bool half_l = false; // hkl grid that stores only l>=0 + bool has_index(int u, int v, int w) const { + bool half_u = (half_l && this->axis_order == AxisOrder::ZYX); + bool half_w = (half_l && this->axis_order != AxisOrder::ZYX); + return std::abs(half_u ? u : 2 * u) < this->nu && + std::abs(2 * v) < this->nv && + std::abs(half_w ? w : 2 * w) < this->nw; + } + void check_index(int u, int v, int w) const { + if (!has_index(u, v, w)) + throw std::out_of_range("ReciprocalGrid: index out of grid."); + } + // Similar to Grid::index_n(), but works only for -nu <= u < nu, etc. + size_t index_n(int u, int v, int w) const { + return this->index_q(u >= 0 ? u : u + this->nu, + v >= 0 ? v : v + this->nv, + w >= 0 ? w : w + this->nw); + } + size_t index_checked(int u, int v, int w) const { + check_index(u, v, w); + return index_n(u, v, w); + } + T get_value(int u, int v, int w) const { + return this->data[index_checked(u, v, w)]; + } + T get_value_or_zero(int u, int v, int w) const { + return has_index(u, v, w) ? this->data[index_n(u, v, w)] : T{}; + } + void set_value(int u, int v, int w, T x) { + this->data[index_checked(u, v, w)] = x; + } + Miller to_hkl(const typename GridBase::Point& point) const { + Miller hkl{{point.u, point.v, point.w}}; + if (2 * point.u >= this->nu && + !(half_l && this->axis_order == AxisOrder::ZYX)) + hkl[0] -= this->nu; + if (2 * point.v >= this->nv) + hkl[1] -= this->nv; + if (2 * point.w >= this->nw && + !(half_l && this->axis_order != AxisOrder::ZYX)) + hkl[2] -= this->nw; + if (this->axis_order == AxisOrder::ZYX) + std::swap(hkl[0], hkl[2]); + return hkl; + } + + double calculate_1_d2(const typename GridBase::Point& point) const { + return this->unit_cell.calculate_1_d2(to_hkl(point)); + } + double calculate_d(const typename GridBase::Point& point) const { + return this->unit_cell.calculate_d(to_hkl(point)); + } + + T get_value_by_hkl(Miller hkl, double unblur=0, + bool mott_bethe=false) const { + if (this->axis_order == AxisOrder::ZYX) + fail("get_value_by_hkl(): ZYX order is not supported yet"); + T value; + if (half_l && hkl[2] < 0) + value = friedel_mate_value(this->get_value(-hkl[0], -hkl[1], -hkl[2])); + else + value = this->get_value(hkl[0], hkl[1], hkl[2]); + + if (unblur != 0. || mott_bethe) { + double inv_d2 = this->unit_cell.calculate_1_d2(hkl); + double mult = 1; + if (unblur != 0) + // cf. reciprocal_space_multiplier() + mult = std::exp(unblur * 0.25 * inv_d2); + if (mott_bethe) + // cf. mott_bethe_factor + mult *= -mott_bethe_const() / inv_d2; + value *= static_cast(mult); + } + return value; + } + + // the result is always sorted by h,k,l + template + AsuData prepare_asu_data(double dmin=0, double unblur=0, + bool with_000=false, bool with_sys_abs=false, + bool mott_bethe=false) { + AsuData asu_data; + if (this->axis_order == AxisOrder::ZYX) + fail("get_asu_values(): ZYX order is not supported yet"); + // Why "- 1" below? To skip the value at Nyquist frequency (±n/2). + // For even lengths of DFT (real -> reciprocal space) the resulting + // h=+nu/2 and h=-nu/2 are both represented by one (strictly real) value. + // The grid should be big enough so that these values are not needed. + int max_h = (this->nu - 1) / 2; + int max_k = (this->nv - 1) / 2; + int max_l = half_l ? this->nw - 1 : (this->nw - 1) / 2; + double max_1_d2 = 0.; + if (dmin != 0.) { + max_1_d2 = 1. / (dmin * dmin); + Miller lim = this->unit_cell.get_hkl_limits(dmin); + max_h = std::min(max_h, lim[0]); + max_k = std::min(max_k, lim[1]); + max_l = std::min(max_l, lim[2]); + } + gemmi::ReciprocalAsu asu(this->spacegroup); + std::unique_ptr gops; + if (!with_sys_abs && this->spacegroup) + gops.reset(new GroupOps(this->spacegroup->operations())); + Miller hkl; + for (hkl[0] = -max_h; hkl[0] <= max_h; ++hkl[0]) { + int hi = hkl[0] >= 0 ? hkl[0] : hkl[0] + this->nu; + int hi_ = -hkl[0] >= 0 ? -hkl[0] : -hkl[0] + this->nu; + for (hkl[1] = -max_k; hkl[1] <= max_k; ++hkl[1]) { + hkl[2] = -max_l; + // (hkl)s with l<0 might be needed to get complete asu. + // If they are absent in the data (Hermitian FFT), use Friedel's pairs. + if (half_l) { + int ki_ = -hkl[1] >= 0 ? -hkl[1] : -hkl[1] + this->nv; + for (; hkl[2] < 0; ++hkl[2]) + if (asu.is_in(hkl) && + (max_1_d2 == 0. || this->unit_cell.calculate_1_d2(hkl) < max_1_d2) && + (with_sys_abs || !gops->is_systematically_absent(hkl))) + asu_data.v.push_back({hkl, + friedel_mate_value(this->get_value_q(hi_, ki_, -hkl[2]))}); + } + int ki = hkl[1] >= 0 ? hkl[1] : hkl[1] + this->nv; + for (; hkl[2] <= max_l; ++hkl[2]) + if (asu.is_in(hkl) && + (max_1_d2 == 0. || this->unit_cell.calculate_1_d2(hkl) < max_1_d2) && + (with_sys_abs || !gops->is_systematically_absent(hkl)) && + (with_000 || !(hkl[0] == 0 && hkl[1] == 0 && hkl[2] == 0))) { + int li = hkl[2] >= 0 ? hkl[2] : hkl[2] + this->nw; + asu_data.v.push_back({hkl, this->get_value_q(hi, ki, li)}); + } + } + } + if (unblur != 0. || mott_bethe) + for (HklValue& hv : asu_data.v) { + double inv_d2 = this->unit_cell.calculate_1_d2(hv.hkl); + double mult = 1; + if (unblur != 0) + // cf. reciprocal_space_multiplier() + mult = std::exp(unblur * 0.25 * inv_d2); + if (mott_bethe) + // cf. mott_bethe_factor + mult *= -mott_bethe_const() / inv_d2; + hv.value *= static_cast(mult); + } + asu_data.unit_cell_ = this->unit_cell; + asu_data.spacegroup_ = this->spacegroup; + return asu_data; + } +}; + +template using FPhiGrid = ReciprocalGrid>; + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/refln.hpp b/gemmi_gph/gemmi/refln.hpp new file mode 100644 index 00000000..d74cc993 --- /dev/null +++ b/gemmi_gph/gemmi/refln.hpp @@ -0,0 +1,253 @@ +// Copyright 2019 Global Phasing Ltd. +// +// Reads reflection data from the mmCIF format. + +#ifndef GEMMI_REFLN_HPP_ +#define GEMMI_REFLN_HPP_ + +#include +#include "cifdoc.hpp" +#include "fail.hpp" // for fail +#include "mmcif_impl.hpp" // for set_cell_from_mmcif, read_spacegroup_from_block +#include "numb.hpp" // for as_number +#include "symmetry.hpp" // for SpaceGroup +#include "unitcell.hpp" // for UnitCell + +namespace gemmi { + +struct ReflnBlock { + cif::Block block; + std::string entry_id; + UnitCell cell; + const SpaceGroup* spacegroup = nullptr; + double wavelength; + int wavelength_count; + cif::Loop* refln_loop = nullptr; + cif::Loop* diffrn_refln_loop = nullptr; + cif::Loop* default_loop = nullptr; + + ReflnBlock() = default; + ReflnBlock(ReflnBlock&& rblock_) = default; + ReflnBlock(cif::Block&& block_) : block(std::move(block_)) { + entry_id = cif::as_string(block.find_value("_entry.id")); + impl::set_cell_from_mmcif(block, cell); + if (const std::string* hm = impl::find_spacegroup_hm_value(block)) + spacegroup = find_spacegroup_by_name(cif::as_string(*hm), + cell.alpha, cell.gamma); + cell.set_cell_images_from_spacegroup(spacegroup); + const char* wave_tag = "_diffrn_radiation_wavelength.wavelength"; + cif::Column wave_col = block.find_values(wave_tag); + wavelength_count = wave_col.length(); + wavelength = wavelength_count == 1 ? cif::as_number(wave_col[0]) : 0.; + refln_loop = block.find_loop("_refln.index_h").get_loop(); + diffrn_refln_loop = block.find_loop("_diffrn_refln.index_h").get_loop(); + default_loop = refln_loop ? refln_loop : diffrn_refln_loop; + } + ReflnBlock& operator=(ReflnBlock&&) = default; + ReflnBlock& operator=(const ReflnBlock& o) { + if (this == &o) + return *this; + block = o.block; + entry_id = o.entry_id; + cell = o.cell; + spacegroup = o.spacegroup; + wavelength = o.wavelength; + wavelength_count = o.wavelength_count; + if (o.refln_loop) + refln_loop = block.find_loop("_refln.index_h").get_loop(); + if (o.diffrn_refln_loop) + diffrn_refln_loop = block.find_loop("_diffrn_refln.index_h").get_loop(); + default_loop = refln_loop ? refln_loop : diffrn_refln_loop; + return *this; + } + + bool ok() const { return default_loop != nullptr; } + void check_ok() const { if (!ok()) fail("Invalid ReflnBlock"); } + + // position after "_refln." or "_diffrn_refln." + size_t tag_offset() const { return default_loop == refln_loop ? 7 : 14; } + + void use_unmerged(bool unmerged) { + default_loop = unmerged ? diffrn_refln_loop : refln_loop; + } + bool is_merged() const { return ok() && default_loop == refln_loop; } + // deprecated + bool is_unmerged() const { return ok() && default_loop == diffrn_refln_loop; } + + std::vector column_labels() const { + check_ok(); + std::vector labels(default_loop->tags.size()); + for (size_t i = 0; i != labels.size(); ++i) + labels[i].assign(default_loop->tags[i], tag_offset(), std::string::npos); + return labels; + } + + int find_column_index(const std::string& tag) const { + if (!ok()) + return -1; + size_t name_pos = tag_offset(); + for (int i = 0; i != (int) default_loop->tags.size(); ++i) + if (default_loop->tags[i].compare(name_pos, std::string::npos, tag) == 0) + return i; + return -1; + } + + size_t get_column_index(const std::string& tag) const { + int idx = find_column_index(tag); + if (idx == -1) { + if (!ok()) + fail("No reflection data in block ", block.name); + const char* category = default_loop == refln_loop ? "_refln." : "_diffrn_refln."; + fail("Column not found in block ", block.name, ": ", category, tag); + } + return idx; + } + + template + std::vector make_vector(const std::string& tag, T null) const { + size_t n = get_column_index(tag); + std::vector v(default_loop->length()); + for (size_t j = 0; j != v.size(); n += default_loop->width(), ++j) + v[j] = cif::as_any(default_loop->values[n], null); + return v; + } + + std::array get_hkl_column_indices() const { + return {{get_column_index("index_h"), + get_column_index("index_k"), + get_column_index("index_l")}}; + } + + std::vector make_miller_vector() const { + auto hkl_idx = get_hkl_column_indices(); + std::vector v(default_loop->length()); + for (size_t j = 0, n = 0; j != v.size(); j++, n += default_loop->width()) + for (int i = 0; i != 3; ++i) + v[j][i] = cif::as_int(default_loop->values[n + hkl_idx[i]]); + return v; + } + + std::vector make_1_d2_vector() const { + if (!cell.is_crystal() || cell.a <= 0) + fail("Unit cell is not known"); + auto hkl_idx = get_hkl_column_indices(); + std::vector r(default_loop->length()); + for (size_t j = 0, n = 0; j != r.size(); j++, n += default_loop->width()) { + Miller hkl; + for (int i = 0; i != 3; ++i) + hkl[i] = cif::as_int(default_loop->values[n + hkl_idx[i]]); + r[j] = cell.calculate_1_d2(hkl); + } + return r; + } + + std::vector make_d_vector() const { + std::vector vec = make_1_d2_vector(); + for (double& d : vec) + d = 1.0 / std::sqrt(d); + return vec; + } +}; + +// moves blocks from the argument to the return value +inline +std::vector as_refln_blocks(std::vector&& blocks) { + std::vector rvec; + rvec.reserve(blocks.size()); + for (cif::Block& block : blocks) + rvec.emplace_back(std::move(block)); + blocks.clear(); + // Some blocks miss space group or unit cell, try to fill it in. + const SpaceGroup* first_sg = nullptr; + const UnitCell* first_cell = nullptr; + for (ReflnBlock& rblock : rvec) { + if (!first_sg) + first_sg = rblock.spacegroup; + else if (!rblock.spacegroup) + rblock.spacegroup = first_sg; + if (rblock.cell.is_crystal()) { + if (!first_cell) + first_cell = &rblock.cell; + } else if (first_cell) { + rblock.cell = *first_cell; + } + } + return rvec; +} + +// Get the first (merged) block with required labels. +// Optionally, block name can be specified. +inline ReflnBlock get_refln_block(std::vector&& blocks, + const std::vector& labels, + const char* block_name=nullptr) { + const SpaceGroup* first_sg = nullptr; + bool has_block = false; + for (cif::Block& block : blocks) { + if (!first_sg) + if (const std::string* hm = impl::find_spacegroup_hm_value(block)) { + first_sg = find_spacegroup_by_name(cif::as_string(*hm)); + if (first_sg && first_sg->ext == 'H') { + UnitCell cell; + impl::set_cell_from_mmcif(block, cell); + first_sg = find_spacegroup_by_name(cif::as_string(*hm), cell.alpha, cell.gamma); + } + } + if (!block_name || block.name == block_name) { + has_block = true; + if (cif::Loop* loop = block.find_loop("_refln.index_h").get_loop()) + if (std::all_of(labels.begin(), labels.end(), + [&](const std::string& s) { return s.empty() || loop->has_tag("_refln."+s); })) { + ReflnBlock rblock(std::move(block)); + if (!rblock.spacegroup && first_sg) + rblock.spacegroup = first_sg; + return rblock; + } + } + } + if (block_name && !has_block) + fail("Required block not found in SF-mmCIF file."); + fail("Tags not found in SF-mmCIF file: _refln.", join_str(labels, ", _refln.")); +} + +inline ReflnBlock hkl_cif_as_refln_block(cif::Block& block) { + ReflnBlock rb; + rb.block.swap(block); + rb.entry_id = rb.block.name; + impl::set_cell_from_mmcif(rb.block, rb.cell, /*mmcif=*/false); + const char* hm_tag = "_symmetry_space_group_name_H-M"; + if (const std::string* hm = rb.block.find_value(hm_tag)) + rb.spacegroup = find_spacegroup_by_name(cif::as_string(*hm), + rb.cell.alpha, rb.cell.gamma); + rb.cell.set_cell_images_from_spacegroup(rb.spacegroup); + rb.refln_loop = rb.block.find_loop("_refln_index_h").get_loop(); + rb.default_loop = rb.refln_loop; + return rb; +} + +// Abstraction of data source, cf. MtzDataProxy. +struct ReflnDataProxy { + const ReflnBlock& rb_; + std::array hkl_cols_; + explicit ReflnDataProxy(const ReflnBlock& rb) + : rb_(rb), hkl_cols_(rb_.get_hkl_column_indices()) {} + size_t stride() const { return loop().tags.size(); } + size_t size() const { return loop().values.size(); } + using num_type = double; + double get_num(size_t n) const { return cif::as_number(loop().values[n]); } + const UnitCell& unit_cell() const { return rb_.cell; } + const SpaceGroup* spacegroup() const { return rb_.spacegroup; } + Miller get_hkl(size_t offset) const { + return {{get_int(offset + hkl_cols_[0]), + get_int(offset + hkl_cols_[1]), + get_int(offset + hkl_cols_[2])}}; + } + size_t column_index(const std::string& label) const { return rb_.get_column_index(label); } +private: + const cif::Loop& loop() const { rb_.check_ok(); return *rb_.default_loop; } + int get_int(size_t n) const { return cif::as_int(loop().values[n]); } +}; + +inline ReflnDataProxy data_proxy(const ReflnBlock& rb) { return ReflnDataProxy(rb); } + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/resinfo.hpp b/gemmi_gph/gemmi/resinfo.hpp new file mode 100644 index 00000000..fb54af40 --- /dev/null +++ b/gemmi_gph/gemmi/resinfo.hpp @@ -0,0 +1,106 @@ +// Copyright 2018 Global Phasing Ltd. +// +// List of common residues with basic data. + +#ifndef GEMMI_RESINFO_HPP_ +#define GEMMI_RESINFO_HPP_ + +#include // for uint8_t +#include +#include +#include "fail.hpp" + +namespace gemmi { + +// Simple approximate classification. +// AA - aminoacid +// AAD - D-aminoacid +// PAA - proline-like aminoacid +// MAA - methylated aminoacid +// RNA, DNA - nucleic acids +// HOH - water or heavy water (OH, H3O, D3O are not included here) +// PYR - pyranose according to the refmac dictionary +// KET - ketopyranose according to the refmac dictionary +// BUF - agent from crystallization buffer according to PISA agents.dat +// ELS - something else (ligand). +enum class ResidueKind : unsigned char { + // when changing this list update check_polymer_type() + UNKNOWN=0, AA, AAD, PAA, MAA, RNA, DNA, BUF, HOH, PYR, KET, ELS +}; + +struct ResidueInfo { + char name[8]; + ResidueKind kind; + // linking type: 0=n/a, 1=peptide-linking, 2=nucl.-linking, 3=(2|1) + std::uint8_t linking_type; + // one-letter code or space (uppercase iff it is a standard residues) + char one_letter_code; + // rough count of hydrogens used to estimate mass with implicit hydrogens + std::uint8_t hydrogen_count; + // molecular weight + float weight; + + bool found() const { return kind != ResidueKind::UNKNOWN; } + bool is_water() const { return kind == ResidueKind::HOH; } + bool is_dna() const { return kind == ResidueKind::DNA; } + bool is_rna() const { return kind == ResidueKind::RNA; } + bool is_nucleic_acid() const { return is_dna() || is_rna(); } + bool is_amino_acid() const { + return kind == ResidueKind::AA || kind == ResidueKind::AAD || + kind == ResidueKind::PAA || kind == ResidueKind::MAA; + } + bool is_buffer_or_water() const { + return kind == ResidueKind::HOH || kind == ResidueKind::BUF; + } + // PDB format has non-standard residues (modified AA) marked as HETATM. + bool is_standard() const { return (one_letter_code & 0x20) == 0; } + char fasta_code() const { return is_standard() ? one_letter_code : 'X'; } + bool is_peptide_linking() const { return (linking_type & 1); } + bool is_na_linking() const { return (linking_type & 2); } +}; + +GEMMI_DLL ResidueInfo& get_residue_info(size_t idx); +GEMMI_DLL size_t find_tabulated_residue_idx(const std::string& name); +constexpr size_t unknown_tabulated_residue_idx() { return 367; }; +GEMMI_DLL ResidueInfo& find_tabulated_residue(const std::string& name); + +/// kind can be AA, RNA or DNA +inline const char* expand_one_letter(char c, ResidueKind kind) { + static const char* names = + // amino-acids (all letters but J are used) + "ALA\0ASX\0CYS\0ASP\0GLU\0PHE\0GLY\0HIS\0ILE\0\0 LYS\0LEU\0MET\0" // A-M + "ASN\0PYL\0PRO\0GLN\0ARG\0SER\0THR\0SEC\0VAL\0TRP\0UNK\0TYR\0GLX\0" // N-Z + // DNA + "DA\0 \0\0 DC\0 \0\0 \0\0 \0\0 DG\0 \0\0 DI\0 \0\0 \0\0 \0\0 \0\0 " // A-M + "DN\0 \0\0 \0\0 \0\0 \0\0 \0\0 DT\0 DU\0 \0\0 \0\0 \0\0 \0\0 \0\0 "; // N-Z + c &= ~0x20; + const char* ret = nullptr; + if (c >= 'A' && c <= 'Z') { + ret = &names[4 * (c - 'A')]; + if (kind == ResidueKind::AA) { + // ret is already set + } else if (kind == ResidueKind::DNA) { + ret += 4 * 26; + } else if (kind == ResidueKind::RNA && c != 'T') { + ret += 4 * 26 + 1; + } else { + ret = nullptr; + } + } + return (ret && *ret) ? ret : nullptr; +} + +/// kind can be AA, RNA or DNA +GEMMI_DLL std::vector expand_one_letter_sequence(const std::string& seq, + ResidueKind kind); + +// deprecated +inline const char* expand_protein_one_letter(char c) { + return expand_one_letter(c, ResidueKind::AA); +} +// deprecated +GEMMI_DLL std::vector expand_protein_one_letter_string(const std::string& s); + + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/riding_h.hpp b/gemmi_gph/gemmi/riding_h.hpp new file mode 100644 index 00000000..1774090f --- /dev/null +++ b/gemmi_gph/gemmi/riding_h.hpp @@ -0,0 +1,33 @@ +// Copyright 2018-2022 Global Phasing Ltd. +// +// Place hydrogens according to bond lengths and angles from monomer library. + +#ifndef GEMMI_RIDING_H_HPP_ +#define GEMMI_RIDING_H_HPP_ + +#include // for isnan +#include "topo.hpp" // for Topo + +namespace gemmi { + +GEMMI_DLL void place_hydrogens_on_all_atoms(Topo& topo); + +inline void adjust_hydrogen_distances(Topo& topo, Restraints::DistanceOf of, + double default_scale=1.) { + for (const Topo::Bond& t : topo.bonds) { + assert(t.atoms[0] != nullptr && t.atoms[1] != nullptr); + if (t.atoms[0]->is_hydrogen() || t.atoms[1]->is_hydrogen()) { + Position u = t.atoms[1]->pos - t.atoms[0]->pos; + double scale = t.restr->distance(of) / u.length(); + if (std::isnan(scale)) + scale = default_scale; + if (t.atoms[1]->is_hydrogen()) + t.atoms[1]->pos = t.atoms[0]->pos + u * scale; + else + t.atoms[0]->pos = t.atoms[1]->pos - u * scale; + } + } +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/scaling.hpp b/gemmi_gph/gemmi/scaling.hpp new file mode 100644 index 00000000..630774c0 --- /dev/null +++ b/gemmi_gph/gemmi/scaling.hpp @@ -0,0 +1,435 @@ +// Copyright 2020 Global Phasing Ltd. +// +// Anisotropic scaling of data (includes scaling of bulk solvent parameters). + +#ifndef GEMMI_SCALING_HPP_ +#define GEMMI_SCALING_HPP_ + +#include "asudata.hpp" +#include "levmar.hpp" +#if WITH_NLOPT +# include +#endif + +namespace gemmi { + +using Vec6 = std::array; + +inline double vec6_dot(const Vec6& a, const SMat33& s) { + return a[0] * s.u11 + a[1] * s.u22 + a[2] * s.u33 + + a[3] * s.u12 + a[4] * s.u13 + a[5] * s.u23; +} + +/// Symmetry constraints of ADP. +/// The number of rows is the number of independent coefficients in U. +/// For example, for tetragonal crystal returns two normalized Vec6 vectors +/// in directions [1 1 0 0 0 0] and [0 0 1 0 0 0]. +inline std::vector adp_symmetry_constraints(const SpaceGroup* sg) { + auto constraints = [](const std::initializer_list& l) { + constexpr double K2 = 0.70710678118654752440; // sqrt(1/2) + constexpr double K3 = 0.57735026918962576451; // sqrt(1/3) + static const Vec6 vv[10] = { + {1, 0, 0, 0, 0, 0}, // 0 + {0, 1, 0, 0, 0, 0}, // 1 + {0, 0, 1, 0, 0, 0}, // 2 + {0, 0, 0, 1, 0, 0}, // 3 + {0, 0, 0, 0, 1, 0}, // 4 + {0, 0, 0, 0, 0, 1}, // 5 + {K2, K2, 0, 0, 0, 0}, // 6 + {2/3., 2/3., 0, 1/3., 0, 0}, // 7 + {K3, K3, K3, 0, 0, 0}, // 8 + {0, 0, 0, K3, K3, K3} // 9 + }; + std::vector c; + c.reserve(l.size()); + for (int i : l) + c.push_back(vv[i]); + return c; + }; + CrystalSystem cr_system = sg ? sg->crystal_system() : CrystalSystem::Triclinic; + switch (cr_system) { + case CrystalSystem::Triclinic: + return constraints({0, 1, 2, 3, 4, 5}); + case CrystalSystem::Monoclinic: + // the last index is: a->5, b->4, c->3 + return constraints({0, 1, 2, 3 + 'c' - sg->monoclinic_unique_axis()}); + case CrystalSystem::Orthorhombic: + return constraints({0, 1, 2}); + case CrystalSystem::Tetragonal: + return constraints({6, 2}); + case CrystalSystem::Hexagonal: + case CrystalSystem::Trigonal: + if (sg->ext == 'R') + return constraints({8, 9}); + return constraints({7, 2}); + case CrystalSystem::Cubic: + return constraints({8}); + } + unreachable(); +} + +template +struct Scaling { + struct Point { + Miller hkl; + double stol2; + std::complex fcmol, fmask; + Real fobs, sigma; + + Miller get_x() const { return hkl; } + double get_y() const { return fobs; } + double get_weight() const { return 1.0 /* / sigma*/; } + }; + + UnitCell cell; + // model parameters + double k_overall = 1.; + // b_star = F B_cart F^T, where F - fractionalization matrix + SMat33 b_star{0, 0, 0, 0, 0, 0}; + std::vector constraint_matrix; + bool use_solvent = false; + bool fix_k_sol = false; + bool fix_b_sol = false; + // initialize with average values (Fokine & Urzhumtsev, 2002) + double k_sol = 0.35; + double b_sol = 46.0; + std::vector points; + + Scaling(const UnitCell& cell_, const SpaceGroup* sg) + : cell(cell_), constraint_matrix(adp_symmetry_constraints(sg)) {} + + // B_{overall} is stored as B* not B_{cartesian}. + // Use getter and setter to convert from/to B_{cartesian}. + void set_b_overall(const SMat33& b_overall) { + b_star = b_overall.transformed_by(cell.frac.mat); + } + SMat33 get_b_overall() const { + return b_star.transformed_by(cell.orth.mat); + } + + // Scale data, optionally adding bulk solvent correction. + void scale_data(AsuData>& asu_data, + const AsuData>* mask_data) const { + if (use_solvent && !(mask_data && mask_data->size() == asu_data.size())) + fail("scale_data(): mask data not prepared"); + bool use_scaling = (k_overall != 1 || !b_star.all_zero()); + for (size_t i = 0; i != asu_data.v.size(); ++i) { + HklValue>& hv = asu_data.v[i]; + if (use_solvent) { + if (hv.hkl != mask_data->v[i].hkl) + fail("scale_data(): data arrays don't match"); + double stol2 = cell.calculate_stol_sq(hv.hkl); + hv.value += (Real)get_solvent_scale(stol2) * mask_data->v[i].value; + } + if (use_scaling) + hv.value *= (Real) get_overall_scale_factor(hv.hkl); + } + } + + std::complex scale_value(const Miller& hkl, std::complex f_value, + std::complex mask_value) { + if (use_solvent) { + double stol2 = cell.calculate_stol_sq(hkl); + f_value += (Real)get_solvent_scale(stol2) * mask_value; + } + return f_value * (Real) get_overall_scale_factor(hkl); + } + + std::vector get_parameters() const { + std::vector ret; + ret.push_back(k_overall); + if (use_solvent) { + if (!fix_k_sol) + ret.push_back(k_sol); + if (!fix_b_sol) + ret.push_back(b_sol); + } + for (const Vec6& v : constraint_matrix) + ret.push_back(vec6_dot(v, b_star)); + return ret; + } + + /// set k_overall, k_sol, b_sol, b_star + void set_parameters(const double* p) { + k_overall = p[0]; + int n = 0; + if (use_solvent) { + if (!fix_k_sol) + k_sol = p[++n]; + if (!fix_b_sol) + b_sol = p[++n]; + } + b_star = {0, 0, 0, 0, 0, 0}; + for (const Vec6& row : constraint_matrix) { + double d = p[++n]; + b_star.u11 += row[0] * d; + b_star.u22 += row[1] * d; + b_star.u33 += row[2] * d; + b_star.u12 += row[3] * d; + b_star.u13 += row[4] * d; + b_star.u23 += row[5] * d; + } + } + + void set_parameters(const std::vector& p) { + set_parameters(p.data()); + } + + // pre: all AsuData args are sorted + void prepare_points(const AsuData>& calc, + const AsuData>& obs, + const AsuData>* mask_data) { + if (use_solvent && !(mask_data && mask_data->size() == calc.size())) + fail("prepare_points(): mask data not prepared"); + std::complex fmask; + points.reserve(std::min(calc.size(), obs.size())); + auto c = calc.v.begin(); + for (const HklValue>& o : obs.v) { + if (c->hkl != o.hkl) { + while (*c < o.hkl) { + ++c; + if (c == calc.v.end()) + return; + } + if (c->hkl != o.hkl) + continue; + } + if (use_solvent) { + const HklValue>& m = mask_data->v[c - calc.v.begin()]; + if (m.hkl != c->hkl) + fail("prepare_points(): unexpected data"); + fmask = m.value; + } + double stol2 = cell.calculate_stol_sq(o.hkl); + if (!std::isnan(o.value.value) && !std::isnan(o.value.sigma)) + points.push_back({o.hkl, stol2, c->value, fmask, o.value.value, o.value.sigma}); + ++c; + if (c == calc.v.end()) + break; + } + } + + + double get_solvent_scale(double stol2) const { + return k_sol * std::exp(-b_sol * stol2); + } + + double get_overall_scale_factor(const Miller& hkl) const { + return k_overall * std::exp(-0.25 * b_star.r_u_r(hkl)); + } + + std::complex get_fcalc(const Point& p) const { + if (!use_solvent) + return p.fcmol; + return p.fcmol + (Real)get_solvent_scale(p.stol2) * p.fmask; + } + + // quick linear fit (ignoring sigma) to get initial k_overall and isotropic B + void fit_isotropic_b_approximately() { + double sx = 0, sy = 0, sxx = 0, sxy = 0; + int n = 0; + for (const Point& p : points) { + if (p.fobs < 1 || p.fobs < p.sigma) // skip weak reflections + continue; + double fcalc = std::abs(get_fcalc(p)); + double x = p.stol2; + double y = std::log(static_cast(p.fobs / fcalc)); + sx += x; + sy += y; + sxx += x * x; + sxy += x * y; + n += 1; + } + if (n <= 5) // this is not expected to happen + return; + double slope = (n * sxy - sx * sy) / (n * sxx - sx * sx); + double intercept = (sy - slope * sx) / n; + double b_iso = -slope; + k_overall = std::exp(intercept); + set_b_overall({b_iso, b_iso, b_iso, 0, 0, 0}); + } + + // least-squares fitting of k_overall only + double lsq_k_overall() const { + double sxx = 0, sxy = 0; + for (const Point& p : points) { + if (p.fobs < 1 || p.fobs < p.sigma) // skip weak reflections + continue; + double x = std::abs(get_fcalc(p)); + double y = p.fobs; + sxx += x * x; + sxy += x * y; + } + return sxx != 0. ? sxy / sxx : 1.; + } + + // For testing only, don't use it. + // Estimates anisotropic b_star using other parameters (incl. isotropic B), + // following P. Afonine et al, doi:10.1107/S0907444913000462 sec. 2.1. + // The symmetry constraints are not implemented - don't use it! + void fit_b_star_approximately() { + double b_iso = 1/3. * get_b_overall().trace(); + //size_t nc = constraint_matrix.size(); + double M[36] = {}; + double b[6] = {}; + //std::vector Vc(nc); + for (const Point& p : points) { + double fcalc = std::abs(get_fcalc(p)); + // the factor 1 / 2 pi^2 will be taken into account later + double Z = std::log(p.fobs / (k_overall * fcalc)) + b_iso * p.stol2; + Vec3 h(p.hkl); + double V[6] = {h.x * h.x, h.y * h.y, h.z * h.z, + 2 * h.x * h.y, 2 * h.x * h.z, 2 * h.y * h.z}; + //for (size_t i = 0; i < nc; ++i) + // Vc[i] = vec6_dot(constraint_matrix[i], V); + for (size_t i = 0; i < 6; ++i) { + for (size_t j = 0; j < 6; ++j) + M[6*i+j] += V[i] * V[j]; + b[i] -= Z * V[i]; + } + } + jordan_solve(M, b, 6); + double b_star_iso = 1/3. * b_star.trace(); + SMat33 u_star{b[0], b[1], b[2], b[3], b[4], b[5]}; + // u_to_b() / (2 pi^2) = 8 pi^2 / 2 pi^2 = 4 + b_star = u_star.scaled(4.0).added_kI(b_star_iso); + //auto e = get_b_overall().elements_pdb(); + //printf("fitted B = {%g %g %g %g %g %g}\n", e[0], e[1], e[2], e[3], e[4], e[5]); + } + + double fit_parameters() { + LevMar levmar; + return levmar.fit(*this); + } + + double calculate_r_factor() const { + double abs_diff_sum = 0; + double denom = 0; + for (const Point& p : points) { + abs_diff_sum += std::fabs(p.fobs - compute_value(p)); + denom += p.fobs; + } + return abs_diff_sum / denom; + } + + // interface for fitting + double compute_value(const Point& p) const { + return std::abs(get_fcalc(p)) * (Real) get_overall_scale_factor(p.hkl); + } + + double compute_value_and_derivatives(const Point& p, std::vector& dy_da) const { + Vec3 h(p.hkl); + double kaniso = std::exp(-0.25 * b_star.r_u_r(h)); + double fcalc_abs; + int n = 1; + if (use_solvent) { + double solv_b = std::exp(-b_sol * p.stol2); + double solv_scale = k_sol * solv_b; + auto fcalc = p.fcmol + (Real)solv_scale * p.fmask; + fcalc_abs = std::abs(fcalc); + double dy_dsol = (fcalc.real() * p.fmask.real() + + fcalc.imag() * p.fmask.imag()) / fcalc_abs * k_overall * kaniso; + if (!fix_k_sol) + dy_da[n++] = solv_b * dy_dsol; + if (!fix_b_sol) + dy_da[n++] = -p.stol2 * solv_scale * dy_dsol; + } else { + fcalc_abs = std::abs(p.fcmol); + } + double fe = fcalc_abs * kaniso; + double y = k_overall * fe; + dy_da[0] = fe; // dy/d k_overall + SMat33 du = { + -0.25 * y * (h.x * h.x), + -0.25 * y * (h.y * h.y), + -0.25 * y * (h.z * h.z), + -0.5 * y * (h.x * h.y), + -0.5 * y * (h.x * h.z), + -0.5 * y * (h.y * h.z), + }; + for (size_t j = 0; j < constraint_matrix.size(); ++j) + dy_da[n+j] = vec6_dot(constraint_matrix[j], du); + return y; + } +}; + +// only for testing and evaluation - scaling with NLOpt +#if WITH_NLOPT +namespace impl { + +template +double calculate_for_nlopt(unsigned n, const double* x, double* grad, void* data) { + auto scaling = static_cast*>(data); + scaling->set_parameters(x); + if (grad) + return compute_gradients(*scaling, n, grad); + else + return compute_wssr(*scaling); +} + +inline const char* nlresult_to_string(nlopt_result r) { + switch (r) { + case NLOPT_FAILURE: return "failure"; + case NLOPT_INVALID_ARGS: return "invalid arguments"; + case NLOPT_OUT_OF_MEMORY: return "out of memory"; + case NLOPT_ROUNDOFF_LIMITED: return "roundoff errors limit progress"; + case NLOPT_FORCED_STOP: return "interrupted"; + case NLOPT_SUCCESS: return "success"; + case NLOPT_STOPVAL_REACHED: return "stop-value reached"; + case NLOPT_FTOL_REACHED: return "ftol-value reached"; + case NLOPT_XTOL_REACHED: return "xtol-value reached"; + case NLOPT_MAXEVAL_REACHED: return "max. evaluation number reached"; + case NLOPT_MAXTIME_REACHED: return "max. time reached"; + default: return ""; + } + unreachable(); +} + +} // namespace impl + +template +double fit_parameters_with_nlopt(Scaling& scaling, const char* optimizer) { + std::vector params = scaling.get_parameters(); + nlopt_opt opt = nlopt_create(nlopt_algorithm_from_string(optimizer), params.size()); + { // prepare bounds + std::vector lb(params.size()); + std::vector ub(params.size()); + lb[0] = 0.7 * params[0]; // k_ov + ub[0] = 1.2 * params[0]; + size_t n = 1; + if (scaling.use_solvent) { + if (!scaling.fix_k_sol) { + lb[n] = 0.15; + ub[n] = 0.5; + ++n; + } + if (!scaling.fix_b_sol) { + lb[n] = 10; + ub[n] = 80; + ++n; + } + } + for (; n < params.size(); ++n) { + lb[n] = -0.01; + ub[n] = 0.01; + } + nlopt_set_lower_bounds(opt, lb.data()); + nlopt_set_upper_bounds(opt, ub.data()); + } + nlopt_set_min_objective(opt, impl::calculate_for_nlopt, &scaling); + nlopt_set_maxeval(opt, 100); + double minf = NAN; + nlopt_result r = nlopt_optimize(opt, ¶ms[0], &minf); + (void) r; + //if (r < 0) + // printf("NLopt result: %s\n", impl::nlresult_to_string(r)); + //else + // printf("NLopt minimum value: %g\n", minf); + nlopt_destroy(opt); + scaling.set_parameters(params); + return minf; +} +#endif // WITH_NLOPT + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/select.hpp b/gemmi_gph/gemmi/select.hpp new file mode 100644 index 00000000..51be4112 --- /dev/null +++ b/gemmi_gph/gemmi/select.hpp @@ -0,0 +1,229 @@ +// Copyright 2018 Global Phasing Ltd. +// +// Selections. + +#ifndef GEMMI_SELECT_HPP_ +#define GEMMI_SELECT_HPP_ + +#include // for INT_MIN, INT_MAX +#include "model.hpp" // for Model + +namespace gemmi { + +// from http://www.ccp4.ac.uk/html/pdbcur.html +// Specification of the selection sets: +// either +// /mdl/chn/s1.i1-s2.i2/at[el]:aloc +// or +// /mdl/chn/*(res).ic/at[el]:aloc +// + +struct GEMMI_DLL Selection { + struct List { + bool all = true; + bool inverted = false; + std::string list; // comma-separated + + std::string str() const { + if (all) + return "*"; + return inverted ? "!" + list : list; + } + + bool has(const std::string& name) const { + if (all) + return true; + bool found = is_in_list(name, list); + return inverted ? !found : found; + } + }; + + struct FlagList { + std::string pattern; + bool has(char flag) const { + if (pattern.empty()) + return true; + bool invert = (pattern[0] == '!'); + bool found = (pattern.find(flag, invert ? 1 : 0) != std::string::npos); + return invert ? !found : found; + } + }; + + struct SequenceId { + int seqnum; + char icode; + + bool empty() const { + return seqnum == INT_MIN || seqnum == INT_MAX; + } + + std::string str() const; + + int compare(const SeqId& seqid) const { + if (seqnum != *seqid.num) + return seqnum < *seqid.num ? -1 : 1; + if (icode != '*' && icode != seqid.icode) + return icode < seqid.icode ? -1 : 1; + return 0; + } + }; + + struct AtomInequality { + char property; + int relation; + double value; + + bool matches(const Atom& a) const { + double atom_value = 0.; + if (property == 'q') + atom_value = a.occ; + else if (property == 'b') + atom_value = a.b_iso; + if (relation < 0) + return atom_value < value; + if (relation > 0) + return atom_value > value; + return atom_value == value; + } + + std::string str() const; + }; + + int mdl = 0; // 0 = all + List chain_ids; + SequenceId from_seqid = {INT_MIN, '*'}; + SequenceId to_seqid = {INT_MAX, '*'}; + List residue_names; + List entity_types; + // array corresponding to enum EntityType + std::array et_flags; + List atom_names; + std::vector elements; + List altlocs; + FlagList residue_flags; + FlagList atom_flags; + std::vector atom_inequalities; + + Selection() = default; + Selection(const std::string& cid); + + std::string str() const; + + bool matches(const Structure&) const { return true; } + bool matches(const Model& model) const { + return mdl == 0 || mdl == model.num; + } + bool matches(const Chain& chain) const { + return chain_ids.has(chain.name); + } + bool matches(const Residue& res) const { + return (entity_types.all || et_flags[(int)res.entity_type]) && + residue_names.has(res.name) && + from_seqid.compare(res.seqid) <= 0 && + to_seqid.compare(res.seqid) >= 0 && + residue_flags.has(res.flag); + } + bool matches(const Atom& a) const { + return atom_names.has(a.name) && + (elements.empty() || elements[a.element.ordinal()]) && + (altlocs.all || altlocs.has(std::string(a.altloc ? 1 : 0, a.altloc))) && + atom_flags.has(a.flag) && + std::all_of(atom_inequalities.begin(), atom_inequalities.end(), + [&](const AtomInequality& i) { return i.matches(a); }); + } + bool matches(const CRA& cra) const { + return (cra.chain == nullptr || matches(*cra.chain)) && + (cra.residue == nullptr || matches(*cra.residue)) && + (cra.atom == nullptr || matches(*cra.atom)); + } + + FilterProxy models(Structure& st) const { + return {*this, st.models}; + } + FilterProxy chains(Model& model) const { + return {*this, model.chains}; + } + FilterProxy residues(Chain& chain) const { + return {*this, chain.residues}; + } + FilterProxy atoms(Residue& residue) const { + return {*this, residue.atoms}; + } + + CRA first_in_model(Model& model) const { + if (matches(model)) + for (Chain& chain : model.chains) { + if (matches(chain)) + for (Residue& res : chain.residues) { + if (matches(res)) + for (Atom& atom : res.atoms) { + if (matches(atom)) + return {&chain, &res, &atom}; + } + } + } + return {nullptr, nullptr, nullptr}; + } + + std::pair first(Structure& st) const { + for (Model& model : st.models) { + CRA cra = first_in_model(model); + if (cra.chain) + return {&model, cra}; + } + return {nullptr, {nullptr, nullptr, nullptr}}; + } + + template + void add_matching_children(const T& orig, T& target) const { + for (const auto& orig_child : orig.children()) + if (matches(orig_child)) { + target.children().push_back(orig_child.empty_copy()); + add_matching_children(orig_child, target.children().back()); + } + } + void add_matching_children(const Atom&, Atom&) const {} + + Selection& set_residue_flags(const std::string& pattern) { + residue_flags.pattern = pattern; + return *this; + } + Selection& set_atom_flags(const std::string& pattern) { + atom_flags.pattern = pattern; + return *this; + } + + template + T copy_selection(const T& orig) const { + T copied = orig.empty_copy(); + add_matching_children(orig, copied); + return copied; + } + + template + void remove_selected(T& t) const { + for (auto& child : t.children()) + if (matches(child)) + remove_selected(child); + vector_remove_if(t.children(), + [&](typename T::child_type& c) { return c.children().empty(); }); + } + void remove_selected(Residue& res) const { + if (atom_names.all && elements.empty() && altlocs.all && + atom_flags.pattern.empty() && atom_inequalities.empty()) + res.atoms.clear(); + else + vector_remove_if(res.atoms, [&](Atom& c) { return matches(c); }); + } + + template + void remove_not_selected(T& t) const { + vector_remove_if(t.children(), [&](typename T::child_type& c) { return !matches(c); }); + for (auto& child : t.children()) + remove_not_selected(child); + } + void remove_not_selected(Atom&) const {} +}; + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/seqalign.hpp b/gemmi_gph/gemmi/seqalign.hpp new file mode 100644 index 00000000..758df8fb --- /dev/null +++ b/gemmi_gph/gemmi/seqalign.hpp @@ -0,0 +1,324 @@ +// Simple pairwise sequence alignment. +// +// Code in this file is based on and derived from files ksw2_gg.c and ksw2.h +// from https://github.com/lh3/ksw2, which is under the MIT license. +// The original code, written by Heng Li, has more features and has more +// efficient variants that use SSE instructions. + +#ifndef GEMMI_SEQALIGN_HPP_ +#define GEMMI_SEQALIGN_HPP_ + +#include +#include // for reverse +#include +#include +#include +#include "fail.hpp" // for fail + +namespace gemmi { + +struct AlignmentScoring { + int match = 1; + int mismatch = -1; + int gapo = -1; // gap opening penalty + int gape = -1; // gap extension penalty + // In a polymer in model, coordinates are used to determine expected gaps. + int good_gapo = 0; // gap opening in expected place in a polymer + int bad_gapo = -2; // gap opening that was not predicted + std::vector score_matrix; + std::vector matrix_encoding; + + static const AlignmentScoring* simple() { + static const AlignmentScoring s{}; + return &s; + } + // Scoring for alignment of partially-modelled polymer to its full sequence + static const AlignmentScoring* partial_model() { + static const AlignmentScoring s = { 100, -10000, -10000, -1, 0, -200, {}, {} }; + return &s; + } + static const AlignmentScoring* blosum62() { + // BLAST uses BLOSUM-62 with gap cost (10,1) + static const AlignmentScoring s = { + 1, -4, -10, -1, 0, -10, + { 4,-1,-2,-2, 0,-1,-1, 0,-2,-1,-1,-1,-1,-2,-1, 1, 0,-3,-2, 0, + -1, 5, 0,-2,-3, 1, 0,-2, 0,-3,-2, 2,-1,-3,-2,-1,-1,-3,-2,-3, + -2, 0, 6, 1,-3, 0, 0, 0, 1,-3,-3, 0,-2,-3,-2, 1, 0,-4,-2,-3, + -2,-2, 1, 6,-3, 0, 2,-1,-1,-3,-4,-1,-3,-3,-1, 0,-1,-4,-3,-3, + 0,-3,-3,-3, 9,-3,-4,-3,-3,-1,-1,-3,-1,-2,-3,-1,-1,-2,-2,-1, + -1, 1, 0, 0,-3, 5, 2,-2, 0,-3,-2, 1, 0,-3,-1, 0,-1,-2,-1,-2, + -1, 0, 0, 2,-4, 2, 5,-2, 0,-3,-3, 1,-2,-3,-1, 0,-1,-3,-2,-2, + 0,-2, 0,-1,-3,-2,-2, 6,-2,-4,-4,-2,-3,-3,-2, 0,-2,-2,-3,-3, + -2, 0, 1,-1,-3, 0, 0,-2, 8,-3,-3,-1,-2,-1,-2,-1,-2,-2, 2,-3, + -1,-3,-3,-3,-1,-3,-3,-4,-3, 4, 2,-3, 1, 0,-3,-2,-1,-3,-1, 3, + -1,-2,-3,-4,-1,-2,-3,-4,-3, 2, 4,-2, 2, 0,-3,-2,-1,-2,-1, 1, + -1, 2, 0,-1,-3, 1, 1,-2,-1,-3,-2, 5,-1,-3,-1, 0,-1,-3,-2,-2, + -1,-1,-2,-3,-1, 0,-2,-3,-2, 1, 2,-1, 5, 0,-2,-1,-1,-1,-1, 1, + -2,-3,-3,-3,-2,-3,-3,-3,-1, 0, 0,-3, 0, 6,-4,-2,-2, 1, 3,-1, + -1,-2,-2,-1,-3,-1,-1,-2,-2,-3,-3,-1,-2,-4, 7,-1,-1,-4,-3,-2, + 1,-1, 1, 0,-1, 0, 0, 0,-1,-2,-2, 0,-1,-2,-1, 4, 1,-3,-2,-2, + 0,-1, 0,-1,-1,-1,-1,-2,-2,-1,-1,-1,-1,-2,-1, 1, 5,-2,-2, 0, + -3,-3,-4,-4,-2,-2,-3,-2,-2,-3,-2,-3,-1, 1,-4,-3,-2,11, 2,-3, + -2,-2,-2,-3,-2,-1,-2,-3, 2,-1,-1,-2,-1, 3,-3,-2,-2, 2, 7,-1, + 0,-3,-3,-3,-1,-2,-2,-3,-3, 3, 1,-2, 1,-1,-2,-2, 0,-3,-1, 4}, + {"ALA", "ARG", "ASN", "ASP", "CYS", "GLN", "GLU", "GLY", "HIS", "ILE", + "LEU", "LYS", "MET", "PHE", "PRO", "SER", "THR", "TRP", "TYR", "VAL"} + }; + return &s; + } +}; + +struct AlignmentResult { + struct Item { + std::uint32_t value; + char op() const { return "MID"[value & 0xf]; } + std::uint32_t len() const { return value >> 4; } + }; + int score = 0; + int match_count = 0; + std::string match_string; + std::vector cigar; + + std::string cigar_str() const { + std::string s; + for (Item item : cigar) { + s += std::to_string(item.len()); + s += item.op(); + } + return s; + } + + // 1=query, 2=target, other=shorter + std::size_t input_length(int which) const { + std::size_t counters[3] = {0, 0, 0}; + for (Item item : cigar) + counters[item.value & 0xf] += item.len(); + if (which == 1 || which == 2) + return counters[0] + counters[which]; + return counters[0] + std::min(counters[1], counters[2]); + } + double calculate_identity(int which=0) const { + return 100. * match_count / input_length(which); + } + + // In the backtrack matrix, value p[] has the following structure: + // bit 0-2: which type gets the max - 0 for H, 1 for E, 2 for F + // bit 3/0x08: 1 if a continuation on the E state + // bit 4/0x10: 1 if a continuation on the F state + void backtrack_to_cigar(const std::uint8_t *p, int i, int j) { + i--; + int j0 = j--; + int state = 0; + while (i >= 0 && j >= 0) { + // at the beginning of the loop, _state_ tells us which state to check + // if requesting the H state, find state one maximizes it. + uint32_t tmp = p[(std::size_t)i * j0 + j]; + if (state == 0 || (tmp & (1 << (state + 2))) == 0) + state = tmp & 7; + if (state == 0) { // match + push_cigar(0, 1); + --i; + --j; + } else if (state == 1) { // deletion + push_cigar(2, 1); + --i; + } else { // insertion + push_cigar(1, 1); + --j; + } + } + if (i >= 0) + push_cigar(2, i + 1); // first deletion + else if (j >= 0) + push_cigar(1, j + 1); // first insertion + std::reverse(cigar.begin(), cigar.end()); + } + + + void count_matches(const std::vector& query, + const std::vector& target) { + match_count = 0; + size_t pos1 = 0, pos2 = 0; + for (Item item : cigar) + if (item.op() == 'M') { + for (uint32_t i = 0; i < item.len(); ++i) + if (query[pos1++] == target[pos2++]) { + ++match_count; + match_string += '|'; + } else { + match_string += '.'; + } + } else if (item.op() == 'I') { + pos1 += item.len(); + match_string.append(item.len(), ' '); + } else /*item.op() == 'D'*/ { + pos2 += item.len(); + match_string.append(item.len(), ' '); + } + } + + std::string add_gaps(const std::string& s, unsigned which) const { + std::string out; + size_t pos = 0; + for (Item item : cigar) { + bool show = (item.value & 0xf) == 0 || (item.value & 0xf) == which; + for (uint32_t i = 0; i < item.len(); ++i) + out += show ? s.at(pos++) : '-'; + } + return out; + } + + std::string formatted(const std::string& a, const std::string& b) const { + std::string r; + r.reserve((match_string.size() + 1) * 3); + r += add_gaps(a, 1); + r += '\n'; + r += match_string; + r += '\n'; + r += add_gaps(b, 2); + r += '\n'; + return r; + } + + // op: 0=match/mismatch, 1=insertion, 2=deletion + void push_cigar(std::uint32_t op, int len) { + if (cigar.empty() || op != (cigar.back().value & 0xf)) + cigar.push_back({len<<4 | op}); + else + cigar.back().value += len<<4; + } +}; + +/// All values in query and target must be less then m. +/// target_gapo, if set, has gap opening penalties at specific positions in target. +inline +AlignmentResult align_sequences(const std::vector& query, + const std::vector& target, + const std::vector& target_gapo, + std::uint8_t m, + const AlignmentScoring& scoring) { + // generate the query profile + std::int16_t *query_profile = new std::int16_t[query.size() * m]; + { + std::uint32_t mat_size = (std::uint32_t) scoring.matrix_encoding.size(); + if (mat_size * mat_size != scoring.score_matrix.size()) + fail("align_sequences: internal error (wrong score_matrix)"); + std::int32_t i = 0; + for (std::uint8_t k = 0; k < m; ++k) + for (std::uint8_t q : query) + if (k < mat_size && q < mat_size) + query_profile[i++] = scoring.score_matrix[k * mat_size + q]; + else + query_profile[i++] = (k == q ? scoring.match : scoring.mismatch); + } + + struct eh_t { std::int32_t h, e; }; + eh_t *eh = new eh_t[query.size() + 1]; + std::int32_t gape = scoring.gape; + std::int32_t gapoe = scoring.gapo + gape; + + // fill the first row + { + std::int32_t gap0 = !target_gapo.empty() ? target_gapo[0] + gape : gapoe; + eh[0].h = 0; + eh[0].e = gap0 + gapoe; + for (std::int32_t j = 1; j <= (std::int32_t)query.size(); ++j) { + eh[j].h = gap0 + gape * (j - 1); + eh[j].e = gap0 + gapoe + gape * j; + } + } + + // backtrack matrix; in each cell: f<<4|e<<2|h + std::uint8_t *z = new std::uint8_t[query.size() * target.size()]; + // DP loop + for (std::int32_t i = 0; i < (std::int32_t)target.size(); ++i) { + std::uint8_t target_item = target[i]; + std::int16_t *scores = &query_profile[target_item * query.size()]; + std::uint8_t *zi = &z[i * query.size()]; + std::int32_t h1 = gapoe + gape * i; + std::int32_t f = gapoe + gapoe + gape * i; + std::int32_t gapx = i+1 < (std::int32_t)target_gapo.size() + ? target_gapo[i+1] + gape : gapoe; + for (std::size_t j = 0; j < query.size(); ++j) { + // At the beginning of the loop: + // eh[j] = { H(i-1,j-1), E(i,j) }, f = F(i,j) and h1 = H(i,j-1) + // Cells are computed in the following order: + // H(i,j) = max{H(i-1,j-1) + S(i,j), E(i,j), F(i,j)} + // E(i+1,j) = max{H(i,j)+gapo, E(i,j)} + gape + // F(i,j+1) = max{H(i,j)+gapo, F(i,j)} + gape + eh_t *p = &eh[j]; + std::int32_t h = p->h; + std::int32_t e = p->e; + p->h = h1; + h += scores[j]; + std::uint8_t direction = 0; + if (h < e) { + direction = 1; // deletion + h = e; + } + if (h <= f) { + direction = 2; // insertion + h = f; + } + h1 = h; + + h += gapoe; + e += gape; + if (e > h) + direction |= 0x08; + else + e = h; + + h = h1 + gapx; + p->e = e; + f += gape; + if (f > h) + direction |= 0x10; + else + f = h; + + // z[i,j] keeps h for the current cell and e/f for the next cell + zi[j] = direction; + } + eh[query.size()].h = h1; + eh[query.size()].e = -0x40000000; // -infinity + } + + AlignmentResult result; + result.score = eh[query.size()].h; + delete [] query_profile; + delete [] eh; + result.backtrack_to_cigar(z, (int)target.size(), (int)query.size()); + delete [] z; + result.count_matches(query, target); + return result; +} + +inline +AlignmentResult align_string_sequences(const std::vector& query, + const std::vector& target, + const std::vector& target_gapo, + const AlignmentScoring* scoring) { + if (scoring == nullptr) + scoring = AlignmentScoring::simple(); + std::map encoding; + for (const std::string& res_name : scoring->matrix_encoding) + encoding.emplace(res_name, (std::uint8_t)encoding.size()); + for (const std::string& s : query) + encoding.emplace(s, (std::uint8_t)encoding.size()); + for (const std::string& s : target) + encoding.emplace(s, (std::uint8_t)encoding.size()); + if (encoding.size() > 255) + return AlignmentResult(); + std::vector encoded_query(query.size()); + for (size_t i = 0; i != query.size(); ++i) + encoded_query[i] = encoding.at(query[i]); + std::vector encoded_target(target.size()); + for (size_t i = 0; i != target.size(); ++i) + encoded_target[i] = encoding.at(target[i]); + return align_sequences(encoded_query, encoded_target, + target_gapo, (std::uint8_t)encoding.size(), *scoring); +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/seqid.hpp b/gemmi_gph/gemmi/seqid.hpp new file mode 100644 index 00000000..1fcd1f62 --- /dev/null +++ b/gemmi_gph/gemmi/seqid.hpp @@ -0,0 +1,168 @@ +// Copyright 2017 Global Phasing Ltd. +// +// SeqId -- residue number and insertion code together. + +#ifndef GEMMI_SEQID_HPP_ +#define GEMMI_SEQID_HPP_ + +#include // for INT_MIN +#include // for strtol +#include // for invalid_argument +#include +#include "util.hpp" // for cat + +namespace gemmi { + +// Optional int value. N is a special value that means not-set. +template struct OptionalInt { + enum { None=N }; + int value = None; + + OptionalInt() = default; + OptionalInt(int n) : value(n) {} + bool has_value() const { return value != None; } + std::string str(char null='?') const { + return has_value() ? std::to_string(value) : std::string(1, null); + } + OptionalInt& operator=(int n) { value = n; return *this; } + bool operator==(const OptionalInt& o) const { return value == o.value; } + bool operator!=(const OptionalInt& o) const { return value != o.value; } + bool operator<(const OptionalInt& o) const { + return has_value() && o.has_value() && value < o.value; + } + bool operator==(int n) const { return value == n; } + bool operator!=(int n) const { return value != n; } + OptionalInt operator+(OptionalInt o) const { + return OptionalInt(has_value() && o.has_value() ? value + o.value : N); + } + OptionalInt operator-(OptionalInt o) const { + return OptionalInt(has_value() && o.has_value() ? value - o.value : N); + } + OptionalInt& operator+=(int n) { if (has_value()) value += n; return *this; } + OptionalInt& operator-=(int n) { if (has_value()) value -= n; return *this; } + explicit operator int() const { return value; } + explicit operator bool() const { return has_value(); } + // these are defined for partial compatibility with C++17 std::optional + using value_type = int; + int& operator*() { return value; } + const int& operator*() const { return value; } + int& emplace(int n) { value = n; return value; } + void reset() noexcept { value = None; } +}; + +struct SeqId { + using OptionalNum = OptionalInt; + + OptionalNum num; // sequence number + char icode = ' '; // insertion code + + SeqId() = default; + SeqId(int num_, char icode_) { num = num_; icode = icode_; } + SeqId(OptionalNum num_, char icode_) { num = num_; icode = icode_; } + explicit SeqId(const std::string& str) { + char* endptr; + num = std::strtol(str.c_str(), &endptr, 10); + if (endptr == str.c_str() || (*endptr != '\0' && endptr[1] != '\0')) + throw std::invalid_argument("Not a seqid: " + str); + icode = (*endptr | 0x20); + } + + bool operator==(const SeqId& o) const { + return num == o.num && ((icode ^ o.icode) & ~0x20) == 0; + } + bool operator!=(const SeqId& o) const { return !operator==(o); } + bool operator<(const SeqId& o) const { + return (*num * 256 + icode) < (*o.num * 256 + o.icode); + } + bool operator<=(const SeqId& o) const { return !(o < *this); } + + char has_icode() const { return icode != ' '; } + + std::string str(bool dot_before_icode=false) const { + std::string r = num.str(); + if (icode != ' ') { + if (dot_before_icode) + r += '.'; + r += icode; + } + return r; + } +}; + +// Sequence ID (sequence number + insertion code) + residue name + segment ID +struct ResidueId { + SeqId seqid; + std::string segment; // segid - up to 4 characters in the PDB file + std::string name; + + // used for first_conformation iterators, etc. + SeqId group_key() const { return seqid; } + + bool matches(const ResidueId& o) const { + return seqid == o.seqid && segment == o.segment && name == o.name; + } + bool matches_noseg(const ResidueId& o) const { + return seqid == o.seqid && name == o.name; + } + bool operator==(const ResidueId& o) const { return matches(o); } + std::string str() const { return cat(seqid.str(), '(', name, ')'); } +}; + +inline std::string atom_str(const std::string& chain_name, + const ResidueId& res_id, + const std::string& atom_name, + char altloc, + bool as_cid=false) { + std::string r = as_cid ? "//" + chain_name : chain_name; + r += '/'; + if (!as_cid) { + r += res_id.name; + r += ' '; + } + r += res_id.seqid.str(as_cid); + if (as_cid && atom_name == "null") + return r; + r += '/'; + r += atom_name; + if (altloc) { + r += as_cid ? ':' : '.'; + r += altloc; + } + return r; +} + +struct AtomAddress { + std::string chain_name; + ResidueId res_id; + std::string atom_name; + char altloc = '\0'; + + AtomAddress() = default; + AtomAddress(const std::string& ch, const ResidueId& resid, + const std::string& atom, char alt='\0') + : chain_name(ch), res_id(resid), atom_name(atom), altloc(alt) {} + AtomAddress(const std::string& ch, const SeqId& seqid, const std::string& res, + const std::string& atom, char alt='\0') + : chain_name(ch), res_id({seqid, "", res}), atom_name(atom), altloc(alt) {} + bool operator==(const AtomAddress& o) const { + return chain_name == o.chain_name && res_id.matches(o.res_id) && + atom_name == o.atom_name && altloc == o.altloc; + } + + std::string str() const { + return atom_str(chain_name, res_id, atom_name, altloc); + } +}; + +} // namespace gemmi + +namespace std { +template <> struct hash { + size_t operator()(const gemmi::ResidueId& r) const { + size_t seqid_hash = (*r.seqid.num << 7) + (r.seqid.icode | 0x20); + return seqid_hash ^ hash()(r.segment) ^ hash()(r.name); + } +}; +} // namespace std + +#endif diff --git a/gemmi_gph/gemmi/seqtools.hpp b/gemmi_gph/gemmi/seqtools.hpp new file mode 100644 index 00000000..4117a1fa --- /dev/null +++ b/gemmi_gph/gemmi/seqtools.hpp @@ -0,0 +1,68 @@ +// Copyright Global Phasing Ltd. +// +// Functions for working with sequences (other than alignment). + +#ifndef GEMMI_SEQTOOLS_HPP_ +#define GEMMI_SEQTOOLS_HPP_ + +#include "resinfo.hpp" // for find_tabulated_residue +#include "metadata.hpp" // for Entity::first_mon, PolymerType + +namespace gemmi { + +constexpr double h2o_weight() { return 2 * 1.00794 + 15.9994; } + +inline double calculate_sequence_weight(const std::vector& seq, + double unknown=100.) { + double weight = 0.; + for (const std::string& item : seq) { + size_t idx = find_tabulated_residue_idx(Entity::first_mon(item)); + if (idx == unknown_tabulated_residue_idx()) + weight += unknown; + else + weight += get_residue_info(idx).weight; + //ResidueInfo res_info = find_tabulated_residue(Entity::first_mon(item)); + //weight += res_info.found() ? res_info.weight : unknown; + } + return weight - (seq.size() - 1) * h2o_weight(); +} + +inline std::string one_letter_code(const std::vector& seq) { + std::string r; + for (const std::string& item : seq) + r += find_tabulated_residue(Entity::first_mon(item)).fasta_code(); + return r; +} + +/// Returns the format used in _entity_poly.pdbx_seq_one_letter_code, +/// in which non-standard amino acids/nucleotides are represented by CCD codes +/// in parenthesis, e.g. AA(MSE)H. +inline std::string pdbx_one_letter_code(const std::vector& seq, + ResidueKind kind) { + std::string r; + for (const std::string& item : seq) { + std::string code = Entity::first_mon(item); + const ResidueInfo ri = find_tabulated_residue(code); + if (ri.is_standard() && ri.kind == kind) + r += ri.one_letter_code; + else + cat_to(r, '(', code, ')'); + } + return r; +} + +/// used with expand_one_letter_sequence() +inline ResidueKind sequence_kind(PolymerType ptype) { + if (is_polypeptide(ptype)) + return ResidueKind::AA; + if (ptype == PolymerType::Dna) + return ResidueKind::DNA; + if (ptype == PolymerType::Rna || ptype == PolymerType::DnaRnaHybrid) + return ResidueKind::RNA; + if (ptype == PolymerType::Unknown) + fail("sequence_kind(): unknown polymer type"); + return ResidueKind::AA; +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/serialize.hpp b/gemmi_gph/gemmi/serialize.hpp new file mode 100644 index 00000000..5d4deeaf --- /dev/null +++ b/gemmi_gph/gemmi/serialize.hpp @@ -0,0 +1,190 @@ +// Copyright Global Phasing Ltd. +// +// Binary serialization for Structure (as well as Model, UnitCell, etc). +// +// Based on zpp::serializer, include third_party/serializer.h first. + +#ifndef GEMMI_SERIALIZE_HPP_ +#define GEMMI_SERIALIZE_HPP_ + +#include "model.hpp" +#include "cifdoc.hpp" + +#define SERIALIZE(Struct, ...) \ +template \ +void serialize(Archive& archive, Struct& o) { archive(__VA_ARGS__); } \ +template \ +void serialize(Archive& archive, const Struct& o) { archive(__VA_ARGS__); } + +#define SERIALIZE_P(Struct, Parent, ...) \ +template \ +void serialize(Archive& archive, Struct& o) \ + { archive(static_cast(o), __VA_ARGS__); } \ +template \ +void serialize(Archive& archive, const Struct& o) \ + { archive(static_cast(o), __VA_ARGS__); } + +#define SERIALIZE_T1(Struct, Typename, ...) \ +template \ +void serialize(Archive& archive, Struct& o) { archive(__VA_ARGS__); } \ +template \ +void serialize(Archive& archive, const Struct& o) { archive(__VA_ARGS__); } + +namespace gemmi { + +SERIALIZE_T1(OptionalInt, int, o.value) + +//SERIALIZE(Element, o.elem) is ambiguous because of El->Element conversion +template +void serialize(Archive& archive, Element& o) { archive((unsigned char&)o.elem); } +template +void serialize(Archive& archive, const Element& o) { archive((unsigned char)o.elem); } + +SERIALIZE_T1(Vec3_, typename, o.x, o.y, o.z) + +SERIALIZE_T1(SMat33, typename, o.u11, o.u22, o.u33, o.u12, o.u13, o.u23) + +SERIALIZE(Mat33, o.a) + +SERIALIZE(Transform, o.mat, o.vec) + +SERIALIZE(NcsOp, o.id, o.given, o.tr) + +SERIALIZE(UnitCellParameters, o.a, o.b, o.c, o.alpha, o.beta, o.gamma) + +SERIALIZE_P(UnitCell, UnitCellParameters, + o.orth, o.frac, o.volume, + o.ar, o.br, o.cr, o.cos_alphar, o.cos_betar, o.cos_gammar, + o.explicit_matrices, o.cs_count, o.images) + +SERIALIZE(SeqId, o.num, o.icode) + +SERIALIZE(AtomAddress, o.chain_name, o.res_id, o.atom_name, o.altloc) + +SERIALIZE(Metadata, o.authors, o.experiments, o.crystals, o.refinement, + o.software, o.solved_by, o.starting_model, o.remark_300_detail) + +SERIALIZE(SoftwareItem, o.name, o.version, o.date, o.description, + o.contact_author, o.contact_author_email, o.classification) + +SERIALIZE(ReflectionsInfo, o.resolution_high, o.resolution_low, o.completeness, + o.redundancy, o.r_merge, o.r_sym, o.mean_I_over_sigma) + +SERIALIZE(ExperimentInfo, o.method, o.number_of_crystals, o.unique_reflections, + o.reflections, o.b_wilson, o.shells, o.diffraction_ids) + +SERIALIZE(DiffractionInfo, o.id, o.temperature, o.source, o.source_type, + o.synchrotron, o.beamline, o.wavelengths, o.scattering_type, + o.mono_or_laue, o.monochromator, o.collection_date, o.optics, + o.detector, o.detector_make) + +SERIALIZE(CrystalInfo, o.id, o.description, o.ph, o.ph_range, o.diffractions) + +SERIALIZE(TlsGroup, o.num_id, o.id, o.selections, o.origin, o.T, o.L, o.S) + +SERIALIZE(TlsGroup::Selection, o.chain, o.res_begin, o.res_end, o.details) + +SERIALIZE(BasicRefinementInfo, o.resolution_high, o.resolution_low, + o.completeness, o.reflection_count, o.work_set_count, o.rfree_set_count, + o.r_all, o.r_work, o.r_free, + o.cc_fo_fc_work, o.cc_fo_fc_free, o.fsc_work, o.fsc_free, + o.cc_intensity_work, o.cc_intensity_free) + +SERIALIZE_P(RefinementInfo, BasicRefinementInfo, o.id, + o.cross_validation_method, o.rfree_selection_method, + o.bin_count, o.bins, o.mean_b, o.aniso_b, + o.luzzati_error, o.dpi_blow_r, o.dpi_blow_rfree, + o.dpi_cruickshank_r, o.dpi_cruickshank_rfree, + o.restr_stats, o.tls_groups, o.remarks) + +SERIALIZE(RefinementInfo::Restr, o.name, o.count, o.weight, o.function, o.dev_ideal) + +SERIALIZE(Entity, o.name, o.subchains, o.entity_type, o.polymer_type, + o.reflects_microhetero, o.dbrefs, o.sifts_unp_acc, o.full_sequence) + +SERIALIZE(Entity::DbRef, o.db_name, o.accession_code, o.id_code, + o.isoform, o.seq_begin, o.seq_end, o.db_begin, o.db_end, + o.label_seq_begin, o.label_seq_end) + +SERIALIZE(SiftsUnpResidue, o.res, o.acc_index, o.num) + +SERIALIZE(Connection, o.name, o.link_id, o.type, o.asu, + o.partner1, o.partner2, o.reported_distance, o.reported_sym) + +SERIALIZE(CisPep, o.partner_c, o.partner_n, o.model_num, o.only_altloc, o.reported_angle) + +SERIALIZE(ModRes, o.chain_name, o.res_id, o.parent_comp_id, o.mod_id, o.details) + +SERIALIZE(Helix, o.start, o.end, o.pdb_helix_class, o.length) + +SERIALIZE(Sheet, o.name, o.strands) + +SERIALIZE(Sheet::Strand, o.start, o.end, o.hbond_atom2, o.hbond_atom1, o.sense, o.name) + +SERIALIZE(Assembly, o.name, o.author_determined, o.software_determined, + o.special_kind, o.oligomeric_count, o.oligomeric_details, + o.software_name, o.absa, o.ssa, o.more, o.generators) + +SERIALIZE(Assembly::Operator, o.name, o.type, o.transform) + +SERIALIZE(Assembly::Gen, o.chains, o.subchains, o.operators) + +SERIALIZE(ResidueId, o.seqid, o.segment, o.name) + +SERIALIZE(Atom, o.name, o.altloc, o.charge, o.element, o.calc_flag, + o.flag, o.tls_group_id, o.serial, o.fraction, o.pos, + o.occ, o.b_iso, o.aniso) + +SERIALIZE_P(Residue, ResidueId, + o.subchain, o.entity_id, o.label_seq, o.entity_type, + o.het_flag, o.flag, o.sifts_unp, o.group_idx, o.atoms) + +SERIALIZE(Chain, o.name, o.residues) + +SERIALIZE(Model, o.num, o.chains) + +SERIALIZE(Structure, o.name, o.cell, o.spacegroup_hm, o.models, + o.ncs, o.entities, o.connections, o.cispeps, o.mod_residues, + o.helices, o.sheets, o.assemblies, o.conect_map, o.meta, + o.input_format, o.has_d_fraction, o.non_ascii_line, o.ter_status, + o.has_origx, o.origx, o.info, o.shortened_ccd_codes, + o.raw_remarks, o.resolution) + + +namespace cif { + +SERIALIZE(Loop, o.tags, o.values) +SERIALIZE(Block, o.name, o.items) +SERIALIZE(Document, o.source, o.blocks) + +template +void serialize(Archive& archive, Item& o) { + archive(o.type, o.line_number); + switch (o.type) { + case ItemType::Pair: + case ItemType::Comment: new(&o.pair) cif::Pair; archive(o.pair); break; + case ItemType::Loop: new(&o.loop) cif::Loop; archive(o.loop); break; + case ItemType::Frame: new(&o.frame) cif::Block; archive(o.frame); break; + case ItemType::Erased: break; + } +} +template +void serialize(Archive& archive, const Item& o) { + archive(o.type, o.line_number); + switch (o.type) { + case ItemType::Pair: + case ItemType::Comment: archive(o.pair); break; + case ItemType::Loop: archive(o.loop); break; + case ItemType::Frame: archive(o.frame); break; + case ItemType::Erased: break; + } +} + +} // namespace cif +} // namespace gemmi + +#undef SERIALIZE +#undef SERIALIZE_P +#undef SERIALIZE_T1 + +#endif diff --git a/gemmi_gph/gemmi/sfcalc.hpp b/gemmi_gph/gemmi/sfcalc.hpp new file mode 100644 index 00000000..bab426ca --- /dev/null +++ b/gemmi_gph/gemmi/sfcalc.hpp @@ -0,0 +1,146 @@ +// Copyright 2019 Global Phasing Ltd. +// +// Direct calculation of structure factors. +// +// It does not use optimizations described in the literature, +// cf. Bourhis et al (2014) https://doi.org/10.1107/S2053273314022207, +// because direct calculations are not used in MX if performance is important. +// For FFT-based calculations see dencalc.hpp + fourier.hpp. + +#ifndef GEMMI_SFCALC_HPP_ +#define GEMMI_SFCALC_HPP_ + +#include +#include "addends.hpp" // for Addends +#include "model.hpp" // for Structure, ... +#include "small.hpp" // for SmallStructure + +namespace gemmi { + +// calculate part of the structure factor: exp(2 pi i r * s) +inline std::complex calculate_sf_part(const Fractional& fpos, + const Miller& hkl) { + double arg = 2 * pi() * (hkl[0]*fpos.x + hkl[1]*fpos.y + hkl[2]*fpos.z); + return std::complex{std::cos(arg), std::sin(arg)}; +} + +template +class StructureFactorCalculator { +public: + using coef_type = typename Table::Coef::coef_type; + + StructureFactorCalculator(const UnitCell& cell) : cell_(cell) {} + + void set_stol2_and_scattering_factors(const Miller& hkl) { + stol2_ = (coef_type) cell_.calculate_stol_sq(hkl); + scattering_factors_.clear(); + scattering_factors_.resize(addends.size(), 0.); + } + + double get_scattering_factor(Element element, signed char charge) { + double& sfactor = scattering_factors_[element.ordinal()]; + if (sfactor == 0.) { + if (!Table::has(element.elem)) + fail("Missing scattering factor for ", element.name()); + sfactor = Table::get(element.elem, charge).calculate_sf(stol2_) + addends.get(element); + } + return sfactor; + } + + // Calculation of Debye-Waller factor with isotropic ADPs + double dwf_iso(const SmallStructure::Site& site) const { + return std::exp(-u_to_b() * stol2_ * site.u_iso); + } + double dwf_iso(const Atom& atom) const { + return std::exp(-stol2_ * atom.b_iso); + } + + // Calculation of Debye-Waller factor exp(-2 pi^2 s.U.s) + // cf. B. Rupp's book, p. 641 or RWGK & Adams 2002, J. Appl. Cryst. 35, 477 + // Small molecule and macromolecular anisotropic U's are defined differently, + // so we have two functions. + double dwf_aniso(const SmallStructure::Site& site, const Vec3& hkl) const { + Vec3 arh(cell_.ar * hkl.x, cell_.br * hkl.y, cell_.cr * hkl.z); + return std::exp(-2 * pi() * pi() * site.aniso.r_u_r(arh)); + } + double dwf_aniso(const Atom& atom, const Vec3& hkl) const { + return std::exp(-2 * pi() * pi() * + atom.aniso.transformed_by<>(cell_.frac.mat).r_u_r(hkl)); + } + + template + std::complex calculate_sf_from_atom_sf(const Fractional& fract, + const Site& site, + const Miller& hkl, + double sf) { + double oc_sf = site.occ * sf; + std::complex sum = calculate_sf_part(fract, hkl); + if (!site.aniso.nonzero()) { + for (const FTransform& image : cell_.images) + sum += calculate_sf_part(image.apply(fract), hkl); + return oc_sf * dwf_iso(site) * sum; + } + Vec3 vhkl(hkl[0], hkl[1], hkl[2]); + sum *= dwf_aniso(site, vhkl); + for (const FTransform& image : cell_.images) + sum += calculate_sf_part(image.apply(fract), hkl) * + dwf_aniso(site, image.mat.left_multiply(vhkl)); + return oc_sf * sum; + } + + template + std::complex calculate_sf_from_atom(const Fractional& fract, + const Site& site, + const Miller& hkl) { + double atom_sf = get_scattering_factor(site.element, site.charge); + return calculate_sf_from_atom_sf(fract, site, hkl, atom_sf); + } + + std::complex calculate_sf_from_model(const Model& model, const Miller& hkl) { + std::complex sf = 0.; + set_stol2_and_scattering_factors(hkl); + for (const Chain& chain : model.chains) + for (const Residue& res : chain.residues) + for (const Atom& site : res.atoms) + sf += calculate_sf_from_atom(cell_.fractionalize(site.pos), site, hkl); + return sf; + } + + // Z part of Mott-Bethe formula (when need to use different model) + std::complex calculate_mb_z(const Model& model, const Miller& hkl, bool only_h) { + std::complex sf = 0.; + stol2_ = (coef_type) cell_.calculate_stol_sq(hkl); + for (const Chain& chain : model.chains) + for (const Residue& res : chain.residues) + for (const Atom& site : res.atoms) + if (!only_h || site.element.is_hydrogen()) + sf += calculate_sf_from_atom_sf(cell_.fractionalize(site.pos), site, hkl, + -site.element.atomic_number()); + return sf; + } + + double mott_bethe_factor() const { + return -mott_bethe_const() / 4 / stol2_; + } + + // The occupancy is assumed to take into account symmetry, + // i.e. to be fractional if the atom is on special position. + std::complex calculate_sf_from_small_structure(const SmallStructure& small_st, + const Miller& hkl) { + std::complex sf = 0.; + set_stol2_and_scattering_factors(hkl); + for (const SmallStructure::Site& site : small_st.sites) + sf += calculate_sf_from_atom(site.fract, site, hkl); + return sf; + } + +private: + const UnitCell& cell_; + coef_type stol2_; + std::vector scattering_factors_; +public: + Addends addends; // usually f' for X-rays +}; + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/small.hpp b/gemmi_gph/gemmi/small.hpp new file mode 100644 index 00000000..336b329b --- /dev/null +++ b/gemmi_gph/gemmi/small.hpp @@ -0,0 +1,248 @@ +// Copyright 2018 Global Phasing Ltd. +// +// Representation of a small molecule or inorganic crystal. +// Flat list of atom sites. Minimal functionality. + +#ifndef GEMMI_SMALL_HPP_ +#define GEMMI_SMALL_HPP_ + +#include // for isalpha +#include // for any_of +#include +#include +#include +#include "elem.hpp" // Element +#include "math.hpp" // SMat33 +#include "symmetry.hpp" // find_spacegroup_by_name +#include "unitcell.hpp" // UnitCell, Fractional +#include "util.hpp" // vector_remove_if + +namespace gemmi { + +inline bool is_complete(const GroupOps& gops) { + for (Op op1 : gops.sym_ops) + for (Op op2 : gops.sym_ops) + if (gops.find_by_rotation((op1 * op2).rot) == nullptr) + return false; + return true; +} + +inline std::vector triplets_to_ops(const std::vector& symops) { + std::vector ops; + ops.reserve(symops.size()); + for (const std::string& xyz : symops) + ops.push_back(parse_triplet(xyz)); + return ops; +} + +struct SmallStructure { + struct Site { + std::string label; + std::string type_symbol; + Fractional fract; + double occ = 1.0; + double u_iso = 0.; + SMat33 aniso = {0, 0, 0, 0, 0, 0}; + int disorder_group = 0; + Element element = El::X; + signed char charge = 0; // [-8, +8] + + Position orth(const gemmi::UnitCell& cell_) const { + return cell_.orthogonalize(fract); + } + + std::string element_and_charge_symbol() const { + std::string s = element.name(); + if (charge != 0) { + s += std::to_string(std::abs(charge)); + s += charge > 0 ? '+' : '-'; + } + return s; + } + }; + + struct AtomType { + std::string symbol; + Element element = El::X; + signed char charge = 0; // [-8, +8] + double dispersion_real; + double dispersion_imag; + }; + + std::string name; + UnitCell cell; + const SpaceGroup* spacegroup = nullptr; + std::string spacegroup_hm; + std::string spacegroup_hall; + int spacegroup_number = 0; + std::vector symops; + std::vector sites; + std::vector atom_types; + double wavelength = 0.; // the first wavelength if multiple + + std::vector get_all_unit_cell_sites() const; + + void determine_and_set_spacegroup(const char* order) { + spacegroup = nullptr; + if (order) + for (const char* c = order; *c != '\0' && spacegroup == nullptr; ++c) { + try { + GroupOps gops; + spacegroup = determine_spacegroup_from(*c, gops); + if (!spacegroup && *(c+1) == '.') { + // If symops don't correspond to tabulated settings, + // we can't set spacegroup, but we can set UnitCell::images. + if (gops.order() == (int) symops.size() && is_complete(gops)) { + cell.set_cell_images_from_groupops(gops); + return; + } + ++c; + } + } catch (std::exception&) {} + } + setup_cell_images(); + } + + const SpaceGroup* determine_spacegroup_from(char c, GroupOps& gops) const { + switch (lower(c)) { + case 's': + if (symops.empty()) + return nullptr; + gops = split_centering_vectors(triplets_to_ops(symops)); + return find_spacegroup_by_ops(gops); + case 'h': + if (spacegroup_hall.empty()) + return nullptr; + return find_spacegroup_by_ops(symops_from_hall(spacegroup_hall.c_str())); + case '1': + case '2': { + if (spacegroup_hm.empty()) + return nullptr; + char prefer[] = {c, '\0'}; + return find_spacegroup_by_name(spacegroup_hm, cell.alpha, cell.gamma, prefer); + } + case 'n': + if (spacegroup_number == 0) + return nullptr; + return find_spacegroup_by_number(spacegroup_number); + default: + throw std::invalid_argument("determine_and_set_spacegroup(): wrong character in 'order'"); + } + } + + std::string check_spacegroup() const { + std::string err; + if (!symops.empty()) + try { + std::vector ops = triplets_to_ops(symops); + for (Op& op : ops) + op.wrap(); + std::sort(ops.begin(), ops.end()); + GroupOps gops = split_centering_vectors(ops); + if (!is_complete(gops)) + cat_to(err, "symops list is incomplete or incorrect\n"); + else if (gops.all_ops_sorted() != ops) + cat_to(err, "symops list is incorrect or incomplete or redundant\n"); + const SpaceGroup* sg = find_spacegroup_by_ops(gops); + if (!sg) + cat_to(err, "space group from symops not found in the table\n"); + else if (sg != spacegroup) + cat_to(err, "space group from symops differs: ", sg->xhm(), '\n'); + } catch (std::exception& e) { + cat_to(err, "error while processing symops: ", e.what(), '\n'); + } + if (!spacegroup_hall.empty()) + try { + const SpaceGroup* sg = find_spacegroup_by_ops(symops_from_hall(spacegroup_hall.c_str())); + if (!sg) + cat_to(err, "space group from Hall symbol (", spacegroup_hall, + ") not found in the table\n"); + else if (spacegroup != sg) + cat_to(err, "space group from Hall symbol (", spacegroup_hall, + ") differs: ", sg->xhm(), '\n'); + } catch (std::exception& e) { + cat_to(err, "error while processing Hall symbol: ", e.what(), '\n'); + } + if (!spacegroup_hm.empty()) { + const SpaceGroup* sg = find_spacegroup_by_name(spacegroup_hm, cell.alpha, cell.gamma, "2"); + if (!sg) + cat_to(err, "H-M symbol (", spacegroup_hm, ") not found in the table\n"); + else if (!spacegroup || strcmp(spacegroup->hm, sg->hm) != 0) + cat_to(err, "space group from H-M symbol (", spacegroup_hm, + ") differs: ", sg->hm, '\n'); + } + if (spacegroup_number != 0 && spacegroup && spacegroup->number != spacegroup_number) + cat_to(err, "space group number (", spacegroup_number, ") differs\n"); + return err; + } + + const AtomType* get_atom_type(const std::string& symbol) const { + for (const AtomType& at : atom_types) + if (at.symbol == symbol) + return &at; + return nullptr; + } + + // similar to Model::present_elements() from model.hpp + std::bitset<(size_t)El::END> present_elements() const { + std::bitset<(size_t)El::END> table; + for (const Site& atom : sites) + table.set((size_t)atom.element.elem); + return table; + } + + void remove_hydrogens() { + vector_remove_if(sites, [](const Site& a) { return a.element.is_hydrogen(); }); + } + + // pre: atoms on special positions have "chemical" occupancy (i.e. not divided + // by n for n-fold symmetry) + void change_occupancies_to_crystallographic(double max_dist=0.4) { + for (Site& site : sites) { + int n_mates = cell.is_special_position(site.fract, max_dist); + if (n_mates != 0) + site.occ /= (n_mates + 1); + } + } + + void setup_cell_images() { + cell.set_cell_images_from_spacegroup(spacegroup); + } +}; + +template +inline void split_element_and_charge(const std::string& label, T* dest) { + int len = label.size() > 1 && std::isalpha(label[1]) ? 2 : 1; + dest->element = len == 1 ? impl::find_single_letter_element(label[0] & ~0x20) + : find_element(label.c_str()); + if (dest->element != El::X && (label.back() == '+' || label.back() == '-')) { + int sign = label.back() == '+' ? 1 : -1; + if (label.size() - len == 1) + dest->charge = sign; + else if (label.size() - len == 2 && label[len] >= '0' && label[len] <= '9') + dest->charge = sign * (label[len] - '0'); + } +} + +inline std::vector +SmallStructure::get_all_unit_cell_sites() const { + const double SPECIAL_POS_TOL = 0.4; + std::vector all; + for (const Site& site : sites) { + size_t start = all.size(); + all.push_back(site); + for (const FTransform& image : cell.images) { + Fractional fpos = image.apply(site.fract); + if (std::any_of(all.begin() + start, all.end(), [&](const Site& other) { + return cell.distance_sq(fpos, other.fract) < sq(SPECIAL_POS_TOL); + })) + continue; + all.push_back(site); + all.back().fract = fpos; + } + } + return all; +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/smcif.hpp b/gemmi_gph/gemmi/smcif.hpp new file mode 100644 index 00000000..3eee365c --- /dev/null +++ b/gemmi_gph/gemmi/smcif.hpp @@ -0,0 +1,190 @@ +// Copyright 2018 Global Phasing Ltd. +// +// Read small molecule CIF file into SmallStructure (from small.hpp). + +#ifndef GEMMI_SMCIF_HPP_ +#define GEMMI_SMCIF_HPP_ + +#include "small.hpp" // for SmallStructure +#include "cifdoc.hpp" +#include "numb.hpp" // for as_number +#include "sprintf.hpp" // for to_str + +namespace gemmi { + +inline +SmallStructure make_small_structure_from_block(const cif::Block& block_) { + using cif::as_number; + cif::Block& block = const_cast(block_); + SmallStructure st; + st.name = block.name; + + // unit cell and symmetry + cif::Table cell = block.find("_cell_", + {"length_a", "length_b", "length_c", + "angle_alpha", "angle_beta", "angle_gamma"}); + if (cell.ok()) { + auto c = cell.one(); + if (!cif::is_null(c[0]) && !cif::is_null(c[1]) && !cif::is_null(c[2])) + st.cell.set(as_number(c[0]), as_number(c[1]), as_number(c[2]), + as_number(c[3]), as_number(c[4]), as_number(c[5])); + } + for (const char* tag : {"_space_group_name_H-M_alt", + "_symmetry_space_group_name_H-M"}) + if (const std::string* val = block.find_value(tag)) { + st.spacegroup_hm = cif::as_string(*val); + break; + } + for (const char* tag : {"_space_group_symop_operation_xyz", + "_symmetry_equiv_pos_as_xyz"}) { + if (const cif::Column col = block.find_values(tag)) { + st.symops.reserve(col.length()); + for (const std::string& value : col) + st.symops.push_back(cif::as_string(value)); + break; + } + } + for (const char* tag : {"_space_group_name_Hall", "_symmetry_space_group_name_Hall"}) + if (const std::string* val = block.find_value(tag)) + st.spacegroup_hall = cif::as_string(*val); + for (const char* tag : {"_space_group_IT_number", "_symmetry_Int_Tables_number"}) + if (const std::string* val = block.find_value(tag)) { + st.spacegroup_number = cif::as_int(*val, 0); + break; + } + st.determine_and_set_spacegroup("S.H2"); + + enum { kLabel, kSymbol, kX, kY, kZ, kUiso, kBiso, kOcc, kDisorderGroup }; + cif::Table atom_table = block.find("_atom_site_", + {"label", + "?type_symbol", + "?fract_x", + "?fract_y", + "?fract_z", + "?U_iso_or_equiv", + "?B_iso_or_equiv", + "?occupancy", + "?disorder_group"}); + for (auto row : atom_table) { + SmallStructure::Site site; + site.label = cif::as_string(row[kLabel]); + if (row.has(kSymbol)) + site.type_symbol = cif::as_string(row[kSymbol]); + else + site.type_symbol = site.label; + if (row.has(kX)) + site.fract.x = as_number(row[kX]); + if (row.has(kY)) + site.fract.y = as_number(row[kY]); + if (row.has(kZ)) + site.fract.z = as_number(row[kZ]); + if (row.has(kUiso)) + site.u_iso = as_number(row[kUiso], 0.0); + else if (row.has(kBiso)) + site.u_iso = as_number(row[kBiso], 0.0) / u_to_b(); + if (row.has(kOcc)) + site.occ = as_number(row[kOcc], 1.0); + if (row.has2(kDisorderGroup)) + // ignore non-integer _atom_site_disorder_group + site.disorder_group = string_to_int(row[kDisorderGroup], false); + split_element_and_charge(site.type_symbol, &site); + st.sites.push_back(site); + } + + std::vector::iterator aniso_site = st.sites.begin(); + for (auto row : block.find("_atom_site_aniso_", + {"label", "U_11", "U_22", "U_33", + "U_12", "U_13", "U_23"})) { + std::string aniso_label = row.str(0); + if (aniso_site == st.sites.end() || aniso_site->label != aniso_label) { + aniso_site = std::find_if(st.sites.begin(), st.sites.end(), + [&](SmallStructure::Site& x) { return x.label == aniso_label; }); + if (aniso_site == st.sites.end()) + continue; + } + aniso_site->aniso.u11 = as_number(row[1], 0.0); + aniso_site->aniso.u22 = as_number(row[2], 0.0); + aniso_site->aniso.u33 = as_number(row[3], 0.0); + aniso_site->aniso.u12 = as_number(row[4], 0.0); + aniso_site->aniso.u13 = as_number(row[5], 0.0); + aniso_site->aniso.u23 = as_number(row[6], 0.0); + ++aniso_site; + } + + for (auto row : block.find("_atom_type_", + {"symbol", + "scat_dispersion_real", + "scat_dispersion_imag"})) { + SmallStructure::AtomType atom_type; + atom_type.symbol = row.str(0); + atom_type.dispersion_real = cif::as_number(row[1]); + atom_type.dispersion_imag = cif::as_number(row[2]); + split_element_and_charge(atom_type.symbol, &atom_type); + st.atom_types.push_back(atom_type); + } + if (cif::Column w_col = block.find_values("_diffrn_radiation_wavelength")) + st.wavelength = cif::as_number(w_col.at(0)); + return st; +} + +inline cif::Block make_cif_block_from_small_structure(const SmallStructure& st) { + cif::Block block; + block.name = st.name; + if (st.cell.is_crystal()) { + block.set_pair("_cell_length_a", to_str(st.cell.a)); + block.set_pair("_cell_length_b", to_str(st.cell.b)); + block.set_pair("_cell_length_c", to_str(st.cell.c)); + block.set_pair("_cell_angle_alpha", to_str(st.cell.alpha)); + block.set_pair("_cell_angle_beta", to_str(st.cell.beta)); + block.set_pair("_cell_angle_gamma", to_str(st.cell.gamma)); + } + if (!st.spacegroup_hm.empty()) + block.set_pair("_symmetry_space_group_name_H-M", cif::quote(st.spacegroup_hm)); + if (st.wavelength != 0) + block.set_pair("_diffrn_radiation_wavelength", to_str(st.wavelength)); + + cif::Loop& atom_loop = block.init_loop("_atom_site_", {"label", + "type_symbol", + "fract_x", + "fract_y", + "fract_z", + "U_iso_or_equiv", + "occupancy", + "disorder_group"}); + for (const SmallStructure::Site& site: st.sites) { + atom_loop.add_row({ + cif::quote(site.label), + site.type_symbol.empty() ? site.element_and_charge_symbol() + : cif::quote(site.type_symbol), + to_str(site.fract.x), + to_str(site.fract.y), + to_str(site.fract.z), + to_str(site.u_iso), + to_str(site.occ), + site.disorder_group == 0 ? "." : std::to_string(site.disorder_group) + }); + } + + bool has_aniso = false; + for (const SmallStructure::Site& site: st.sites) + if (site.aniso.nonzero()) { + has_aniso = true; + break; + } + if (has_aniso) { + cif::Loop& aniso_loop = block.init_loop("_atom_site_aniso_", { + "label", "U_11", "U_22", "U_33", "U_12", "U_13", "U_23"}); + for (const SmallStructure::Site& site: st.sites) + if (site.aniso.nonzero()) + aniso_loop.add_row({ + cif::quote(site.label), + to_str(site.aniso.u11), to_str(site.aniso.u22), to_str(site.aniso.u33), + to_str(site.aniso.u12), to_str(site.aniso.u13), to_str(site.aniso.u23) + }); + } + + return block; +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/solmask.hpp b/gemmi_gph/gemmi/solmask.hpp new file mode 100644 index 00000000..7d32929f --- /dev/null +++ b/gemmi_gph/gemmi/solmask.hpp @@ -0,0 +1,538 @@ +// Copyright 2021 Global Phasing Ltd. +// +// Flat bulk solvent mask. With helper tools that modify data on grid. + +#ifndef GEMMI_SOLMASK_HPP_ +#define GEMMI_SOLMASK_HPP_ + +#include "grid.hpp" // for Grid +#include "floodfill.hpp" // for FloodFill +#include "model.hpp" // for Model, Atom, ... + +namespace gemmi { + +enum class AtomicRadiiSet { VanDerWaals, Cctbx, Refmac, Constant }; + +// data from cctbx/eltbx/van_der_waals_radii.py used to generate identical mask +inline float cctbx_vdw_radius(El el) { + static constexpr float radii[] = { + /*X*/ 1.00f, + /*H*/ 1.20f, /*He*/ 1.40f, + /*Li*/ 1.82f, /*Be*/ 0.63f, /*B*/ 1.75f, /*C*/ 1.775f, /*N*/ 1.50f, + /*O*/ 1.45f, /*F*/ 1.47f, /*Ne*/ 1.54f, + /*Na*/ 2.27f, /*Mg*/ 1.73f, /*Al*/ 1.50f, /*Si*/ 2.10f, /*P*/ 1.90f, + /*S*/ 1.80f, /*Cl*/ 1.75f, /*Ar*/ 1.88f, + /*K*/ 2.75f, /*Ca*/ 1.95f, /*Sc*/ 1.32f, /*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*/ 1.48f, /*As*/ 0.83f, + /*Se*/ 1.90f, /*Br*/ 1.85f, /*Kr*/ 2.02f, + /*Rb*/ 2.65f, /*Sr*/ 2.02f, /*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*/ 1.12f, + /*Te*/ 1.26f, /*I*/ 1.98f, /*Xe*/ 2.16f, + /*Cs*/ 3.01f, /*Ba*/ 2.41f, /*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.72f, /*Au*/ 1.66f, + /*Hg*/ 1.55f, /*Tl*/ 1.96f, /*Pb*/ 2.02f, /*Bi*/ 1.73f, /*Po*/ 1.21f, + /*At*/ 1.12f, /*Rn*/ 2.30f, + /*Fr*/ 3.24f, /*Ra*/ 2.57f, /*Ac*/ 2.12f, /*Th*/ 1.84f, /*Pa*/ 1.60f, + /*U*/ 1.75f, /*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(El::D)], 1.2f), "Hmm"); + static_assert(sizeof(radii) / sizeof(radii[0]) == + static_cast(El::END) + 1, "Hmm"); + return radii[static_cast(el)]; +} + +// Data from Refmac's ener_lib.cif: ionic radius - 0.2A or vdW radius + 0.2A. +// For full compatibility use r_probe=1.0A and r_shrink=0.8A. +inline float refmac_radius_for_bulk_solvent(El el) { +#if 0 + static constexpr float radii[] = { + /*X*/ 1.00f, + /*H*/ 1.40f, /*He*/ 1.60f, + /*Li*/ 0.53f, /*Be*/ 0.21f, /*B*/ 0.05f, /*C*/ 1.90f, /*N*/ 1.12f, + /*O*/ 1.08f, /*F*/ 0.99f, /*Ne*/ 0.92f, + /*Na*/ 0.93f, /*Mg*/ 0.51f, /*Al*/ 0.33f, /*Si*/ 0.20f, /*P*/ 0.39f, + /*S*/ 0.20f, /*Cl*/ 1.47f, /*Ar*/ 1.34f, + /*K*/ 1.31f, /*Ca*/ 0.94f, /*Sc*/ 0.69f, /*Ti*/ 0.36f, /*V*/ 0.48f, + /*Cr*/ 0.33f, /*Mn*/ 0.26f, /*Fe*/ 0.48f, /*Co*/ 0.34f, /*Ni*/ 0.43f, + /*Cu*/ 0.51f, /*Zn*/ 0.54f, /*Ga*/ 0.41f, /*Ge*/ 0.20f, /*As*/ 0.28f, + /*Se*/ 0.22f, /*Br*/ 0.53f, /*Kr*/ 1.49f, + /*Rb*/ 1.28f, /*Sr*/ 1.12f, /*Y*/ 0.84f, /*Zr*/ 0.53f, /*Nb*/ 0.42f, + /*Mo*/ 0.35f, /*Tc*/ 0.31f, /*Ru*/ 0.32f, /*Rh*/ 0.49f, /*Pd*/ 0.58f, + /*Ag*/ 0.61f, /*Cd*/ 0.72f, /*In*/ 0.56f, /*Sn*/ 0.49f, /*Sb*/ 0.70f, + /*Te*/ 0.37f, /*I*/ 0.36f, /*Xe*/ 1.70f, + /*Cs*/ 1.61f, /*Ba*/ 1.29f, /*La*/ 0.97f, /*Ce*/ 0.81f, /*Pr*/ 0.79f, + /*Nd*/ 0.92f, /*Pm*/ 0.91f, /*Sm*/ 0.90f, /*Eu*/ 0.89f, /*Gd*/ 0.88f, + /*Tb*/ 0.70f, /*Dy*/ 0.85f, /*Ho*/ 0.84f, /*Er*/ 0.83f, /*Tm*/ 0.82f, + /*Yb*/ 0.81f, /*Lu*/ 0.80f, /*Hf*/ 0.52f, /*Ta*/ 0.58f, /*W*/ 0.36f, + /*Re*/ 0.32f, /*Os*/ 0.33f, /*Ir*/ 0.51f, /*Pt*/ 0.51f, /*Au*/ 0.51f, + /*Hg*/ 0.90f, /*Tl*/ 0.69f, /*Pb*/ 0.59f, /*Bi*/ 0.70f, /*Po*/ 0.61f, + /*At*/ 0.56f, /*Rn*/ 1.80f, + /*Fr*/ 1.74f, /*Ra*/ 1.42f, /*Ac*/ 1.06f, /*Th*/ 0.88f, /*Pa*/ 0.72f, + /*U*/ 0.46f, /*Np*/ 0.65f, /*Pu*/ 0.65f, /*Am*/ 0.79f, /*Cm*/ 0.79f, + /*Bk*/ 0.77f, /*Cf*/ 0.76f, /*Es*/ 1.00f, /*Fm*/ 1.00f, /*Md*/ 1.00f, + /*No*/ 1.00f, /*Lr*/ 1.00f, /*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.40f, /*END*/0.f + }; + static_assert(ce_almost_eq(radii[static_cast(El::D)], 1.40f), "Hmm"); + return radii[static_cast(el)]; +#else + // temporary solution used in Refmac + switch (el) { + case El::H: return 1.4f; + case El::D: return 1.4f; + case El::O: return 1.08f; + case El::C: return 2.0f; + case El::N: return 1.12f; + default: return 1.6f; + }; +#endif +} + +// mask utilities + +template +void mask_points_in_radius(Grid& mask, const Model& model, + AtomicRadiiSet atomic_radii_set, + double r_probe, T value, + bool ignore_hydrogen, + bool ignore_zero_occupancy_atoms) { + for (const Chain& chain : model.chains) + for (const Residue& res : chain.residues) + for (const Atom& atom : res.atoms) { + if ((ignore_hydrogen && atom.is_hydrogen()) || + (ignore_zero_occupancy_atoms && atom.occ <= 0)) + continue; + El elem = atom.element.elem; + double r = r_probe; + switch (atomic_radii_set) { + case AtomicRadiiSet::VanDerWaals: r += vdw_radius(elem); break; + case AtomicRadiiSet::Cctbx: r += cctbx_vdw_radius(elem); break; + case AtomicRadiiSet::Refmac: r += refmac_radius_for_bulk_solvent(elem); break; + case AtomicRadiiSet::Constant: /* r is included in r_probe */ break; + } + mask.set_points_around(atom.pos, r, value); + } +} + +// deprecated +template +void mask_points_in_constant_radius(Grid& mask, const Model& model, + double radius, T value, + bool ignore_hydrogen, + bool ignore_zero_occupancy_atoms) { + mask_points_in_radius(mask, model, AtomicRadiiSet::Constant, radius, value, + ignore_hydrogen, ignore_zero_occupancy_atoms); +} + + +inline std::string distinct_altlocs(const Model& model) { + std::string altlocs; + for (const Chain& chain : model.chains) + for (const Residue& res : chain.residues) + add_distinct_altlocs(res, altlocs); + return altlocs; +} + +/// mask all grid points within a varying radius around model atoms +/// @param mask existing mask object +/// @param model model to consider +/// @param atomic_radii_set set of atomic radii +/// @param r_probe probe radius to add to atomic radius (in A) +/// @param value value to use (unless use_atom_occupancy is set to true) +/// @param ignore_hydrogen should we skip hydrogen atoms +/// @param ignore_zero_occupancy_atoms should we skip atoms with occupancy of zero +/// @param use_atom_occupancy should we use the occupancy of each atom (to build up a non-binary mask) +inline +void mask_points_using_occupancy(Grid& mask, const Model& model, + AtomicRadiiSet atomic_radii_set, double r_probe, + bool ignore_hydrogen, + bool ignore_zero_occupancy_atoms) { + + std::string altlocs = distinct_altlocs(model); + altlocs += '\0'; // no altloc + + gemmi::Grid m; + m.copy_metadata_from(mask); + for (char& altloc : altlocs) { + m.fill(0.0); + for (const Chain& chain : model.chains) { + for (const Residue& res : chain.residues) { + for (const Atom& atom : res.atoms) { + if ((ignore_hydrogen && atom.is_hydrogen()) || + (ignore_zero_occupancy_atoms && atom.occ <= 0)) + continue; + if (atom.altloc == altloc) { + El elem = atom.element.elem; + double r = r_probe; + switch (atomic_radii_set) { + case AtomicRadiiSet::VanDerWaals: r += vdw_radius(elem); break; + case AtomicRadiiSet::Cctbx: r += cctbx_vdw_radius(elem); break; + case AtomicRadiiSet::Refmac: r += refmac_radius_for_bulk_solvent(elem); break; + case AtomicRadiiSet::Constant: /* r is included in r_probe */ break; + } + Fractional fpos = m.unit_cell.fractionalize(atom.pos); + m.use_points_around(fpos, r, [&](float& ref, double) { + ref = std::min(ref, -atom.occ); + }, false); + } + } + } + } + + // reduce starting mask + for (size_t i = 0; i < m.data.size(); ++i) { + if (m.data[i] < 0.0) + mask.data[i] += m.data[i]; + } + } + + // When we reduced the solvent mask by the atom occupancy each time + // we hit a grid point, we might end up with a value below zero (due + // to overlapping atoms) and need to limit it here. + for (float& d : mask.data) + d = std::max(d, 0.f); +} + +/// All points close to a grid point which is currently set below a value are changed +/// @param mask given mask +/// @param r distance value (in A) +/// @param value consider only grid points that are currently set to this value +/// @param margin_value new value for grid points satsifying criteria +template +void set_margin_around(Grid& mask, double r, T value, T margin_value) { + int du = (int) std::floor(r / mask.spacing[0]); + int dv = (int) std::floor(r / mask.spacing[1]); + int dw = (int) std::floor(r / mask.spacing[2]); + double max_spacing2 = sq(std::max(std::max(mask.unit_cell.a / mask.nu, + mask.unit_cell.b / mask.nv), + mask.unit_cell.c / mask.nw)) + 1e-6; + if (2 * du >= mask.nu || 2 * dv >= mask.nv || 2 * dw >= mask.nw) + fail("grid operation failed: radius bigger than half the unit cell?"); + std::vector> stencil1; + std::vector> stencil2; + for (int w = -dw; w <= dw; ++w) + for (int v = -dv; v <= dv; ++v) + for (int u = -du; u <= du; ++u) { + Fractional fdelta = mask.get_fractional(u, v, w); + double r2 = mask.unit_cell.orthogonalize_difference(fdelta).length_sq(); + if (r2 <= r * r && r2 != 0.) { + std::array wvu{{w <= 0 ? w : w - mask.nw, + v <= 0 ? v : v - mask.nv, + u <= 0 ? u : u - mask.nu}}; + if (r2 < max_spacing2) + stencil1.push_back(wvu); + else + stencil2.push_back(wvu); + } + } + if (stencil2.empty()) { + // r is small; it should be faster to go through masked points and + // for each one check if any point in given radius are unmasked. + for (typename Grid::Point p : mask) + if (*p.value < value) { + for (const auto& wvu : stencil1) { + size_t idx = mask.index_near_zero(p.u + wvu[2], p.v + wvu[1], p.w + wvu[0]); + if (mask.data[idx] >= value) { + *p.value = margin_value; + break; + } + } + } + } else { + // r is large; it should be faster to go through unmasked points + // and for each one check if it has masked near neighbors (stencil1). + // These neighbors get marked as margin. If all near neighbors are + // unmasked, we can skip further neighbors (they will be checked + // from other points). + for (typename Grid::Point p : mask) { + if (*p.value >= value) { + bool found = false; + for (const auto& wvu : stencil1) { + size_t idx = mask.index_near_zero(p.u + wvu[2], p.v + wvu[1], p.w + wvu[0]); + if (mask.data[idx] < value) { + mask.data[idx] = margin_value; + found = true; + } + } + if (found) + for (const auto& wvu : stencil2) { + size_t idx = mask.index_near_zero(p.u + wvu[2], p.v + wvu[1], p.w + wvu[0]); + if (mask.data[idx] < value) + mask.data[idx] = margin_value; + } + } + } + } + //printf("stencil sizes: %zu\n", stencil1.size(), stencil2.size()); + //printf("margin: %zu\n", std::count(mask.data.begin(), mask.data.end(), margin_value)); +} + +struct SolventMasker { + AtomicRadiiSet atomic_radii_set; + bool ignore_hydrogen; + bool ignore_zero_occupancy_atoms; + bool use_atom_occupancy = false; + double rprobe; + double rshrink; + double island_min_volume; + double constant_r; + double requested_spacing = 0.; + + SolventMasker(AtomicRadiiSet choice, double constant_r_=0.) { + set_radii(choice, constant_r_); + } + + // currently this function sets also parameters other than radii + void set_radii(AtomicRadiiSet choice, double constant_r_=0.) { + atomic_radii_set = choice; + constant_r = constant_r_; + ignore_hydrogen = true; + ignore_zero_occupancy_atoms = true; + switch (choice) { + case AtomicRadiiSet::VanDerWaals: + rprobe = 1.0; + rshrink = 1.1; + island_min_volume = 0.; + break; + case AtomicRadiiSet::Cctbx: + rprobe = 1.1; + rshrink = 0.9; + island_min_volume = 0.; + break; + case AtomicRadiiSet::Refmac: + rprobe = 1.0; + rshrink = 0.8; + island_min_volume = 50; // the exact value used in Refmac is yet to be found + break; + case AtomicRadiiSet::Constant: + rprobe = 0; + rshrink = 0; + island_min_volume = 0.; + break; + } + } + + /// fill whole grid with 1 + template void clear(Grid& grid) const { grid.fill((T)1); } + + /// set grid points around atoms to 0 + template void mask_points(Grid& grid, const Model& model) const { + mask_points_in_radius(grid, model, atomic_radii_set, constant_r + rprobe, (T)0, + ignore_hydrogen, ignore_zero_occupancy_atoms); + } + + void mask_points(Grid& grid, const Model& model) const { + if (use_atom_occupancy) + mask_points_using_occupancy(grid, model, atomic_radii_set, constant_r + rprobe, + ignore_hydrogen, ignore_zero_occupancy_atoms); + else + mask_points(grid, model); + } + + /// Apply symmetry to grid (to fill whole grid). If we are using an + /// integer/binary mask this is done by distributing all 0-value + /// grid points - while for a float map/mask we are setting each + /// grid point to the minimum value. + template void symmetrize(Grid& grid) const { + if (std::is_same::value) { + grid.symmetrize([&](T a, T b) { return a == (T)0 || b == (T)0 ? (T)0 : (T)1; }); + } + else { + grid.symmetrize([&](T a, T b) { return a < b ? a : b; }); + } + } + + template void shrink(Grid& grid) const { + if (rshrink > 0) { + set_margin_around(grid, rshrink, (T)1, (T)-1); + grid.change_values((T)-1, (T)1); + } + } + + template void invert(Grid& grid) const { + for (auto& v : grid.data) + v = (T)1 - v; + } + + + // Removes small islands of Land=1 in the sea of 0. Uses flood fill. + // cf. find_blobs_by_flood_fill() + // Currently doesn't work mask from mask_points_using_occupancy(). + template int remove_islands(Grid& grid) const { + if (island_min_volume <= 0) + return 0; + size_t limit = static_cast(island_min_volume * grid.point_count() + / grid.unit_cell.volume); + int counter = 0; + FloodFill flood_fill{grid}; + flood_fill.for_each_islands([&](typename FloodFill::Result& r) { + //printf("island %d: %zu in %zu (limit: %zu)\n", + // counter, r.point_count(), lines.size(), limit); + if (r.point_count() <= limit) { + ++counter; + flood_fill.set_volume_values(r, (T)0); + } + }); + return counter; + } + + template void put_mask_on_grid(Grid& grid, const Model& model) const { + clear(grid); + assert(!grid.data.empty()); + mask_points(grid, model); + symmetrize(grid); + remove_islands(grid); + shrink(grid); + } + + void set_to_zero(Grid& grid, const Model& model) const { + mask_points(grid, model); + grid.symmetrize([&](float a, float b) { return b == 0.f ? 0.f : a; }); + } + +#if 0 + template void put_mask_on_grid(Grid& grid, const Model& model) { + // use twice finer grid for solvent mask + Grid mask; + mask.copy_metadata_from(grid); + mask.set_size(2*grid.nu, 2*grid.nv, 2*grid.nw); + mask.data.resize(8 * grid.data.size(), 1); + put_mask_on_grid(mask, model); + for (int w = 0, idx = 0; w < grid.nw; ++w) + for (int v = 0; v < grid.nv; ++v) + for (int u = 0; u < grid.nu; ++u, ++idx) { + grid.data[idx] = 0; + for (int wa = 0; wa < 2; ++wa) + for (int va = 0; va < 2; ++va) + for (int ua = 0; ua < 2; ++ua) + grid.data[idx] += mask.get_value_q(2*u + ua, 2*v + va, 2*w + wa); + grid.data[idx] *= 1. / 8; + } +#endif +}; + +struct NodeInfo { + double dist_sq; // distance from the nearest atom + bool found = false; // the mask flag + //Element elem = El::X; + int u = 0, v = 0, w = 0; // not normalized near-model grid coordinates +}; + +/// Populate NodeInfo grid for nodes near the model. +inline void mask_with_node_info(Grid& mask, const Model& model, double radius) { + NodeInfo default_ni; + default_ni.dist_sq = radius * radius; + mask.fill(default_ni); + // cf. use_points_around() + int du = (int) std::ceil(radius / mask.spacing[0]); + int dv = (int) std::ceil(radius / mask.spacing[1]); + int dw = (int) std::ceil(radius / mask.spacing[2]); + mask.template check_size_for_points_in_box(du, dv, dw, false); + for (const Chain& chain : model.chains) + for (const Residue& res : chain.residues) + for (const Atom& atom : res.atoms) { + Fractional frac0 = mask.unit_cell.fractionalize(atom.pos); + mask.template do_use_points_in_box( + frac0, du, dv, dw, + [&](NodeInfo& ni, double d2, const Position&, int u, int v, int w) { + if (d2 < ni.dist_sq) { + ni.dist_sq = d2; + ni.found = true; + //ni.elem = atom.element; + ni.u = u; + ni.v = v; + ni.w = w; + } + }, + radius); + } +} + +/// Skip nodes that are closer to a symmetry mate of the model than +/// to the original model. A node is closer to a symmetry mate when it has +/// a symmetry image that is closer to the original model than the node. +/// We ignore NCS here. +inline void unmask_symmetry_mates(Grid& mask) { + std::vector symmetry_ops = mask.get_scaled_ops_except_id(); + size_t idx = 0; + for (int w = 0; w != mask.nw; ++w) + for (int v = 0; v != mask.nv; ++v) + for (int u = 0; u != mask.nu; ++u, ++idx) { + NodeInfo& ni = mask.data[idx]; + if (ni.found) + for (const GridOp& grid_op : symmetry_ops) { + std::array t = grid_op.apply(u, v, w); + NodeInfo& im_ni = mask.data[mask.index_n(t[0], t[1], t[2])]; + if (im_ni.found && im_ni.dist_sq > ni.dist_sq) + im_ni.found = false; + } + } +} + +// TODO: would it be better to use src_model rather than dest_model? +template +void interpolate_grid_around_model(Grid& dest, const Grid& src, + const Transform& tr, + const Model& dest_model, double radius, + int order=1) { + Grid mask; + mask.copy_metadata_from(dest); + mask_with_node_info(mask, dest_model, radius); + unmask_symmetry_mates(mask); + // Interpolate values for selected nodes. + FTransform frac_tr = src.unit_cell.frac.combine(tr.combine(dest.unit_cell.orth)); + for (size_t idx = 0; idx != mask.data.size(); ++idx) { + const NodeInfo& ni = mask.data[idx]; + if (ni.found) { + Fractional dest_fr = dest.get_fractional(ni.u, ni.v, ni.w); + Fractional src_fr = frac_tr.apply(dest_fr); + dest.data[idx] = src.interpolate_value(src_fr, order); + } + } +} + + +// add soft edge to 1/0 mask using raised cosine function +template +void add_soft_edge_to_mask(Grid& grid, double width) { + const double width2 = width * width; + const int du = (int) std::ceil(width / grid.spacing[0]); + const int dv = (int) std::ceil(width / grid.spacing[1]); + const int dw = (int) std::ceil(width / grid.spacing[2]); + + for (int w = 0; w < grid.nw; ++w) + for (int v = 0; v < grid.nv; ++v) + for (int u = 0; u < grid.nu; ++u) { + size_t idx = grid.index_q(u, v, w); + if (grid.data[idx] >= 1e-3) continue; + double min_d2 = width2 + 1; + Fractional fctr = grid.get_fractional(u, v, w); + grid.template use_points_in_box( + fctr, du, dv, dw, + [&](T& point, double d2, const Position&, int, int, int) { + if (point > 0.999) { + if (d2 < min_d2) + min_d2 = d2; + } + }); + if (min_d2 < width2) + grid.data[idx] = T(0.5 + 0.5 * std::cos(pi() * std::sqrt(min_d2) / width)); + } +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/span.hpp b/gemmi_gph/gemmi/span.hpp new file mode 100644 index 00000000..695bc78f --- /dev/null +++ b/gemmi_gph/gemmi/span.hpp @@ -0,0 +1,137 @@ +// Copyright 2019 Global Phasing Ltd. +// +// Span - span of array or std::vector. +// MutableVectorSpan - span of std::vector with insert() and erase() + +#ifndef GEMMI_SPAN_HPP_ +#define GEMMI_SPAN_HPP_ + +#include // for find_if, find_if_not +#include +#include // for out_of_range +#include // for remove_cv, conditional, is_const + +namespace gemmi { + +template struct MutableVectorSpan; + +// Minimalistic Span, somewhat similar to C++20 std::span. +template struct Span { + using iterator = Item*; + using const_iterator = Item const*; + using element_type = Item; + using value_type = typename std::remove_cv::type; + + friend Span; + friend MutableVectorSpan; + + Span() = default; + Span(iterator begin, std::size_t n) : begin_(begin), size_(n) {} + +#if !defined(_MSC_VER) || _MSC_VER-0 >= 1926 + // constructor only for const Item, to allow non-const -> const conversion + template + Span(const Span& o, + typename std::enable_if::value>::type* = 0) +#else + // older MSVC don't like the version above + Span(const Span& o) +#endif + : begin_(o.begin_), size_(o.size_) {} + + void set_begin(iterator begin) { begin_ = begin; } + void set_size(std::size_t n) { size_ = n; } + const_iterator begin() const { return begin_; } + const_iterator end() const { return begin_ + size_; } + iterator begin() { return begin_; } + iterator end() { return begin_ + size_; } + + Item& front() { return *begin_; } + const Item& front() const { return *begin_; } + Item& back() { return *(begin_ + size_ - 1); } + const Item& back() const { return *(begin_ + size_ - 1); } + + const Item& operator[](std::size_t i) const { return *(begin_ + i); } + Item& operator[](std::size_t i) { return *(begin_ + i); } + + Item& at(std::size_t i) { + if (i >= size()) + throw std::out_of_range("item index ouf of range: #" + std::to_string(i)); + return *(begin_ + i); + } + const Item& at(std::size_t i) const { + return const_cast(this)->at(i); + } + + std::size_t size() const { return size_; } + bool empty() const { return size_ == 0; } + explicit operator bool() const { return size_ != 0; } + + template Span sub(Iter first, Iter last) { + return Span(&*first, last - first); + } + + template Span subspan(F&& func) { + iterator group_begin = std::find_if(this->begin(), this->end(), func); + iterator group_end = std::find_if_not(group_begin, this->end(), func); + return Span(&*group_begin, group_end - group_begin); + } + template Span subspan(F&& func) const { + using V = const value_type; + return const_cast(this)->subspan(std::forward(func)); + } + + // we use children() to iterate over Model, Chain, etc + Span& children() { return *this; } + const Span& children() const { return *this; } + +private: + iterator begin_ = nullptr; + std::size_t size_ = 0; +}; + +// Span of std::vector, implements insert() and erase(). +template struct MutableVectorSpan : Span { + using vector_type = std::vector::value_type>; + using iterator = typename Span::iterator; + //friend Span; + MutableVectorSpan() = default; + MutableVectorSpan(Span&& p, vector_type* v) + : Span(p), vector_(v) {} + MutableVectorSpan(vector_type& v, iterator begin, std::size_t n) + : Span(begin, n), vector_(&v) {} + + template MutableVectorSpan sub(Iter first, Iter last) { + return {Span::sub(first, last), vector_}; + } + + template MutableVectorSpan subspan(F&& func) { + return {Span::subspan(std::forward(func)), vector_}; + } + template MutableVectorSpan subspan(F&& func) const { + return {Span::subspan(std::forward(func)), vector_}; + } + + iterator insert(iterator pos, Item&& item) { + auto offset = this->begin_ - this->vector_->data(); + auto iter = vector_->begin() + (pos - this->vector_->data()); + auto ret = vector_->insert(iter, std::move(item)); + this->begin_ = vector_->data() + offset; + ++this->size_; + return &*ret; + } + + void erase(iterator pos) { + vector_->erase(vector_->begin() + (pos - vector_->data())); + --this->size_; + } + + bool is_beginning() const { return this->begin() == vector_->data(); } + bool is_ending() const { return this->end() == vector_->data() + vector_->size(); } + +private: + vector_type* vector_ = nullptr; // for insert() and erase() +}; + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/stats.hpp b/gemmi_gph/gemmi/stats.hpp new file mode 100644 index 00000000..5178c4c1 --- /dev/null +++ b/gemmi_gph/gemmi/stats.hpp @@ -0,0 +1,136 @@ +// Copyright 2018 Global Phasing Ltd. +// +// Statistics utilities: classes Covariance, Correlation, DataStats + +#ifndef GEMMI_STATS_HPP_ +#define GEMMI_STATS_HPP_ + +#include // for size_t +#include // for sqrt, NAN, INFINITY +#include + +namespace gemmi { + +// popular single-pass algorithm for calculating variance and mean +struct Variance { + int n = 0; + double sum_sq = 0.; + double mean_x = 0.; + + Variance() = default; + template Variance(T begin, T end) : Variance() { + for (auto i = begin; i != end; ++i) + add_point(*i); + } + void add_point(double x) { + ++n; + double dx = x - mean_x; + mean_x += dx / n; + sum_sq += dx * (x - mean_x); + } + double for_sample() const { return sum_sq / (n - 1); } + double for_population() const { return sum_sq / n; } +}; + +struct Covariance : Variance { + double mean_y = 0.; + void add_point(double x, double y) { + ++n; + double dx = x - mean_x; + mean_x += dx / n; + mean_y += (y - mean_y) / n; + sum_sq += dx * (y - mean_y); + } +}; + +struct Correlation { + int n = 0; + double sum_xx = 0.; + double sum_yy = 0.; + double sum_xy = 0.; + double mean_x = 0.; + double mean_y = 0.; + void add_point(double x, double y) { + ++n; + double weight = (double)(n - 1) / n; + double dx = x - mean_x; + double dy = y - mean_y; + sum_xx += weight * dx * dx; + sum_yy += weight * dy * dy; + sum_xy += weight * dx * dy; + mean_x += dx / n; + mean_y += dy / n; + } + double coefficient() const { return sum_xy / std::sqrt(sum_xx * sum_yy); } + double x_variance() const { return sum_xx / n; } + double y_variance() const { return sum_yy / n; } + double covariance() const { return sum_xy / n; } + double mean_ratio() const { return mean_y / mean_x; } + // the regression line + double slope() const { return sum_xy / sum_xx; } + double intercept() const { return mean_y - slope() * mean_x; } +}; + + +inline Correlation combine_two_correlations(const Correlation& a, const Correlation& b) { + auto sq = [](double x) { return x * x; }; + Correlation r; + r.n = a.n + b.n; + r.mean_x = (a.n * a.mean_x + b.n * b.mean_x) / r.n; + r.mean_y = (a.n * a.mean_y + b.n * b.mean_y) / r.n; + r.sum_xx = a.sum_xx + a.n * sq(a.mean_x - r.mean_x) + + b.sum_xx + b.n * sq(b.mean_x - r.mean_x); + r.sum_yy = a.sum_yy + a.n * sq(a.mean_y - r.mean_y) + + b.sum_yy + b.n * sq(b.mean_y - r.mean_y); + r.sum_xy = a.sum_xy + a.n * (a.mean_x - r.mean_x) * (a.mean_y - r.mean_y) + + b.sum_xy + b.n * (b.mean_x - r.mean_x) * (b.mean_y - r.mean_y); + return r; +} + +inline Correlation combine_correlations(const std::vector& cors) { + Correlation result; + for (const Correlation& cor : cors) + result = combine_two_correlations(result, cor); + return result; +} + + +struct DataStats { + double dmin = NAN; + double dmax = NAN; + double dmean = NAN; + double rms = NAN; + size_t nan_count = 0; +}; + +template +DataStats calculate_data_statistics(const std::vector& data) { + DataStats stats; + double sum = 0; + double sq_sum = 0; + stats.dmin = INFINITY; + stats.dmax = -INFINITY; + for (double d : data) { + if (std::isnan(d)) { + stats.nan_count++; + continue; + } + sum += d; + sq_sum += d * d; + if (d < stats.dmin) + stats.dmin = d; + if (d > stats.dmax) + stats.dmax = d; + } + if (stats.nan_count != data.size()) { + stats.dmean = sum / (data.size() - stats.nan_count); + stats.rms = std::sqrt(sq_sum / (data.size() - stats.nan_count) - stats.dmean * stats.dmean); + } else { + stats.dmin = NAN; + stats.dmax = NAN; + } + return stats; +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/third_party/half.hpp b/gemmi_gph/gemmi/third_party/half.hpp new file mode 100644 index 00000000..d802fb68 --- /dev/null +++ b/gemmi_gph/gemmi/third_party/half.hpp @@ -0,0 +1,4603 @@ +// half - IEEE 754-based half-precision floating-point library. +// +// Copyright (c) 2012-2025 Christian Rau +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, +// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// Version 2.2.1 + +/// \file +/// Main header file for half-precision functionality. + +#ifndef HALF_HALF_HPP +#define HALF_HALF_HPP + +#define HALF_GCC_VERSION (__GNUC__*100+__GNUC_MINOR__) + +#if defined(__INTEL_COMPILER) + #define HALF_ICC_VERSION __INTEL_COMPILER +#elif defined(__ICC) + #define HALF_ICC_VERSION __ICC +#elif defined(__ICL) + #define HALF_ICC_VERSION __ICL +#else + #define HALF_ICC_VERSION 0 +#endif + +// check C++11 language features +#if defined(__clang__) // clang + #if __has_feature(cxx_static_assert) && !defined(HALF_ENABLE_CPP11_STATIC_ASSERT) + #define HALF_ENABLE_CPP11_STATIC_ASSERT 1 + #endif + #if __has_feature(cxx_constexpr) && !defined(HALF_ENABLE_CPP11_CONSTEXPR) + #define HALF_ENABLE_CPP11_CONSTEXPR 1 + #endif + #if __has_feature(cxx_noexcept) && !defined(HALF_ENABLE_CPP11_NOEXCEPT) + #define HALF_ENABLE_CPP11_NOEXCEPT 1 + #endif + #if __has_feature(cxx_user_literals) && !defined(HALF_ENABLE_CPP11_USER_LITERALS) + #define HALF_ENABLE_CPP11_USER_LITERALS 1 + #endif + #if __has_feature(cxx_thread_local) && !defined(HALF_ENABLE_CPP11_THREAD_LOCAL) + #define HALF_ENABLE_CPP11_THREAD_LOCAL 1 + #endif + #if (defined(__GXX_EXPERIMENTAL_CXX0X__) || __cplusplus >= 201103L) && !defined(HALF_ENABLE_CPP11_LONG_LONG) + #define HALF_ENABLE_CPP11_LONG_LONG 1 + #endif +#elif HALF_ICC_VERSION && defined(__INTEL_CXX11_MODE__) // Intel C++ + #if HALF_ICC_VERSION >= 1500 && !defined(HALF_ENABLE_CPP11_THREAD_LOCAL) + #define HALF_ENABLE_CPP11_THREAD_LOCAL 1 + #endif + #if HALF_ICC_VERSION >= 1500 && !defined(HALF_ENABLE_CPP11_USER_LITERALS) + #define HALF_ENABLE_CPP11_USER_LITERALS 1 + #endif + #if HALF_ICC_VERSION >= 1400 && !defined(HALF_ENABLE_CPP11_CONSTEXPR) + #define HALF_ENABLE_CPP11_CONSTEXPR 1 + #endif + #if HALF_ICC_VERSION >= 1400 && !defined(HALF_ENABLE_CPP11_NOEXCEPT) + #define HALF_ENABLE_CPP11_NOEXCEPT 1 + #endif + #if HALF_ICC_VERSION >= 1110 && !defined(HALF_ENABLE_CPP11_STATIC_ASSERT) + #define HALF_ENABLE_CPP11_STATIC_ASSERT 1 + #endif + #if HALF_ICC_VERSION >= 1110 && !defined(HALF_ENABLE_CPP11_LONG_LONG) + #define HALF_ENABLE_CPP11_LONG_LONG 1 + #endif +#elif defined(__GNUC__) // gcc + #if defined(__GXX_EXPERIMENTAL_CXX0X__) || __cplusplus >= 201103L + #if HALF_GCC_VERSION >= 408 && !defined(HALF_ENABLE_CPP11_THREAD_LOCAL) + #define HALF_ENABLE_CPP11_THREAD_LOCAL 1 + #endif + #if HALF_GCC_VERSION >= 407 && !defined(HALF_ENABLE_CPP11_USER_LITERALS) + #define HALF_ENABLE_CPP11_USER_LITERALS 1 + #endif + #if HALF_GCC_VERSION >= 406 && !defined(HALF_ENABLE_CPP11_CONSTEXPR) + #define HALF_ENABLE_CPP11_CONSTEXPR 1 + #endif + #if HALF_GCC_VERSION >= 406 && !defined(HALF_ENABLE_CPP11_NOEXCEPT) + #define HALF_ENABLE_CPP11_NOEXCEPT 1 + #endif + #if HALF_GCC_VERSION >= 403 && !defined(HALF_ENABLE_CPP11_STATIC_ASSERT) + #define HALF_ENABLE_CPP11_STATIC_ASSERT 1 + #endif + #if !defined(HALF_ENABLE_CPP11_LONG_LONG) + #define HALF_ENABLE_CPP11_LONG_LONG 1 + #endif + #endif + #define HALF_TWOS_COMPLEMENT_INT 1 +#elif defined(_MSC_VER) // Visual C++ + #if _MSC_VER >= 1900 && !defined(HALF_ENABLE_CPP11_THREAD_LOCAL) + #define HALF_ENABLE_CPP11_THREAD_LOCAL 1 + #endif + #if _MSC_VER >= 1900 && !defined(HALF_ENABLE_CPP11_USER_LITERALS) + #define HALF_ENABLE_CPP11_USER_LITERALS 1 + #endif + #if _MSC_VER >= 1900 && !defined(HALF_ENABLE_CPP11_CONSTEXPR) + #define HALF_ENABLE_CPP11_CONSTEXPR 1 + #endif + #if _MSC_VER >= 1900 && !defined(HALF_ENABLE_CPP11_NOEXCEPT) + #define HALF_ENABLE_CPP11_NOEXCEPT 1 + #endif + #if _MSC_VER >= 1600 && !defined(HALF_ENABLE_CPP11_STATIC_ASSERT) + #define HALF_ENABLE_CPP11_STATIC_ASSERT 1 + #endif + #if _MSC_VER >= 1310 && !defined(HALF_ENABLE_CPP11_LONG_LONG) + #define HALF_ENABLE_CPP11_LONG_LONG 1 + #endif + #define HALF_TWOS_COMPLEMENT_INT 1 + #define HALF_POP_WARNINGS 1 + #pragma warning(push) + #pragma warning(disable : 4099 4127 4146) //struct vs class, constant in if, negative unsigned +#endif + +// check C++11 library features +#include +#if defined(_LIBCPP_VERSION) // libc++ + #if defined(__GXX_EXPERIMENTAL_CXX0X__) || __cplusplus >= 201103 + #ifndef HALF_ENABLE_CPP11_TYPE_TRAITS + #define HALF_ENABLE_CPP11_TYPE_TRAITS 1 + #endif + #ifndef HALF_ENABLE_CPP11_CSTDINT + #define HALF_ENABLE_CPP11_CSTDINT 1 + #endif + #ifndef HALF_ENABLE_CPP11_CMATH + #define HALF_ENABLE_CPP11_CMATH 1 + #endif + #ifndef HALF_ENABLE_CPP11_HASH + #define HALF_ENABLE_CPP11_HASH 1 + #endif + #ifndef HALF_ENABLE_CPP11_CFENV + #define HALF_ENABLE_CPP11_CFENV 1 + #endif + #endif +#elif defined(__GLIBCXX__) // libstdc++ + #if defined(__GXX_EXPERIMENTAL_CXX0X__) || __cplusplus >= 201103 + #ifdef __clang__ + #if __GLIBCXX__ >= 20080606 && !defined(HALF_ENABLE_CPP11_TYPE_TRAITS) + #define HALF_ENABLE_CPP11_TYPE_TRAITS 1 + #endif + #if __GLIBCXX__ >= 20080606 && !defined(HALF_ENABLE_CPP11_CSTDINT) + #define HALF_ENABLE_CPP11_CSTDINT 1 + #endif + #if __GLIBCXX__ >= 20080606 && !defined(HALF_ENABLE_CPP11_CMATH) + #define HALF_ENABLE_CPP11_CMATH 1 + #endif + #if __GLIBCXX__ >= 20080606 && !defined(HALF_ENABLE_CPP11_HASH) + #define HALF_ENABLE_CPP11_HASH 1 + #endif + #if __GLIBCXX__ >= 20080606 && !defined(HALF_ENABLE_CPP11_CFENV) + #define HALF_ENABLE_CPP11_CFENV 1 + #endif + #else + #if HALF_GCC_VERSION >= 403 && !defined(HALF_ENABLE_CPP11_TYPE_TRAITS) + #define HALF_ENABLE_CPP11_TYPE_TRAITS 1 + #endif + #if HALF_GCC_VERSION >= 403 && !defined(HALF_ENABLE_CPP11_CSTDINT) + #define HALF_ENABLE_CPP11_CSTDINT 1 + #endif + #if HALF_GCC_VERSION >= 403 && !defined(HALF_ENABLE_CPP11_CMATH) + #define HALF_ENABLE_CPP11_CMATH 1 + #endif + #if HALF_GCC_VERSION >= 403 && !defined(HALF_ENABLE_CPP11_HASH) + #define HALF_ENABLE_CPP11_HASH 1 + #endif + #if HALF_GCC_VERSION >= 403 && !defined(HALF_ENABLE_CPP11_CFENV) + #define HALF_ENABLE_CPP11_CFENV 1 + #endif + #endif + #endif +#elif defined(_CPPLIB_VER) // Dinkumware/Visual C++ + #if _CPPLIB_VER >= 520 && !defined(HALF_ENABLE_CPP11_TYPE_TRAITS) + #define HALF_ENABLE_CPP11_TYPE_TRAITS 1 + #endif + #if _CPPLIB_VER >= 520 && !defined(HALF_ENABLE_CPP11_CSTDINT) + #define HALF_ENABLE_CPP11_CSTDINT 1 + #endif + #if _CPPLIB_VER >= 520 && !defined(HALF_ENABLE_CPP11_HASH) + #define HALF_ENABLE_CPP11_HASH 1 + #endif + #if _CPPLIB_VER >= 610 && !defined(HALF_ENABLE_CPP11_CMATH) + #define HALF_ENABLE_CPP11_CMATH 1 + #endif + #if _CPPLIB_VER >= 610 && !defined(HALF_ENABLE_CPP11_CFENV) + #define HALF_ENABLE_CPP11_CFENV 1 + #endif +#endif +#undef HALF_GCC_VERSION +#undef HALF_ICC_VERSION + +// any error throwing C++ exceptions? +#if defined(HALF_ERRHANDLING_THROW_INVALID) || defined(HALF_ERRHANDLING_THROW_DIVBYZERO) || defined(HALF_ERRHANDLING_THROW_OVERFLOW) || defined(HALF_ERRHANDLING_THROW_UNDERFLOW) || defined(HALF_ERRHANDLING_THROW_INEXACT) +#define HALF_ERRHANDLING_THROWS 1 +#endif + +// any error handling enabled? +#define HALF_ERRHANDLING (HALF_ERRHANDLING_FLAGS||HALF_ERRHANDLING_ERRNO||HALF_ERRHANDLING_FENV||HALF_ERRHANDLING_THROWS) + +#if HALF_ERRHANDLING + #define HALF_UNUSED_NOERR(name) name +#else + #define HALF_UNUSED_NOERR(name) +#endif + +// support constexpr +#if HALF_ENABLE_CPP11_CONSTEXPR + #define HALF_CONSTEXPR constexpr + #define HALF_CONSTEXPR_CONST constexpr + #if HALF_ERRHANDLING + #define HALF_CONSTEXPR_NOERR + #else + #define HALF_CONSTEXPR_NOERR constexpr + #endif +#else + #define HALF_CONSTEXPR + #define HALF_CONSTEXPR_CONST const + #define HALF_CONSTEXPR_NOERR +#endif + +// support noexcept +#if HALF_ENABLE_CPP11_NOEXCEPT + #define HALF_NOEXCEPT noexcept + #define HALF_NOTHROW noexcept +#else + #define HALF_NOEXCEPT + #define HALF_NOTHROW throw() +#endif + +// support thread storage +#if HALF_ENABLE_CPP11_THREAD_LOCAL + #define HALF_THREAD_LOCAL thread_local +#else + #define HALF_THREAD_LOCAL static +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if HALF_ENABLE_CPP11_TYPE_TRAITS + #include +#endif +#if HALF_ENABLE_CPP11_CSTDINT + #include +#endif +#if HALF_ERRHANDLING_ERRNO + #include +#endif +#if HALF_ENABLE_CPP11_CFENV + #include +#endif +#if HALF_ENABLE_CPP11_HASH + #include +#endif + + +#ifndef HALF_ENABLE_F16C_INTRINSICS + /// Enable F16C intruction set intrinsics. + /// Defining this to 1 enables the use of [F16C compiler intrinsics](https://en.wikipedia.org/wiki/F16C) for converting between + /// half-precision and single-precision values which may result in improved performance. This will not perform additional checks + /// for support of the F16C instruction set, so an appropriate target platform is required when enabling this feature. + /// + /// Unless predefined it will be enabled automatically when the `__F16C__` symbol is defined, which some compilers do on supporting platforms. + #define HALF_ENABLE_F16C_INTRINSICS __F16C__ +#endif +#if HALF_ENABLE_F16C_INTRINSICS + #include +#endif + +#ifdef HALF_DOXYGEN_ONLY +/// Type for internal floating-point computations. +/// This can be predefined to a built-in floating-point type (`float`, `double` or `long double`) to override the internal +/// half-precision implementation to use this type for computing arithmetic operations and mathematical functions (if available). +/// This can result in improved performance for arithmetic operators and mathematical functions but might cause results to +/// deviate from the specified half-precision rounding mode and inhibits proper detection of half-precision exceptions. +#define HALF_ARITHMETIC_TYPE (undefined) + +/// Enable internal exception flags. +/// Defining this to 1 causes operations on half-precision values to raise internal floating-point exception flags according to +/// the IEEE 754 standard. These can then be cleared and checked with clearexcept(), testexcept(). +#define HALF_ERRHANDLING_FLAGS 0 + +/// Enable exception propagation to `errno`. +/// Defining this to 1 causes operations on half-precision values to propagate floating-point exceptions to +/// [errno](https://en.cppreference.com/w/cpp/error/errno) from ``. Specifically this will propagate domain errors as +/// [EDOM](https://en.cppreference.com/w/cpp/error/errno_macros) and pole, overflow and underflow errors as +/// [ERANGE](https://en.cppreference.com/w/cpp/error/errno_macros). Inexact errors won't be propagated. +#define HALF_ERRHANDLING_ERRNO 0 + +/// Enable exception propagation to built-in floating-point platform. +/// Defining this to 1 causes operations on half-precision values to propagate floating-point exceptions to the built-in +/// single- and double-precision implementation's exception flags using the +/// [C++11 floating-point environment control](https://en.cppreference.com/w/cpp/numeric/fenv) from ``. However, this +/// does not work in reverse and single- or double-precision exceptions will not raise the corresponding half-precision +/// exception flags, nor will explicitly clearing flags clear the corresponding built-in flags. +#define HALF_ERRHANDLING_FENV 0 + +/// Throw C++ exception on domain errors. +/// Defining this to a string literal causes operations on half-precision values to throw a +/// [std::domain_error](https://en.cppreference.com/w/cpp/error/domain_error) with the specified message on domain errors. +#define HALF_ERRHANDLING_THROW_INVALID (undefined) + +/// Throw C++ exception on pole errors. +/// Defining this to a string literal causes operations on half-precision values to throw a +/// [std::domain_error](https://en.cppreference.com/w/cpp/error/domain_error) with the specified message on pole errors. +#define HALF_ERRHANDLING_THROW_DIVBYZERO (undefined) + +/// Throw C++ exception on overflow errors. +/// Defining this to a string literal causes operations on half-precision values to throw a +/// [std::overflow_error](https://en.cppreference.com/w/cpp/error/overflow_error) with the specified message on overflows. +#define HALF_ERRHANDLING_THROW_OVERFLOW (undefined) + +/// Throw C++ exception on underflow errors. +/// Defining this to a string literal causes operations on half-precision values to throw a +/// [std::underflow_error](https://en.cppreference.com/w/cpp/error/underflow_error) with the specified message on underflows. +#define HALF_ERRHANDLING_THROW_UNDERFLOW (undefined) + +/// Throw C++ exception on rounding errors. +/// Defining this to 1 causes operations on half-precision values to throw a +/// [std::range_error](https://en.cppreference.com/w/cpp/error/range_error) with the specified message on general rounding errors. +#define HALF_ERRHANDLING_THROW_INEXACT (undefined) +#endif + +#ifndef HALF_ERRHANDLING_OVERFLOW_TO_INEXACT +/// Raise INEXACT exception on overflow. +/// Defining this to 1 (default) causes overflow errors to also raise inexact exceptions. +/// These will be raised after any possible handling of the underflow exception. +#define HALF_ERRHANDLING_OVERFLOW_TO_INEXACT 1 +#endif + +#ifndef HALF_ERRHANDLING_UNDERFLOW_TO_INEXACT +/// Raise INEXACT exception on underflow. +/// Defining this to 1 (default) causes underflow errors to also raise inexact exceptions. +/// These will be raised after any possible handling of the underflow exception. +/// +/// **Note:** This will actually cause underflow (and the accompanying inexact) exceptions to be raised *only* when the result +/// is inexact, while if disabled bare underflow errors will be raised for *any* (possibly exact) subnormal result. +#define HALF_ERRHANDLING_UNDERFLOW_TO_INEXACT 1 +#endif + +/// Default rounding mode. +/// This specifies the rounding mode used for all conversions between [half](\ref half_float::half)s and more precise types +/// (unless using half_cast() and specifying the rounding mode directly) as well as in arithmetic operations and mathematical +/// functions. It can be redefined (before including half.hpp) to one of the standard rounding modes using their respective +/// constants or the equivalent values of +/// [std::float_round_style](https://en.cppreference.com/w/cpp/types/numeric_limits/float_round_style): +/// +/// `std::float_round_style` | value | rounding +/// ---------------------------------|-------|------------------------- +/// `std::round_indeterminate` | -1 | fastest +/// `std::round_toward_zero` | 0 | toward zero +/// `std::round_to_nearest` | 1 | to nearest (default) +/// `std::round_toward_infinity` | 2 | toward positive infinity +/// `std::round_toward_neg_infinity` | 3 | toward negative infinity +/// +/// By default this is set to `1` (`std::round_to_nearest`), which rounds results to the nearest representable value. It can even +/// be set to [std::numeric_limits::round_style](https://en.cppreference.com/w/cpp/types/numeric_limits/round_style) to synchronize +/// the rounding mode with that of the built-in single-precision implementation (which is likely `std::round_to_nearest`, though). +#ifndef HALF_ROUND_STYLE + #define HALF_ROUND_STYLE 1 // = std::round_to_nearest +#endif + +/// Value signaling overflow. +/// In correspondence with `HUGE_VAL[F|L]` from `` this symbol expands to a positive value signaling the overflow of an +/// operation, in particular it just evaluates to positive infinity. +/// +/// **See also:** Documentation for [HUGE_VAL](https://en.cppreference.com/w/cpp/numeric/math/HUGE_VAL) +#define HUGE_VALH std::numeric_limits::infinity() + +/// Fast half-precision fma function. +/// This symbol is defined if the fma() function generally executes as fast as, or faster than, a separate +/// half-precision multiplication followed by an addition, which is always the case. +/// +/// **See also:** Documentation for [FP_FAST_FMA](https://en.cppreference.com/w/cpp/numeric/math/fma) +#define FP_FAST_FMAH 1 + +/// Half rounding mode. +/// In correspondence with `FLT_ROUNDS` from `` this symbol expands to the rounding mode used for +/// half-precision operations. It is an alias for [HALF_ROUND_STYLE](\ref HALF_ROUND_STYLE). +/// +/// **See also:** Documentation for [FLT_ROUNDS](https://en.cppreference.com/w/cpp/types/climits/FLT_ROUNDS) +#define HLF_ROUNDS HALF_ROUND_STYLE + +#ifndef FP_ILOGB0 + #define FP_ILOGB0 INT_MIN +#endif +#ifndef FP_ILOGBNAN + #define FP_ILOGBNAN INT_MAX +#endif +#ifndef FP_SUBNORMAL + #define FP_SUBNORMAL 0 +#endif +#ifndef FP_ZERO + #define FP_ZERO 1 +#endif +#ifndef FP_NAN + #define FP_NAN 2 +#endif +#ifndef FP_INFINITE + #define FP_INFINITE 3 +#endif +#ifndef FP_NORMAL + #define FP_NORMAL 4 +#endif + +#if !HALF_ENABLE_CPP11_CFENV && !defined(FE_ALL_EXCEPT) + #define FE_INVALID 0x10 + #define FE_DIVBYZERO 0x08 + #define FE_OVERFLOW 0x04 + #define FE_UNDERFLOW 0x02 + #define FE_INEXACT 0x01 + #define FE_ALL_EXCEPT (FE_INVALID|FE_DIVBYZERO|FE_OVERFLOW|FE_UNDERFLOW|FE_INEXACT) +#endif + + +/// Main namespace for half-precision functionality. +/// This namespace contains all the functionality provided by the library. +namespace half_float +{ + class half; + +#if HALF_ENABLE_CPP11_USER_LITERALS + /// Library-defined half-precision literals. + /// Import this namespace to enable half-precision floating-point literals: + /// ~~~~{.cpp} + /// using namespace half_float::literal; + /// half_float::half = 4.2_h; + /// ~~~~ + namespace literal + { + half operator""_h(long double); + } +#endif + + /// \internal + /// \brief Implementation details. + namespace detail + { + #if HALF_ENABLE_CPP11_TYPE_TRAITS + /// Conditional type. + template struct conditional : std::conditional {}; + + /// Helper for tag dispatching. + template struct bool_type : std::integral_constant {}; + using std::true_type; + using std::false_type; + + /// Type traits for floating-point types. + template struct is_float : std::is_floating_point {}; + #else + /// Conditional type. + template struct conditional { typedef T type; }; + template struct conditional { typedef F type; }; + + /// Helper for tag dispatching. + template struct bool_type {}; + typedef bool_type true_type; + typedef bool_type false_type; + + /// Type traits for floating-point types. + template struct is_float : false_type {}; + template struct is_float : is_float {}; + template struct is_float : is_float {}; + template struct is_float : is_float {}; + template<> struct is_float : true_type {}; + template<> struct is_float : true_type {}; + template<> struct is_float : true_type {}; + #endif + + /// Type traits for floating-point bits. + template struct bits { typedef unsigned char type; }; + template struct bits : bits {}; + template struct bits : bits {}; + template struct bits : bits {}; + + #if HALF_ENABLE_CPP11_CSTDINT + /// Unsigned integer of (at least) 16 bits width. + typedef std::uint_least16_t uint16; + + /// Fastest unsigned integer of (at least) 32 bits width. + typedef std::uint_fast32_t uint32; + + /// Fastest signed integer of (at least) 32 bits width. + typedef std::int_fast32_t int32; + + /// Unsigned integer of (at least) 32 bits width. + template<> struct bits { typedef std::uint_least32_t type; }; + + /// Unsigned integer of (at least) 64 bits width. + template<> struct bits { typedef std::uint_least64_t type; }; + #else + /// Unsigned integer of (at least) 16 bits width. + typedef unsigned short uint16; + + /// Fastest unsigned integer of (at least) 32 bits width. + typedef unsigned long uint32; + + /// Fastest unsigned integer of (at least) 32 bits width. + typedef long int32; + + /// Unsigned integer of (at least) 32 bits width. + template<> struct bits : conditional::digits>=32,unsigned int,unsigned long> {}; + + #if HALF_ENABLE_CPP11_LONG_LONG + /// Unsigned integer of (at least) 64 bits width. + template<> struct bits : conditional::digits>=64,unsigned long,unsigned long long> {}; + #else + /// Unsigned integer of (at least) 64 bits width. + template<> struct bits { typedef unsigned long type; }; + #endif + #endif + + #ifdef HALF_ARITHMETIC_TYPE + /// Type to use for arithmetic computations and mathematical functions internally. + typedef HALF_ARITHMETIC_TYPE internal_t; + #endif + + /// Tag type for binary construction. + struct binary_t {}; + + /// Tag for binary construction. + HALF_CONSTEXPR_CONST binary_t binary = binary_t(); + + /// \name Implementation defined classification and arithmetic + /// \{ + + /// Check for infinity. + /// \tparam T argument type (builtin floating-point type) + /// \param arg value to query + /// \retval true if infinity + /// \retval false else + template bool builtin_isinf(T arg) + { + #if HALF_ENABLE_CPP11_CMATH + return std::isinf(arg); + #elif defined(_MSC_VER) + return !::_finite(static_cast(arg)) && !::_isnan(static_cast(arg)); + #else + return arg == std::numeric_limits::infinity() || arg == -std::numeric_limits::infinity(); + #endif + } + + /// Check for NaN. + /// \tparam T argument type (builtin floating-point type) + /// \param arg value to query + /// \retval true if not a number + /// \retval false else + template bool builtin_isnan(T arg) + { + #if HALF_ENABLE_CPP11_CMATH + return std::isnan(arg); + #elif defined(_MSC_VER) + return ::_isnan(static_cast(arg)) != 0; + #else + return arg != arg; + #endif + } + + /// Check sign. + /// \tparam T argument type (builtin floating-point type) + /// \param arg value to query + /// \retval true if signbit set + /// \retval false else + template bool builtin_signbit(T arg) + { + #if HALF_ENABLE_CPP11_CMATH + return std::signbit(arg); + #else + return arg < T() || (arg == T() && T(1)/arg < T()); + #endif + } + + /// Platform-independent sign mask. + /// \param arg integer value in two's complement + /// \retval -1 if \a arg negative + /// \retval 0 if \a arg positive + inline uint32 sign_mask(uint32 arg) + { + static const int N = std::numeric_limits::digits - 1; + #if HALF_TWOS_COMPLEMENT_INT + return static_cast(arg) >> N; + #else + return -((arg>>N)&1); + #endif + } + + /// Platform-independent arithmetic right shift. + /// \param arg integer value in two's complement + /// \param i shift amount (at most 31) + /// \return \a arg right shifted for \a i bits with possible sign extension + inline uint32 arithmetic_shift(uint32 arg, int i) + { + #if HALF_TWOS_COMPLEMENT_INT + return static_cast(arg) >> i; + #else + return static_cast(arg)/(static_cast(1)<>(std::numeric_limits::digits-1))&1); + #endif + } + + /// \} + /// \name Error handling + /// \{ + + /// Internal exception flags. + /// \return reference to global exception flags + inline int& errflags() { HALF_THREAD_LOCAL int flags = 0; return flags; } + + /// Raise floating-point exception. + /// \param flags exceptions to raise + /// \param cond condition to raise exceptions for + inline void raise(int HALF_UNUSED_NOERR(flags), bool HALF_UNUSED_NOERR(cond) = true) + { + #if HALF_ERRHANDLING + if(!cond) + return; + #if HALF_ERRHANDLING_FLAGS + errflags() |= flags; + #endif + #if HALF_ERRHANDLING_ERRNO + if(flags & FE_INVALID) + errno = EDOM; + else if(flags & (FE_DIVBYZERO|FE_OVERFLOW|FE_UNDERFLOW)) + errno = ERANGE; + #endif + #if HALF_ERRHANDLING_FENV && HALF_ENABLE_CPP11_CFENV + std::feraiseexcept(flags); + #endif + #ifdef HALF_ERRHANDLING_THROW_INVALID + if(flags & FE_INVALID) + throw std::domain_error(HALF_ERRHANDLING_THROW_INVALID); + #endif + #ifdef HALF_ERRHANDLING_THROW_DIVBYZERO + if(flags & FE_DIVBYZERO) + throw std::domain_error(HALF_ERRHANDLING_THROW_DIVBYZERO); + #endif + #ifdef HALF_ERRHANDLING_THROW_OVERFLOW + if(flags & FE_OVERFLOW) + throw std::overflow_error(HALF_ERRHANDLING_THROW_OVERFLOW); + #endif + #ifdef HALF_ERRHANDLING_THROW_UNDERFLOW + if(flags & FE_UNDERFLOW) + throw std::underflow_error(HALF_ERRHANDLING_THROW_UNDERFLOW); + #endif + #ifdef HALF_ERRHANDLING_THROW_INEXACT + if(flags & FE_INEXACT) + throw std::range_error(HALF_ERRHANDLING_THROW_INEXACT); + #endif + #if HALF_ERRHANDLING_UNDERFLOW_TO_INEXACT + if((flags & FE_UNDERFLOW) && !(flags & FE_INEXACT)) + detail::raise(FE_INEXACT); + #endif + #if HALF_ERRHANDLING_OVERFLOW_TO_INEXACT + if((flags & FE_OVERFLOW) && !(flags & FE_INEXACT)) + detail::raise(FE_INEXACT); + #endif + #endif + } + + /// Check and signal for any NaN. + /// \param x first half-precision value to check + /// \param y second half-precision value to check + /// \retval true if either \a x or \a y is NaN + /// \retval false else + /// \exception FE_INVALID if \a x or \a y is NaN + inline HALF_CONSTEXPR_NOERR bool compsignal(unsigned int x, unsigned int y) + { + #if HALF_ERRHANDLING + detail::raise(FE_INVALID, (x&0x7FFF)>0x7C00 || (y&0x7FFF)>0x7C00); + #endif + return (x&0x7FFF) > 0x7C00 || (y&0x7FFF) > 0x7C00; + } + + /// Signal and silence signaling NaN. + /// \param nan half-precision NaN value + /// \return quiet NaN + /// \exception FE_INVALID if \a nan is signaling NaN + inline HALF_CONSTEXPR_NOERR unsigned int signal(unsigned int nan) + { + #if HALF_ERRHANDLING + detail::raise(FE_INVALID, !(nan&0x200)); + #endif + return nan | 0x200; + } + + /// Signal and silence signaling NaNs. + /// \param x first half-precision value to check + /// \param y second half-precision value to check + /// \return quiet NaN + /// \exception FE_INVALID if \a x or \a y is signaling NaN + inline HALF_CONSTEXPR_NOERR unsigned int signal(unsigned int x, unsigned int y) + { + #if HALF_ERRHANDLING + detail::raise(FE_INVALID, ((x&0x7FFF)>0x7C00 && !(x&0x200)) || ((y&0x7FFF)>0x7C00 && !(y&0x200))); + #endif + return ((x&0x7FFF)>0x7C00) ? (x|0x200) : (y|0x200); + } + + /// Signal and silence signaling NaNs. + /// \param x first half-precision value to check + /// \param y second half-precision value to check + /// \param z third half-precision value to check + /// \return quiet NaN + /// \exception FE_INVALID if \a x, \a y or \a z is signaling NaN + inline HALF_CONSTEXPR_NOERR unsigned int signal(unsigned int x, unsigned int y, unsigned int z) + { + #if HALF_ERRHANDLING + detail::raise(FE_INVALID, ((x&0x7FFF)>0x7C00 && !(x&0x200)) || ((y&0x7FFF)>0x7C00 && !(y&0x200)) || ((z&0x7FFF)>0x7C00 && !(z&0x200))); + #endif + return ((x&0x7FFF)>0x7C00) ? (x|0x200) : ((y&0x7FFF)>0x7C00) ? (y|0x200) : (z|0x200); + } + + /// Select value or signaling NaN. + /// \param x preferred half-precision value + /// \param y ignored half-precision value except for signaling NaN + /// \return \a y if signaling NaN, \a x otherwise + /// \exception FE_INVALID if \a y is signaling NaN + inline HALF_CONSTEXPR_NOERR unsigned int select(unsigned int x, unsigned int HALF_UNUSED_NOERR(y)) + { + #if HALF_ERRHANDLING + return (((y&0x7FFF)>0x7C00) && !(y&0x200)) ? detail::signal(y) : x; + #else + return x; + #endif + } + + /// Raise domain error and return NaN. + /// return quiet NaN + /// \exception FE_INVALID + inline HALF_CONSTEXPR_NOERR unsigned int invalid() + { + #if HALF_ERRHANDLING + detail::raise(FE_INVALID); + #endif + return 0x7FFF; + } + + /// Raise pole error and return infinity. + /// \param sign half-precision value with sign bit only + /// \return half-precision infinity with sign of \a sign + /// \exception FE_DIVBYZERO + inline HALF_CONSTEXPR_NOERR unsigned int pole(unsigned int sign = 0) + { + #if HALF_ERRHANDLING + detail::raise(FE_DIVBYZERO); + #endif + return sign | 0x7C00; + } + + /// Check value for underflow. + /// \param arg non-zero half-precision value to check + /// \return \a arg + /// \exception FE_UNDERFLOW if arg is subnormal + inline HALF_CONSTEXPR_NOERR unsigned int check_underflow(unsigned int arg) + { + #if HALF_ERRHANDLING && !HALF_ERRHANDLING_UNDERFLOW_TO_INEXACT + detail::raise(FE_UNDERFLOW, !(arg&0x7C00)); + #endif + return arg; + } + + /// \} + /// \name Conversion and rounding + /// \{ + + /// Half-precision overflow. + /// \tparam R rounding mode to use + /// \param sign half-precision value with sign bit only + /// \return rounded overflowing half-precision value + /// \exception FE_OVERFLOW + template HALF_CONSTEXPR_NOERR unsigned int overflow(unsigned int sign = 0) + { + #if HALF_ERRHANDLING + detail::raise(FE_OVERFLOW); + #endif + return (R==std::round_toward_infinity) ? (sign+0x7C00-(sign>>15)) : + (R==std::round_toward_neg_infinity) ? (sign+0x7BFF+(sign>>15)) : + (R==std::round_toward_zero) ? (sign|0x7BFF) : + (sign|0x7C00); + } + + /// Half-precision underflow. + /// \tparam R rounding mode to use + /// \param sign half-precision value with sign bit only + /// \return rounded underflowing half-precision value + /// \exception FE_UNDERFLOW + template HALF_CONSTEXPR_NOERR unsigned int underflow(unsigned int sign = 0) + { + #if HALF_ERRHANDLING + detail::raise(FE_UNDERFLOW); + #endif + return (R==std::round_toward_infinity) ? (sign+1-(sign>>15)) : + (R==std::round_toward_neg_infinity) ? (sign+(sign>>15)) : + sign; + } + + /// Round half-precision number. + /// \tparam R rounding mode to use + /// \tparam I `true` to always raise INEXACT exception, `false` to raise only for rounded results + /// \param value finite half-precision number to round + /// \param g guard bit (most significant discarded bit) + /// \param s sticky bit (or of all but the most significant discarded bits) + /// \return rounded half-precision value + /// \exception FE_OVERFLOW on overflows + /// \exception FE_UNDERFLOW on underflows + /// \exception FE_INEXACT if value had to be rounded or \a I is `true` + template HALF_CONSTEXPR_NOERR unsigned int rounded(unsigned int value, int g, int s) + { + #if HALF_ERRHANDLING + value += (R==std::round_to_nearest) ? (g&(s|value)) : + (R==std::round_toward_infinity) ? (~(value>>15)&(g|s)) : + (R==std::round_toward_neg_infinity) ? ((value>>15)&(g|s)) : 0; + if((value&0x7C00) == 0x7C00) + detail::raise(FE_OVERFLOW); + else if(value & 0x7C00) + detail::raise(FE_INEXACT, I || (g|s)!=0); + else + detail::raise(FE_UNDERFLOW, !(HALF_ERRHANDLING_UNDERFLOW_TO_INEXACT) || I || (g|s)!=0); + return value; + #else + return (R==std::round_to_nearest) ? (value+(g&(s|value))) : + (R==std::round_toward_infinity) ? (value+(~(value>>15)&(g|s))) : + (R==std::round_toward_neg_infinity) ? (value+((value>>15)&(g|s))) : + value; + #endif + } + + /// Round half-precision number to nearest integer value. + /// \tparam R rounding mode to use + /// \tparam E `true` for round to even, `false` for round away from zero + /// \tparam I `true` to raise INEXACT exception (if inexact), `false` to never raise it + /// \param value half-precision value to round + /// \return nearest integral half-precision value + /// \exception FE_INVALID for signaling NaN + /// \exception FE_INEXACT if value had to be rounded and \a I is `true` + template unsigned int integral(unsigned int value) + { + unsigned int abs = value & 0x7FFF; + if(abs < 0x3C00) + { + detail::raise(FE_INEXACT, I); + return ((R==std::round_to_nearest) ? (0x3C00&-static_cast(abs>=(0x3800+E))) : + (R==std::round_toward_infinity) ? (0x3C00&-(~(value>>15)&(abs!=0))) : + (R==std::round_toward_neg_infinity) ? (0x3C00&-static_cast(value>0x8000)) : + 0) | (value&0x8000); + } + if(abs >= 0x6400) + return (abs>0x7C00) ? detail::signal(value) : value; + unsigned int exp = 25 - (abs>>10), mask = (1<>exp)&E)) : + (R==std::round_toward_infinity) ? (mask&((value>>15)-1)) : + (R==std::round_toward_neg_infinity) ? (mask&-(value>>15)) : + 0) + value) & ~mask; + } + + /// Convert fixed point to half-precision floating-point. + /// \tparam R rounding mode to use + /// \tparam F number of fractional bits in [11,31] + /// \tparam S `true` for signed, `false` for unsigned + /// \tparam N `true` for additional normalization step, `false` if already normalized to 1.F + /// \tparam I `true` to always raise INEXACT exception, `false` to raise only for rounded results + /// \param m mantissa in Q1.F fixed point format + /// \param exp biased exponent - 1 + /// \param sign half-precision value with sign bit only + /// \param s sticky bit (or of all but the most significant already discarded bits) + /// \return value converted to half-precision + /// \exception FE_OVERFLOW on overflows + /// \exception FE_UNDERFLOW on underflows + /// \exception FE_INEXACT if value had to be rounded or \a I is `true` + template unsigned int fixed2half(uint32 m, int exp = 14, unsigned int sign = 0, int s = 0) + { + if(S) + { + uint32 msign = sign_mask(m); + m = (m^msign) - msign; + sign = msign & 0x8000; + } + if(N) + for(; m<(static_cast(1)<(sign+static_cast(m>>(F-10-exp)), static_cast((m>>(F-11-exp))&1), s|((m&((static_cast(1)<<(F-11-exp))-1))!=0)); + return rounded(sign+(exp<<10)+static_cast(m>>(F-10)), static_cast((m>>(F-11))&1), s|((m&((static_cast(1)<<(F-11))-1))!=0)); + } + + /// Convert IEEE single-precision to half-precision. + /// Credit for this goes to [Jeroen van der Zijp](ftp://ftp.fox-toolkit.org/pub/fasthalffloatconversion.pdf). + /// \tparam R rounding mode to use + /// \param value single-precision value to convert + /// \return rounded half-precision value + /// \exception FE_OVERFLOW on overflows + /// \exception FE_UNDERFLOW on underflows + /// \exception FE_INEXACT if value had to be rounded + template unsigned int float2half_impl(float value, true_type) + { + #if HALF_ENABLE_F16C_INTRINSICS + return _mm_cvtsi128_si32(_mm_cvtps_ph(_mm_set_ss(value), + (R==std::round_to_nearest) ? _MM_FROUND_TO_NEAREST_INT : + (R==std::round_toward_zero) ? _MM_FROUND_TO_ZERO : + (R==std::round_toward_infinity) ? _MM_FROUND_TO_POS_INF : + (R==std::round_toward_neg_infinity) ? _MM_FROUND_TO_NEG_INF : + _MM_FROUND_CUR_DIRECTION)); + #else + bits::type fbits; + std::memcpy(&fbits, &value, sizeof(float)); + #if 1 + unsigned int sign = (fbits>>16) & 0x8000; + fbits &= 0x7FFFFFFF; + if(fbits >= 0x7F800000) + return sign | 0x7C00 | ((fbits>0x7F800000) ? (0x200|((fbits>>13)&0x3FF)) : 0); + if(fbits >= 0x47800000) + return overflow(sign); + if(fbits >= 0x38800000) + return rounded(sign|(((fbits>>23)-112)<<10)|((fbits>>13)&0x3FF), (fbits>>12)&1, (fbits&0xFFF)!=0); + if(fbits >= 0x33000000) + { + int i = 125 - (fbits>>23); + fbits = (fbits&0x7FFFFF) | 0x800000; + return rounded(sign|(fbits>>(i+1)), (fbits>>i)&1, (fbits&((static_cast(1)<(sign); + return sign; + #else + static const uint16 base_table[512] = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080, 0x0100, + 0x0200, 0x0400, 0x0800, 0x0C00, 0x1000, 0x1400, 0x1800, 0x1C00, 0x2000, 0x2400, 0x2800, 0x2C00, 0x3000, 0x3400, 0x3800, 0x3C00, + 0x4000, 0x4400, 0x4800, 0x4C00, 0x5000, 0x5400, 0x5800, 0x5C00, 0x6000, 0x6400, 0x6800, 0x6C00, 0x7000, 0x7400, 0x7800, 0x7BFF, + 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, + 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, + 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, + 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, + 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, + 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, + 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7BFF, 0x7C00, + 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, + 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, + 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, + 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, + 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, + 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, + 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8001, 0x8002, 0x8004, 0x8008, 0x8010, 0x8020, 0x8040, 0x8080, 0x8100, + 0x8200, 0x8400, 0x8800, 0x8C00, 0x9000, 0x9400, 0x9800, 0x9C00, 0xA000, 0xA400, 0xA800, 0xAC00, 0xB000, 0xB400, 0xB800, 0xBC00, + 0xC000, 0xC400, 0xC800, 0xCC00, 0xD000, 0xD400, 0xD800, 0xDC00, 0xE000, 0xE400, 0xE800, 0xEC00, 0xF000, 0xF400, 0xF800, 0xFBFF, + 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, + 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, + 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, + 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, + 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, + 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, + 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFBFF, 0xFC00 }; + static const unsigned char shift_table[256] = { + 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 25, 25, 25, 25, 25, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 13 }; + int sexp = fbits >> 23, exp = sexp & 0xFF, i = shift_table[exp]; + fbits &= 0x7FFFFF; + uint32 m = (fbits|((exp!=0)<<23)) & -static_cast(exp!=0xFF); + return rounded(base_table[sexp]+(fbits>>i), (m>>(i-1))&1, (((static_cast(1)<<(i-1))-1)&m)!=0); + #endif + #endif + } + + /// Convert IEEE double-precision to half-precision. + /// \tparam R rounding mode to use + /// \param value double-precision value to convert + /// \return rounded half-precision value + /// \exception FE_OVERFLOW on overflows + /// \exception FE_UNDERFLOW on underflows + /// \exception FE_INEXACT if value had to be rounded + template unsigned int float2half_impl(double value, true_type) + { + #if HALF_ENABLE_F16C_INTRINSICS + if(R == std::round_indeterminate) + return _mm_cvtsi128_si32(_mm_cvtps_ph(_mm_cvtpd_ps(_mm_set_sd(value)), _MM_FROUND_CUR_DIRECTION)); + #endif + bits::type dbits; + std::memcpy(&dbits, &value, sizeof(double)); + uint32 hi = dbits >> 32, lo = dbits & 0xFFFFFFFF; + unsigned int sign = (hi>>16) & 0x8000; + hi &= 0x7FFFFFFF; + if(hi >= 0x7FF00000) + return sign | 0x7C00 | ((dbits&0xFFFFFFFFFFFFF) ? (0x200|((hi>>10)&0x3FF)) : 0); + if(hi >= 0x40F00000) + return overflow(sign); + if(hi >= 0x3F100000) + return rounded(sign|static_cast(((hi>>20)-1008)<<10)|static_cast((hi>>10)&0x3FF), static_cast((hi>>9)&1), ((hi&0x1FF)|lo)!=0); + if(hi >= 0x3E600000) + { + int i = static_cast(1018 - (hi>>20)); + hi = (hi&0xFFFFF) | 0x100000; + return rounded(sign|static_cast(hi>>(i+1)), static_cast((hi>>i)&1), ((hi&((static_cast(1)<(sign); + return sign; + } + + /// Convert non-IEEE floating-point to half-precision. + /// \tparam R rounding mode to use + /// \tparam T source type (builtin floating-point type) + /// \param value floating-point value to convert + /// \return rounded half-precision value + /// \exception FE_OVERFLOW on overflows + /// \exception FE_UNDERFLOW on underflows + /// \exception FE_INEXACT if value had to be rounded + template unsigned int float2half_impl(T value, ...) + { + unsigned int hbits = static_cast(builtin_signbit(value)) << 15; + if(value == T()) + return hbits; + if(builtin_isnan(value)) + return hbits | 0x7FFF; + if(builtin_isinf(value)) + return hbits | 0x7C00; + int exp; + std::frexp(value, &exp); + if(exp > 16) + return overflow(hbits); + if(exp < -13) + value = std::ldexp(value, 25); + else + { + value = std::ldexp(value, 12-exp); + hbits |= ((exp+13)<<10); + } + T ival, frac = std::modf(value, &ival); + int m = std::abs(static_cast(ival)); + return rounded(hbits+(m>>1), m&1, frac!=T()); + } + + /// Convert floating-point to half-precision. + /// \tparam R rounding mode to use + /// \tparam T source type (builtin floating-point type) + /// \param value floating-point value to convert + /// \return rounded half-precision value + /// \exception FE_OVERFLOW on overflows + /// \exception FE_UNDERFLOW on underflows + /// \exception FE_INEXACT if value had to be rounded + template unsigned int float2half(T value) + { + return float2half_impl(value, bool_type::is_iec559&&sizeof(typename bits::type)==sizeof(T)>()); + } + + /// Convert integer to half-precision floating-point. + /// \tparam R rounding mode to use + /// \tparam T type to convert (builtin integer type) + /// \param value integral value to convert + /// \return rounded half-precision value + /// \exception FE_OVERFLOW on overflows + /// \exception FE_INEXACT if value had to be rounded + template unsigned int int2half(T value) + { + unsigned int bits = static_cast(value<0) << 15; + if(!value) + return bits; + if(value > 0xFFE0 || (bits&&value < -0xFFE0)) + return overflow(bits); + unsigned int m = static_cast(value), exp = 24; + if(bits) + m = -m; + for(; m<0x400; m<<=1,--exp) ; + for(; m>0x7FF; m>>=1,++exp) ; + bits |= (exp<<10) + m; + return (exp>24) ? rounded(bits, static_cast(value>>(exp-25))&1, (((1<<(exp-25))-1)&value)!=0) : bits; + } + + /// Convert half-precision to IEEE single-precision. + /// Credit for this goes to [Jeroen van der Zijp](ftp://ftp.fox-toolkit.org/pub/fasthalffloatconversion.pdf). + /// \param value half-precision value to convert + /// \return single-precision value + inline float half2float_impl(unsigned int value, float, true_type) + { + #if HALF_ENABLE_F16C_INTRINSICS + return _mm_cvtss_f32(_mm_cvtph_ps(_mm_cvtsi32_si128(value))); + #else + #if 0 + bits::type fbits = static_cast::type>(value&0x8000) << 16; + int abs = value & 0x7FFF; + if(abs) + { + fbits |= 0x38000000 << static_cast(abs>=0x7C00); + for(; abs<0x400; abs<<=1,fbits-=0x800000) ; + fbits += static_cast::type>(abs) << 13; + } + #else + static const bits::type mantissa_table[2048] = { + 0x00000000, 0x33800000, 0x34000000, 0x34400000, 0x34800000, 0x34A00000, 0x34C00000, 0x34E00000, 0x35000000, 0x35100000, 0x35200000, 0x35300000, 0x35400000, 0x35500000, 0x35600000, 0x35700000, + 0x35800000, 0x35880000, 0x35900000, 0x35980000, 0x35A00000, 0x35A80000, 0x35B00000, 0x35B80000, 0x35C00000, 0x35C80000, 0x35D00000, 0x35D80000, 0x35E00000, 0x35E80000, 0x35F00000, 0x35F80000, + 0x36000000, 0x36040000, 0x36080000, 0x360C0000, 0x36100000, 0x36140000, 0x36180000, 0x361C0000, 0x36200000, 0x36240000, 0x36280000, 0x362C0000, 0x36300000, 0x36340000, 0x36380000, 0x363C0000, + 0x36400000, 0x36440000, 0x36480000, 0x364C0000, 0x36500000, 0x36540000, 0x36580000, 0x365C0000, 0x36600000, 0x36640000, 0x36680000, 0x366C0000, 0x36700000, 0x36740000, 0x36780000, 0x367C0000, + 0x36800000, 0x36820000, 0x36840000, 0x36860000, 0x36880000, 0x368A0000, 0x368C0000, 0x368E0000, 0x36900000, 0x36920000, 0x36940000, 0x36960000, 0x36980000, 0x369A0000, 0x369C0000, 0x369E0000, + 0x36A00000, 0x36A20000, 0x36A40000, 0x36A60000, 0x36A80000, 0x36AA0000, 0x36AC0000, 0x36AE0000, 0x36B00000, 0x36B20000, 0x36B40000, 0x36B60000, 0x36B80000, 0x36BA0000, 0x36BC0000, 0x36BE0000, + 0x36C00000, 0x36C20000, 0x36C40000, 0x36C60000, 0x36C80000, 0x36CA0000, 0x36CC0000, 0x36CE0000, 0x36D00000, 0x36D20000, 0x36D40000, 0x36D60000, 0x36D80000, 0x36DA0000, 0x36DC0000, 0x36DE0000, + 0x36E00000, 0x36E20000, 0x36E40000, 0x36E60000, 0x36E80000, 0x36EA0000, 0x36EC0000, 0x36EE0000, 0x36F00000, 0x36F20000, 0x36F40000, 0x36F60000, 0x36F80000, 0x36FA0000, 0x36FC0000, 0x36FE0000, + 0x37000000, 0x37010000, 0x37020000, 0x37030000, 0x37040000, 0x37050000, 0x37060000, 0x37070000, 0x37080000, 0x37090000, 0x370A0000, 0x370B0000, 0x370C0000, 0x370D0000, 0x370E0000, 0x370F0000, + 0x37100000, 0x37110000, 0x37120000, 0x37130000, 0x37140000, 0x37150000, 0x37160000, 0x37170000, 0x37180000, 0x37190000, 0x371A0000, 0x371B0000, 0x371C0000, 0x371D0000, 0x371E0000, 0x371F0000, + 0x37200000, 0x37210000, 0x37220000, 0x37230000, 0x37240000, 0x37250000, 0x37260000, 0x37270000, 0x37280000, 0x37290000, 0x372A0000, 0x372B0000, 0x372C0000, 0x372D0000, 0x372E0000, 0x372F0000, + 0x37300000, 0x37310000, 0x37320000, 0x37330000, 0x37340000, 0x37350000, 0x37360000, 0x37370000, 0x37380000, 0x37390000, 0x373A0000, 0x373B0000, 0x373C0000, 0x373D0000, 0x373E0000, 0x373F0000, + 0x37400000, 0x37410000, 0x37420000, 0x37430000, 0x37440000, 0x37450000, 0x37460000, 0x37470000, 0x37480000, 0x37490000, 0x374A0000, 0x374B0000, 0x374C0000, 0x374D0000, 0x374E0000, 0x374F0000, + 0x37500000, 0x37510000, 0x37520000, 0x37530000, 0x37540000, 0x37550000, 0x37560000, 0x37570000, 0x37580000, 0x37590000, 0x375A0000, 0x375B0000, 0x375C0000, 0x375D0000, 0x375E0000, 0x375F0000, + 0x37600000, 0x37610000, 0x37620000, 0x37630000, 0x37640000, 0x37650000, 0x37660000, 0x37670000, 0x37680000, 0x37690000, 0x376A0000, 0x376B0000, 0x376C0000, 0x376D0000, 0x376E0000, 0x376F0000, + 0x37700000, 0x37710000, 0x37720000, 0x37730000, 0x37740000, 0x37750000, 0x37760000, 0x37770000, 0x37780000, 0x37790000, 0x377A0000, 0x377B0000, 0x377C0000, 0x377D0000, 0x377E0000, 0x377F0000, + 0x37800000, 0x37808000, 0x37810000, 0x37818000, 0x37820000, 0x37828000, 0x37830000, 0x37838000, 0x37840000, 0x37848000, 0x37850000, 0x37858000, 0x37860000, 0x37868000, 0x37870000, 0x37878000, + 0x37880000, 0x37888000, 0x37890000, 0x37898000, 0x378A0000, 0x378A8000, 0x378B0000, 0x378B8000, 0x378C0000, 0x378C8000, 0x378D0000, 0x378D8000, 0x378E0000, 0x378E8000, 0x378F0000, 0x378F8000, + 0x37900000, 0x37908000, 0x37910000, 0x37918000, 0x37920000, 0x37928000, 0x37930000, 0x37938000, 0x37940000, 0x37948000, 0x37950000, 0x37958000, 0x37960000, 0x37968000, 0x37970000, 0x37978000, + 0x37980000, 0x37988000, 0x37990000, 0x37998000, 0x379A0000, 0x379A8000, 0x379B0000, 0x379B8000, 0x379C0000, 0x379C8000, 0x379D0000, 0x379D8000, 0x379E0000, 0x379E8000, 0x379F0000, 0x379F8000, + 0x37A00000, 0x37A08000, 0x37A10000, 0x37A18000, 0x37A20000, 0x37A28000, 0x37A30000, 0x37A38000, 0x37A40000, 0x37A48000, 0x37A50000, 0x37A58000, 0x37A60000, 0x37A68000, 0x37A70000, 0x37A78000, + 0x37A80000, 0x37A88000, 0x37A90000, 0x37A98000, 0x37AA0000, 0x37AA8000, 0x37AB0000, 0x37AB8000, 0x37AC0000, 0x37AC8000, 0x37AD0000, 0x37AD8000, 0x37AE0000, 0x37AE8000, 0x37AF0000, 0x37AF8000, + 0x37B00000, 0x37B08000, 0x37B10000, 0x37B18000, 0x37B20000, 0x37B28000, 0x37B30000, 0x37B38000, 0x37B40000, 0x37B48000, 0x37B50000, 0x37B58000, 0x37B60000, 0x37B68000, 0x37B70000, 0x37B78000, + 0x37B80000, 0x37B88000, 0x37B90000, 0x37B98000, 0x37BA0000, 0x37BA8000, 0x37BB0000, 0x37BB8000, 0x37BC0000, 0x37BC8000, 0x37BD0000, 0x37BD8000, 0x37BE0000, 0x37BE8000, 0x37BF0000, 0x37BF8000, + 0x37C00000, 0x37C08000, 0x37C10000, 0x37C18000, 0x37C20000, 0x37C28000, 0x37C30000, 0x37C38000, 0x37C40000, 0x37C48000, 0x37C50000, 0x37C58000, 0x37C60000, 0x37C68000, 0x37C70000, 0x37C78000, + 0x37C80000, 0x37C88000, 0x37C90000, 0x37C98000, 0x37CA0000, 0x37CA8000, 0x37CB0000, 0x37CB8000, 0x37CC0000, 0x37CC8000, 0x37CD0000, 0x37CD8000, 0x37CE0000, 0x37CE8000, 0x37CF0000, 0x37CF8000, + 0x37D00000, 0x37D08000, 0x37D10000, 0x37D18000, 0x37D20000, 0x37D28000, 0x37D30000, 0x37D38000, 0x37D40000, 0x37D48000, 0x37D50000, 0x37D58000, 0x37D60000, 0x37D68000, 0x37D70000, 0x37D78000, + 0x37D80000, 0x37D88000, 0x37D90000, 0x37D98000, 0x37DA0000, 0x37DA8000, 0x37DB0000, 0x37DB8000, 0x37DC0000, 0x37DC8000, 0x37DD0000, 0x37DD8000, 0x37DE0000, 0x37DE8000, 0x37DF0000, 0x37DF8000, + 0x37E00000, 0x37E08000, 0x37E10000, 0x37E18000, 0x37E20000, 0x37E28000, 0x37E30000, 0x37E38000, 0x37E40000, 0x37E48000, 0x37E50000, 0x37E58000, 0x37E60000, 0x37E68000, 0x37E70000, 0x37E78000, + 0x37E80000, 0x37E88000, 0x37E90000, 0x37E98000, 0x37EA0000, 0x37EA8000, 0x37EB0000, 0x37EB8000, 0x37EC0000, 0x37EC8000, 0x37ED0000, 0x37ED8000, 0x37EE0000, 0x37EE8000, 0x37EF0000, 0x37EF8000, + 0x37F00000, 0x37F08000, 0x37F10000, 0x37F18000, 0x37F20000, 0x37F28000, 0x37F30000, 0x37F38000, 0x37F40000, 0x37F48000, 0x37F50000, 0x37F58000, 0x37F60000, 0x37F68000, 0x37F70000, 0x37F78000, + 0x37F80000, 0x37F88000, 0x37F90000, 0x37F98000, 0x37FA0000, 0x37FA8000, 0x37FB0000, 0x37FB8000, 0x37FC0000, 0x37FC8000, 0x37FD0000, 0x37FD8000, 0x37FE0000, 0x37FE8000, 0x37FF0000, 0x37FF8000, + 0x38000000, 0x38004000, 0x38008000, 0x3800C000, 0x38010000, 0x38014000, 0x38018000, 0x3801C000, 0x38020000, 0x38024000, 0x38028000, 0x3802C000, 0x38030000, 0x38034000, 0x38038000, 0x3803C000, + 0x38040000, 0x38044000, 0x38048000, 0x3804C000, 0x38050000, 0x38054000, 0x38058000, 0x3805C000, 0x38060000, 0x38064000, 0x38068000, 0x3806C000, 0x38070000, 0x38074000, 0x38078000, 0x3807C000, + 0x38080000, 0x38084000, 0x38088000, 0x3808C000, 0x38090000, 0x38094000, 0x38098000, 0x3809C000, 0x380A0000, 0x380A4000, 0x380A8000, 0x380AC000, 0x380B0000, 0x380B4000, 0x380B8000, 0x380BC000, + 0x380C0000, 0x380C4000, 0x380C8000, 0x380CC000, 0x380D0000, 0x380D4000, 0x380D8000, 0x380DC000, 0x380E0000, 0x380E4000, 0x380E8000, 0x380EC000, 0x380F0000, 0x380F4000, 0x380F8000, 0x380FC000, + 0x38100000, 0x38104000, 0x38108000, 0x3810C000, 0x38110000, 0x38114000, 0x38118000, 0x3811C000, 0x38120000, 0x38124000, 0x38128000, 0x3812C000, 0x38130000, 0x38134000, 0x38138000, 0x3813C000, + 0x38140000, 0x38144000, 0x38148000, 0x3814C000, 0x38150000, 0x38154000, 0x38158000, 0x3815C000, 0x38160000, 0x38164000, 0x38168000, 0x3816C000, 0x38170000, 0x38174000, 0x38178000, 0x3817C000, + 0x38180000, 0x38184000, 0x38188000, 0x3818C000, 0x38190000, 0x38194000, 0x38198000, 0x3819C000, 0x381A0000, 0x381A4000, 0x381A8000, 0x381AC000, 0x381B0000, 0x381B4000, 0x381B8000, 0x381BC000, + 0x381C0000, 0x381C4000, 0x381C8000, 0x381CC000, 0x381D0000, 0x381D4000, 0x381D8000, 0x381DC000, 0x381E0000, 0x381E4000, 0x381E8000, 0x381EC000, 0x381F0000, 0x381F4000, 0x381F8000, 0x381FC000, + 0x38200000, 0x38204000, 0x38208000, 0x3820C000, 0x38210000, 0x38214000, 0x38218000, 0x3821C000, 0x38220000, 0x38224000, 0x38228000, 0x3822C000, 0x38230000, 0x38234000, 0x38238000, 0x3823C000, + 0x38240000, 0x38244000, 0x38248000, 0x3824C000, 0x38250000, 0x38254000, 0x38258000, 0x3825C000, 0x38260000, 0x38264000, 0x38268000, 0x3826C000, 0x38270000, 0x38274000, 0x38278000, 0x3827C000, + 0x38280000, 0x38284000, 0x38288000, 0x3828C000, 0x38290000, 0x38294000, 0x38298000, 0x3829C000, 0x382A0000, 0x382A4000, 0x382A8000, 0x382AC000, 0x382B0000, 0x382B4000, 0x382B8000, 0x382BC000, + 0x382C0000, 0x382C4000, 0x382C8000, 0x382CC000, 0x382D0000, 0x382D4000, 0x382D8000, 0x382DC000, 0x382E0000, 0x382E4000, 0x382E8000, 0x382EC000, 0x382F0000, 0x382F4000, 0x382F8000, 0x382FC000, + 0x38300000, 0x38304000, 0x38308000, 0x3830C000, 0x38310000, 0x38314000, 0x38318000, 0x3831C000, 0x38320000, 0x38324000, 0x38328000, 0x3832C000, 0x38330000, 0x38334000, 0x38338000, 0x3833C000, + 0x38340000, 0x38344000, 0x38348000, 0x3834C000, 0x38350000, 0x38354000, 0x38358000, 0x3835C000, 0x38360000, 0x38364000, 0x38368000, 0x3836C000, 0x38370000, 0x38374000, 0x38378000, 0x3837C000, + 0x38380000, 0x38384000, 0x38388000, 0x3838C000, 0x38390000, 0x38394000, 0x38398000, 0x3839C000, 0x383A0000, 0x383A4000, 0x383A8000, 0x383AC000, 0x383B0000, 0x383B4000, 0x383B8000, 0x383BC000, + 0x383C0000, 0x383C4000, 0x383C8000, 0x383CC000, 0x383D0000, 0x383D4000, 0x383D8000, 0x383DC000, 0x383E0000, 0x383E4000, 0x383E8000, 0x383EC000, 0x383F0000, 0x383F4000, 0x383F8000, 0x383FC000, + 0x38400000, 0x38404000, 0x38408000, 0x3840C000, 0x38410000, 0x38414000, 0x38418000, 0x3841C000, 0x38420000, 0x38424000, 0x38428000, 0x3842C000, 0x38430000, 0x38434000, 0x38438000, 0x3843C000, + 0x38440000, 0x38444000, 0x38448000, 0x3844C000, 0x38450000, 0x38454000, 0x38458000, 0x3845C000, 0x38460000, 0x38464000, 0x38468000, 0x3846C000, 0x38470000, 0x38474000, 0x38478000, 0x3847C000, + 0x38480000, 0x38484000, 0x38488000, 0x3848C000, 0x38490000, 0x38494000, 0x38498000, 0x3849C000, 0x384A0000, 0x384A4000, 0x384A8000, 0x384AC000, 0x384B0000, 0x384B4000, 0x384B8000, 0x384BC000, + 0x384C0000, 0x384C4000, 0x384C8000, 0x384CC000, 0x384D0000, 0x384D4000, 0x384D8000, 0x384DC000, 0x384E0000, 0x384E4000, 0x384E8000, 0x384EC000, 0x384F0000, 0x384F4000, 0x384F8000, 0x384FC000, + 0x38500000, 0x38504000, 0x38508000, 0x3850C000, 0x38510000, 0x38514000, 0x38518000, 0x3851C000, 0x38520000, 0x38524000, 0x38528000, 0x3852C000, 0x38530000, 0x38534000, 0x38538000, 0x3853C000, + 0x38540000, 0x38544000, 0x38548000, 0x3854C000, 0x38550000, 0x38554000, 0x38558000, 0x3855C000, 0x38560000, 0x38564000, 0x38568000, 0x3856C000, 0x38570000, 0x38574000, 0x38578000, 0x3857C000, + 0x38580000, 0x38584000, 0x38588000, 0x3858C000, 0x38590000, 0x38594000, 0x38598000, 0x3859C000, 0x385A0000, 0x385A4000, 0x385A8000, 0x385AC000, 0x385B0000, 0x385B4000, 0x385B8000, 0x385BC000, + 0x385C0000, 0x385C4000, 0x385C8000, 0x385CC000, 0x385D0000, 0x385D4000, 0x385D8000, 0x385DC000, 0x385E0000, 0x385E4000, 0x385E8000, 0x385EC000, 0x385F0000, 0x385F4000, 0x385F8000, 0x385FC000, + 0x38600000, 0x38604000, 0x38608000, 0x3860C000, 0x38610000, 0x38614000, 0x38618000, 0x3861C000, 0x38620000, 0x38624000, 0x38628000, 0x3862C000, 0x38630000, 0x38634000, 0x38638000, 0x3863C000, + 0x38640000, 0x38644000, 0x38648000, 0x3864C000, 0x38650000, 0x38654000, 0x38658000, 0x3865C000, 0x38660000, 0x38664000, 0x38668000, 0x3866C000, 0x38670000, 0x38674000, 0x38678000, 0x3867C000, + 0x38680000, 0x38684000, 0x38688000, 0x3868C000, 0x38690000, 0x38694000, 0x38698000, 0x3869C000, 0x386A0000, 0x386A4000, 0x386A8000, 0x386AC000, 0x386B0000, 0x386B4000, 0x386B8000, 0x386BC000, + 0x386C0000, 0x386C4000, 0x386C8000, 0x386CC000, 0x386D0000, 0x386D4000, 0x386D8000, 0x386DC000, 0x386E0000, 0x386E4000, 0x386E8000, 0x386EC000, 0x386F0000, 0x386F4000, 0x386F8000, 0x386FC000, + 0x38700000, 0x38704000, 0x38708000, 0x3870C000, 0x38710000, 0x38714000, 0x38718000, 0x3871C000, 0x38720000, 0x38724000, 0x38728000, 0x3872C000, 0x38730000, 0x38734000, 0x38738000, 0x3873C000, + 0x38740000, 0x38744000, 0x38748000, 0x3874C000, 0x38750000, 0x38754000, 0x38758000, 0x3875C000, 0x38760000, 0x38764000, 0x38768000, 0x3876C000, 0x38770000, 0x38774000, 0x38778000, 0x3877C000, + 0x38780000, 0x38784000, 0x38788000, 0x3878C000, 0x38790000, 0x38794000, 0x38798000, 0x3879C000, 0x387A0000, 0x387A4000, 0x387A8000, 0x387AC000, 0x387B0000, 0x387B4000, 0x387B8000, 0x387BC000, + 0x387C0000, 0x387C4000, 0x387C8000, 0x387CC000, 0x387D0000, 0x387D4000, 0x387D8000, 0x387DC000, 0x387E0000, 0x387E4000, 0x387E8000, 0x387EC000, 0x387F0000, 0x387F4000, 0x387F8000, 0x387FC000, + 0x38000000, 0x38002000, 0x38004000, 0x38006000, 0x38008000, 0x3800A000, 0x3800C000, 0x3800E000, 0x38010000, 0x38012000, 0x38014000, 0x38016000, 0x38018000, 0x3801A000, 0x3801C000, 0x3801E000, + 0x38020000, 0x38022000, 0x38024000, 0x38026000, 0x38028000, 0x3802A000, 0x3802C000, 0x3802E000, 0x38030000, 0x38032000, 0x38034000, 0x38036000, 0x38038000, 0x3803A000, 0x3803C000, 0x3803E000, + 0x38040000, 0x38042000, 0x38044000, 0x38046000, 0x38048000, 0x3804A000, 0x3804C000, 0x3804E000, 0x38050000, 0x38052000, 0x38054000, 0x38056000, 0x38058000, 0x3805A000, 0x3805C000, 0x3805E000, + 0x38060000, 0x38062000, 0x38064000, 0x38066000, 0x38068000, 0x3806A000, 0x3806C000, 0x3806E000, 0x38070000, 0x38072000, 0x38074000, 0x38076000, 0x38078000, 0x3807A000, 0x3807C000, 0x3807E000, + 0x38080000, 0x38082000, 0x38084000, 0x38086000, 0x38088000, 0x3808A000, 0x3808C000, 0x3808E000, 0x38090000, 0x38092000, 0x38094000, 0x38096000, 0x38098000, 0x3809A000, 0x3809C000, 0x3809E000, + 0x380A0000, 0x380A2000, 0x380A4000, 0x380A6000, 0x380A8000, 0x380AA000, 0x380AC000, 0x380AE000, 0x380B0000, 0x380B2000, 0x380B4000, 0x380B6000, 0x380B8000, 0x380BA000, 0x380BC000, 0x380BE000, + 0x380C0000, 0x380C2000, 0x380C4000, 0x380C6000, 0x380C8000, 0x380CA000, 0x380CC000, 0x380CE000, 0x380D0000, 0x380D2000, 0x380D4000, 0x380D6000, 0x380D8000, 0x380DA000, 0x380DC000, 0x380DE000, + 0x380E0000, 0x380E2000, 0x380E4000, 0x380E6000, 0x380E8000, 0x380EA000, 0x380EC000, 0x380EE000, 0x380F0000, 0x380F2000, 0x380F4000, 0x380F6000, 0x380F8000, 0x380FA000, 0x380FC000, 0x380FE000, + 0x38100000, 0x38102000, 0x38104000, 0x38106000, 0x38108000, 0x3810A000, 0x3810C000, 0x3810E000, 0x38110000, 0x38112000, 0x38114000, 0x38116000, 0x38118000, 0x3811A000, 0x3811C000, 0x3811E000, + 0x38120000, 0x38122000, 0x38124000, 0x38126000, 0x38128000, 0x3812A000, 0x3812C000, 0x3812E000, 0x38130000, 0x38132000, 0x38134000, 0x38136000, 0x38138000, 0x3813A000, 0x3813C000, 0x3813E000, + 0x38140000, 0x38142000, 0x38144000, 0x38146000, 0x38148000, 0x3814A000, 0x3814C000, 0x3814E000, 0x38150000, 0x38152000, 0x38154000, 0x38156000, 0x38158000, 0x3815A000, 0x3815C000, 0x3815E000, + 0x38160000, 0x38162000, 0x38164000, 0x38166000, 0x38168000, 0x3816A000, 0x3816C000, 0x3816E000, 0x38170000, 0x38172000, 0x38174000, 0x38176000, 0x38178000, 0x3817A000, 0x3817C000, 0x3817E000, + 0x38180000, 0x38182000, 0x38184000, 0x38186000, 0x38188000, 0x3818A000, 0x3818C000, 0x3818E000, 0x38190000, 0x38192000, 0x38194000, 0x38196000, 0x38198000, 0x3819A000, 0x3819C000, 0x3819E000, + 0x381A0000, 0x381A2000, 0x381A4000, 0x381A6000, 0x381A8000, 0x381AA000, 0x381AC000, 0x381AE000, 0x381B0000, 0x381B2000, 0x381B4000, 0x381B6000, 0x381B8000, 0x381BA000, 0x381BC000, 0x381BE000, + 0x381C0000, 0x381C2000, 0x381C4000, 0x381C6000, 0x381C8000, 0x381CA000, 0x381CC000, 0x381CE000, 0x381D0000, 0x381D2000, 0x381D4000, 0x381D6000, 0x381D8000, 0x381DA000, 0x381DC000, 0x381DE000, + 0x381E0000, 0x381E2000, 0x381E4000, 0x381E6000, 0x381E8000, 0x381EA000, 0x381EC000, 0x381EE000, 0x381F0000, 0x381F2000, 0x381F4000, 0x381F6000, 0x381F8000, 0x381FA000, 0x381FC000, 0x381FE000, + 0x38200000, 0x38202000, 0x38204000, 0x38206000, 0x38208000, 0x3820A000, 0x3820C000, 0x3820E000, 0x38210000, 0x38212000, 0x38214000, 0x38216000, 0x38218000, 0x3821A000, 0x3821C000, 0x3821E000, + 0x38220000, 0x38222000, 0x38224000, 0x38226000, 0x38228000, 0x3822A000, 0x3822C000, 0x3822E000, 0x38230000, 0x38232000, 0x38234000, 0x38236000, 0x38238000, 0x3823A000, 0x3823C000, 0x3823E000, + 0x38240000, 0x38242000, 0x38244000, 0x38246000, 0x38248000, 0x3824A000, 0x3824C000, 0x3824E000, 0x38250000, 0x38252000, 0x38254000, 0x38256000, 0x38258000, 0x3825A000, 0x3825C000, 0x3825E000, + 0x38260000, 0x38262000, 0x38264000, 0x38266000, 0x38268000, 0x3826A000, 0x3826C000, 0x3826E000, 0x38270000, 0x38272000, 0x38274000, 0x38276000, 0x38278000, 0x3827A000, 0x3827C000, 0x3827E000, + 0x38280000, 0x38282000, 0x38284000, 0x38286000, 0x38288000, 0x3828A000, 0x3828C000, 0x3828E000, 0x38290000, 0x38292000, 0x38294000, 0x38296000, 0x38298000, 0x3829A000, 0x3829C000, 0x3829E000, + 0x382A0000, 0x382A2000, 0x382A4000, 0x382A6000, 0x382A8000, 0x382AA000, 0x382AC000, 0x382AE000, 0x382B0000, 0x382B2000, 0x382B4000, 0x382B6000, 0x382B8000, 0x382BA000, 0x382BC000, 0x382BE000, + 0x382C0000, 0x382C2000, 0x382C4000, 0x382C6000, 0x382C8000, 0x382CA000, 0x382CC000, 0x382CE000, 0x382D0000, 0x382D2000, 0x382D4000, 0x382D6000, 0x382D8000, 0x382DA000, 0x382DC000, 0x382DE000, + 0x382E0000, 0x382E2000, 0x382E4000, 0x382E6000, 0x382E8000, 0x382EA000, 0x382EC000, 0x382EE000, 0x382F0000, 0x382F2000, 0x382F4000, 0x382F6000, 0x382F8000, 0x382FA000, 0x382FC000, 0x382FE000, + 0x38300000, 0x38302000, 0x38304000, 0x38306000, 0x38308000, 0x3830A000, 0x3830C000, 0x3830E000, 0x38310000, 0x38312000, 0x38314000, 0x38316000, 0x38318000, 0x3831A000, 0x3831C000, 0x3831E000, + 0x38320000, 0x38322000, 0x38324000, 0x38326000, 0x38328000, 0x3832A000, 0x3832C000, 0x3832E000, 0x38330000, 0x38332000, 0x38334000, 0x38336000, 0x38338000, 0x3833A000, 0x3833C000, 0x3833E000, + 0x38340000, 0x38342000, 0x38344000, 0x38346000, 0x38348000, 0x3834A000, 0x3834C000, 0x3834E000, 0x38350000, 0x38352000, 0x38354000, 0x38356000, 0x38358000, 0x3835A000, 0x3835C000, 0x3835E000, + 0x38360000, 0x38362000, 0x38364000, 0x38366000, 0x38368000, 0x3836A000, 0x3836C000, 0x3836E000, 0x38370000, 0x38372000, 0x38374000, 0x38376000, 0x38378000, 0x3837A000, 0x3837C000, 0x3837E000, + 0x38380000, 0x38382000, 0x38384000, 0x38386000, 0x38388000, 0x3838A000, 0x3838C000, 0x3838E000, 0x38390000, 0x38392000, 0x38394000, 0x38396000, 0x38398000, 0x3839A000, 0x3839C000, 0x3839E000, + 0x383A0000, 0x383A2000, 0x383A4000, 0x383A6000, 0x383A8000, 0x383AA000, 0x383AC000, 0x383AE000, 0x383B0000, 0x383B2000, 0x383B4000, 0x383B6000, 0x383B8000, 0x383BA000, 0x383BC000, 0x383BE000, + 0x383C0000, 0x383C2000, 0x383C4000, 0x383C6000, 0x383C8000, 0x383CA000, 0x383CC000, 0x383CE000, 0x383D0000, 0x383D2000, 0x383D4000, 0x383D6000, 0x383D8000, 0x383DA000, 0x383DC000, 0x383DE000, + 0x383E0000, 0x383E2000, 0x383E4000, 0x383E6000, 0x383E8000, 0x383EA000, 0x383EC000, 0x383EE000, 0x383F0000, 0x383F2000, 0x383F4000, 0x383F6000, 0x383F8000, 0x383FA000, 0x383FC000, 0x383FE000, + 0x38400000, 0x38402000, 0x38404000, 0x38406000, 0x38408000, 0x3840A000, 0x3840C000, 0x3840E000, 0x38410000, 0x38412000, 0x38414000, 0x38416000, 0x38418000, 0x3841A000, 0x3841C000, 0x3841E000, + 0x38420000, 0x38422000, 0x38424000, 0x38426000, 0x38428000, 0x3842A000, 0x3842C000, 0x3842E000, 0x38430000, 0x38432000, 0x38434000, 0x38436000, 0x38438000, 0x3843A000, 0x3843C000, 0x3843E000, + 0x38440000, 0x38442000, 0x38444000, 0x38446000, 0x38448000, 0x3844A000, 0x3844C000, 0x3844E000, 0x38450000, 0x38452000, 0x38454000, 0x38456000, 0x38458000, 0x3845A000, 0x3845C000, 0x3845E000, + 0x38460000, 0x38462000, 0x38464000, 0x38466000, 0x38468000, 0x3846A000, 0x3846C000, 0x3846E000, 0x38470000, 0x38472000, 0x38474000, 0x38476000, 0x38478000, 0x3847A000, 0x3847C000, 0x3847E000, + 0x38480000, 0x38482000, 0x38484000, 0x38486000, 0x38488000, 0x3848A000, 0x3848C000, 0x3848E000, 0x38490000, 0x38492000, 0x38494000, 0x38496000, 0x38498000, 0x3849A000, 0x3849C000, 0x3849E000, + 0x384A0000, 0x384A2000, 0x384A4000, 0x384A6000, 0x384A8000, 0x384AA000, 0x384AC000, 0x384AE000, 0x384B0000, 0x384B2000, 0x384B4000, 0x384B6000, 0x384B8000, 0x384BA000, 0x384BC000, 0x384BE000, + 0x384C0000, 0x384C2000, 0x384C4000, 0x384C6000, 0x384C8000, 0x384CA000, 0x384CC000, 0x384CE000, 0x384D0000, 0x384D2000, 0x384D4000, 0x384D6000, 0x384D8000, 0x384DA000, 0x384DC000, 0x384DE000, + 0x384E0000, 0x384E2000, 0x384E4000, 0x384E6000, 0x384E8000, 0x384EA000, 0x384EC000, 0x384EE000, 0x384F0000, 0x384F2000, 0x384F4000, 0x384F6000, 0x384F8000, 0x384FA000, 0x384FC000, 0x384FE000, + 0x38500000, 0x38502000, 0x38504000, 0x38506000, 0x38508000, 0x3850A000, 0x3850C000, 0x3850E000, 0x38510000, 0x38512000, 0x38514000, 0x38516000, 0x38518000, 0x3851A000, 0x3851C000, 0x3851E000, + 0x38520000, 0x38522000, 0x38524000, 0x38526000, 0x38528000, 0x3852A000, 0x3852C000, 0x3852E000, 0x38530000, 0x38532000, 0x38534000, 0x38536000, 0x38538000, 0x3853A000, 0x3853C000, 0x3853E000, + 0x38540000, 0x38542000, 0x38544000, 0x38546000, 0x38548000, 0x3854A000, 0x3854C000, 0x3854E000, 0x38550000, 0x38552000, 0x38554000, 0x38556000, 0x38558000, 0x3855A000, 0x3855C000, 0x3855E000, + 0x38560000, 0x38562000, 0x38564000, 0x38566000, 0x38568000, 0x3856A000, 0x3856C000, 0x3856E000, 0x38570000, 0x38572000, 0x38574000, 0x38576000, 0x38578000, 0x3857A000, 0x3857C000, 0x3857E000, + 0x38580000, 0x38582000, 0x38584000, 0x38586000, 0x38588000, 0x3858A000, 0x3858C000, 0x3858E000, 0x38590000, 0x38592000, 0x38594000, 0x38596000, 0x38598000, 0x3859A000, 0x3859C000, 0x3859E000, + 0x385A0000, 0x385A2000, 0x385A4000, 0x385A6000, 0x385A8000, 0x385AA000, 0x385AC000, 0x385AE000, 0x385B0000, 0x385B2000, 0x385B4000, 0x385B6000, 0x385B8000, 0x385BA000, 0x385BC000, 0x385BE000, + 0x385C0000, 0x385C2000, 0x385C4000, 0x385C6000, 0x385C8000, 0x385CA000, 0x385CC000, 0x385CE000, 0x385D0000, 0x385D2000, 0x385D4000, 0x385D6000, 0x385D8000, 0x385DA000, 0x385DC000, 0x385DE000, + 0x385E0000, 0x385E2000, 0x385E4000, 0x385E6000, 0x385E8000, 0x385EA000, 0x385EC000, 0x385EE000, 0x385F0000, 0x385F2000, 0x385F4000, 0x385F6000, 0x385F8000, 0x385FA000, 0x385FC000, 0x385FE000, + 0x38600000, 0x38602000, 0x38604000, 0x38606000, 0x38608000, 0x3860A000, 0x3860C000, 0x3860E000, 0x38610000, 0x38612000, 0x38614000, 0x38616000, 0x38618000, 0x3861A000, 0x3861C000, 0x3861E000, + 0x38620000, 0x38622000, 0x38624000, 0x38626000, 0x38628000, 0x3862A000, 0x3862C000, 0x3862E000, 0x38630000, 0x38632000, 0x38634000, 0x38636000, 0x38638000, 0x3863A000, 0x3863C000, 0x3863E000, + 0x38640000, 0x38642000, 0x38644000, 0x38646000, 0x38648000, 0x3864A000, 0x3864C000, 0x3864E000, 0x38650000, 0x38652000, 0x38654000, 0x38656000, 0x38658000, 0x3865A000, 0x3865C000, 0x3865E000, + 0x38660000, 0x38662000, 0x38664000, 0x38666000, 0x38668000, 0x3866A000, 0x3866C000, 0x3866E000, 0x38670000, 0x38672000, 0x38674000, 0x38676000, 0x38678000, 0x3867A000, 0x3867C000, 0x3867E000, + 0x38680000, 0x38682000, 0x38684000, 0x38686000, 0x38688000, 0x3868A000, 0x3868C000, 0x3868E000, 0x38690000, 0x38692000, 0x38694000, 0x38696000, 0x38698000, 0x3869A000, 0x3869C000, 0x3869E000, + 0x386A0000, 0x386A2000, 0x386A4000, 0x386A6000, 0x386A8000, 0x386AA000, 0x386AC000, 0x386AE000, 0x386B0000, 0x386B2000, 0x386B4000, 0x386B6000, 0x386B8000, 0x386BA000, 0x386BC000, 0x386BE000, + 0x386C0000, 0x386C2000, 0x386C4000, 0x386C6000, 0x386C8000, 0x386CA000, 0x386CC000, 0x386CE000, 0x386D0000, 0x386D2000, 0x386D4000, 0x386D6000, 0x386D8000, 0x386DA000, 0x386DC000, 0x386DE000, + 0x386E0000, 0x386E2000, 0x386E4000, 0x386E6000, 0x386E8000, 0x386EA000, 0x386EC000, 0x386EE000, 0x386F0000, 0x386F2000, 0x386F4000, 0x386F6000, 0x386F8000, 0x386FA000, 0x386FC000, 0x386FE000, + 0x38700000, 0x38702000, 0x38704000, 0x38706000, 0x38708000, 0x3870A000, 0x3870C000, 0x3870E000, 0x38710000, 0x38712000, 0x38714000, 0x38716000, 0x38718000, 0x3871A000, 0x3871C000, 0x3871E000, + 0x38720000, 0x38722000, 0x38724000, 0x38726000, 0x38728000, 0x3872A000, 0x3872C000, 0x3872E000, 0x38730000, 0x38732000, 0x38734000, 0x38736000, 0x38738000, 0x3873A000, 0x3873C000, 0x3873E000, + 0x38740000, 0x38742000, 0x38744000, 0x38746000, 0x38748000, 0x3874A000, 0x3874C000, 0x3874E000, 0x38750000, 0x38752000, 0x38754000, 0x38756000, 0x38758000, 0x3875A000, 0x3875C000, 0x3875E000, + 0x38760000, 0x38762000, 0x38764000, 0x38766000, 0x38768000, 0x3876A000, 0x3876C000, 0x3876E000, 0x38770000, 0x38772000, 0x38774000, 0x38776000, 0x38778000, 0x3877A000, 0x3877C000, 0x3877E000, + 0x38780000, 0x38782000, 0x38784000, 0x38786000, 0x38788000, 0x3878A000, 0x3878C000, 0x3878E000, 0x38790000, 0x38792000, 0x38794000, 0x38796000, 0x38798000, 0x3879A000, 0x3879C000, 0x3879E000, + 0x387A0000, 0x387A2000, 0x387A4000, 0x387A6000, 0x387A8000, 0x387AA000, 0x387AC000, 0x387AE000, 0x387B0000, 0x387B2000, 0x387B4000, 0x387B6000, 0x387B8000, 0x387BA000, 0x387BC000, 0x387BE000, + 0x387C0000, 0x387C2000, 0x387C4000, 0x387C6000, 0x387C8000, 0x387CA000, 0x387CC000, 0x387CE000, 0x387D0000, 0x387D2000, 0x387D4000, 0x387D6000, 0x387D8000, 0x387DA000, 0x387DC000, 0x387DE000, + 0x387E0000, 0x387E2000, 0x387E4000, 0x387E6000, 0x387E8000, 0x387EA000, 0x387EC000, 0x387EE000, 0x387F0000, 0x387F2000, 0x387F4000, 0x387F6000, 0x387F8000, 0x387FA000, 0x387FC000, 0x387FE000 }; + static const bits::type exponent_table[64] = { + 0x00000000, 0x00800000, 0x01000000, 0x01800000, 0x02000000, 0x02800000, 0x03000000, 0x03800000, 0x04000000, 0x04800000, 0x05000000, 0x05800000, 0x06000000, 0x06800000, 0x07000000, 0x07800000, + 0x08000000, 0x08800000, 0x09000000, 0x09800000, 0x0A000000, 0x0A800000, 0x0B000000, 0x0B800000, 0x0C000000, 0x0C800000, 0x0D000000, 0x0D800000, 0x0E000000, 0x0E800000, 0x0F000000, 0x47800000, + 0x80000000, 0x80800000, 0x81000000, 0x81800000, 0x82000000, 0x82800000, 0x83000000, 0x83800000, 0x84000000, 0x84800000, 0x85000000, 0x85800000, 0x86000000, 0x86800000, 0x87000000, 0x87800000, + 0x88000000, 0x88800000, 0x89000000, 0x89800000, 0x8A000000, 0x8A800000, 0x8B000000, 0x8B800000, 0x8C000000, 0x8C800000, 0x8D000000, 0x8D800000, 0x8E000000, 0x8E800000, 0x8F000000, 0xC7800000 }; + static const unsigned short offset_table[64] = { + 0, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, + 0, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024 }; + bits::type fbits = mantissa_table[offset_table[value>>10]+(value&0x3FF)] + exponent_table[value>>10]; + #endif + float out; + std::memcpy(&out, &fbits, sizeof(float)); + return out; + #endif + } + + /// Convert half-precision to IEEE double-precision. + /// \param value half-precision value to convert + /// \return double-precision value + inline double half2float_impl(unsigned int value, double, true_type) + { + #if HALF_ENABLE_F16C_INTRINSICS + return _mm_cvtsd_f64(_mm_cvtps_pd(_mm_cvtph_ps(_mm_cvtsi32_si128(value)))); + #else + uint32 hi = static_cast(value&0x8000) << 16; + unsigned int abs = value & 0x7FFF; + if(abs) + { + hi |= 0x3F000000 << static_cast(abs>=0x7C00); + for(; abs<0x400; abs<<=1,hi-=0x100000) ; + hi += static_cast(abs) << 10; + } + bits::type dbits = static_cast::type>(hi) << 32; + double out; + std::memcpy(&out, &dbits, sizeof(double)); + return out; + #endif + } + + /// Convert half-precision to non-IEEE floating-point. + /// \tparam T type to convert to (builtin integer type) + /// \param value half-precision value to convert + /// \return floating-point value + template T half2float_impl(unsigned int value, T, ...) + { + T out; + unsigned int abs = value & 0x7FFF; + if(abs > 0x7C00) + out = (std::numeric_limits::has_signaling_NaN && !(abs&0x200)) ? std::numeric_limits::signaling_NaN() : + std::numeric_limits::has_quiet_NaN ? std::numeric_limits::quiet_NaN() : T(); + else if(abs == 0x7C00) + out = std::numeric_limits::has_infinity ? std::numeric_limits::infinity() : std::numeric_limits::max(); + else if(abs > 0x3FF) + out = std::ldexp(static_cast((abs&0x3FF)|0x400), (abs>>10)-25); + else + out = std::ldexp(static_cast(abs), -24); + return (value&0x8000) ? -out : out; + } + + /// Convert half-precision to floating-point. + /// \tparam T type to convert to (builtin integer type) + /// \param value half-precision value to convert + /// \return floating-point value + template T half2float(unsigned int value) + { + return half2float_impl(value, T(), bool_type::is_iec559&&sizeof(typename bits::type)==sizeof(T)>()); + } + + /// Convert half-precision floating-point to integer. + /// \tparam R rounding mode to use + /// \tparam E `true` for round to even, `false` for round away from zero + /// \tparam I `true` to raise INEXACT exception (if inexact), `false` to never raise it + /// \tparam T type to convert to (buitlin integer type with at least 16 bits precision, excluding any implicit sign bits) + /// \param value half-precision value to convert + /// \return rounded integer value + /// \exception FE_INVALID if value is not representable in type \a T + /// \exception FE_INEXACT if value had to be rounded and \a I is `true` + template T half2int(unsigned int value) + { + unsigned int abs = value & 0x7FFF; + if(abs >= 0x7C00) + { + detail::raise(FE_INVALID); + return (value&0x8000) ? std::numeric_limits::min() : std::numeric_limits::max(); + } + if(abs < 0x3800) + { + detail::raise(FE_INEXACT, I); + return (R==std::round_toward_infinity) ? T(~(value>>15)&(abs!=0)) : + (R==std::round_toward_neg_infinity) ? -T(value>0x8000) : + T(); + } + int exp = 25 - (abs>>10); + unsigned int m = (value&0x3FF) | 0x400; + int32 i = static_cast((exp<=0) ? (m<<-exp) : ((m+( + (R==std::round_to_nearest) ? ((1<<(exp-1))-(~(m>>exp)&E)) : + (R==std::round_toward_infinity) ? (((1<>15)-1)) : + (R==std::round_toward_neg_infinity) ? (((1<>15)) : 0))>>exp)); + if((!std::numeric_limits::is_signed && (value&0x8000)) || (std::numeric_limits::digits<16 && + ((value&0x8000) ? (-i::min()) : (i>std::numeric_limits::max())))) + detail::raise(FE_INVALID); + else if(I && exp > 0 && (m&((1<((value&0x8000) ? -i : i); + } + + /// \} + /// \name Mathematics + /// \{ + + /// upper part of 64-bit multiplication. + /// \tparam R rounding mode to use + /// \param x first factor + /// \param y second factor + /// \return upper 32 bit of \a x * \a y + template uint32 mulhi(uint32 x, uint32 y) + { + uint32 xy = (x>>16) * (y&0xFFFF), yx = (x&0xFFFF) * (y>>16), c = (xy&0xFFFF) + (yx&0xFFFF) + (((x&0xFFFF)*(y&0xFFFF))>>16); + return (x>>16)*(y>>16) + (xy>>16) + (yx>>16) + (c>>16) + + ((R==std::round_to_nearest) ? ((c>>15)&1) : (R==std::round_toward_infinity) ? ((c&0xFFFF)!=0) : 0); + } + + /// 64-bit multiplication. + /// \param x first factor + /// \param y second factor + /// \return upper 32 bit of \a x * \a y rounded to nearest + inline uint32 multiply64(uint32 x, uint32 y) + { + #if HALF_ENABLE_CPP11_LONG_LONG + return static_cast((static_cast(x)*static_cast(y)+0x80000000)>>32); + #else + return mulhi(x, y); + #endif + } + + /// 64-bit division. + /// \param x upper 32 bit of dividend + /// \param y divisor + /// \param s variable to store sticky bit for rounding + /// \return (\a x << 32) / \a y + inline uint32 divide64(uint32 x, uint32 y, int &s) + { + #if HALF_ENABLE_CPP11_LONG_LONG + unsigned long long xx = static_cast(x) << 32; + return s = (xx%y!=0), static_cast(xx/y); + #else + y >>= 1; + uint32 rem = x, div = 0; + for(unsigned int i=0; i<32; ++i) + { + div <<= 1; + if(rem >= y) + { + rem -= y; + div |= 1; + } + rem <<= 1; + } + return s = rem > 1, div; + #endif + } + + /// Half precision positive modulus. + /// \tparam Q `true` to compute full quotient, `false` else + /// \tparam R `true` to compute signed remainder, `false` for positive remainder + /// \param x first operand as positive finite half-precision value + /// \param y second operand as positive finite half-precision value + /// \param quo adress to store quotient at, `nullptr` if \a Q `false` + /// \return modulus of \a x / \a y + template unsigned int mod(unsigned int x, unsigned int y, int *quo = NULL) + { + unsigned int q = 0; + if(x > y) + { + int absx = x, absy = y, expx = 0, expy = 0; + for(; absx<0x400; absx<<=1,--expx) ; + for(; absy<0x400; absy<<=1,--expy) ; + expx += absx >> 10; + expy += absy >> 10; + int mx = (absx&0x3FF) | 0x400, my = (absy&0x3FF) | 0x400; + for(int d=expx-expy; d; --d) + { + if(!Q && mx == my) + return 0; + if(mx >= my) + { + mx -= my; + q += Q; + } + mx <<= 1; + q <<= static_cast(Q); + } + if(!Q && mx == my) + return 0; + if(mx >= my) + { + mx -= my; + ++q; + } + if(Q) + { + q &= (1<<(std::numeric_limits::digits-1)) - 1; + if(!mx) + return *quo = q, 0; + } + for(; mx<0x400; mx<<=1,--expy) ; + x = (expy>0) ? ((expy<<10)|(mx&0x3FF)) : (mx>>(1-expy)); + } + if(R) + { + unsigned int a, b; + if(y < 0x800) + { + a = (x<0x400) ? (x<<1) : (x+0x400); + b = y; + } + else + { + a = x; + b = y - 0x400; + } + if(a > b || (a == b && (q&1))) + { + int exp = (y>>10) + (y<=0x3FF), d = exp - (x>>10) - (x<=0x3FF); + int m = (((y&0x3FF)|((y>0x3FF)<<10))<<1) - (((x&0x3FF)|((x>0x3FF)<<10))<<(1-d)); + for(; m<0x800 && exp>1; m<<=1,--exp) ; + x = 0x8000 + ((exp-1)<<10) + (m>>1); + q += Q; + } + } + if(Q) + *quo = q; + return x; + } + + /// Fixed point square root. + /// \tparam F number of fractional bits + /// \param r radicand in Q1.F fixed point format + /// \param exp exponent + /// \return square root as Q1.F/2 + template uint32 sqrt(uint32 &r, int &exp) + { + int i = exp & 1; + r <<= i; + exp = (exp-i) / 2; + uint32 m = 0; + for(uint32 bit=static_cast(1)<>=2) + { + if(r < m+bit) + m >>= 1; + else + { + r -= m + bit; + m = (m>>1) + bit; + } + } + return m; + } + + /// Fixed point binary exponential. + /// This uses the BKM algorithm in E-mode. + /// \param m exponent in [0,1) as Q0.31 + /// \param n number of iterations (at most 32) + /// \return 2 ^ \a m as Q1.31 + inline uint32 exp2(uint32 m, unsigned int n = 32) + { + static const uint32 logs[] = { + 0x80000000, 0x4AE00D1D, 0x2934F098, 0x15C01A3A, 0x0B31FB7D, 0x05AEB4DD, 0x02DCF2D1, 0x016FE50B, + 0x00B84E23, 0x005C3E10, 0x002E24CA, 0x001713D6, 0x000B8A47, 0x0005C53B, 0x0002E2A3, 0x00017153, + 0x0000B8AA, 0x00005C55, 0x00002E2B, 0x00001715, 0x00000B8B, 0x000005C5, 0x000002E3, 0x00000171, + 0x000000B9, 0x0000005C, 0x0000002E, 0x00000017, 0x0000000C, 0x00000006, 0x00000003, 0x00000001 }; + if(!m) + return 0x80000000; + uint32 mx = 0x80000000, my = 0; + for(unsigned int i=1; i> i; + } + } + return mx; + } + + /// Fixed point binary logarithm. + /// This uses the BKM algorithm in L-mode. + /// \param m mantissa in [1,2) as Q1.30 + /// \param n number of iterations (at most 32) + /// \return log2(\a m) as Q0.31 + inline uint32 log2(uint32 m, unsigned int n = 32) + { + static const uint32 logs[] = { + 0x80000000, 0x4AE00D1D, 0x2934F098, 0x15C01A3A, 0x0B31FB7D, 0x05AEB4DD, 0x02DCF2D1, 0x016FE50B, + 0x00B84E23, 0x005C3E10, 0x002E24CA, 0x001713D6, 0x000B8A47, 0x0005C53B, 0x0002E2A3, 0x00017153, + 0x0000B8AA, 0x00005C55, 0x00002E2B, 0x00001715, 0x00000B8B, 0x000005C5, 0x000002E3, 0x00000171, + 0x000000B9, 0x0000005C, 0x0000002E, 0x00000017, 0x0000000C, 0x00000006, 0x00000003, 0x00000001 }; + if(m == 0x40000000) + return 0; + uint32 mx = 0x40000000, my = 0; + for(unsigned int i=1; i>i); + if(mz <= m) + { + mx = mz; + my += logs[i]; + } + } + return my; + } + + /// Fixed point sine and cosine. + /// This uses the CORDIC algorithm in rotation mode. + /// \param mz angle in [-pi/2,pi/2] as Q1.30 + /// \param n number of iterations (at most 31) + /// \return sine and cosine of \a mz as Q1.30 + inline std::pair sincos(uint32 mz, unsigned int n = 31) + { + static const uint32 angles[] = { + 0x3243F6A9, 0x1DAC6705, 0x0FADBAFD, 0x07F56EA7, 0x03FEAB77, 0x01FFD55C, 0x00FFFAAB, 0x007FFF55, + 0x003FFFEB, 0x001FFFFD, 0x00100000, 0x00080000, 0x00040000, 0x00020000, 0x00010000, 0x00008000, + 0x00004000, 0x00002000, 0x00001000, 0x00000800, 0x00000400, 0x00000200, 0x00000100, 0x00000080, + 0x00000040, 0x00000020, 0x00000010, 0x00000008, 0x00000004, 0x00000002, 0x00000001 }; + uint32 mx = 0x26DD3B6A, my = 0; + for(unsigned int i=0; i0x3FF)<<10); + int exp = (abs>>10) + (abs<=0x3FF) - 15; + if(abs < 0x3A48) + return k = 0, m << (exp+20); + #if HALF_ENABLE_CPP11_LONG_LONG + unsigned long long y = m * 0xA2F9836E4E442, mask = (1ULL<<(62-exp)) - 1, yi = (y+(mask>>1)) & ~mask, f = y - yi; + uint32 sign = -static_cast(f>>63); + k = static_cast(yi>>(62-exp)); + return (multiply64(static_cast((sign ? -f : f)>>(31-exp)), 0xC90FDAA2)^sign) - sign; + #else + uint32 yh = m*0xA2F98 + mulhi(m, 0x36E4E442), yl = (m*0x36E4E442) & 0xFFFFFFFF; + uint32 mask = (static_cast(1)<<(30-exp)) - 1, yi = (yh+(mask>>1)) & ~mask, sign = -static_cast(yi>yh); + k = static_cast(yi>>(30-exp)); + uint32 fh = (yh^sign) + (yi^~sign) - ~sign, fl = (yl^sign) - sign; + return (multiply64((exp>-1) ? (((fh<<(1+exp))&0xFFFFFFFF)|((fl&0xFFFFFFFF)>>(31-exp))) : fh, 0xC90FDAA2)^sign) - sign; + #endif + } + + /// Get arguments for atan2 function. + /// \param abs half-precision floating-point value + /// \return \a abs and sqrt(1 - \a abs^2) as Q0.30 + inline std::pair atan2_args(unsigned int abs) + { + int exp = -15; + for(; abs<0x400; abs<<=1,--exp) ; + exp += abs >> 10; + uint32 my = ((abs&0x3FF)|0x400) << 5, r = my * my; + int rexp = 2 * exp; + r = 0x40000000 - ((rexp>-31) ? ((r>>-rexp)|((r&((static_cast(1)<<-rexp)-1))!=0)) : 1); + for(rexp=0; r<0x40000000; r<<=1,--rexp) ; + uint32 mx = sqrt<30>(r, rexp); + int d = exp - rexp; + if(d < 0) + return std::make_pair((d<-14) ? ((my>>(-d-14))+((my>>(-d-15))&1)) : (my<<(14+d)), (mx<<14)+(r<<13)/mx); + if(d > 0) + return std::make_pair(my<<14, (d>14) ? ((mx>>(d-14))+((mx>>(d-15))&1)) : ((d==14) ? mx : ((mx<<(14-d))+(r<<(13-d))/mx))); + return std::make_pair(my<<13, (mx<<13)+(r<<12)/mx); + } + + /// Get exponentials for hyperbolic computation + /// \param abs half-precision floating-point value + /// \param exp variable to take unbiased exponent of larger result + /// \param n number of BKM iterations (at most 32) + /// \return exp(abs) and exp(-\a abs) as Q1.31 with same exponent + inline std::pair hyperbolic_args(unsigned int abs, int &exp, unsigned int n = 32) + { + uint32 mx = detail::multiply64(static_cast((abs&0x3FF)+((abs>0x3FF)<<10))<<21, 0xB8AA3B29), my; + int e = (abs>>10) + (abs<=0x3FF); + if(e < 14) + { + exp = 0; + mx >>= 14 - e; + } + else + { + exp = static_cast(mx >> (45-e)); + mx = (mx<<(e-14)) & 0x7FFFFFFF; + } + mx = exp2(mx, n); + int d = exp << 1, s; + if(mx > 0x80000000) + { + my = divide64(0x80000000, mx, s); + my |= s; + ++d; + } + else + my = mx; + return std::make_pair(mx, (d<31) ? ((my>>d)|((my&((static_cast(1)< unsigned int exp2_post(uint32 m, int exp, bool esign, unsigned int sign = 0, unsigned int n = 32) + { + if(esign) + { + exp = -exp - (m!=0); + if(exp < -25) + return underflow(sign); + else if(exp == -25) + return rounded(sign, 1, m!=0); + } + else if(exp > 15) + return overflow(sign); + if(!m) + return sign | (((exp+=15)>0) ? (exp<<10) : check_underflow(0x200>>-exp)); + m = exp2(m, n); + int s = 0; + if(esign) + m = divide64(0x80000000, m, s); + return fixed2half(m, exp+14, sign, s); + } + + /// Postprocessing for binary logarithm. + /// \tparam R rounding mode to use + /// \tparam L logarithm for base transformation as Q1.31 + /// \param m fractional part of logarithm as Q0.31 + /// \param ilog signed integer part of logarithm + /// \param exp biased exponent of result + /// \param sign sign bit of result + /// \return value base-transformed and converted to half-precision + /// \exception FE_OVERFLOW on overflows + /// \exception FE_UNDERFLOW on underflows + /// \exception FE_INEXACT if no other exception occurred + template unsigned int log2_post(uint32 m, int ilog, int exp, unsigned int sign = 0) + { + uint32 msign = sign_mask(ilog); + m = (((static_cast(ilog)<<27)+(m>>4))^msign) - msign; + if(!m) + return 0; + for(; m<0x80000000; m<<=1,--exp) ; + int i = m >= L, s; + exp += i; + m >>= 1 + i; + sign ^= msign & 0x8000; + if(exp < -11) + return underflow(sign); + m = divide64(m, L, s); + return fixed2half(m, exp, sign, 1); + } + + /// Hypotenuse square root and postprocessing. + /// \tparam R rounding mode to use + /// \param r mantissa as Q2.30 + /// \param exp biased exponent + /// \return square root converted to half-precision + /// \exception FE_OVERFLOW on overflows + /// \exception FE_UNDERFLOW on underflows + /// \exception FE_INEXACT if value had to be rounded + template unsigned int hypot_post(uint32 r, int exp) + { + int i = static_cast(r >> 31); + if((exp+=i) > 46) + return overflow(); + if(exp < -34) + return underflow(); + r = (r>>i) | (r&i); + uint32 m = sqrt<30>(r, exp+=15); + return fixed2half(m, exp-1, 0, r!=0); + } + + /// Division and postprocessing for tangents. + /// \tparam R rounding mode to use + /// \param my dividend as Q1.31 + /// \param mx divisor as Q1.31 + /// \param exp biased exponent of result + /// \param sign sign bit of result + /// \return quotient converted to half-precision + /// \exception FE_OVERFLOW on overflows + /// \exception FE_UNDERFLOW on underflows + /// \exception FE_INEXACT if no other exception occurred + template unsigned int tangent_post(uint32 my, uint32 mx, int exp, unsigned int sign = 0) + { + int i = my >= mx, s; + exp += i; + if(exp > 29) + return overflow(sign); + if(exp < -11) + return underflow(sign); + uint32 m = divide64(my>>(i+1), mx, s); + return fixed2half(m, exp, sign, s); + } + + /// Area function and postprocessing. + /// This computes the value directly in Q2.30 using the representation `asinh|acosh(x) = log(x+sqrt(x^2+|-1))`. + /// \tparam R rounding mode to use + /// \tparam S `true` for asinh, `false` for acosh + /// \param arg half-precision argument + /// \return asinh|acosh(\a arg) converted to half-precision + /// \exception FE_OVERFLOW on overflows + /// \exception FE_UNDERFLOW on underflows + /// \exception FE_INEXACT if no other exception occurred + template unsigned int area(unsigned int arg) + { + int abs = arg & 0x7FFF, expx = (abs>>10) + (abs<=0x3FF) - 15, expy = -15, ilog; + uint32 mx = static_cast((abs&0x3FF)|((abs>0x3FF)<<10)) << 20, my, r; + for(; abs<0x400; abs<<=1,--expy) ; + expy += abs >> 10; + r = ((abs&0x3FF)|0x400) << 5; + r *= r; + int i = static_cast(r >> 31); + expy = 2*expy + i; + r >>= i; + if(S) + { + if(expy < 0) + { + r = 0x40000000 + ((expy>-30) ? ((r>>-expy)|((r&((static_cast(1)<<-expy)-1))!=0)) : 1); + expy = 0; + } + else + { + r += 0x40000000 >> expy; + i = static_cast(r >> 31); + r = (r>>i) | (r&i); + expy += i; + } + } + else + { + r -= 0x40000000 >> expy; + for(; r<0x40000000; r<<=1,--expy) ; + } + my = sqrt<30>(r, expy); + my = (my<<15) + (r<<14)/my; + if(S) + { + mx >>= expy - expx; + ilog = expy; + } + else + { + my >>= expx - expy; + ilog = expx; + } + my += mx; + i = static_cast(my >> 31); + static const int G = S && (R==std::round_to_nearest); + return log2_post(log2(my>>i, 26+S+G)+(G<<3), ilog+i, 17, arg&(static_cast(S)<<15)); + } + + /// Class for 1.31 unsigned floating-point computation + struct f31 + { + /// Constructor. + /// \param mant mantissa as 1.31 + /// \param e exponent + HALF_CONSTEXPR f31(uint32 mant, int e) : m(mant), exp(e) {} + + /// Constructor. + /// \param abs unsigned half-precision value + f31(unsigned int abs) : exp(-15) + { + for(; abs<0x400; abs<<=1,--exp) ; + m = static_cast((abs&0x3FF)|0x400) << 21; + exp += (abs>>10); + } + + /// Addition operator. + /// \param a first operand + /// \param b second operand + /// \return \a a + \a b + friend f31 operator+(f31 a, f31 b) + { + if(b.exp > a.exp) + std::swap(a, b); + int d = a.exp - b.exp; + uint32 mant = a.m + ((d<32) ? (b.m>>d) : 0); + int i = (mant&0xFFFFFFFF) < a.m; + return f31(((mant+i)>>i)|0x80000000, a.exp+i); + } + + /// Subtraction operator. + /// \param a first operand + /// \param b second operand + /// \return \a a - \a b + friend f31 operator-(f31 a, f31 b) + { + int d = a.exp - b.exp, out_exp = a.exp; + uint32 mant = a.m - ((d<32) ? (b.m>>d) : 0); + if(!mant) + return f31(0, -32); + for(; mant<0x80000000; mant<<=1,--out_exp) ; + return f31(mant, out_exp); + } + + /// Multiplication operator. + /// \param a first operand + /// \param b second operand + /// \return \a a * \a b + friend f31 operator*(f31 a, f31 b) + { + uint32 mant = multiply64(a.m, b.m); + int i = static_cast(mant >> 31); + return f31(mant<<(1-i), a.exp + b.exp + i); + } + + /// Division operator. + /// \param a first operand + /// \param b second operand + /// \return \a a / \a b + friend f31 operator/(f31 a, f31 b) + { + int i = a.m >= b.m, s; + uint32 mant = divide64((a.m+i)>>i, b.m, s); + return f31(mant, a.exp - b.exp + i - 1); + } + + uint32 m; ///< mantissa as 1.31. + int exp; ///< exponent. + }; + + /// Error function and postprocessing. + /// This computes the value directly in Q1.31 using the approximations given + /// [here](https://en.wikipedia.org/wiki/Error_function#Approximation_with_elementary_functions). + /// \tparam R rounding mode to use + /// \tparam C `true` for comlementary error function, `false` else + /// \param arg half-precision function argument + /// \return approximated value of error function in half-precision + /// \exception FE_OVERFLOW on overflows + /// \exception FE_UNDERFLOW on underflows + /// \exception FE_INEXACT if no other exception occurred + template unsigned int erf(unsigned int arg) + { + unsigned int abs = arg & 0x7FFF, sign = arg & 0x8000; + f31 x(abs), x2 = x * x * f31(0xB8AA3B29, 0), t = f31(0x80000000, 0) / (f31(0x80000000, 0)+f31(0xA7BA054A, -2)*x), t2 = t * t; + f31 e = ((f31(0x87DC2213, 0)*t2+f31(0xB5F0E2AE, 0))*t2+f31(0x82790637, -2)-(f31(0xBA00E2B8, 0)*t2+f31(0x91A98E62, -2))*t) * t / + ((x2.exp<0) ? f31(exp2((x2.exp>-32) ? (x2.m>>-x2.exp) : 0, 30), 0) : f31(exp2((x2.m<(x2.m>>(31-x2.exp)))); + return (!C || sign) ? fixed2half(0x80000000-(e.m>>(C-e.exp)), 14+C, sign&(C-1U)) : + (e.exp<-25) ? underflow() : fixed2half(e.m>>1, e.exp+14, 0, e.m&1); + } + + /// Gamma function and postprocessing. + /// This approximates the value of either the gamma function or its logarithm directly in Q1.31. + /// \tparam R rounding mode to use + /// \tparam L `true` for lograithm of gamma function, `false` for gamma function + /// \param arg half-precision floating-point value + /// \return lgamma/tgamma(\a arg) in half-precision + /// \exception FE_OVERFLOW on overflows + /// \exception FE_UNDERFLOW on underflows + /// \exception FE_INEXACT if \a arg is not a positive integer + template unsigned int gamma(unsigned int arg) + { +/* static const double p[] ={ 2.50662827563479526904, 225.525584619175212544, -268.295973841304927459, 80.9030806934622512966, -5.00757863970517583837, 0.0114684895434781459556 }; + double t = arg + 4.65, s = p[0]; + for(unsigned int i=0; i<5; ++i) + s += p[i+1] / (arg+i); + return std::log(s) + (arg-0.5)*std::log(t) - t; +*/ static const f31 pi(0xC90FDAA2, 1), lbe(0xB8AA3B29, 0); + unsigned int abs = arg & 0x7FFF, sign = arg & 0x8000; + bool bsign = sign != 0; + f31 z(abs), x = sign ? (z+f31(0x80000000, 0)) : z, t = x + f31(0x94CCCCCD, 2), s = + f31(0xA06C9901, 1) + f31(0xBBE654E2, -7)/(x+f31(0x80000000, 2)) + f31(0xA1CE6098, 6)/(x+f31(0x80000000, 1)) + + f31(0xE1868CB7, 7)/x - f31(0x8625E279, 8)/(x+f31(0x80000000, 0)) - f31(0xA03E158F, 2)/(x+f31(0xC0000000, 1)); + int i = (s.exp>=2) + (s.exp>=4) + (s.exp>=8) + (s.exp>=16); + s = f31((static_cast(s.exp)<<(31-i))+(log2(s.m>>1, 28)>>i), i) / lbe; + if(x.exp != -1 || x.m != 0x80000000) + { + i = (t.exp>=2) + (t.exp>=4) + (t.exp>=8); + f31 l = f31((static_cast(t.exp)<<(31-i))+(log2(t.m>>1, 30)>>i), i) / lbe; + s = (x.exp<-1) ? (s-(f31(0x80000000, -1)-x)*l) : (s+(x-f31(0x80000000, -1))*l); + } + s = x.exp ? (s-t) : (t-s); + if(bsign) + { + if(z.exp >= 0) + { + sign &= (L|((z.m>>(31-z.exp))&1)) - 1; + for(z=f31((z.m<<(1+z.exp))&0xFFFFFFFF, -1); z.m<0x80000000; z.m<<=1,--z.exp) ; + } + if(z.exp == -1) + z = f31(0x80000000, 0) - z; + if(z.exp < -1) + { + z = z * pi; + z.m = sincos(z.m>>(1-z.exp), 30).first; + for(z.exp=1; z.m<0x80000000; z.m<<=1,--z.exp) ; + } + else + z = f31(0x80000000, 0); + } + if(L) + { + if(bsign) + { + f31 l(0x92868247, 0); + if(z.exp < 0) + { + uint32 m = log2((z.m+1)>>1, 27); + z = f31(-((static_cast(z.exp)<<26)+(m>>5)), 5); + for(; z.m<0x80000000; z.m<<=1,--z.exp) ; + l = l + z / lbe; + } + sign = static_cast(x.exp&&(l.exp(x.exp==0) << 15; + if(s.exp < -24) + return underflow(sign); + if(s.exp > 15) + return overflow(sign); + } + } + else + { + s = s * lbe; + uint32 m; + if(s.exp < 0) + { + m = s.m >> -s.exp; + s.exp = 0; + } + else + { + m = (s.m<(s.m>>(31-s.exp)); + } + s.m = exp2(m, 27); + if(!x.exp) + s = f31(0x80000000, 0) / s; + if(bsign) + { + if(z.exp < 0) + s = s * z; + s = pi / s; + if(s.exp < -24) + return underflow(sign); + } + else if(z.exp > 0 && !(z.m&((1<<(31-z.exp))-1))) + return ((s.exp+14)<<10) + static_cast(s.m>>21); + if(s.exp > 15) + return overflow(sign); + } + return fixed2half(s.m, s.exp+14, sign); + } + /// \} + + template struct half_caster; + } + + /// Half-precision floating-point type. + /// This class implements an IEEE-conformant half-precision floating-point type with the usual arithmetic + /// operators and conversions. It is implicitly convertible to single-precision floating-point, which makes artihmetic + /// expressions and functions with mixed-type operands to be of the most precise operand type. + /// + /// According to the C++98/03 definition, the half type is not a POD type. But according to C++11's less strict and + /// extended definitions it is both a standard layout type and a trivially copyable type (even if not a POD type), which + /// means it can be standard-conformantly copied using raw binary copies. But in this context some more words about the + /// actual size of the type. Although the half is representing an IEEE 16-bit type, it does not neccessarily have to be of + /// exactly 16-bits size. But on any reasonable implementation the actual binary representation of this type will most + /// probably not ivolve any additional "magic" or padding beyond the simple binary representation of the underlying 16-bit + /// IEEE number, even if not strictly guaranteed by the standard. But even then it only has an actual size of 16 bits if + /// your C++ implementation supports an unsigned integer type of exactly 16 bits width. But this should be the case on + /// nearly any reasonable platform. + /// + /// So if your C++ implementation is not totally exotic or imposes special alignment requirements, it is a reasonable + /// assumption that the data of a half is just comprised of the 2 bytes of the underlying IEEE representation. + class half + { + public: + /// \name Construction and assignment + /// \{ + + /// Default constructor. + /// This initializes the half to 0. Although this does not match the builtin types' default-initialization semantics + /// and may be less efficient than no initialization, it is needed to provide proper value-initialization semantics. + HALF_CONSTEXPR half() HALF_NOEXCEPT : data_() {} + + /// Conversion constructor. + /// \param rhs float to convert + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + explicit half(float rhs) : data_(static_cast(detail::float2half(rhs))) {} + + /// Conversion to single-precision. + /// \return single precision value representing expression value + operator float() const { return detail::half2float(data_); } + + /// Assignment operator. + /// \param rhs single-precision value to copy from + /// \return reference to this half + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + half& operator=(float rhs) { data_ = static_cast(detail::float2half(rhs)); return *this; } + + /// \} + /// \name Arithmetic updates + /// \{ + + /// Arithmetic assignment. + /// \tparam T type of concrete half expression + /// \param rhs half expression to add + /// \return reference to this half + /// \exception FE_... according to operator+(half,half) + half& operator+=(half rhs) { return *this = *this + rhs; } + + /// Arithmetic assignment. + /// \tparam T type of concrete half expression + /// \param rhs half expression to subtract + /// \return reference to this half + /// \exception FE_... according to operator-(half,half) + half& operator-=(half rhs) { return *this = *this - rhs; } + + /// Arithmetic assignment. + /// \tparam T type of concrete half expression + /// \param rhs half expression to multiply with + /// \return reference to this half + /// \exception FE_... according to operator*(half,half) + half& operator*=(half rhs) { return *this = *this * rhs; } + + /// Arithmetic assignment. + /// \tparam T type of concrete half expression + /// \param rhs half expression to divide by + /// \return reference to this half + /// \exception FE_... according to operator/(half,half) + half& operator/=(half rhs) { return *this = *this / rhs; } + + /// Arithmetic assignment. + /// \param rhs single-precision value to add + /// \return reference to this half + /// \exception FE_... according to operator=() + half& operator+=(float rhs) { return *this = *this + rhs; } + + /// Arithmetic assignment. + /// \param rhs single-precision value to subtract + /// \return reference to this half + /// \exception FE_... according to operator=() + half& operator-=(float rhs) { return *this = *this - rhs; } + + /// Arithmetic assignment. + /// \param rhs single-precision value to multiply with + /// \return reference to this half + /// \exception FE_... according to operator=() + half& operator*=(float rhs) { return *this = *this * rhs; } + + /// Arithmetic assignment. + /// \param rhs single-precision value to divide by + /// \return reference to this half + /// \exception FE_... according to operator=() + half& operator/=(float rhs) { return *this = *this / rhs; } + + /// \} + /// \name Increment and decrement + /// \{ + + /// Prefix increment. + /// \return incremented half value + /// \exception FE_... according to operator+(half,half) + half& operator++() { return *this = *this + half(detail::binary, 0x3C00); } + + /// Prefix decrement. + /// \return decremented half value + /// \exception FE_... according to operator-(half,half) + half& operator--() { return *this = *this + half(detail::binary, 0xBC00); } + + /// Postfix increment. + /// \return non-incremented half value + /// \exception FE_... according to operator+(half,half) + half operator++(int) { half out(*this); ++*this; return out; } + + /// Postfix decrement. + /// \return non-decremented half value + /// \exception FE_... according to operator-(half,half) + half operator--(int) { half out(*this); --*this; return out; } + /// \} + + private: + /// Rounding mode to use + static const std::float_round_style round_style = (std::float_round_style)(HALF_ROUND_STYLE); + + /// Constructor. + /// \param bits binary representation to set half to + HALF_CONSTEXPR half(detail::binary_t, unsigned int bits) HALF_NOEXCEPT : data_(static_cast(bits)) {} + + /// Internal binary representation + detail::uint16 data_; + + #ifndef HALF_DOXYGEN_ONLY + friend HALF_CONSTEXPR_NOERR bool operator==(half, half); + friend HALF_CONSTEXPR_NOERR bool operator!=(half, half); + friend HALF_CONSTEXPR_NOERR bool operator<(half, half); + friend HALF_CONSTEXPR_NOERR bool operator>(half, half); + friend HALF_CONSTEXPR_NOERR bool operator<=(half, half); + friend HALF_CONSTEXPR_NOERR bool operator>=(half, half); + friend HALF_CONSTEXPR half operator-(half); + friend half operator+(half, half); + friend half operator-(half, half); + friend half operator*(half, half); + friend half operator/(half, half); + template friend std::basic_ostream& operator<<(std::basic_ostream&, half); + template friend std::basic_istream& operator>>(std::basic_istream&, half&); + friend HALF_CONSTEXPR half fabs(half); + friend half fmod(half, half); + friend half remainder(half, half); + friend half remquo(half, half, int*); + friend half fma(half, half, half); + friend HALF_CONSTEXPR_NOERR half fmax(half, half); + friend HALF_CONSTEXPR_NOERR half fmin(half, half); + friend half fdim(half, half); + friend half nanh(const char*); + friend half exp(half); + friend half exp2(half); + friend half expm1(half); + friend half log(half); + friend half log10(half); + friend half log2(half); + friend half log1p(half); + friend half sqrt(half); + friend half rsqrt(half); + friend half cbrt(half); + friend half hypot(half, half); + friend half hypot(half, half, half); + friend half pow(half, half); + friend void sincos(half, half*, half*); + friend half sin(half); + friend half cos(half); + friend half tan(half); + friend half asin(half); + friend half acos(half); + friend half atan(half); + friend half atan2(half, half); + friend half sinh(half); + friend half cosh(half); + friend half tanh(half); + friend half asinh(half); + friend half acosh(half); + friend half atanh(half); + friend half erf(half); + friend half erfc(half); + friend half lgamma(half); + friend half tgamma(half); + friend half ceil(half); + friend half floor(half); + friend half trunc(half); + friend half round(half); + friend long lround(half); + friend half rint(half); + friend long lrint(half); + friend half nearbyint(half); + #ifdef HALF_ENABLE_CPP11_LONG_LONG + friend long long llround(half); + friend long long llrint(half); + #endif + friend half frexp(half, int*); + friend half scalbln(half, long); + friend half modf(half, half*); + friend int ilogb(half); + friend half logb(half); + friend half nextafter(half, half); + friend half nexttoward(half, long double); + friend HALF_CONSTEXPR half copysign(half, half); + friend HALF_CONSTEXPR int fpclassify(half); + friend HALF_CONSTEXPR bool isfinite(half); + friend HALF_CONSTEXPR bool isinf(half); + friend HALF_CONSTEXPR bool isnan(half); + friend HALF_CONSTEXPR bool isnormal(half); + friend HALF_CONSTEXPR bool signbit(half); + friend HALF_CONSTEXPR bool isgreater(half, half); + friend HALF_CONSTEXPR bool isgreaterequal(half, half); + friend HALF_CONSTEXPR bool isless(half, half); + friend HALF_CONSTEXPR bool islessequal(half, half); + friend HALF_CONSTEXPR bool islessgreater(half, half); + template friend struct detail::half_caster; + friend class std::numeric_limits; + #if HALF_ENABLE_CPP11_HASH + friend struct std::hash; + #endif + #if HALF_ENABLE_CPP11_USER_LITERALS + friend half literal::operator""_h(long double); + #endif + #endif + }; + +#if HALF_ENABLE_CPP11_USER_LITERALS + namespace literal + { + /// Half literal. + /// While this returns a properly rounded half-precision value, half literals can unfortunately not be constant + /// expressions due to rather involved conversions. So don't expect this to be a literal literal without involving + /// conversion operations at runtime. It is a convenience feature, not a performance optimization. + /// \param value literal value + /// \return half with of given value (possibly rounded) + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half operator""_h(long double value) { return half(detail::binary, detail::float2half(value)); } + } +#endif + + namespace detail + { + /// Helper class for half casts. + /// This class template has to be specialized for all valid cast arguments to define an appropriate static + /// `cast` member function and a corresponding `type` member denoting its return type. + /// \tparam T destination type + /// \tparam U source type + /// \tparam R rounding mode to use + template struct half_caster {}; + template struct half_caster + { + #if HALF_ENABLE_CPP11_STATIC_ASSERT && HALF_ENABLE_CPP11_TYPE_TRAITS + static_assert(std::is_arithmetic::value, "half_cast from non-arithmetic type not supported"); + #endif + + static half cast(U arg) { return cast_impl(arg, is_float()); }; + + private: + static half cast_impl(U arg, true_type) { return half(binary, float2half(arg)); } + static half cast_impl(U arg, false_type) { return half(binary, int2half(arg)); } + }; + template struct half_caster + { + #if HALF_ENABLE_CPP11_STATIC_ASSERT && HALF_ENABLE_CPP11_TYPE_TRAITS + static_assert(std::is_arithmetic::value, "half_cast to non-arithmetic type not supported"); + #endif + + static T cast(half arg) { return cast_impl(arg, is_float()); } + + private: + static T cast_impl(half arg, true_type) { return half2float(arg.data_); } + static T cast_impl(half arg, false_type) { return half2int(arg.data_); } + }; + template struct half_caster + { + static half cast(half arg) { return arg; } + }; + } +} + +/// Extensions to the C++ standard library. +namespace std +{ + /// Numeric limits for half-precision floats. + /// **See also:** Documentation for [std::numeric_limits](https://en.cppreference.com/w/cpp/types/numeric_limits) + template<> class numeric_limits + { + public: + /// Is template specialization. + static HALF_CONSTEXPR_CONST bool is_specialized = true; + + /// Supports signed values. + static HALF_CONSTEXPR_CONST bool is_signed = true; + + /// Is not an integer type. + static HALF_CONSTEXPR_CONST bool is_integer = false; + + /// Is not exact. + static HALF_CONSTEXPR_CONST bool is_exact = false; + + /// Doesn't provide modulo arithmetic. + static HALF_CONSTEXPR_CONST bool is_modulo = false; + + /// Has a finite set of values. + static HALF_CONSTEXPR_CONST bool is_bounded = true; + + /// IEEE conformant. + static HALF_CONSTEXPR_CONST bool is_iec559 = true; + + /// Supports infinity. + static HALF_CONSTEXPR_CONST bool has_infinity = true; + + /// Supports quiet NaNs. + static HALF_CONSTEXPR_CONST bool has_quiet_NaN = true; + + /// Supports signaling NaNs. + static HALF_CONSTEXPR_CONST bool has_signaling_NaN = true; + + /// Supports subnormal values. + static HALF_CONSTEXPR_CONST float_denorm_style has_denorm = denorm_present; + + /// Does not support denormalization detection. + static HALF_CONSTEXPR_CONST bool has_denorm_loss = false; + + #if HALF_ERRHANDLING_THROWS + static HALF_CONSTEXPR_CONST bool traps = true; + #else + /// Traps only if [HALF_ERRHANDLING_THROW_...](\ref HALF_ERRHANDLING_THROW_INVALID) is acitvated. + static HALF_CONSTEXPR_CONST bool traps = false; + #endif + + /// Does not support pre-rounding underflow detection. + static HALF_CONSTEXPR_CONST bool tinyness_before = false; + + /// Rounding mode. + static HALF_CONSTEXPR_CONST float_round_style round_style = half_float::half::round_style; + + /// Significant digits. + static HALF_CONSTEXPR_CONST int digits = 11; + + /// Significant decimal digits. + static HALF_CONSTEXPR_CONST int digits10 = 3; + + /// Required decimal digits to represent all possible values. + static HALF_CONSTEXPR_CONST int max_digits10 = 5; + + /// Number base. + static HALF_CONSTEXPR_CONST int radix = 2; + + /// One more than smallest exponent. + static HALF_CONSTEXPR_CONST int min_exponent = -13; + + /// Smallest normalized representable power of 10. + static HALF_CONSTEXPR_CONST int min_exponent10 = -4; + + /// One more than largest exponent + static HALF_CONSTEXPR_CONST int max_exponent = 16; + + /// Largest finitely representable power of 10. + static HALF_CONSTEXPR_CONST int max_exponent10 = 4; + + /// Smallest positive normal value. + static HALF_CONSTEXPR half_float::half min() HALF_NOTHROW { return half_float::half(half_float::detail::binary, 0x0400); } + + /// Smallest finite value. + static HALF_CONSTEXPR half_float::half lowest() HALF_NOTHROW { return half_float::half(half_float::detail::binary, 0xFBFF); } + + /// Largest finite value. + static HALF_CONSTEXPR half_float::half max() HALF_NOTHROW { return half_float::half(half_float::detail::binary, 0x7BFF); } + + /// Difference between 1 and next representable value. + static HALF_CONSTEXPR half_float::half epsilon() HALF_NOTHROW { return half_float::half(half_float::detail::binary, 0x1400); } + + /// Maximum rounding error in ULP (units in the last place). + static HALF_CONSTEXPR half_float::half round_error() HALF_NOTHROW + { return half_float::half(half_float::detail::binary, (round_style==std::round_to_nearest) ? 0x3800 : 0x3C00); } + + /// Positive infinity. + static HALF_CONSTEXPR half_float::half infinity() HALF_NOTHROW { return half_float::half(half_float::detail::binary, 0x7C00); } + + /// Quiet NaN. + static HALF_CONSTEXPR half_float::half quiet_NaN() HALF_NOTHROW { return half_float::half(half_float::detail::binary, 0x7FFF); } + + /// Signaling NaN. + static HALF_CONSTEXPR half_float::half signaling_NaN() HALF_NOTHROW { return half_float::half(half_float::detail::binary, 0x7DFF); } + + /// Smallest positive subnormal value. + static HALF_CONSTEXPR half_float::half denorm_min() HALF_NOTHROW { return half_float::half(half_float::detail::binary, 0x0001); } + }; + +#if HALF_ENABLE_CPP11_HASH + /// Hash function for half-precision floats. + /// This is only defined if C++11 `std::hash` is supported and enabled. + /// + /// **See also:** Documentation for [std::hash](https://en.cppreference.com/w/cpp/utility/hash) + template<> struct hash + { + /// Type of function argument. + typedef half_float::half argument_type; + + /// Function return type. + typedef size_t result_type; + + /// Compute hash function. + /// \param arg half to hash + /// \return hash value + result_type operator()(argument_type arg) const { return hash()(arg.data_&-static_cast(arg.data_!=0x8000)); } + }; +#endif +} + +namespace half_float +{ + /// \anchor compop + /// \name Comparison operators + /// \{ + + /// Comparison for equality. + /// \param x first operand + /// \param y second operand + /// \retval true if operands equal + /// \retval false else + /// \exception FE_INVALID if \a x or \a y is NaN + inline HALF_CONSTEXPR_NOERR bool operator==(half x, half y) + { + return !detail::compsignal(x.data_, y.data_) && (x.data_==y.data_ || !((x.data_|y.data_)&0x7FFF)); + } + + /// Comparison for inequality. + /// \param x first operand + /// \param y second operand + /// \retval true if operands not equal + /// \retval false else + /// \exception FE_INVALID if \a x or \a y is NaN + inline HALF_CONSTEXPR_NOERR bool operator!=(half x, half y) + { + return detail::compsignal(x.data_, y.data_) || (x.data_!=y.data_ && ((x.data_|y.data_)&0x7FFF)); + } + + /// Comparison for less than. + /// \param x first operand + /// \param y second operand + /// \retval true if \a x less than \a y + /// \retval false else + /// \exception FE_INVALID if \a x or \a y is NaN + inline HALF_CONSTEXPR_NOERR bool operator<(half x, half y) + { + return !detail::compsignal(x.data_, y.data_) && + ((x.data_^(0x8000|(0x8000-(x.data_>>15))))+(x.data_>>15)) < ((y.data_^(0x8000|(0x8000-(y.data_>>15))))+(y.data_>>15)); + } + + /// Comparison for greater than. + /// \param x first operand + /// \param y second operand + /// \retval true if \a x greater than \a y + /// \retval false else + /// \exception FE_INVALID if \a x or \a y is NaN + inline HALF_CONSTEXPR_NOERR bool operator>(half x, half y) + { + return !detail::compsignal(x.data_, y.data_) && + ((x.data_^(0x8000|(0x8000-(x.data_>>15))))+(x.data_>>15)) > ((y.data_^(0x8000|(0x8000-(y.data_>>15))))+(y.data_>>15)); + } + + /// Comparison for less equal. + /// \param x first operand + /// \param y second operand + /// \retval true if \a x less equal \a y + /// \retval false else + /// \exception FE_INVALID if \a x or \a y is NaN + inline HALF_CONSTEXPR_NOERR bool operator<=(half x, half y) + { + return !detail::compsignal(x.data_, y.data_) && + ((x.data_^(0x8000|(0x8000-(x.data_>>15))))+(x.data_>>15)) <= ((y.data_^(0x8000|(0x8000-(y.data_>>15))))+(y.data_>>15)); + } + + /// Comparison for greater equal. + /// \param x first operand + /// \param y second operand + /// \retval true if \a x greater equal \a y + /// \retval false else + /// \exception FE_INVALID if \a x or \a y is NaN + inline HALF_CONSTEXPR_NOERR bool operator>=(half x, half y) + { + return !detail::compsignal(x.data_, y.data_) && + ((x.data_^(0x8000|(0x8000-(x.data_>>15))))+(x.data_>>15)) >= ((y.data_^(0x8000|(0x8000-(y.data_>>15))))+(y.data_>>15)); + } + + /// \} + /// \anchor arithmetics + /// \name Arithmetic operators + /// \{ + + /// Identity. + /// \param arg operand + /// \return unchanged operand + inline HALF_CONSTEXPR half operator+(half arg) { return arg; } + + /// Negation. + /// \param arg operand + /// \return negated operand + inline HALF_CONSTEXPR half operator-(half arg) { return half(detail::binary, arg.data_^0x8000); } + + /// Addition. + /// This operation is exact to rounding for all rounding modes. + /// \param x left operand + /// \param y right operand + /// \return sum of half expressions + /// \exception FE_INVALID if \a x and \a y are infinities with different signs or signaling NaNs + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half operator+(half x, half y) + { + #ifdef HALF_ARITHMETIC_TYPE + return half(detail::binary, detail::float2half(detail::half2float(x.data_)+detail::half2float(y.data_))); + #else + int absx = x.data_ & 0x7FFF, absy = y.data_ & 0x7FFF; + bool sub = ((x.data_^y.data_)&0x8000) != 0; + if(absx >= 0x7C00 || absy >= 0x7C00) + return half(detail::binary, (absx>0x7C00 || absy>0x7C00) ? detail::signal(x.data_, y.data_) : (absy!=0x7C00) ? x.data_ : + (sub && absx==0x7C00) ? detail::invalid() : y.data_); + if(!absx) + return absy ? y : half(detail::binary, (half::round_style==std::round_toward_neg_infinity) ? (x.data_|y.data_) : (x.data_&y.data_)); + if(!absy) + return x; + unsigned int sign = ((sub && absy>absx) ? y.data_ : x.data_) & 0x8000; + if(absy > absx) + std::swap(absx, absy); + int exp = (absx>>10) + (absx<=0x3FF), d = exp - (absy>>10) - (absy<=0x3FF), mx = ((absx&0x3FF)|((absx>0x3FF)<<10)) << 3, my; + if(d < 13) + { + my = ((absy&0x3FF)|((absy>0x3FF)<<10)) << 3; + my = (my>>d) | ((my&((1<(half::round_style==std::round_toward_neg_infinity)<<15); + for(; mx<0x2000 && exp>1; mx<<=1,--exp) ; + } + else + { + mx += my; + int i = mx >> 14; + if((exp+=i) > 30) + return half(detail::binary, detail::overflow(sign)); + mx = (mx>>i) | (mx&i); + } + return half(detail::binary, detail::rounded(sign+((exp-1)<<10)+(mx>>3), (mx>>2)&1, (mx&0x3)!=0)); + #endif + } + + /// Subtraction. + /// This operation is exact to rounding for all rounding modes. + /// \param x left operand + /// \param y right operand + /// \return difference of half expressions + /// \exception FE_INVALID if \a x and \a y are infinities with equal signs or signaling NaNs + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half operator-(half x, half y) + { + #ifdef HALF_ARITHMETIC_TYPE + return half(detail::binary, detail::float2half(detail::half2float(x.data_)-detail::half2float(y.data_))); + #else + return x + -y; + #endif + } + + /// Multiplication. + /// This operation is exact to rounding for all rounding modes. + /// \param x left operand + /// \param y right operand + /// \return product of half expressions + /// \exception FE_INVALID if multiplying 0 with infinity or if \a x or \a y is signaling NaN + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half operator*(half x, half y) + { + #ifdef HALF_ARITHMETIC_TYPE + return half(detail::binary, detail::float2half(detail::half2float(x.data_)*detail::half2float(y.data_))); + #else + int absx = x.data_ & 0x7FFF, absy = y.data_ & 0x7FFF, exp = -16; + unsigned int sign = (x.data_^y.data_) & 0x8000; + if(absx >= 0x7C00 || absy >= 0x7C00) + return half(detail::binary, (absx>0x7C00 || absy>0x7C00) ? detail::signal(x.data_, y.data_) : + ((absx==0x7C00 && !absy)||(absy==0x7C00 && !absx)) ? detail::invalid() : (sign|0x7C00)); + if(!absx || !absy) + return half(detail::binary, sign); + for(; absx<0x400; absx<<=1,--exp) ; + for(; absy<0x400; absy<<=1,--exp) ; + detail::uint32 m = static_cast((absx&0x3FF)|0x400) * static_cast((absy&0x3FF)|0x400); + int i = static_cast(m >> 21), s = static_cast(m & i); + exp += (absx>>10) + (absy>>10) + i; + if(exp > 29) + return half(detail::binary, detail::overflow(sign)); + else if(exp < -11) + return half(detail::binary, detail::underflow(sign)); + return half(detail::binary, detail::fixed2half(m>>i, exp, sign, s)); + #endif + } + + /// Division. + /// This operation is exact to rounding for all rounding modes. + /// \param x left operand + /// \param y right operand + /// \return quotient of half expressions + /// \exception FE_INVALID if dividing 0s or infinities with each other or if \a x or \a y is signaling NaN + /// \exception FE_DIVBYZERO if dividing finite value by 0 + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half operator/(half x, half y) + { + #ifdef HALF_ARITHMETIC_TYPE + return half(detail::binary, detail::float2half(detail::half2float(x.data_)/detail::half2float(y.data_))); + #else + int absx = x.data_ & 0x7FFF, absy = y.data_ & 0x7FFF, exp = 14; + unsigned int sign = (x.data_^y.data_) & 0x8000; + if(absx >= 0x7C00 || absy >= 0x7C00) + return half(detail::binary, (absx>0x7C00 || absy>0x7C00) ? detail::signal(x.data_, y.data_) : + (absx==absy) ? detail::invalid() : (sign|((absx==0x7C00) ? 0x7C00 : 0))); + if(!absx) + return half(detail::binary, absy ? sign : detail::invalid()); + if(!absy) + return half(detail::binary, detail::pole(sign)); + for(; absx<0x400; absx<<=1,--exp) ; + for(; absy<0x400; absy<<=1,++exp) ; + detail::uint32 mx = (absx&0x3FF) | 0x400, my = (absy&0x3FF) | 0x400; + int i = mx < my; + exp += (absx>>10) - (absy>>10) - i; + if(exp > 29) + return half(detail::binary, detail::overflow(sign)); + else if(exp < -11) + return half(detail::binary, detail::underflow(sign)); + mx <<= 12 + i; + my <<= 1; + return half(detail::binary, detail::fixed2half(mx/my, exp, sign, mx%my!=0)); + #endif + } + + /// \} + /// \anchor streaming + /// \name Input and output + /// \{ + + /// Output operator. + /// This uses the built-in functionality for streaming out floating-point numbers. + /// \param out output stream to write into + /// \param arg half expression to write + /// \return reference to output stream + template std::basic_ostream& operator<<(std::basic_ostream &out, half arg) + { + #ifdef HALF_ARITHMETIC_TYPE + return out << detail::half2float(arg.data_); + #else + return out << detail::half2float(arg.data_); + #endif + } + + /// Input operator. + /// This uses the built-in functionality for streaming in floating-point numbers, specifically double precision floating + /// point numbers (unless overridden with [HALF_ARITHMETIC_TYPE](\ref HALF_ARITHMETIC_TYPE)). So the input string is first + /// rounded to double precision using the underlying platform's current floating-point rounding mode before being rounded + /// to half-precision using the library's half-precision rounding mode. + /// \param in input stream to read from + /// \param arg half to read into + /// \return reference to input stream + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + template std::basic_istream& operator>>(std::basic_istream &in, half &arg) + { + #ifdef HALF_ARITHMETIC_TYPE + detail::internal_t f; + #else + double f; + #endif + if(in >> f) + arg.data_ = detail::float2half(f); + return in; + } + + /// \} + /// \anchor basic + /// \name Basic mathematical operations + /// \{ + + /// Absolute value. + /// **See also:** Documentation for [std::fabs](https://en.cppreference.com/w/cpp/numeric/math/fabs). + /// \param arg operand + /// \return absolute value of \a arg + inline HALF_CONSTEXPR half fabs(half arg) { return half(detail::binary, arg.data_&0x7FFF); } + + /// Absolute value. + /// **See also:** Documentation for [std::abs](https://en.cppreference.com/w/cpp/numeric/math/fabs). + /// \param arg operand + /// \return absolute value of \a arg + inline HALF_CONSTEXPR half abs(half arg) { return fabs(arg); } + + /// Remainder of division. + /// **See also:** Documentation for [std::fmod](https://en.cppreference.com/w/cpp/numeric/math/fmod). + /// \param x first operand + /// \param y second operand + /// \return remainder of floating-point division. + /// \exception FE_INVALID if \a x is infinite or \a y is 0 or if \a x or \a y is signaling NaN + inline half fmod(half x, half y) + { + unsigned int absx = x.data_ & 0x7FFF, absy = y.data_ & 0x7FFF, sign = x.data_ & 0x8000; + if(absx >= 0x7C00 || absy >= 0x7C00) + return half(detail::binary, (absx>0x7C00 || absy>0x7C00) ? detail::signal(x.data_, y.data_) : + (absx==0x7C00) ? detail::invalid() : x.data_); + if(!absy) + return half(detail::binary, detail::invalid()); + if(!absx) + return x; + if(absx == absy) + return half(detail::binary, sign); + return half(detail::binary, sign|detail::mod(absx, absy)); + } + + /// Remainder of division. + /// **See also:** Documentation for [std::remainder](https://en.cppreference.com/w/cpp/numeric/math/remainder). + /// \param x first operand + /// \param y second operand + /// \return remainder of floating-point division. + /// \exception FE_INVALID if \a x is infinite or \a y is 0 or if \a x or \a y is signaling NaN + inline half remainder(half x, half y) + { + unsigned int absx = x.data_ & 0x7FFF, absy = y.data_ & 0x7FFF, sign = x.data_ & 0x8000; + if(absx >= 0x7C00 || absy >= 0x7C00) + return half(detail::binary, (absx>0x7C00 || absy>0x7C00) ? detail::signal(x.data_, y.data_) : + (absx==0x7C00) ? detail::invalid() : x.data_); + if(!absy) + return half(detail::binary, detail::invalid()); + if(absx == absy) + return half(detail::binary, sign); + return half(detail::binary, sign^detail::mod(absx, absy)); + } + + /// Remainder of division. + /// **See also:** Documentation for [std::remquo](https://en.cppreference.com/w/cpp/numeric/math/remquo). + /// \param x first operand + /// \param y second operand + /// \param quo address to store some bits of quotient at + /// \return remainder of floating-point division. + /// \exception FE_INVALID if \a x is infinite or \a y is 0 or if \a x or \a y is signaling NaN + inline half remquo(half x, half y, int *quo) + { + unsigned int absx = x.data_ & 0x7FFF, absy = y.data_ & 0x7FFF, value = x.data_ & 0x8000; + if(absx >= 0x7C00 || absy >= 0x7C00) + return half(detail::binary, (absx>0x7C00 || absy>0x7C00) ? detail::signal(x.data_, y.data_) : + (absx==0x7C00) ? detail::invalid() : (*quo = 0, x.data_)); + if(!absy) + return half(detail::binary, detail::invalid()); + bool qsign = ((value^y.data_)&0x8000) != 0; + int q = 1; + if(absx != absy) + value ^= detail::mod(absx, absy, &q); + return *quo = qsign ? -q : q, half(detail::binary, value); + } + + /// Fused multiply add. + /// This function is exact to rounding for all rounding modes. + /// + /// **See also:** Documentation for [std::fma](https://en.cppreference.com/w/cpp/numeric/math/fma). + /// \param x first operand + /// \param y second operand + /// \param z third operand + /// \return ( \a x * \a y ) + \a z rounded as one operation. + /// \exception FE_INVALID according to operator*() and operator+() unless any argument is a quiet NaN and no argument is a signaling NaN + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding the final addition + inline half fma(half x, half y, half z) + { + #ifdef HALF_ARITHMETIC_TYPE + detail::internal_t fx = detail::half2float(x.data_), fy = detail::half2float(y.data_), fz = detail::half2float(z.data_); + #if HALF_ENABLE_CPP11_CMATH && FP_FAST_FMA + return half(detail::binary, detail::float2half(std::fma(fx, fy, fz))); + #else + return half(detail::binary, detail::float2half(fx*fy+fz)); + #endif + #else + int absx = x.data_ & 0x7FFF, absy = y.data_ & 0x7FFF, absz = z.data_ & 0x7FFF, exp = -15; + unsigned int sign = (x.data_^y.data_) & 0x8000; + bool sub = ((sign^z.data_)&0x8000) != 0; + if(absx >= 0x7C00 || absy >= 0x7C00 || absz >= 0x7C00) + return (absx>0x7C00 || absy>0x7C00 || absz>0x7C00) ? half(detail::binary, detail::signal(x.data_, y.data_, z.data_)) : + (absx==0x7C00) ? half(detail::binary, (!absy || (sub && absz==0x7C00)) ? detail::invalid() : (sign|0x7C00)) : + (absy==0x7C00) ? half(detail::binary, (!absx || (sub && absz==0x7C00)) ? detail::invalid() : (sign|0x7C00)) : z; + if(!absx || !absy) + return absz ? z : half(detail::binary, (half::round_style==std::round_toward_neg_infinity) ? (z.data_|sign) : (z.data_&sign)); + for(; absx<0x400; absx<<=1,--exp) ; + for(; absy<0x400; absy<<=1,--exp) ; + detail::uint32 m = static_cast((absx&0x3FF)|0x400) * static_cast((absy&0x3FF)|0x400); + int i = static_cast(m >> 21); + exp += (absx>>10) + (absy>>10) + i; + m <<= 3 - i; + if(absz) + { + int expz = 0; + for(; absz<0x400; absz<<=1,--expz) ; + expz += absz >> 10; + detail::uint32 mz = static_cast((absz&0x3FF)|0x400) << 13; + if(expz > exp || (expz == exp && mz > m)) + { + std::swap(m, mz); + std::swap(exp, expz); + if(sub) + sign = z.data_ & 0x8000; + } + int d = exp - expz; + mz = (d<23) ? ((mz>>d)|((mz&((static_cast(1)<(half::round_style==std::round_toward_neg_infinity)<<15); + for(; m<0x800000; m<<=1,--exp) ; + } + else + { + m += mz; + i = static_cast(m >> 24); + m = (m>>i) | (m&i); + exp += i; + } + } + if(exp > 30) + return half(detail::binary, detail::overflow(sign)); + else if(exp < -10) + return half(detail::binary, detail::underflow(sign)); + return half(detail::binary, detail::fixed2half(m, exp-1, sign)); + #endif + } + + /// Maximum of half expressions. + /// **See also:** Documentation for [std::fmax](https://en.cppreference.com/w/cpp/numeric/math/fmax). + /// \param x first operand + /// \param y second operand + /// \return maximum of operands, ignoring quiet NaNs + /// \exception FE_INVALID if \a x or \a y is signaling NaN + inline HALF_CONSTEXPR_NOERR half fmax(half x, half y) + { + return half(detail::binary, (!isnan(y) && (isnan(x) || (x.data_^(0x8000|(0x8000-(x.data_>>15)))) < + (y.data_^(0x8000|(0x8000-(y.data_>>15)))))) ? detail::select(y.data_, x.data_) : detail::select(x.data_, y.data_)); + } + + /// Minimum of half expressions. + /// **See also:** Documentation for [std::fmin](https://en.cppreference.com/w/cpp/numeric/math/fmin). + /// \param x first operand + /// \param y second operand + /// \return minimum of operands, ignoring quiet NaNs + /// \exception FE_INVALID if \a x or \a y is signaling NaN + inline HALF_CONSTEXPR_NOERR half fmin(half x, half y) + { + return half(detail::binary, (!isnan(y) && (isnan(x) || (x.data_^(0x8000|(0x8000-(x.data_>>15)))) > + (y.data_^(0x8000|(0x8000-(y.data_>>15)))))) ? detail::select(y.data_, x.data_) : detail::select(x.data_, y.data_)); + } + + /// Positive difference. + /// This function is exact to rounding for all rounding modes. + /// + /// **See also:** Documentation for [std::fdim](https://en.cppreference.com/w/cpp/numeric/math/fdim). + /// \param x first operand + /// \param y second operand + /// \return \a x - \a y or 0 if difference negative + /// \exception FE_... according to operator-(half,half) + inline half fdim(half x, half y) + { + if(isnan(x) || isnan(y)) + return half(detail::binary, detail::signal(x.data_, y.data_)); + return (x.data_^(0x8000|(0x8000-(x.data_>>15)))) <= (y.data_^(0x8000|(0x8000-(y.data_>>15)))) ? half(detail::binary, 0) : (x-y); + } + + /// Get NaN value. + /// **See also:** Documentation for [std::nan](https://en.cppreference.com/w/cpp/numeric/math/nan). + /// \param arg string code + /// \return quiet NaN + inline half nanh(const char *arg) + { + unsigned int value = 0x7FFF; + while(*arg) + value ^= static_cast(*arg++) & 0xFF; + return half(detail::binary, value); + } + + /// \} + /// \anchor exponential + /// \name Exponential functions + /// \{ + + /// Exponential function. + /// This function is exact to rounding for all rounding modes. + /// + /// **See also:** Documentation for [std::exp](https://en.cppreference.com/w/cpp/numeric/math/exp). + /// \param arg function argument + /// \return e raised to \a arg + /// \exception FE_INVALID for signaling NaN + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half exp(half arg) + { + #ifdef HALF_ARITHMETIC_TYPE + return half(detail::binary, detail::float2half(std::exp(detail::half2float(arg.data_)))); + #else + int abs = arg.data_ & 0x7FFF, e = (abs>>10) + (abs<=0x3FF), exp; + if(!abs) + return half(detail::binary, 0x3C00); + if(abs >= 0x7C00) + return half(detail::binary, (abs==0x7C00) ? (0x7C00&((arg.data_>>15)-1U)) : detail::signal(arg.data_)); + if(abs >= 0x4C80) + return half(detail::binary, (arg.data_&0x8000) ? detail::underflow() : detail::overflow()); + detail::uint32 m = detail::multiply64(static_cast((abs&0x3FF)+((abs>0x3FF)<<10))<<21, 0xB8AA3B29); + if(e < 14) + { + exp = 0; + m >>= 14 - e; + } + else + { + exp = static_cast(m >> (45-e)); + m = (m<<(e-14)) & 0x7FFFFFFF; + } + return half(detail::binary, detail::exp2_post(m, exp, (arg.data_&0x8000)!=0, 0, 26)); + #endif + } + + /// Binary exponential. + /// This function is exact to rounding for all rounding modes. + /// + /// **See also:** Documentation for [std::exp2](https://en.cppreference.com/w/cpp/numeric/math/exp2). + /// \param arg function argument + /// \return 2 raised to \a arg + /// \exception FE_INVALID for signaling NaN + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half exp2(half arg) + { + #if defined(HALF_ARITHMETIC_TYPE) && HALF_ENABLE_CPP11_CMATH + return half(detail::binary, detail::float2half(std::exp2(detail::half2float(arg.data_)))); + #else + int abs = arg.data_ & 0x7FFF, e = (abs>>10) + (abs<=0x3FF), exp = (abs&0x3FF) + ((abs>0x3FF)<<10); + if(!abs) + return half(detail::binary, 0x3C00); + if(abs >= 0x7C00) + return half(detail::binary, (abs==0x7C00) ? (0x7C00&((arg.data_>>15)-1U)) : detail::signal(arg.data_)); + if(abs >= 0x4E40) + return half(detail::binary, (arg.data_&0x8000) ? detail::underflow() : detail::overflow()); + return half(detail::binary, detail::exp2_post( + (static_cast(exp)<<(6+e))&0x7FFFFFFF, exp>>(25-e), (arg.data_&0x8000)!=0, 0, 28)); + #endif + } + + /// Exponential minus one. + /// This function may be 1 ULP off the correctly rounded exact result in <0.05% of inputs for `std::round_to_nearest` + /// and in <1% of inputs for any other rounding mode. + /// + /// **See also:** Documentation for [std::expm1](https://en.cppreference.com/w/cpp/numeric/math/expm1). + /// \param arg function argument + /// \return e raised to \a arg and subtracted by 1 + /// \exception FE_INVALID for signaling NaN + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half expm1(half arg) + { + #if defined(HALF_ARITHMETIC_TYPE) && HALF_ENABLE_CPP11_CMATH + return half(detail::binary, detail::float2half(std::expm1(detail::half2float(arg.data_)))); + #else + unsigned int abs = arg.data_ & 0x7FFF, sign = arg.data_ & 0x8000, e = (abs>>10) + (abs<=0x3FF), exp; + if(!abs) + return arg; + if(abs >= 0x7C00) + return half(detail::binary, (abs==0x7C00) ? (0x7C00+(sign>>1)) : detail::signal(arg.data_)); + if(abs >= 0x4A00) + return half(detail::binary, (arg.data_&0x8000) ? detail::rounded(0xBBFF, 1, 1) : detail::overflow()); + detail::uint32 m = detail::multiply64(static_cast((abs&0x3FF)+((abs>0x3FF)<<10))<<21, 0xB8AA3B29); + if(e < 14) + { + exp = 0; + m >>= 14 - e; + } + else + { + exp = static_cast(m >> (45-e)); + m = (m<<(e-14)) & 0x7FFFFFFF; + } + m = detail::exp2(m); + if(sign) + { + int s = 0; + if(m > 0x80000000) + { + ++exp; + m = detail::divide64(0x80000000, m, s); + } + m = 0x80000000 - ((m>>exp)|((m&((static_cast(1)<>exp) : 1; + for(exp+=14; m<0x80000000 && exp; m<<=1,--exp) ; + if(exp > 29) + return half(detail::binary, detail::overflow()); + return half(detail::binary, detail::rounded(sign+(exp<<10)+static_cast(m>>21), static_cast((m>>20)&1), (m&0xFFFFF)!=0)); + #endif + } + + /// Natural logarithm. + /// This function is exact to rounding for all rounding modes. + /// + /// **See also:** Documentation for [std::log](https://en.cppreference.com/w/cpp/numeric/math/log). + /// \param arg function argument + /// \return logarithm of \a arg to base e + /// \exception FE_INVALID for signaling NaN or negative argument + /// \exception FE_DIVBYZERO for 0 + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half log(half arg) + { + #ifdef HALF_ARITHMETIC_TYPE + return half(detail::binary, detail::float2half(std::log(detail::half2float(arg.data_)))); + #else + int abs = arg.data_ & 0x7FFF, exp = -15; + if(!abs) + return half(detail::binary, detail::pole(0x8000)); + if(arg.data_ & 0x8000) + return half(detail::binary, (arg.data_<=0xFC00) ? detail::invalid() : detail::signal(arg.data_)); + if(abs >= 0x7C00) + return (abs==0x7C00) ? arg : half(detail::binary, detail::signal(arg.data_)); + for(; abs<0x400; abs<<=1,--exp) ; + exp += abs >> 10; + return half(detail::binary, detail::log2_post( + detail::log2(static_cast((abs&0x3FF)|0x400)<<20, 27)+8, exp, 17)); + #endif + } + + /// Common logarithm. + /// This function is exact to rounding for all rounding modes. + /// + /// **See also:** Documentation for [std::log10](https://en.cppreference.com/w/cpp/numeric/math/log10). + /// \param arg function argument + /// \return logarithm of \a arg to base 10 + /// \exception FE_INVALID for signaling NaN or negative argument + /// \exception FE_DIVBYZERO for 0 + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half log10(half arg) + { + #ifdef HALF_ARITHMETIC_TYPE + return half(detail::binary, detail::float2half(std::log10(detail::half2float(arg.data_)))); + #else + int abs = arg.data_ & 0x7FFF, exp = -15; + if(!abs) + return half(detail::binary, detail::pole(0x8000)); + if(arg.data_ & 0x8000) + return half(detail::binary, (arg.data_<=0xFC00) ? detail::invalid() : detail::signal(arg.data_)); + if(abs >= 0x7C00) + return (abs==0x7C00) ? arg : half(detail::binary, detail::signal(arg.data_)); + switch(abs) + { + case 0x4900: return half(detail::binary, 0x3C00); + case 0x5640: return half(detail::binary, 0x4000); + case 0x63D0: return half(detail::binary, 0x4200); + case 0x70E2: return half(detail::binary, 0x4400); + } + for(; abs<0x400; abs<<=1,--exp) ; + exp += abs >> 10; + return half(detail::binary, detail::log2_post( + detail::log2(static_cast((abs&0x3FF)|0x400)<<20, 27)+8, exp, 16)); + #endif + } + + /// Binary logarithm. + /// This function is exact to rounding for all rounding modes. + /// + /// **See also:** Documentation for [std::log2](https://en.cppreference.com/w/cpp/numeric/math/log2). + /// \param arg function argument + /// \return logarithm of \a arg to base 2 + /// \exception FE_INVALID for signaling NaN or negative argument + /// \exception FE_DIVBYZERO for 0 + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half log2(half arg) + { + #if defined(HALF_ARITHMETIC_TYPE) && HALF_ENABLE_CPP11_CMATH + return half(detail::binary, detail::float2half(std::log2(detail::half2float(arg.data_)))); + #else + int abs = arg.data_ & 0x7FFF, exp = -15, s = 0; + if(!abs) + return half(detail::binary, detail::pole(0x8000)); + if(arg.data_ & 0x8000) + return half(detail::binary, (arg.data_<=0xFC00) ? detail::invalid() : detail::signal(arg.data_)); + if(abs >= 0x7C00) + return (abs==0x7C00) ? arg : half(detail::binary, detail::signal(arg.data_)); + if(abs == 0x3C00) + return half(detail::binary, 0); + for(; abs<0x400; abs<<=1,--exp) ; + exp += (abs>>10); + if(!(abs&0x3FF)) + { + unsigned int value = static_cast(exp<0) << 15, m = std::abs(exp) << 6; + for(exp=18; m<0x400; m<<=1,--exp) ; + return half(detail::binary, value+(exp<<10)+m); + } + detail::uint32 ilog = exp, sign = detail::sign_mask(ilog), m = + (((ilog<<27)+(detail::log2(static_cast((abs&0x3FF)|0x400)<<20, 28)>>4))^sign) - sign; + if(!m) + return half(detail::binary, 0); + for(exp=14; m<0x8000000 && exp; m<<=1,--exp) ; + for(; m>0xFFFFFFF; m>>=1,++exp) + s |= m & 1; + return half(detail::binary, detail::fixed2half(m, exp, sign&0x8000, s)); + #endif + } + + /// Natural logarithm plus one. + /// This function may be 1 ULP off the correctly rounded exact result in <0.05% of inputs for `std::round_to_nearest` + /// and in ~1% of inputs for any other rounding mode. + /// + /// **See also:** Documentation for [std::log1p](https://en.cppreference.com/w/cpp/numeric/math/log1p). + /// \param arg function argument + /// \return logarithm of \a arg plus 1 to base e + /// \exception FE_INVALID for signaling NaN or argument <-1 + /// \exception FE_DIVBYZERO for -1 + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half log1p(half arg) + { + #if defined(HALF_ARITHMETIC_TYPE) && HALF_ENABLE_CPP11_CMATH + return half(detail::binary, detail::float2half(std::log1p(detail::half2float(arg.data_)))); + #else + if(arg.data_ >= 0xBC00) + return half(detail::binary, (arg.data_==0xBC00) ? detail::pole(0x8000) : (arg.data_<=0xFC00) ? detail::invalid() : detail::signal(arg.data_)); + int abs = arg.data_ & 0x7FFF, exp = -15; + if(!abs || abs >= 0x7C00) + return (abs>0x7C00) ? half(detail::binary, detail::signal(arg.data_)) : arg; + for(; abs<0x400; abs<<=1,--exp) ; + exp += abs >> 10; + detail::uint32 m = static_cast((abs&0x3FF)|0x400) << 20; + if(arg.data_ & 0x8000) + { + m = 0x40000000 - (m>>-exp); + for(exp=0; m<0x40000000; m<<=1,--exp) ; + } + else + { + if(exp < 0) + { + m = 0x40000000 + (m>>-exp); + exp = 0; + } + else + { + m += 0x40000000 >> exp; + int i = static_cast(m >> 31); + m >>= i; + exp += i; + } + } + return half(detail::binary, detail::log2_post(detail::log2(m), exp, 17)); + #endif + } + + /// \} + /// \anchor power + /// \name Power functions + /// \{ + + /// Square root. + /// This function is exact to rounding for all rounding modes. + /// + /// **See also:** Documentation for [std::sqrt](https://en.cppreference.com/w/cpp/numeric/math/sqrt). + /// \param arg function argument + /// \return square root of \a arg + /// \exception FE_INVALID for signaling NaN and negative arguments + /// \exception FE_INEXACT according to rounding + inline half sqrt(half arg) + { + #ifdef HALF_ARITHMETIC_TYPE + return half(detail::binary, detail::float2half(std::sqrt(detail::half2float(arg.data_)))); + #else + int abs = arg.data_ & 0x7FFF, exp = 15; + if(!abs || arg.data_ >= 0x7C00) + return half(detail::binary, (abs>0x7C00) ? detail::signal(arg.data_) : (arg.data_>0x8000) ? detail::invalid() : arg.data_); + for(; abs<0x400; abs<<=1,--exp) ; + detail::uint32 r = static_cast((abs&0x3FF)|0x400) << 10, m = detail::sqrt<20>(r, exp+=abs>>10); + return half(detail::binary, detail::rounded((exp<<10)+(m&0x3FF), r>m, r!=0)); + #endif + } + + /// Inverse square root. + /// This function is exact to rounding for all rounding modes and thus generally more accurate than directly computing + /// 1 / sqrt(\a arg) in half-precision, in addition to also being faster. + /// \param arg function argument + /// \return reciprocal of square root of \a arg + /// \exception FE_INVALID for signaling NaN and negative arguments + /// \exception FE_INEXACT according to rounding + inline half rsqrt(half arg) + { + #ifdef HALF_ARITHMETIC_TYPE + return half(detail::binary, detail::float2half(detail::internal_t(1)/std::sqrt(detail::half2float(arg.data_)))); + #else + unsigned int abs = arg.data_ & 0x7FFF, bias = 0x4000; + if(!abs || arg.data_ >= 0x7C00) + return half(detail::binary, (abs>0x7C00) ? detail::signal(arg.data_) : (arg.data_>0x8000) ? + detail::invalid() : !abs ? detail::pole(arg.data_&0x8000) : 0); + for(; abs<0x400; abs<<=1,bias-=0x400) ; + unsigned int frac = (abs+=bias) & 0x7FF; + if(frac == 0x400) + return half(detail::binary, 0x7A00-(abs>>1)); + if((half::round_style == std::round_to_nearest && (frac == 0x3FE || frac == 0x76C)) || + (half::round_style != std::round_to_nearest && (frac == 0x15A || frac == 0x3FC || frac == 0x401 || frac == 0x402 || frac == 0x67B))) + return pow(arg, half(detail::binary, 0xB800)); + detail::uint32 f = 0x17376 - abs, mx = (abs&0x3FF) | 0x400, my = ((f>>1)&0x3FF) | 0x400, mz = my * my; + int expy = static_cast(f>>11) - 31, expx = 32 - (abs>>10), i = static_cast(mz >> 21); + for(mz=0x60000000-(((mz>>i)*mx)>>(expx-2*expy-i)); mz<0x40000000; mz<<=1,--expy) ; + i = static_cast((my*=mz>>10) >> 31); + expy += i; + my = (my>>(20+i)) + 1; + i = static_cast((mz=my*my) >> 21); + for(mz=0x60000000-(((mz>>i)*mx)>>(expx-2*expy-i)); mz<0x40000000; mz<<=1,--expy) ; + i = static_cast((my*=(mz>>10)+1) >> 31); + return half(detail::binary, detail::fixed2half(my>>i, expy+i+14)); + #endif + } + + /// Cubic root. + /// This function is exact to rounding for all rounding modes. + /// + /// **See also:** Documentation for [std::cbrt](https://en.cppreference.com/w/cpp/numeric/math/cbrt). + /// \param arg function argument + /// \return cubic root of \a arg + /// \exception FE_INVALID for signaling NaN + /// \exception FE_INEXACT according to rounding + inline half cbrt(half arg) + { + #if defined(HALF_ARITHMETIC_TYPE) && HALF_ENABLE_CPP11_CMATH + return half(detail::binary, detail::float2half(std::cbrt(detail::half2float(arg.data_)))); + #else + int abs = arg.data_ & 0x7FFF, exp = -15; + if(!abs || abs == 0x3C00 || abs >= 0x7C00) + return (abs>0x7C00) ? half(detail::binary, detail::signal(arg.data_)) : arg; + for(; abs<0x400; abs<<=1, --exp); + detail::uint32 ilog = exp + (abs>>10), sign = detail::sign_mask(ilog), f, m = + (((ilog<<27)+(detail::log2(static_cast((abs&0x3FF)|0x400)<<20, 24)>>4))^sign) - sign; + for(exp=2; m<0x80000000; m<<=1,--exp) ; + m = detail::multiply64(m, 0xAAAAAAAB); + int i = static_cast(m >> 31), s; + exp += i; + m <<= 1 - i; + if(exp < 0) + { + f = m >> -exp; + exp = 0; + } + else + { + f = (m<(m >> (31-exp)); + } + m = detail::exp2(f, (half::round_style==std::round_to_nearest) ? 29 : 26); + if(sign) + { + if(m > 0x80000000) + { + m = detail::divide64(0x80000000, m, s); + ++exp; + } + exp = -exp; + } + return half(detail::binary, (half::round_style==std::round_to_nearest) ? + detail::fixed2half(m, exp+14, arg.data_&0x8000) : + detail::fixed2half((m+0x80)>>8, exp+14, arg.data_&0x8000)); + #endif + } + + /// Hypotenuse function. + /// This function is exact to rounding for all rounding modes. + /// + /// **See also:** Documentation for [std::hypot](https://en.cppreference.com/w/cpp/numeric/math/hypot). + /// \param x first argument + /// \param y second argument + /// \return square root of sum of squares without internal over- or underflows + /// \exception FE_INVALID if \a x or \a y is signaling NaN + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding of the final square root + inline half hypot(half x, half y) + { + #ifdef HALF_ARITHMETIC_TYPE + detail::internal_t fx = detail::half2float(x.data_), fy = detail::half2float(y.data_); + #if HALF_ENABLE_CPP11_CMATH + return half(detail::binary, detail::float2half(std::hypot(fx, fy))); + #else + return half(detail::binary, detail::float2half(std::sqrt(fx*fx+fy*fy))); + #endif + #else + int absx = x.data_ & 0x7FFF, absy = y.data_ & 0x7FFF, expx = 0, expy = 0; + if(absx >= 0x7C00 || absy >= 0x7C00) + return half(detail::binary, (absx==0x7C00) ? detail::select(0x7C00, y.data_) : + (absy==0x7C00) ? detail::select(0x7C00, x.data_) : detail::signal(x.data_, y.data_)); + if(!absx) + return half(detail::binary, absy ? detail::check_underflow(absy) : 0); + if(!absy) + return half(detail::binary, detail::check_underflow(absx)); + if(absy > absx) + std::swap(absx, absy); + for(; absx<0x400; absx<<=1,--expx) ; + for(; absy<0x400; absy<<=1,--expy) ; + detail::uint32 mx = (absx&0x3FF) | 0x400, my = (absy&0x3FF) | 0x400; + mx *= mx; + my *= my; + int ix = static_cast(mx >> 21), iy = static_cast(my >> 21); + expx = 2*(expx+(absx>>10)) - 15 + ix; + expy = 2*(expy+(absy>>10)) - 15 + iy; + mx <<= 10 - ix; + my <<= 10 - iy; + int d = expx - expy; + my = (d<30) ? ((my>>d)|((my&((static_cast(1)<(mx+my, expx)); + #endif + } + + /// Hypotenuse function. + /// This function is exact to rounding for all rounding modes. + /// + /// **See also:** Documentation for [std::hypot](https://en.cppreference.com/w/cpp/numeric/math/hypot). + /// \param x first argument + /// \param y second argument + /// \param z third argument + /// \return square root of sum of squares without internal over- or underflows + /// \exception FE_INVALID if \a x, \a y or \a z is signaling NaN + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding of the final square root + inline half hypot(half x, half y, half z) + { + #ifdef HALF_ARITHMETIC_TYPE + detail::internal_t fx = detail::half2float(x.data_), fy = detail::half2float(y.data_), fz = detail::half2float(z.data_); + return half(detail::binary, detail::float2half(std::sqrt(fx*fx+fy*fy+fz*fz))); + #else + int absx = x.data_ & 0x7FFF, absy = y.data_ & 0x7FFF, absz = z.data_ & 0x7FFF, expx = 0, expy = 0, expz = 0; + if(!absx) + return hypot(y, z); + if(!absy) + return hypot(x, z); + if(!absz) + return hypot(x, y); + if(absx >= 0x7C00 || absy >= 0x7C00 || absz >= 0x7C00) + return half(detail::binary, (absx==0x7C00) ? detail::select(0x7C00, detail::select(y.data_, z.data_)) : + (absy==0x7C00) ? detail::select(0x7C00, detail::select(x.data_, z.data_)) : + (absz==0x7C00) ? detail::select(0x7C00, detail::select(x.data_, y.data_)) : + detail::signal(x.data_, y.data_, z.data_)); + if(absz > absy) + std::swap(absy, absz); + if(absy > absx) + std::swap(absx, absy); + if(absz > absy) + std::swap(absy, absz); + for(; absx<0x400; absx<<=1,--expx) ; + for(; absy<0x400; absy<<=1,--expy) ; + for(; absz<0x400; absz<<=1,--expz) ; + detail::uint32 mx = (absx&0x3FF) | 0x400, my = (absy&0x3FF) | 0x400, mz = (absz&0x3FF) | 0x400; + mx *= mx; + my *= my; + mz *= mz; + int ix = static_cast(mx >> 21), iy = static_cast(my >> 21), iz = static_cast(mz >> 21); + expx = 2*(expx+(absx>>10)) - 15 + ix; + expy = 2*(expy+(absy>>10)) - 15 + iy; + expz = 2*(expz+(absz>>10)) - 15 + iz; + mx <<= 10 - ix; + my <<= 10 - iy; + mz <<= 10 - iz; + int d = expy - expz; + mz = (d<30) ? ((mz>>d)|((mz&((static_cast(1)<>1) | (my&1); + if(++expy > expx) + { + std::swap(mx, my); + std::swap(expx, expy); + } + } + d = expx - expy; + my = (d<30) ? ((my>>d)|((my&((static_cast(1)<(mx+my, expx)); + #endif + } + + /// Power function. + /// This function may be 1 ULP off the correctly rounded exact result for any rounding mode in ~0.00025% of inputs. + /// + /// **See also:** Documentation for [std::pow](https://en.cppreference.com/w/cpp/numeric/math/pow). + /// \param x base + /// \param y exponent + /// \return \a x raised to \a y + /// \exception FE_INVALID if \a x or \a y is signaling NaN or if \a x is finite an negative and \a y is finite and not integral + /// \exception FE_DIVBYZERO if \a x is 0 and \a y is negative + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half pow(half x, half y) + { + #ifdef HALF_ARITHMETIC_TYPE + return half(detail::binary, detail::float2half(std::pow(detail::half2float(x.data_), detail::half2float(y.data_)))); + #else + int absx = x.data_ & 0x7FFF, absy = y.data_ & 0x7FFF, exp = -15; + if(!absy || x.data_ == 0x3C00) + return half(detail::binary, detail::select(0x3C00, (x.data_==0x3C00) ? y.data_ : x.data_)); + bool is_int = absy >= 0x6400 || (absy>=0x3C00 && !(absy&((1<<(25-(absy>>10)))-1))); + unsigned int sign = x.data_ & (static_cast((absy<0x6800)&&is_int&&((absy>>(25-(absy>>10)))&1))<<15); + if(absx >= 0x7C00 || absy >= 0x7C00) + return half(detail::binary, (absx>0x7C00 || absy>0x7C00) ? detail::signal(x.data_, y.data_) : + (absy==0x7C00) ? ((absx==0x3C00) ? 0x3C00 : (!absx && y.data_==0xFC00) ? detail::pole() : + (0x7C00&-((y.data_>>15)^(absx>0x3C00)))) : (sign|(0x7C00&((y.data_>>15)-1U)))); + if(!absx) + return half(detail::binary, (y.data_&0x8000) ? detail::pole(sign) : sign); + if((x.data_&0x8000) && !is_int) + return half(detail::binary, detail::invalid()); + if(x.data_ == 0xBC00) + return half(detail::binary, sign|0x3C00); + switch(y.data_) + { + case 0x3800: return sqrt(x); + case 0x3C00: return half(detail::binary, detail::check_underflow(x.data_)); + case 0x4000: return x * x; + case 0xBC00: return half(detail::binary, 0x3C00) / x; + } + for(; absx<0x400; absx<<=1,--exp) ; + detail::uint32 ilog = exp + (absx>>10), msign = detail::sign_mask(ilog), f, m = + (((ilog<<27)+((detail::log2(static_cast((absx&0x3FF)|0x400)<<20)+8)>>4))^msign) - msign; + for(exp=-11; m<0x80000000; m<<=1,--exp) ; + for(; absy<0x400; absy<<=1,--exp) ; + m = detail::multiply64(m, static_cast((absy&0x3FF)|0x400)<<21); + int i = static_cast(m >> 31); + exp += (absy>>10) + i; + m <<= 1 - i; + if(exp < 0) + { + f = m >> -exp; + exp = 0; + } + else + { + f = (m<(m >> (31-exp)); + } + return half(detail::binary, detail::exp2_post(f, exp, ((msign&1)^(y.data_>>15))!=0, sign)); + #endif + } + + /// \} + /// \anchor trigonometric + /// \name Trigonometric functions + /// \{ + + /// Compute sine and cosine simultaneously. + /// This returns the same results as sin() and cos() but is faster than calling each function individually. + /// + /// This function is exact to rounding for all rounding modes. + /// \param arg function argument + /// \param sin variable to take sine of \a arg + /// \param cos variable to take cosine of \a arg + /// \exception FE_INVALID for signaling NaN or infinity + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline void sincos(half arg, half *sin, half *cos) + { + #ifdef HALF_ARITHMETIC_TYPE + detail::internal_t f = detail::half2float(arg.data_); + *sin = half(detail::binary, detail::float2half(std::sin(f))); + *cos = half(detail::binary, detail::float2half(std::cos(f))); + #else + int abs = arg.data_ & 0x7FFF, sign = arg.data_ >> 15, k; + if(abs >= 0x7C00) + *sin = *cos = half(detail::binary, (abs==0x7C00) ? detail::invalid() : detail::signal(arg.data_)); + else if(!abs) + { + *sin = arg; + *cos = half(detail::binary, 0x3C00); + } + else if(abs < 0x2500) + { + *sin = half(detail::binary, detail::rounded(arg.data_-1, 1, 1)); + *cos = half(detail::binary, detail::rounded(0x3BFF, 1, 1)); + } + else + { + if(half::round_style != std::round_to_nearest) + { + switch(abs) + { + case 0x48B7: + *sin = half(detail::binary, detail::rounded((~arg.data_&0x8000)|0x1D07, 1, 1)); + *cos = half(detail::binary, detail::rounded(0xBBFF, 1, 1)); + return; + case 0x598C: + *sin = half(detail::binary, detail::rounded((arg.data_&0x8000)|0x3BFF, 1, 1)); + *cos = half(detail::binary, detail::rounded(0x80FC, 1, 1)); + return; + case 0x6A64: + *sin = half(detail::binary, detail::rounded((~arg.data_&0x8000)|0x3BFE, 1, 1)); + *cos = half(detail::binary, detail::rounded(0x27FF, 1, 1)); + return; + case 0x6D8C: + *sin = half(detail::binary, detail::rounded((arg.data_&0x8000)|0x0FE6, 1, 1)); + *cos = half(detail::binary, detail::rounded(0x3BFF, 1, 1)); + return; + } + } + std::pair sc = detail::sincos(detail::angle_arg(abs, k), 28); + switch(k & 3) + { + case 1: sc = std::make_pair(sc.second, -sc.first); break; + case 2: sc = std::make_pair(-sc.first, -sc.second); break; + case 3: sc = std::make_pair(-sc.second, sc.first); break; + } + *sin = half(detail::binary, detail::fixed2half((sc.first^-static_cast(sign))+sign)); + *cos = half(detail::binary, detail::fixed2half(sc.second)); + } + #endif + } + + /// Sine function. + /// This function is exact to rounding for all rounding modes. + /// + /// **See also:** Documentation for [std::sin](https://en.cppreference.com/w/cpp/numeric/math/sin). + /// \param arg function argument + /// \return sine value of \a arg + /// \exception FE_INVALID for signaling NaN or infinity + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half sin(half arg) + { + #ifdef HALF_ARITHMETIC_TYPE + return half(detail::binary, detail::float2half(std::sin(detail::half2float(arg.data_)))); + #else + int abs = arg.data_ & 0x7FFF, k; + if(!abs) + return arg; + if(abs >= 0x7C00) + return half(detail::binary, (abs==0x7C00) ? detail::invalid() : detail::signal(arg.data_)); + if(abs < 0x2900) + return half(detail::binary, detail::rounded(arg.data_-1, 1, 1)); + if(half::round_style != std::round_to_nearest) + switch(abs) + { + case 0x48B7: return half(detail::binary, detail::rounded((~arg.data_&0x8000)|0x1D07, 1, 1)); + case 0x6A64: return half(detail::binary, detail::rounded((~arg.data_&0x8000)|0x3BFE, 1, 1)); + case 0x6D8C: return half(detail::binary, detail::rounded((arg.data_&0x8000)|0x0FE6, 1, 1)); + } + std::pair sc = detail::sincos(detail::angle_arg(abs, k), 28); + detail::uint32 sign = -static_cast(((k>>1)&1)^(arg.data_>>15)); + return half(detail::binary, detail::fixed2half((((k&1) ? sc.second : sc.first)^sign) - sign)); + #endif + } + + /// Cosine function. + /// This function is exact to rounding for all rounding modes. + /// + /// **See also:** Documentation for [std::cos](https://en.cppreference.com/w/cpp/numeric/math/cos). + /// \param arg function argument + /// \return cosine value of \a arg + /// \exception FE_INVALID for signaling NaN or infinity + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half cos(half arg) + { + #ifdef HALF_ARITHMETIC_TYPE + return half(detail::binary, detail::float2half(std::cos(detail::half2float(arg.data_)))); + #else + int abs = arg.data_ & 0x7FFF, k; + if(!abs) + return half(detail::binary, 0x3C00); + if(abs >= 0x7C00) + return half(detail::binary, (abs==0x7C00) ? detail::invalid() : detail::signal(arg.data_)); + if(abs < 0x2500) + return half(detail::binary, detail::rounded(0x3BFF, 1, 1)); + if(half::round_style != std::round_to_nearest && abs == 0x598C) + return half(detail::binary, detail::rounded(0x80FC, 1, 1)); + std::pair sc = detail::sincos(detail::angle_arg(abs, k), 28); + detail::uint32 sign = -static_cast(((k>>1)^k)&1); + return half(detail::binary, detail::fixed2half((((k&1) ? sc.first : sc.second)^sign) - sign)); + #endif + } + + /// Tangent function. + /// This function is exact to rounding for all rounding modes. + /// + /// **See also:** Documentation for [std::tan](https://en.cppreference.com/w/cpp/numeric/math/tan). + /// \param arg function argument + /// \return tangent value of \a arg + /// \exception FE_INVALID for signaling NaN or infinity + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half tan(half arg) + { + #ifdef HALF_ARITHMETIC_TYPE + return half(detail::binary, detail::float2half(std::tan(detail::half2float(arg.data_)))); + #else + int abs = arg.data_ & 0x7FFF, exp = 13, k; + if(!abs) + return arg; + if(abs >= 0x7C00) + return half(detail::binary, (abs==0x7C00) ? detail::invalid() : detail::signal(arg.data_)); + if(abs < 0x2700) + return half(detail::binary, detail::rounded(arg.data_, 0, 1)); + if(half::round_style != std::round_to_nearest) + switch(abs) + { + case 0x658C: return half(detail::binary, detail::rounded((arg.data_&0x8000)|0x07E6, 1, 1)); + case 0x7330: return half(detail::binary, detail::rounded((~arg.data_&0x8000)|0x4B62, 1, 1)); + } + std::pair sc = detail::sincos(detail::angle_arg(abs, k), 30); + if(k & 1) + sc = std::make_pair(-sc.second, sc.first); + detail::uint32 signy = detail::sign_mask(sc.first), signx = detail::sign_mask(sc.second); + detail::uint32 my = (sc.first^signy) - signy, mx = (sc.second^signx) - signx; + for(; my<0x80000000; my<<=1,--exp) ; + for(; mx<0x80000000; mx<<=1,++exp) ; + return half(detail::binary, detail::tangent_post(my, mx, exp, (signy^signx^arg.data_)&0x8000)); + #endif + } + + /// Arc sine. + /// This function is exact to rounding for all rounding modes. + /// + /// **See also:** Documentation for [std::asin](https://en.cppreference.com/w/cpp/numeric/math/asin). + /// \param arg function argument + /// \return arc sine value of \a arg + /// \exception FE_INVALID for signaling NaN or if abs(\a arg) > 1 + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half asin(half arg) + { + #ifdef HALF_ARITHMETIC_TYPE + return half(detail::binary, detail::float2half(std::asin(detail::half2float(arg.data_)))); + #else + unsigned int abs = arg.data_ & 0x7FFF, sign = arg.data_ & 0x8000; + if(!abs) + return arg; + if(abs >= 0x3C00) + return half(detail::binary, (abs>0x7C00) ? detail::signal(arg.data_) : (abs>0x3C00) ? detail::invalid() : + detail::rounded(sign|0x3E48, 0, 1)); + if(abs < 0x2900) + return half(detail::binary, detail::rounded(arg.data_, 0, 1)); + if(half::round_style != std::round_to_nearest && (abs == 0x2B44 || abs == 0x2DC3)) + return half(detail::binary, detail::rounded(arg.data_+1, 1, 1)); + std::pair sc = detail::atan2_args(abs); + detail::uint32 m = detail::atan2(sc.first, sc.second, (half::round_style==std::round_to_nearest) ? 27 : 26); + return half(detail::binary, detail::fixed2half(m, 14, sign)); + #endif + } + + /// Arc cosine function. + /// This function is exact to rounding for all rounding modes. + /// + /// **See also:** Documentation for [std::acos](https://en.cppreference.com/w/cpp/numeric/math/acos). + /// \param arg function argument + /// \return arc cosine value of \a arg + /// \exception FE_INVALID for signaling NaN or if abs(\a arg) > 1 + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half acos(half arg) + { + #ifdef HALF_ARITHMETIC_TYPE + return half(detail::binary, detail::float2half(std::acos(detail::half2float(arg.data_)))); + #else + unsigned int abs = arg.data_ & 0x7FFF, sign = arg.data_ >> 15; + if(!abs) + return half(detail::binary, detail::rounded(0x3E48, 0, 1)); + if(abs >= 0x3C00) + return half(detail::binary, (abs>0x7C00) ? detail::signal(arg.data_) : (abs>0x3C00) ? detail::invalid() : + sign ? detail::rounded(0x4248, 0, 1) : 0); + std::pair cs = detail::atan2_args(abs); + detail::uint32 m = detail::atan2(cs.second, cs.first, 28); + return half(detail::binary, detail::fixed2half(sign ? (0xC90FDAA2-m) : m, 15, 0, sign)); + #endif + } + + /// Arc tangent function. + /// This function is exact to rounding for all rounding modes. + /// + /// **See also:** Documentation for [std::atan](https://en.cppreference.com/w/cpp/numeric/math/atan). + /// \param arg function argument + /// \return arc tangent value of \a arg + /// \exception FE_INVALID for signaling NaN + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half atan(half arg) + { + #ifdef HALF_ARITHMETIC_TYPE + return half(detail::binary, detail::float2half(std::atan(detail::half2float(arg.data_)))); + #else + unsigned int abs = arg.data_ & 0x7FFF, sign = arg.data_ & 0x8000; + if(!abs) + return arg; + if(abs >= 0x7C00) + return half(detail::binary, (abs==0x7C00) ? detail::rounded(sign|0x3E48, 0, 1) : detail::signal(arg.data_)); + if(abs <= 0x2700) + return half(detail::binary, detail::rounded(arg.data_-1, 1, 1)); + int exp = (abs>>10) + (abs<=0x3FF); + detail::uint32 my = (abs&0x3FF) | ((abs>0x3FF)<<10); + detail::uint32 m = (exp>15) ? detail::atan2(my<<19, 0x20000000>>(exp-15), (half::round_style==std::round_to_nearest) ? 26 : 24) : + detail::atan2(my<<(exp+4), 0x20000000, (half::round_style==std::round_to_nearest) ? 30 : 28); + return half(detail::binary, detail::fixed2half(m, 14, sign)); + #endif + } + + /// Arc tangent function. + /// This function may be 1 ULP off the correctly rounded exact result in ~0.005% of inputs for `std::round_to_nearest`, + /// in ~0.1% of inputs for `std::round_toward_zero` and in ~0.02% of inputs for any other rounding mode. + /// + /// **See also:** Documentation for [std::atan2](https://en.cppreference.com/w/cpp/numeric/math/atan2). + /// \param y numerator + /// \param x denominator + /// \return arc tangent value + /// \exception FE_INVALID if \a x or \a y is signaling NaN + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half atan2(half y, half x) + { + #ifdef HALF_ARITHMETIC_TYPE + return half(detail::binary, detail::float2half(std::atan2(detail::half2float(y.data_), detail::half2float(x.data_)))); + #else + unsigned int absx = x.data_ & 0x7FFF, absy = y.data_ & 0x7FFF, signx = x.data_ >> 15, signy = y.data_ & 0x8000; + if(absx >= 0x7C00 || absy >= 0x7C00) + { + if(absx > 0x7C00 || absy > 0x7C00) + return half(detail::binary, detail::signal(x.data_, y.data_)); + if(absy == 0x7C00) + return half(detail::binary, (absx<0x7C00) ? detail::rounded(signy|0x3E48, 0, 1) : + signx ? detail::rounded(signy|0x40B6, 0, 1) : + detail::rounded(signy|0x3A48, 0, 1)); + return (x.data_==0x7C00) ? half(detail::binary, signy) : half(detail::binary, detail::rounded(signy|0x4248, 0, 1)); + } + if(!absy) + return signx ? half(detail::binary, detail::rounded(signy|0x4248, 0, 1)) : y; + if(!absx) + return half(detail::binary, detail::rounded(signy|0x3E48, 0, 1)); + int d = (absy>>10) + (absy<=0x3FF) - (absx>>10) - (absx<=0x3FF); + if(d > (signx ? 18 : 12)) + return half(detail::binary, detail::rounded(signy|0x3E48, 0, 1)); + if(signx && d < -11) + return half(detail::binary, detail::rounded(signy|0x4248, 0, 1)); + if(!signx && d < ((half::round_style==std::round_toward_zero) ? -15 : -9)) + { + for(; absy<0x400; absy<<=1,--d) ; + detail::uint32 mx = ((absx<<1)&0x7FF) | 0x800, my = ((absy<<1)&0x7FF) | 0x800; + int i = my < mx; + d -= i; + if(d < -25) + return half(detail::binary, detail::underflow(signy)); + my <<= 11 + i; + return half(detail::binary, detail::fixed2half(my/mx, d+14, signy, my%mx!=0)); + } + detail::uint32 m = detail::atan2( ((absy&0x3FF)|((absy>0x3FF)<<10))<<(19+((d<0) ? d : (d>0) ? 0 : -1)), + ((absx&0x3FF)|((absx>0x3FF)<<10))<<(19-((d>0) ? d : (d<0) ? 0 : 1))); + return half(detail::binary, detail::fixed2half(signx ? (0xC90FDAA2-m) : m, 15, signy, signx)); + #endif + } + + /// \} + /// \anchor hyperbolic + /// \name Hyperbolic functions + /// \{ + + /// Hyperbolic sine. + /// This function is exact to rounding for all rounding modes. + /// + /// **See also:** Documentation for [std::sinh](https://en.cppreference.com/w/cpp/numeric/math/sinh). + /// \param arg function argument + /// \return hyperbolic sine value of \a arg + /// \exception FE_INVALID for signaling NaN + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half sinh(half arg) + { + #ifdef HALF_ARITHMETIC_TYPE + return half(detail::binary, detail::float2half(std::sinh(detail::half2float(arg.data_)))); + #else + int abs = arg.data_ & 0x7FFF, exp; + if(!abs || abs >= 0x7C00) + return (abs>0x7C00) ? half(detail::binary, detail::signal(arg.data_)) : arg; + if(abs <= 0x2900) + return half(detail::binary, detail::rounded(arg.data_, 0, 1)); + std::pair mm = detail::hyperbolic_args(abs, exp, (half::round_style==std::round_to_nearest) ? 29 : 27); + detail::uint32 m = mm.first - mm.second; + for(exp+=13; m<0x80000000 && exp; m<<=1,--exp) ; + unsigned int sign = arg.data_ & 0x8000; + if(exp > 29) + return half(detail::binary, detail::overflow(sign)); + return half(detail::binary, detail::fixed2half(m, exp, sign)); + #endif + } + + /// Hyperbolic cosine. + /// This function is exact to rounding for all rounding modes. + /// + /// **See also:** Documentation for [std::cosh](https://en.cppreference.com/w/cpp/numeric/math/cosh). + /// \param arg function argument + /// \return hyperbolic cosine value of \a arg + /// \exception FE_INVALID for signaling NaN + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half cosh(half arg) + { + #ifdef HALF_ARITHMETIC_TYPE + return half(detail::binary, detail::float2half(std::cosh(detail::half2float(arg.data_)))); + #else + int abs = arg.data_ & 0x7FFF, exp; + if(!abs) + return half(detail::binary, 0x3C00); + if(abs >= 0x7C00) + return half(detail::binary, (abs>0x7C00) ? detail::signal(arg.data_) : 0x7C00); + std::pair mm = detail::hyperbolic_args(abs, exp, (half::round_style==std::round_to_nearest) ? 23 : 26); + detail::uint32 m = mm.first + mm.second; + int i = static_cast((~m & 0xFFFFFFFF) >> 31); + m = (m>>i) | (m&i) | 0x80000000; + if((exp+=13+i) > 29) + return half(detail::binary, detail::overflow()); + return half(detail::binary, detail::fixed2half(m, exp)); + #endif + } + + /// Hyperbolic tangent. + /// This function is exact to rounding for all rounding modes. + /// + /// **See also:** Documentation for [std::tanh](https://en.cppreference.com/w/cpp/numeric/math/tanh). + /// \param arg function argument + /// \return hyperbolic tangent value of \a arg + /// \exception FE_INVALID for signaling NaN + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half tanh(half arg) + { + #ifdef HALF_ARITHMETIC_TYPE + return half(detail::binary, detail::float2half(std::tanh(detail::half2float(arg.data_)))); + #else + int abs = arg.data_ & 0x7FFF, exp; + if(!abs) + return arg; + if(abs >= 0x7C00) + return half(detail::binary, (abs>0x7C00) ? detail::signal(arg.data_) : (arg.data_-0x4000)); + if(abs >= 0x4500) + return half(detail::binary, detail::rounded((arg.data_&0x8000)|0x3BFF, 1, 1)); + if(abs < 0x2700) + return half(detail::binary, detail::rounded(arg.data_-1, 1, 1)); + if(half::round_style != std::round_to_nearest && abs == 0x2D3F) + return half(detail::binary, detail::rounded(arg.data_-3, 0, 1)); + std::pair mm = detail::hyperbolic_args(abs, exp, 27); + detail::uint32 my = mm.first - mm.second - (half::round_style!=std::round_to_nearest), mx = mm.first + mm.second; + int i = static_cast((~mx&0xFFFFFFFF) >> 31); + for(exp=13; my<0x80000000; my<<=1,--exp) ; + mx = (mx>>i) | 0x80000000; + return half(detail::binary, detail::tangent_post(my, mx, exp-i, arg.data_&0x8000)); + #endif + } + + /// Hyperbolic area sine. + /// This function is exact to rounding for all rounding modes. + /// + /// **See also:** Documentation for [std::asinh](https://en.cppreference.com/w/cpp/numeric/math/asinh). + /// \param arg function argument + /// \return area sine value of \a arg + /// \exception FE_INVALID for signaling NaN + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half asinh(half arg) + { + #if defined(HALF_ARITHMETIC_TYPE) && HALF_ENABLE_CPP11_CMATH + return half(detail::binary, detail::float2half(std::asinh(detail::half2float(arg.data_)))); + #else + int abs = arg.data_ & 0x7FFF; + if(!abs || abs >= 0x7C00) + return (abs>0x7C00) ? half(detail::binary, detail::signal(arg.data_)) : arg; + if(abs <= 0x2900) + return half(detail::binary, detail::rounded(arg.data_-1, 1, 1)); + if(half::round_style != std::round_to_nearest) + switch(abs) + { + case 0x32D4: return half(detail::binary, detail::rounded(arg.data_-13, 1, 1)); + case 0x3B5B: return half(detail::binary, detail::rounded(arg.data_-197, 1, 1)); + } + return half(detail::binary, detail::area(arg.data_)); + #endif + } + + /// Hyperbolic area cosine. + /// This function is exact to rounding for all rounding modes. + /// + /// **See also:** Documentation for [std::acosh](https://en.cppreference.com/w/cpp/numeric/math/acosh). + /// \param arg function argument + /// \return area cosine value of \a arg + /// \exception FE_INVALID for signaling NaN or arguments <1 + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half acosh(half arg) + { + #if defined(HALF_ARITHMETIC_TYPE) && HALF_ENABLE_CPP11_CMATH + return half(detail::binary, detail::float2half(std::acosh(detail::half2float(arg.data_)))); + #else + int abs = arg.data_ & 0x7FFF; + if((arg.data_&0x8000) || abs < 0x3C00) + return half(detail::binary, (abs<=0x7C00) ? detail::invalid() : detail::signal(arg.data_)); + if(abs == 0x3C00) + return half(detail::binary, 0); + if(arg.data_ >= 0x7C00) + return (abs>0x7C00) ? half(detail::binary, detail::signal(arg.data_)) : arg; + return half(detail::binary, detail::area(arg.data_)); + #endif + } + + /// Hyperbolic area tangent. + /// This function is exact to rounding for all rounding modes. + /// + /// **See also:** Documentation for [std::atanh](https://en.cppreference.com/w/cpp/numeric/math/atanh). + /// \param arg function argument + /// \return area tangent value of \a arg + /// \exception FE_INVALID for signaling NaN or if abs(\a arg) > 1 + /// \exception FE_DIVBYZERO for +/-1 + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half atanh(half arg) + { + #if defined(HALF_ARITHMETIC_TYPE) && HALF_ENABLE_CPP11_CMATH + return half(detail::binary, detail::float2half(std::atanh(detail::half2float(arg.data_)))); + #else + int abs = arg.data_ & 0x7FFF, exp = 0; + if(!abs) + return arg; + if(abs >= 0x3C00) + return half(detail::binary, (abs==0x3C00) ? detail::pole(arg.data_&0x8000) : (abs<=0x7C00) ? detail::invalid() : detail::signal(arg.data_)); + if(abs < 0x2700) + return half(detail::binary, detail::rounded(arg.data_, 0, 1)); + detail::uint32 m = static_cast((abs&0x3FF)|((abs>0x3FF)<<10)) << ((abs>>10)+(abs<=0x3FF)+6), my = 0x80000000 + m, mx = 0x80000000 - m; + for(; mx<0x80000000; mx<<=1,++exp) ; + int i = my >= mx, s; + return half(detail::binary, detail::log2_post(detail::log2( + (detail::divide64(my>>i, mx, s)+1)>>1, 27)+0x10, exp+i-1, 16, arg.data_&0x8000)); + #endif + } + + /// \} + /// \anchor special + /// \name Error and gamma functions + /// \{ + + /// Error function. + /// This function may be 1 ULP off the correctly rounded exact result for any rounding mode in <0.5% of inputs. + /// + /// **See also:** Documentation for [std::erf](https://en.cppreference.com/w/cpp/numeric/math/erf). + /// \param arg function argument + /// \return error function value of \a arg + /// \exception FE_INVALID for signaling NaN + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half erf(half arg) + { + #if defined(HALF_ARITHMETIC_TYPE) && HALF_ENABLE_CPP11_CMATH + return half(detail::binary, detail::float2half(std::erf(detail::half2float(arg.data_)))); + #else + unsigned int abs = arg.data_ & 0x7FFF; + if(!abs || abs >= 0x7C00) + return (abs>=0x7C00) ? half(detail::binary, (abs==0x7C00) ? (arg.data_-0x4000) : detail::signal(arg.data_)) : arg; + if(abs >= 0x4200) + return half(detail::binary, detail::rounded((arg.data_&0x8000)|0x3BFF, 1, 1)); + return half(detail::binary, detail::erf(arg.data_)); + #endif + } + + /// Complementary error function. + /// This function may be 1 ULP off the correctly rounded exact result for any rounding mode in <0.5% of inputs. + /// + /// **See also:** Documentation for [std::erfc](https://en.cppreference.com/w/cpp/numeric/math/erfc). + /// \param arg function argument + /// \return 1 minus error function value of \a arg + /// \exception FE_INVALID for signaling NaN + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half erfc(half arg) + { + #if defined(HALF_ARITHMETIC_TYPE) && HALF_ENABLE_CPP11_CMATH + return half(detail::binary, detail::float2half(std::erfc(detail::half2float(arg.data_)))); + #else + unsigned int abs = arg.data_ & 0x7FFF, sign = arg.data_ & 0x8000; + if(abs >= 0x7C00) + return (abs>=0x7C00) ? half(detail::binary, (abs==0x7C00) ? (sign>>1) : detail::signal(arg.data_)) : arg; + if(!abs) + return half(detail::binary, 0x3C00); + if(abs >= 0x4400) + return half(detail::binary, detail::rounded((sign>>1)-(sign>>15), sign>>15, 1)); + return half(detail::binary, detail::erf(arg.data_)); + #endif + } + + /// Natural logarithm of gamma function. + /// This function may be 1 ULP off the correctly rounded exact result for any rounding mode in ~0.025% of inputs. + /// + /// **See also:** Documentation for [std::lgamma](https://en.cppreference.com/w/cpp/numeric/math/lgamma). + /// \param arg function argument + /// \return natural logarith of gamma function for \a arg + /// \exception FE_INVALID for signaling NaN + /// \exception FE_DIVBYZERO for 0 or negative integer arguments + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half lgamma(half arg) + { + #if defined(HALF_ARITHMETIC_TYPE) && HALF_ENABLE_CPP11_CMATH + return half(detail::binary, detail::float2half(std::lgamma(detail::half2float(arg.data_)))); + #else + int abs = arg.data_ & 0x7FFF; + if(abs >= 0x7C00) + return half(detail::binary, (abs==0x7C00) ? 0x7C00 : detail::signal(arg.data_)); + if(!abs || arg.data_ >= 0xE400 || (arg.data_ >= 0xBC00 && !(abs&((1<<(25-(abs>>10)))-1)))) + return half(detail::binary, detail::pole()); + if(arg.data_ == 0x3C00 || arg.data_ == 0x4000) + return half(detail::binary, 0); + return half(detail::binary, detail::gamma(arg.data_)); + #endif + } + + /// Gamma function. + /// This function may be 1 ULP off the correctly rounded exact result for any rounding mode in <0.25% of inputs. + /// + /// **See also:** Documentation for [std::tgamma](https://en.cppreference.com/w/cpp/numeric/math/tgamma). + /// \param arg function argument + /// \return gamma function value of \a arg + /// \exception FE_INVALID for signaling NaN, negative infinity or negative integer arguments + /// \exception FE_DIVBYZERO for 0 + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half tgamma(half arg) + { + #if defined(HALF_ARITHMETIC_TYPE) && HALF_ENABLE_CPP11_CMATH + return half(detail::binary, detail::float2half(std::tgamma(detail::half2float(arg.data_)))); + #else + unsigned int abs = arg.data_ & 0x7FFF; + if(!abs) + return half(detail::binary, detail::pole(arg.data_)); + if(abs >= 0x7C00) + return (arg.data_==0x7C00) ? arg : half(detail::binary, detail::signal(arg.data_)); + if(arg.data_ >= 0xE400 || (arg.data_ >= 0xBC00 && !(abs&((1<<(25-(abs>>10)))-1)))) + return half(detail::binary, detail::invalid()); + if(arg.data_ >= 0xCA80) + return half(detail::binary, detail::underflow((1-((abs>>(25-(abs>>10)))&1))<<15)); + if(arg.data_ <= 0x100 || (arg.data_ >= 0x4900 && arg.data_ < 0x8000)) + return half(detail::binary, detail::overflow()); + if(arg.data_ == 0x3C00) + return arg; + return half(detail::binary, detail::gamma(arg.data_)); + #endif + } + + /// \} + /// \anchor rounding + /// \name Rounding + /// \{ + + /// Nearest integer not less than half value. + /// **See also:** Documentation for [std::ceil](https://en.cppreference.com/w/cpp/numeric/math/ceil). + /// \param arg half to round + /// \return nearest integer not less than \a arg + /// \exception FE_INVALID for signaling NaN + /// \exception FE_INEXACT if value had to be rounded + inline half ceil(half arg) { return half(detail::binary, detail::integral(arg.data_)); } + + /// Nearest integer not greater than half value. + /// **See also:** Documentation for [std::floor](https://en.cppreference.com/w/cpp/numeric/math/floor). + /// \param arg half to round + /// \return nearest integer not greater than \a arg + /// \exception FE_INVALID for signaling NaN + /// \exception FE_INEXACT if value had to be rounded + inline half floor(half arg) { return half(detail::binary, detail::integral(arg.data_)); } + + /// Nearest integer not greater in magnitude than half value. + /// **See also:** Documentation for [std::trunc](https://en.cppreference.com/w/cpp/numeric/math/trunc). + /// \param arg half to round + /// \return nearest integer not greater in magnitude than \a arg + /// \exception FE_INVALID for signaling NaN + /// \exception FE_INEXACT if value had to be rounded + inline half trunc(half arg) { return half(detail::binary, detail::integral(arg.data_)); } + + /// Nearest integer. + /// **See also:** Documentation for [std::round](https://en.cppreference.com/w/cpp/numeric/math/round). + /// \param arg half to round + /// \return nearest integer, rounded away from zero in half-way cases + /// \exception FE_INVALID for signaling NaN + /// \exception FE_INEXACT if value had to be rounded + inline half round(half arg) { return half(detail::binary, detail::integral(arg.data_)); } + + /// Nearest integer. + /// **See also:** Documentation for [std::lround](https://en.cppreference.com/w/cpp/numeric/math/round). + /// \param arg half to round + /// \return nearest integer, rounded away from zero in half-way cases + /// \exception FE_INVALID if value is not representable as `long` + inline long lround(half arg) { return detail::half2int(arg.data_); } + + /// Nearest integer using half's internal rounding mode. + /// **See also:** Documentation for [std::rint](https://en.cppreference.com/w/cpp/numeric/math/rint). + /// \param arg half expression to round + /// \return nearest integer using default rounding mode + /// \exception FE_INVALID for signaling NaN + /// \exception FE_INEXACT if value had to be rounded + inline half rint(half arg) { return half(detail::binary, detail::integral(arg.data_)); } + + /// Nearest integer using half's internal rounding mode. + /// **See also:** Documentation for [std::lrint](https://en.cppreference.com/w/cpp/numeric/math/rint). + /// \param arg half expression to round + /// \return nearest integer using default rounding mode + /// \exception FE_INVALID if value is not representable as `long` + /// \exception FE_INEXACT if value had to be rounded + inline long lrint(half arg) { return detail::half2int(arg.data_); } + + /// Nearest integer using half's internal rounding mode. + /// **See also:** Documentation for [std::nearbyint](https://en.cppreference.com/w/cpp/numeric/math/nearbyint). + /// \param arg half expression to round + /// \return nearest integer using default rounding mode + /// \exception FE_INVALID for signaling NaN + inline half nearbyint(half arg) { return half(detail::binary, detail::integral(arg.data_)); } +#if HALF_ENABLE_CPP11_LONG_LONG + /// Nearest integer. + /// **See also:** Documentation for [std::llround](https://en.cppreference.com/w/cpp/numeric/math/round). + /// \param arg half to round + /// \return nearest integer, rounded away from zero in half-way cases + /// \exception FE_INVALID if value is not representable as `long long` + inline long long llround(half arg) { return detail::half2int(arg.data_); } + + /// Nearest integer using half's internal rounding mode. + /// **See also:** Documentation for [std::llrint](https://en.cppreference.com/w/cpp/numeric/math/rint). + /// \param arg half expression to round + /// \return nearest integer using default rounding mode + /// \exception FE_INVALID if value is not representable as `long long` + /// \exception FE_INEXACT if value had to be rounded + inline long long llrint(half arg) { return detail::half2int(arg.data_); } +#endif + + /// \} + /// \anchor float + /// \name Floating point manipulation + /// \{ + + /// Decompress floating-point number. + /// **See also:** Documentation for [std::frexp](https://en.cppreference.com/w/cpp/numeric/math/frexp). + /// \param arg number to decompress + /// \param exp address to store exponent at + /// \return significant in range [0.5, 1) + /// \exception FE_INVALID for signaling NaN + inline half frexp(half arg, int *exp) + { + *exp = 0; + unsigned int abs = arg.data_ & 0x7FFF; + if(abs >= 0x7C00 || !abs) + return (abs>0x7C00) ? half(detail::binary, detail::signal(arg.data_)) : arg; + for(; abs<0x400; abs<<=1,--*exp) ; + *exp += (abs>>10) - 14; + return half(detail::binary, (arg.data_&0x8000)|0x3800|(abs&0x3FF)); + } + + /// Multiply by power of two. + /// This function is exact to rounding for all rounding modes. + /// + /// **See also:** Documentation for [std::scalbln](https://en.cppreference.com/w/cpp/numeric/math/scalbn). + /// \param arg number to modify + /// \param exp power of two to multiply with + /// \return \a arg multplied by 2 raised to \a exp + /// \exception FE_INVALID for signaling NaN + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half scalbln(half arg, long exp) + { + unsigned int abs = arg.data_ & 0x7FFF, sign = arg.data_ & 0x8000; + if(abs >= 0x7C00 || !abs) + return (abs>0x7C00) ? half(detail::binary, detail::signal(arg.data_)) : arg; + for(; abs<0x400; abs<<=1,--exp) ; + exp += abs >> 10; + if(exp > 30) + return half(detail::binary, detail::overflow(sign)); + else if(exp < -10) + return half(detail::binary, detail::underflow(sign)); + else if(exp > 0) + return half(detail::binary, sign|(exp<<10)|(abs&0x3FF)); + unsigned int m = (abs&0x3FF) | 0x400; + return half(detail::binary, detail::rounded(sign|(m>>(1-exp)), (m>>-exp)&1, (m&((1<<-exp)-1))!=0)); + } + + /// Multiply by power of two. + /// This function is exact to rounding for all rounding modes. + /// + /// **See also:** Documentation for [std::scalbn](https://en.cppreference.com/w/cpp/numeric/math/scalbn). + /// \param arg number to modify + /// \param exp power of two to multiply with + /// \return \a arg multplied by 2 raised to \a exp + /// \exception FE_INVALID for signaling NaN + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half scalbn(half arg, int exp) { return scalbln(arg, exp); } + + /// Multiply by power of two. + /// This function is exact to rounding for all rounding modes. + /// + /// **See also:** Documentation for [std::ldexp](https://en.cppreference.com/w/cpp/numeric/math/ldexp). + /// \param arg number to modify + /// \param exp power of two to multiply with + /// \return \a arg multplied by 2 raised to \a exp + /// \exception FE_INVALID for signaling NaN + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + inline half ldexp(half arg, int exp) { return scalbln(arg, exp); } + + /// Extract integer and fractional parts. + /// **See also:** Documentation for [std::modf](https://en.cppreference.com/w/cpp/numeric/math/modf). + /// \param arg number to decompress + /// \param iptr address to store integer part at + /// \return fractional part + /// \exception FE_INVALID for signaling NaN + inline half modf(half arg, half *iptr) + { + unsigned int abs = arg.data_ & 0x7FFF; + if(abs > 0x7C00) + { + arg = half(detail::binary, detail::signal(arg.data_)); + return *iptr = arg, arg; + } + if(abs >= 0x6400) + return *iptr = arg, half(detail::binary, arg.data_&0x8000); + if(abs < 0x3C00) + return iptr->data_ = arg.data_ & 0x8000, arg; + unsigned int exp = abs >> 10, mask = (1<<(25-exp)) - 1, m = arg.data_ & mask; + iptr->data_ = arg.data_ & ~mask; + if(!m) + return half(detail::binary, arg.data_&0x8000); + for(; m<0x400; m<<=1,--exp) ; + return half(detail::binary, (arg.data_&0x8000)|(exp<<10)|(m&0x3FF)); + } + + /// Extract exponent. + /// **See also:** Documentation for [std::ilogb](https://en.cppreference.com/w/cpp/numeric/math/ilogb). + /// \param arg number to query + /// \return floating-point exponent + /// \retval FP_ILOGB0 for zero + /// \retval FP_ILOGBNAN for NaN + /// \retval INT_MAX for infinity + /// \exception FE_INVALID for 0 or infinite values + inline int ilogb(half arg) + { + int abs = arg.data_ & 0x7FFF, exp; + if(!abs || abs >= 0x7C00) + { + detail::raise(FE_INVALID); + return !abs ? FP_ILOGB0 : (abs==0x7C00) ? INT_MAX : FP_ILOGBNAN; + } + for(exp=(abs>>10)-15; abs<0x200; abs<<=1,--exp) ; + return exp; + } + + /// Extract exponent. + /// **See also:** Documentation for [std::logb](https://en.cppreference.com/w/cpp/numeric/math/logb). + /// \param arg number to query + /// \return floating-point exponent + /// \exception FE_INVALID for signaling NaN + /// \exception FE_DIVBYZERO for 0 + inline half logb(half arg) + { + int abs = arg.data_ & 0x7FFF, exp; + if(!abs) + return half(detail::binary, detail::pole(0x8000)); + if(abs >= 0x7C00) + return half(detail::binary, (abs==0x7C00) ? 0x7C00 : detail::signal(arg.data_)); + for(exp=(abs>>10)-15; abs<0x200; abs<<=1,--exp) ; + unsigned int value = static_cast(exp<0) << 15; + if(exp) + { + unsigned int m = std::abs(exp) << 6; + for(exp=18; m<0x400; m<<=1,--exp) ; + value |= (exp<<10) + m; + } + return half(detail::binary, value); + } + + /// Next representable value. + /// **See also:** Documentation for [std::nextafter](https://en.cppreference.com/w/cpp/numeric/math/nextafter). + /// \param from value to compute next representable value for + /// \param to direction towards which to compute next value + /// \return next representable value after \a from in direction towards \a to + /// \exception FE_INVALID for signaling NaN + /// \exception FE_OVERFLOW for infinite result from finite argument + /// \exception FE_UNDERFLOW for subnormal result + inline half nextafter(half from, half to) + { + int fabs = from.data_ & 0x7FFF, tabs = to.data_ & 0x7FFF; + if(fabs > 0x7C00 || tabs > 0x7C00) + return half(detail::binary, detail::signal(from.data_, to.data_)); + if(from.data_ == to.data_ || !(fabs|tabs)) + return to; + if(!fabs) + { + detail::raise(FE_UNDERFLOW, !HALF_ERRHANDLING_UNDERFLOW_TO_INEXACT); + return half(detail::binary, (to.data_&0x8000)+1); + } + unsigned int out = from.data_ + (((from.data_>>15)^static_cast( + (from.data_^(0x8000|(0x8000-(from.data_>>15))))<(to.data_^(0x8000|(0x8000-(to.data_>>15))))))<<1) - 1; + detail::raise(FE_OVERFLOW, fabs<0x7C00 && (out&0x7C00)==0x7C00); + detail::raise(FE_UNDERFLOW, !HALF_ERRHANDLING_UNDERFLOW_TO_INEXACT && (out&0x7C00)<0x400); + return half(detail::binary, out); + } + + /// Next representable value. + /// **See also:** Documentation for [std::nexttoward](https://en.cppreference.com/w/cpp/numeric/math/nexttoward). + /// \param from value to compute next representable value for + /// \param to direction towards which to compute next value + /// \return next representable value after \a from in direction towards \a to + /// \exception FE_INVALID for signaling NaN + /// \exception FE_OVERFLOW for infinite result from finite argument + /// \exception FE_UNDERFLOW for subnormal result + inline half nexttoward(half from, long double to) + { + int fabs = from.data_ & 0x7FFF; + if(fabs > 0x7C00) + return half(detail::binary, detail::signal(from.data_)); + long double lfrom = static_cast(from); + if(detail::builtin_isnan(to) || lfrom == to) + return half(static_cast(to)); + if(!fabs) + { + detail::raise(FE_UNDERFLOW, !HALF_ERRHANDLING_UNDERFLOW_TO_INEXACT); + return half(detail::binary, (static_cast(detail::builtin_signbit(to))<<15)+1); + } + unsigned int out = from.data_ + (((from.data_>>15)^static_cast(lfrom 0x7C00; } + + /// Check if normal number. + /// **See also:** Documentation for [std::isnormal](https://en.cppreference.com/w/cpp/numeric/math/isnormal). + /// \param arg number to check + /// \retval true if normal number + /// \retval false if either subnormal, zero, infinity or NaN + inline HALF_CONSTEXPR bool isnormal(half arg) { return ((arg.data_&0x7C00)!=0) & ((arg.data_&0x7C00)!=0x7C00); } + + /// Check sign. + /// **See also:** Documentation for [std::signbit](https://en.cppreference.com/w/cpp/numeric/math/signbit). + /// \param arg number to check + /// \retval true for negative number + /// \retval false for positive number + inline HALF_CONSTEXPR bool signbit(half arg) { return (arg.data_&0x8000) != 0; } + + /// \} + /// \anchor compfunc + /// \name Comparison + /// \{ + + /// Quiet comparison for greater than. + /// **See also:** Documentation for [std::isgreater](https://en.cppreference.com/w/cpp/numeric/math/isgreater). + /// \param x first operand + /// \param y second operand + /// \retval true if \a x greater than \a y + /// \retval false else + inline HALF_CONSTEXPR bool isgreater(half x, half y) + { + return ((x.data_^(0x8000|(0x8000-(x.data_>>15))))+(x.data_>>15)) > ((y.data_^(0x8000|(0x8000-(y.data_>>15))))+(y.data_>>15)) && !isnan(x) && !isnan(y); + } + + /// Quiet comparison for greater equal. + /// **See also:** Documentation for [std::isgreaterequal](https://en.cppreference.com/w/cpp/numeric/math/isgreaterequal). + /// \param x first operand + /// \param y second operand + /// \retval true if \a x greater equal \a y + /// \retval false else + inline HALF_CONSTEXPR bool isgreaterequal(half x, half y) + { + return ((x.data_^(0x8000|(0x8000-(x.data_>>15))))+(x.data_>>15)) >= ((y.data_^(0x8000|(0x8000-(y.data_>>15))))+(y.data_>>15)) && !isnan(x) && !isnan(y); + } + + /// Quiet comparison for less than. + /// **See also:** Documentation for [std::isless](https://en.cppreference.com/w/cpp/numeric/math/isless). + /// \param x first operand + /// \param y second operand + /// \retval true if \a x less than \a y + /// \retval false else + inline HALF_CONSTEXPR bool isless(half x, half y) + { + return ((x.data_^(0x8000|(0x8000-(x.data_>>15))))+(x.data_>>15)) < ((y.data_^(0x8000|(0x8000-(y.data_>>15))))+(y.data_>>15)) && !isnan(x) && !isnan(y); + } + + /// Quiet comparison for less equal. + /// **See also:** Documentation for [std::islessequal](https://en.cppreference.com/w/cpp/numeric/math/islessequal). + /// \param x first operand + /// \param y second operand + /// \retval true if \a x less equal \a y + /// \retval false else + inline HALF_CONSTEXPR bool islessequal(half x, half y) + { + return ((x.data_^(0x8000|(0x8000-(x.data_>>15))))+(x.data_>>15)) <= ((y.data_^(0x8000|(0x8000-(y.data_>>15))))+(y.data_>>15)) && !isnan(x) && !isnan(y); + } + + /// Quiet comarison for less or greater. + /// **See also:** Documentation for [std::islessgreater](https://en.cppreference.com/w/cpp/numeric/math/islessgreater). + /// \param x first operand + /// \param y second operand + /// \retval true if either less or greater + /// \retval false else + inline HALF_CONSTEXPR bool islessgreater(half x, half y) + { + return x.data_!=y.data_ && ((x.data_|y.data_)&0x7FFF) && !isnan(x) && !isnan(y); + } + + /// Quiet check if unordered. + /// **See also:** Documentation for [std::isunordered](https://en.cppreference.com/w/cpp/numeric/math/isunordered). + /// \param x first operand + /// \param y second operand + /// \retval true if unordered (one or two NaN operands) + /// \retval false else + inline HALF_CONSTEXPR bool isunordered(half x, half y) { return isnan(x) || isnan(y); } + + /// \} + /// \anchor casting + /// \name Casting + /// \{ + + /// Cast to or from half-precision floating-point number. + /// This casts between [half](\ref half_float::half) and any built-in arithmetic type. The values are converted + /// directly using the default rounding mode, without any roundtrip over `float` that a `static_cast` would otherwise do. + /// + /// Using this cast with neither of the two types being a [half](\ref half_float::half) or with any of the two types + /// not being a built-in arithmetic type (apart from [half](\ref half_float::half), of course) results in a compiler + /// error and casting between [half](\ref half_float::half)s returns the argument unmodified. + /// \tparam T destination type (half or built-in arithmetic type) + /// \tparam U source type (half or built-in arithmetic type) + /// \param arg value to cast + /// \return \a arg converted to destination type + /// \exception FE_INVALID if \a T is integer type and result is not representable as \a T + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + template T half_cast(U arg) { return detail::half_caster::cast(arg); } + + /// Cast to or from half-precision floating-point number. + /// This casts between [half](\ref half_float::half) and any built-in arithmetic type. The values are converted + /// directly using the specified rounding mode, without any roundtrip over `float` that a `static_cast` would otherwise do. + /// + /// Using this cast with neither of the two types being a [half](\ref half_float::half) or with any of the two types + /// not being a built-in arithmetic type (apart from [half](\ref half_float::half), of course) results in a compiler + /// error and casting between [half](\ref half_float::half)s returns the argument unmodified. + /// \tparam T destination type (half or built-in arithmetic type) + /// \tparam R rounding mode to use. + /// \tparam U source type (half or built-in arithmetic type) + /// \param arg value to cast + /// \return \a arg converted to destination type + /// \exception FE_INVALID if \a T is integer type and result is not representable as \a T + /// \exception FE_OVERFLOW, ...UNDERFLOW, ...INEXACT according to rounding + template T half_cast(U arg) { return detail::half_caster::cast(arg); } + /// \} + + /// \} + /// \anchor errors + /// \name Error handling + /// \{ + + /// Clear exception flags. + /// This function works even if [automatic exception flag handling](\ref HALF_ERRHANDLING_FLAGS) is disabled, + /// but in that case manual flag management is the only way to raise flags. + /// + /// **See also:** Documentation for [std::feclearexcept](https://en.cppreference.com/w/cpp/numeric/fenv/feclearexcept). + /// \param excepts OR of exceptions to clear + /// \retval 0 all selected flags cleared successfully + inline int feclearexcept(int excepts) { detail::errflags() &= ~excepts; return 0; } + + /// Test exception flags. + /// This function works even if [automatic exception flag handling](\ref HALF_ERRHANDLING_FLAGS) is disabled, + /// but in that case manual flag management is the only way to raise flags. + /// + /// **See also:** Documentation for [std::fetestexcept](https://en.cppreference.com/w/cpp/numeric/fenv/fetestexcept). + /// \param excepts OR of exceptions to test + /// \return OR of selected exceptions if raised + inline int fetestexcept(int excepts) { return detail::errflags() & excepts; } + + /// Raise exception flags. + /// This raises the specified floating point exceptions and also invokes any additional automatic exception handling as + /// configured with the [HALF_ERRHANDLIG_...](\ref HALF_ERRHANDLING_ERRNO) preprocessor symbols. + /// This function works even if [automatic exception flag handling](\ref HALF_ERRHANDLING_FLAGS) is disabled, + /// but in that case manual flag management is the only way to raise flags. + /// + /// **See also:** Documentation for [std::feraiseexcept](https://en.cppreference.com/w/cpp/numeric/fenv/feraiseexcept). + /// \param excepts OR of exceptions to raise + /// \retval 0 all selected exceptions raised successfully + inline int feraiseexcept(int excepts) { detail::errflags() |= excepts; detail::raise(excepts); return 0; } + + /// Save exception flags. + /// This function works even if [automatic exception flag handling](\ref HALF_ERRHANDLING_FLAGS) is disabled, + /// but in that case manual flag management is the only way to raise flags. + /// + /// **See also:** Documentation for [std::fegetexceptflag](https://en.cppreference.com/w/cpp/numeric/fenv/feexceptflag). + /// \param flagp adress to store flag state at + /// \param excepts OR of flags to save + /// \retval 0 for success + inline int fegetexceptflag(int *flagp, int excepts) { *flagp = detail::errflags() & excepts; return 0; } + + /// Restore exception flags. + /// This only copies the specified exception state (including unset flags) without incurring any additional exception handling. + /// This function works even if [automatic exception flag handling](\ref HALF_ERRHANDLING_FLAGS) is disabled, + /// but in that case manual flag management is the only way to raise flags. + /// + /// **See also:** Documentation for [std::fesetexceptflag](https://en.cppreference.com/w/cpp/numeric/fenv/feexceptflag). + /// \param flagp adress to take flag state from + /// \param excepts OR of flags to restore + /// \retval 0 for success + inline int fesetexceptflag(const int *flagp, int excepts) { detail::errflags() = (detail::errflags()|(*flagp&excepts)) & (*flagp|~excepts); return 0; } + + /// Throw C++ exceptions based on set exception flags. + /// This function manually throws a corresponding C++ exception if one of the specified flags is set, + /// no matter if automatic throwing (via [HALF_ERRHANDLING_THROW_...](\ref HALF_ERRHANDLING_THROW_INVALID)) is enabled or not. + /// This function works even if [automatic exception flag handling](\ref HALF_ERRHANDLING_FLAGS) is disabled, + /// but in that case manual flag management is the only way to raise flags. + /// \param excepts OR of exceptions to test + /// \param msg error message to use for exception description + /// \throw std::domain_error if `FE_INVALID` or `FE_DIVBYZERO` is selected and set + /// \throw std::overflow_error if `FE_OVERFLOW` is selected and set + /// \throw std::underflow_error if `FE_UNDERFLOW` is selected and set + /// \throw std::range_error if `FE_INEXACT` is selected and set + inline void fethrowexcept(int excepts, const char *msg = "") + { + excepts &= detail::errflags(); + if(excepts & (FE_INVALID|FE_DIVBYZERO)) + throw std::domain_error(msg); + if(excepts & FE_OVERFLOW) + throw std::overflow_error(msg); + if(excepts & FE_UNDERFLOW) + throw std::underflow_error(msg); + if(excepts & FE_INEXACT) + throw std::range_error(msg); + } + /// \} +} + + +#undef HALF_UNUSED_NOERR +#undef HALF_CONSTEXPR +#undef HALF_CONSTEXPR_CONST +#undef HALF_CONSTEXPR_NOERR +#undef HALF_NOEXCEPT +#undef HALF_NOTHROW +#undef HALF_THREAD_LOCAL +#undef HALF_TWOS_COMPLEMENT_INT +#ifdef HALF_POP_WARNINGS + #pragma warning(pop) + #undef HALF_POP_WARNINGS +#endif + +#endif diff --git a/gemmi_gph/gemmi/third_party/pocketfft_hdronly.h b/gemmi_gph/gemmi/third_party/pocketfft_hdronly.h new file mode 100644 index 00000000..57db7d78 --- /dev/null +++ b/gemmi_gph/gemmi/third_party/pocketfft_hdronly.h @@ -0,0 +1,3744 @@ +/* +This file is part of pocketfft. + +Copyright (C) 2010-2024 Max-Planck-Society +Copyright (C) 2019-2020 Peter Bell + +For the odd-sized DCT-IV transforms: + Copyright (C) 2003, 2007-14 Matteo Frigo + Copyright (C) 2003, 2007-14 Massachusetts Institute of Technology + +For the prev_good_size search: + Copyright (C) 2024 Tan Ping Liang, Peter Bell + +For the safeguards against integer overflow in good_size search: + Copyright (C) 2024 Cris Luengo + +Authors: Martin Reinecke, Peter Bell + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. +* Neither the name of the copyright holder nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef POCKETFFT_HDRONLY_H +#define POCKETFFT_HDRONLY_H + +#ifndef __cplusplus +#error This file is C++ and requires a C++ compiler. +#endif + +#if !(__cplusplus >= 201103L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201103L)) +#error This file requires at least C++11 support. +#endif + +#ifndef POCKETFFT_CACHE_SIZE +#define POCKETFFT_CACHE_SIZE 0 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if POCKETFFT_CACHE_SIZE!=0 +#include +#include +#endif + +#ifndef POCKETFFT_NO_MULTITHREADING +#include +#include +#include +#include +#include +#include +#include + +#ifdef POCKETFFT_PTHREADS +# include +#endif +#endif + +#if defined(__GNUC__) +#define POCKETFFT_NOINLINE __attribute__((noinline)) +#define POCKETFFT_RESTRICT __restrict__ +#elif defined(_MSC_VER) +#define POCKETFFT_NOINLINE __declspec(noinline) +#define POCKETFFT_RESTRICT __restrict +#else +#define POCKETFFT_NOINLINE +#define POCKETFFT_RESTRICT +#endif + +namespace pocketfft { + +namespace detail { +using std::size_t; +using std::ptrdiff_t; + +// Always use std:: for functions +template T cos(T) = delete; +template T sin(T) = delete; +template T sqrt(T) = delete; + +using shape_t = std::vector; +using stride_t = std::vector; + +constexpr bool FORWARD = true, + BACKWARD = false; + +// only enable vector support for gcc>=5.0 and clang>=5.0 +#ifndef POCKETFFT_NO_VECTORS +#define POCKETFFT_NO_VECTORS +#if defined(__INTEL_COMPILER) +// do nothing. This is necessary because this compiler also sets __GNUC__. +#elif defined(__clang__) +// AppleClang has their own version numbering +#ifdef __apple_build_version__ +# if (__clang_major__ > 9) || (__clang_major__ == 9 && __clang_minor__ >= 1) +# undef POCKETFFT_NO_VECTORS +# endif +#elif __clang_major__ >= 5 +# undef POCKETFFT_NO_VECTORS +#endif +#elif defined(__GNUC__) +#if __GNUC__>=5 +#undef POCKETFFT_NO_VECTORS +#endif +#endif +#endif + +template struct VLEN { static constexpr size_t val=1; }; + +#ifndef POCKETFFT_NO_VECTORS +#if (defined(__AVX512F__)) +template<> struct VLEN { static constexpr size_t val=16; }; +template<> struct VLEN { static constexpr size_t val=8; }; +#elif (defined(__AVX__)) +template<> struct VLEN { static constexpr size_t val=8; }; +template<> struct VLEN { static constexpr size_t val=4; }; +#elif (defined(__SSE2__)) +template<> struct VLEN { static constexpr size_t val=4; }; +template<> struct VLEN { static constexpr size_t val=2; }; +#elif (defined(__VSX__)) +template<> struct VLEN { static constexpr size_t val=4; }; +template<> struct VLEN { static constexpr size_t val=2; }; +#elif (defined(__ARM_NEON__) || defined(__ARM_NEON)) +template<> struct VLEN { static constexpr size_t val=4; }; +template<> struct VLEN { static constexpr size_t val=2; }; +#else +#define POCKETFFT_NO_VECTORS +#endif +#endif + +// std::aligned_alloc is a bit cursed ... it doesn't exist on MacOS < 10.15 +// and in musl, and other OSes seem to have even more peculiarities. +// Let's unconditionally work around it for now. +# if 0 +//#if (__cplusplus >= 201703L) && (!defined(__MINGW32__)) && (!defined(_MSC_VER)) && (__MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_15) +inline void *aligned_alloc(size_t align, size_t size) + { + // aligned_alloc() requires that the requested size is a multiple of "align" + void *ptr = ::aligned_alloc(align,(size+align-1)&(~(align-1))); + if (!ptr) throw std::bad_alloc(); + return ptr; + } +inline void aligned_dealloc(void *ptr) + { free(ptr); } +#else // portable emulation +inline void *aligned_alloc(size_t align, size_t size) + { + align = std::max(align, alignof(max_align_t)); + void *ptr = malloc(size+align); + if (!ptr) throw std::bad_alloc(); + void *res = reinterpret_cast + ((reinterpret_cast(ptr) & ~(uintptr_t(align-1))) + uintptr_t(align)); + (reinterpret_cast(res))[-1] = ptr; + return res; + } +inline void aligned_dealloc(void *ptr) + { if (ptr) free((reinterpret_cast(ptr))[-1]); } +#endif + +template class arr + { + private: + T *p; + size_t sz; + +#if defined(POCKETFFT_NO_VECTORS) + static T *ralloc(size_t num) + { + if (num==0) return nullptr; + void *res = malloc(num*sizeof(T)); + if (!res) throw std::bad_alloc(); + return reinterpret_cast(res); + } + static void dealloc(T *ptr) + { free(ptr); } +#else + static T *ralloc(size_t num) + { + if (num==0) return nullptr; + void *ptr = aligned_alloc(64, num*sizeof(T)); + return static_cast(ptr); + } + static void dealloc(T *ptr) + { aligned_dealloc(ptr); } +#endif + + public: + arr() : p(0), sz(0) {} + arr(size_t n) : p(ralloc(n)), sz(n) {} + arr(arr &&other) + : p(other.p), sz(other.sz) + { other.p=nullptr; other.sz=0; } + ~arr() { dealloc(p); } + + void resize(size_t n) + { + if (n==sz) return; + dealloc(p); + p = ralloc(n); + sz = n; + } + + T &operator[](size_t idx) { return p[idx]; } + const T &operator[](size_t idx) const { return p[idx]; } + + T *data() { return p; } + const T *data() const { return p; } + + size_t size() const { return sz; } + }; + +template struct cmplx { + T r, i; + cmplx() {} + cmplx(T r_, T i_) : r(r_), i(i_) {} + void Set(T r_, T i_) { r=r_; i=i_; } + void Set(T r_) { r=r_; i=T(0); } + cmplx &operator+= (const cmplx &other) + { r+=other.r; i+=other.i; return *this; } + templatecmplx &operator*= (T2 other) + { r*=other; i*=other; return *this; } + templatecmplx &operator*= (const cmplx &other) + { + T tmp = r*other.r - i*other.i; + i = r*other.i + i*other.r; + r = tmp; + return *this; + } + templatecmplx &operator+= (const cmplx &other) + { r+=other.r; i+=other.i; return *this; } + templatecmplx &operator-= (const cmplx &other) + { r-=other.r; i-=other.i; return *this; } + template auto operator* (const T2 &other) const + -> cmplx + { return {r*other, i*other}; } + template auto operator+ (const cmplx &other) const + -> cmplx + { return {r+other.r, i+other.i}; } + template auto operator- (const cmplx &other) const + -> cmplx + { return {r-other.r, i-other.i}; } + template auto operator* (const cmplx &other) const + -> cmplx + { return {r*other.r-i*other.i, r*other.i + i*other.r}; } + template auto special_mul (const cmplx &other) const + -> cmplx + { + using Tres = cmplx; + return fwd ? Tres(r*other.r+i*other.i, i*other.r-r*other.i) + : Tres(r*other.r-i*other.i, r*other.i+i*other.r); + } +}; +template inline void PM(T &a, T &b, T c, T d) + { a=c+d; b=c-d; } +template inline void PMINPLACE(T &a, T &b) + { T t = a; a+=b; b=t-b; } +template inline void MPINPLACE(T &a, T &b) + { T t = a; a-=b; b=t+b; } +template cmplx conj(const cmplx &a) + { return {a.r, -a.i}; } +template void special_mul (const cmplx &v1, const cmplx &v2, cmplx &res) + { + res = fwd ? cmplx(v1.r*v2.r+v1.i*v2.i, v1.i*v2.r-v1.r*v2.i) + : cmplx(v1.r*v2.r-v1.i*v2.i, v1.r*v2.i+v1.i*v2.r); + } + +template void ROT90(cmplx &a) + { auto tmp_=a.r; a.r=-a.i; a.i=tmp_; } +template void ROTX90(cmplx &a) + { auto tmp_= fwd ? -a.r : a.r; a.r = fwd ? a.i : -a.i; a.i=tmp_; } + +// +// twiddle factor section +// +template class sincos_2pibyn + { + private: + using Thigh = typename std::conditional<(sizeof(T)>sizeof(double)), T, double>::type; + size_t N, mask, shift; + arr> v1, v2; + + static cmplx calc(size_t x, size_t n, Thigh ang) + { + x<<=3; + if (x<4*n) // first half + { + if (x<2*n) // first quadrant + { + if (x(std::cos(Thigh(x)*ang), std::sin(Thigh(x)*ang)); + return cmplx(std::sin(Thigh(2*n-x)*ang), std::cos(Thigh(2*n-x)*ang)); + } + else // second quadrant + { + x-=2*n; + if (x(-std::sin(Thigh(x)*ang), std::cos(Thigh(x)*ang)); + return cmplx(-std::cos(Thigh(2*n-x)*ang), std::sin(Thigh(2*n-x)*ang)); + } + } + else + { + x=8*n-x; + if (x<2*n) // third quadrant + { + if (x(std::cos(Thigh(x)*ang), -std::sin(Thigh(x)*ang)); + return cmplx(std::sin(Thigh(2*n-x)*ang), -std::cos(Thigh(2*n-x)*ang)); + } + else // fourth quadrant + { + x-=2*n; + if (x(-std::sin(Thigh(x)*ang), -std::cos(Thigh(x)*ang)); + return cmplx(-std::cos(Thigh(2*n-x)*ang), -std::sin(Thigh(2*n-x)*ang)); + } + } + } + + public: + POCKETFFT_NOINLINE sincos_2pibyn(size_t n) + : N(n) + { + constexpr auto pi = 3.141592653589793238462643383279502884197L; + Thigh ang = Thigh(0.25L*pi/n); + size_t nval = (n+2)/2; + shift = 1; + while((size_t(1)< operator[](size_t idx) const + { + if (2*idx<=N) + { + auto x1=v1[idx&mask], x2=v2[idx>>shift]; + return cmplx(T(x1.r*x2.r-x1.i*x2.i), T(x1.r*x2.i+x1.i*x2.r)); + } + idx = N-idx; + auto x1=v1[idx&mask], x2=v2[idx>>shift]; + return cmplx(T(x1.r*x2.r-x1.i*x2.i), -T(x1.r*x2.i+x1.i*x2.r)); + } + }; + +struct util // hack to avoid duplicate symbols + { + static POCKETFFT_NOINLINE size_t largest_prime_factor (size_t n) + { + size_t res=1; + while ((n&1)==0) + { res=2; n>>=1; } + for (size_t x=3; x*x<=n; x+=2) + while ((n%x)==0) + { res=x; n/=x; } + if (n>1) res=n; + return res; + } + + static POCKETFFT_NOINLINE double cost_guess (size_t n) + { + constexpr double lfp=1.1; // penalty for non-hardcoded larger factors + size_t ni=n; + double result=0.; + while ((n&1)==0) + { result+=2; n>>=1; } + for (size_t x=3; x*x<=n; x+=2) + while ((n%x)==0) + { + result+= (x<=5) ? double(x) : lfp*double(x); // penalize larger prime factors + n/=x; + } + if (n>1) result+=(n<=5) ? double(n) : lfp*double(n); + return result*double(ni); + } + + /* inner workings of good_size_cmplx() */ + template + static POCKETFFT_NOINLINE UIntT good_size_cmplx_typed(UIntT n) + { + static_assert(std::numeric_limits::is_integer && (!std::numeric_limits::is_signed), + "type must be unsigned integer"); + if (n<=12) return n; + if (n>std::numeric_limits::max()/11/2) + { + // The algorithm below doesn't work for this value, the multiplication can overflow. + if (sizeof(UIntT)(n); + if (res<=std::numeric_limits::max()) + return static_cast(res); + } + // Otherwise, this size is ridiculously large, people shouldn't be computing FFTs this large. + throw std::runtime_error("FFT size is too large."); + } + + UIntT bestfac=2*n; + for (UIntT f11=1; f11n) + { + if (x>=1; + } + else + return n; + } + } + return bestfac; + } + /* returns the smallest composite of 2, 3, 5, 7 and 11 which is >= n */ + static POCKETFFT_NOINLINE size_t good_size_cmplx(size_t n) + { + return good_size_cmplx_typed(n); + } + /* returns the smallest composite of 2, 3, 5, 7 and 11 which is >= n + and a multiple of required_factor. */ + static POCKETFFT_NOINLINE size_t good_size_cmplx(size_t n, + size_t required_factor) + { + if (required_factor<1) + throw std::runtime_error("required factor must not be 0"); + return good_size_cmplx((n+required_factor-1)/required_factor) * required_factor; + } + + /* inner workings of good_size_real() */ + template + static POCKETFFT_NOINLINE UIntT good_size_real_typed(UIntT n) + { + static_assert(std::numeric_limits::is_integer && (!std::numeric_limits::is_signed), + "type must be unsigned integer"); + if (n<=6) return n; + if (n>std::numeric_limits::max()/5/2) + { + // The algorithm below doesn't work for this value, the multiplication can overflow. + if (sizeof(UIntT)(n); + if (res<=std::numeric_limits::max()) + return static_cast(res); + } + // Otherwise, this size is ridiculously large, people shouldn't be computing FFTs this large. + throw std::runtime_error("FFT size is too large."); + } + + UIntT bestfac=2*n; + for (UIntT f5=1; f5n) + { + if (x>=1; + } + else + return n; + } + } + return bestfac; + } + /* returns the smallest composite of 2, 3, 5 which is >= n */ + static POCKETFFT_NOINLINE size_t good_size_real(size_t n) + { + return good_size_real_typed(n); + } + /* returns the smallest composite of 2, 3, 5 which is >= n + and a multiple of required_factor. */ + static POCKETFFT_NOINLINE size_t good_size_real(size_t n, + size_t required_factor) + { + if (required_factor<1) + throw std::runtime_error("required factor must not be 0"); + return good_size_real((n+required_factor-1)/required_factor) * required_factor; + } + + /* inner workings of prev_good_size_cmplx() */ + template + static POCKETFFT_NOINLINE UIntT prev_good_size_cmplx_typed(UIntT n) + { + static_assert(std::numeric_limits::is_integer && (!std::numeric_limits::is_signed), + "type must be unsigned integer"); + if (n<=12) return n; + if (n>std::numeric_limits::max()/11) + { + // The algorithm below doesn't work for this value, the multiplication can overflow. + if (sizeof(UIntT)(n); + if (res<=std::numeric_limits::max()) + return static_cast(res); + } + // Otherwise, this size is ridiculously large, people shouldn't be computing FFTs this large. + throw std::runtime_error("FFT size is too large."); + } + + UIntT bestfound = 1; + for (UIntT f11 = 1;f11 <= n; f11 *= 11) + for (UIntT f117 = f11; f117 <= n; f117 *= 7) + for (UIntT f1175 = f117; f1175 <= n; f1175 *= 5) + { + UIntT x = f1175; + while (x*2 <= n) x *= 2; + if (x > bestfound) bestfound = x; + while (true) + { + if (x * 3 <= n) x *= 3; + else if (x % 2 == 0) x /= 2; + else break; + + if (x > bestfound) bestfound = x; + } + } + return bestfound; + } + /* returns the largest composite of 2, 3, 5, 7 and 11 which is <= n */ + static POCKETFFT_NOINLINE size_t prev_good_size_cmplx(size_t n) + { + return prev_good_size_cmplx_typed(n); + } + + /* inner workings of prev_good_size_real() */ + template + static POCKETFFT_NOINLINE UIntT prev_good_size_real_typed(UIntT n) + { + static_assert(std::numeric_limits::is_integer && (!std::numeric_limits::is_signed), + "type must be unsigned integer"); + if (n<=6) return n; + if (n>std::numeric_limits::max()/5) + { + // The algorithm below doesn't work for this value, the multiplication can overflow. + if (sizeof(UIntT)(n); + if (res<=std::numeric_limits::max()) + return static_cast(res); + } + // Otherwise, this size is ridiculously large, people shouldn't be computing FFTs this large. + throw std::runtime_error("FFT size is too large."); + } + + UIntT bestfound = 1; + for (UIntT f5 = 1; f5 <= n; f5 *= 5) + { + UIntT x = f5; + while (x*2 <= n) x *= 2; + if (x > bestfound) bestfound = x; + while (true) + { + if (x * 3 <= n) x *= 3; + else if (x % 2 == 0) x /= 2; + else break; + + if (x > bestfound) bestfound = x; + } + } + return bestfound; + } + /* returns the largest composite of 2, 3, 5 which is <= n */ + static POCKETFFT_NOINLINE size_t prev_good_size_real(size_t n) + { + return prev_good_size_real_typed(n); + } + + static size_t prod(const shape_t &shape) + { + size_t res=1; + for (auto sz: shape) + res*=sz; + return res; + } + + static POCKETFFT_NOINLINE void sanity_check(const shape_t &shape, + const stride_t &stride_in, const stride_t &stride_out, bool inplace) + { + auto ndim = shape.size(); + if (ndim<1) throw std::runtime_error("ndim must be >= 1"); + if ((stride_in.size()!=ndim) || (stride_out.size()!=ndim)) + throw std::runtime_error("stride dimension mismatch"); + if (inplace && (stride_in!=stride_out)) + throw std::runtime_error("stride mismatch"); + } + + static POCKETFFT_NOINLINE void sanity_check(const shape_t &shape, + const stride_t &stride_in, const stride_t &stride_out, bool inplace, + const shape_t &axes) + { + sanity_check(shape, stride_in, stride_out, inplace); + auto ndim = shape.size(); + shape_t tmp(ndim,0); + for (auto ax : axes) + { + if (ax>=ndim) throw std::invalid_argument("bad axis number"); + if (++tmp[ax]>1) throw std::invalid_argument("axis specified repeatedly"); + } + } + + static POCKETFFT_NOINLINE void sanity_check(const shape_t &shape, + const stride_t &stride_in, const stride_t &stride_out, bool inplace, + size_t axis) + { + sanity_check(shape, stride_in, stride_out, inplace); + if (axis>=shape.size()) throw std::invalid_argument("bad axis number"); + } + +#ifdef POCKETFFT_NO_MULTITHREADING + static size_t thread_count (size_t /*nthreads*/, const shape_t &/*shape*/, + size_t /*axis*/, size_t /*vlen*/) + { return 1; } +#else + static size_t thread_count (size_t nthreads, const shape_t &shape, + size_t axis, size_t vlen) + { + if (nthreads==1) return 1; + size_t size = prod(shape); + size_t parallel = size / (shape[axis] * vlen); + if (shape[axis] < 1000) + parallel /= 4; + size_t max_threads = nthreads == 0 ? + std::thread::hardware_concurrency() : nthreads; + return std::max(size_t(1), std::min(parallel, max_threads)); + } +#endif + }; + +namespace threading { + +#ifdef POCKETFFT_NO_MULTITHREADING + +constexpr inline size_t thread_id() { return 0; } +constexpr inline size_t num_threads() { return 1; } + +template +void thread_map(size_t /* nthreads */, Func f) + { f(); } + +#else + +inline size_t &thread_id() + { + static thread_local size_t thread_id_=0; + return thread_id_; + } +inline size_t &num_threads() + { + static thread_local size_t num_threads_=1; + return num_threads_; + } +static const size_t max_threads = std::max(1u, std::thread::hardware_concurrency()); + +class latch + { + std::atomic num_left_; + std::mutex mut_; + std::condition_variable completed_; + using lock_t = std::unique_lock; + + public: + latch(size_t n): num_left_(n) {} + + void count_down() + { + lock_t lock(mut_); + if (--num_left_) + return; + completed_.notify_all(); + } + + void wait() + { + lock_t lock(mut_); + completed_.wait(lock, [this]{ return is_ready(); }); + } + bool is_ready() { return num_left_ == 0; } + }; + +template class concurrent_queue + { + std::queue q_; + std::mutex mut_; + std::atomic size_; + using lock_t = std::lock_guard; + + public: + + void push(T val) + { + lock_t lock(mut_); + ++size_; + q_.push(std::move(val)); + } + + bool try_pop(T &val) + { + if (size_ == 0) return false; + lock_t lock(mut_); + // Queue might have been emptied while we acquired the lock + if (q_.empty()) return false; + + val = std::move(q_.front()); + --size_; + q_.pop(); + return true; + } + + bool empty() const { return size_==0; } + }; + +// C++ allocator with support for over-aligned types +template struct aligned_allocator + { + using value_type = T; + template + aligned_allocator(const aligned_allocator&) {} + aligned_allocator() = default; + + T *allocate(size_t n) + { + void* mem = aligned_alloc(alignof(T), n*sizeof(T)); + return static_cast(mem); + } + + void deallocate(T *p, size_t /*n*/) + { aligned_dealloc(p); } + }; + +class thread_pool + { + // A reasonable guess, probably close enough for most hardware + static constexpr size_t cache_line_size = 64; + struct alignas(cache_line_size) worker + { + std::thread thread; + std::condition_variable work_ready; + std::mutex mut; + std::atomic_flag busy_flag = ATOMIC_FLAG_INIT; + std::function work; + + void worker_main( + std::atomic &shutdown_flag, + std::atomic &unscheduled_tasks, + concurrent_queue> &overflow_work) + { + using lock_t = std::unique_lock; + bool expect_work = true; + while (!shutdown_flag || expect_work) + { + std::function local_work; + if (expect_work || unscheduled_tasks == 0) + { + lock_t lock(mut); + // Wait until there is work to be executed + work_ready.wait(lock, [&]{ return (work || shutdown_flag); }); + local_work.swap(work); + expect_work = false; + } + + bool marked_busy = false; + if (local_work) + { + marked_busy = true; + local_work(); + } + + if (!overflow_work.empty()) + { + if (!marked_busy && busy_flag.test_and_set()) + { + expect_work = true; + continue; + } + marked_busy = true; + + while (overflow_work.try_pop(local_work)) + { + --unscheduled_tasks; + local_work(); + } + } + + if (marked_busy) busy_flag.clear(); + } + } + }; + + concurrent_queue> overflow_work_; + std::mutex mut_; + std::vector> workers_; + std::atomic shutdown_; + std::atomic unscheduled_tasks_; + using lock_t = std::lock_guard; + + void create_threads() + { + lock_t lock(mut_); + size_t nthreads=workers_.size(); + for (size_t i=0; ibusy_flag.clear(); + worker->work = nullptr; + worker->thread = std::thread([worker, this] + { + worker->worker_main(shutdown_, unscheduled_tasks_, overflow_work_); + }); + } + catch (...) + { + shutdown_locked(); + throw; + } + } + } + + void shutdown_locked() + { + shutdown_ = true; + for (auto &worker : workers_) + worker.work_ready.notify_all(); + + for (auto &worker : workers_) + if (worker.thread.joinable()) + worker.thread.join(); + } + + public: + explicit thread_pool(size_t nthreads): + workers_(nthreads) + { create_threads(); } + + thread_pool(): thread_pool(max_threads) {} + + ~thread_pool() { shutdown(); } + + void submit(std::function work) + { + lock_t lock(mut_); + if (shutdown_) + throw std::runtime_error("Work item submitted after shutdown"); + + ++unscheduled_tasks_; + + // First check for any idle workers and wake those + for (auto &worker : workers_) + if (!worker.busy_flag.test_and_set()) + { + --unscheduled_tasks_; + { + lock_t lock(worker.mut); + worker.work = std::move(work); + } + worker.work_ready.notify_one(); + return; + } + + // If no workers were idle, push onto the overflow queue for later + overflow_work_.push(std::move(work)); + } + + void shutdown() + { + lock_t lock(mut_); + shutdown_locked(); + } + + void restart() + { + shutdown_ = false; + create_threads(); + } + }; + +inline thread_pool & get_pool() + { + static thread_pool pool; +#ifdef POCKETFFT_PTHREADS + static std::once_flag f; + std::call_once(f, + []{ + pthread_atfork( + +[]{ get_pool().shutdown(); }, // prepare + +[]{ get_pool().restart(); }, // parent + +[]{ get_pool().restart(); } // child + ); + }); +#endif + + return pool; + } + +/** Map a function f over nthreads */ +template +void thread_map(size_t nthreads, Func f) + { + if (nthreads == 0) + nthreads = max_threads; + + if (nthreads == 1) + { f(); return; } + + auto & pool = get_pool(); + latch counter(nthreads); + std::exception_ptr ex; + std::mutex ex_mut; + for (size_t i=0; i lock(ex_mut); + ex = std::current_exception(); + } + counter.count_down(); + }); + } + counter.wait(); + if (ex) + std::rethrow_exception(ex); + } + +#endif + +} + +// +// complex FFTPACK transforms +// + +template class cfftp + { + private: + struct fctdata + { + size_t fct; + cmplx *tw, *tws; + }; + + size_t length; + arr> mem; + std::vector fact; + + void add_factor(size_t factor) + { fact.push_back({factor, nullptr, nullptr}); } + +template void pass2 (size_t ido, size_t l1, + const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const cmplx * POCKETFFT_RESTRICT wa) const + { + auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+l1*c)]; }; + auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+2*c)]; }; + auto WA = [wa, ido](size_t x, size_t i) + { return wa[i-1+x*(ido-1)]; }; + + if (ido==1) + for (size_t k=0; k(CC(i,0,k)-CC(i,1,k),WA(0,i),CH(i,k,1)); + } + } + } + +#define POCKETFFT_PREP3(idx) \ + T t0 = CC(idx,0,k), t1, t2; \ + PM (t1,t2,CC(idx,1,k),CC(idx,2,k)); \ + CH(idx,k,0)=t0+t1; +#define POCKETFFT_PARTSTEP3a(u1,u2,twr,twi) \ + { \ + T ca=t0+t1*twr; \ + T cb{-t2.i*twi, t2.r*twi}; \ + PM(CH(0,k,u1),CH(0,k,u2),ca,cb) ;\ + } +#define POCKETFFT_PARTSTEP3b(u1,u2,twr,twi) \ + { \ + T ca=t0+t1*twr; \ + T cb{-t2.i*twi, t2.r*twi}; \ + special_mul(ca+cb,WA(u1-1,i),CH(i,k,u1)); \ + special_mul(ca-cb,WA(u2-1,i),CH(i,k,u2)); \ + } +template void pass3 (size_t ido, size_t l1, + const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const cmplx * POCKETFFT_RESTRICT wa) const + { + constexpr T0 tw1r=-0.5, + tw1i= (fwd ? -1: 1) * T0(0.8660254037844386467637231707529362L); + + auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+l1*c)]; }; + auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+3*c)]; }; + auto WA = [wa, ido](size_t x, size_t i) + { return wa[i-1+x*(ido-1)]; }; + + if (ido==1) + for (size_t k=0; k void pass4 (size_t ido, size_t l1, + const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const cmplx * POCKETFFT_RESTRICT wa) const + { + auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+l1*c)]; }; + auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+4*c)]; }; + auto WA = [wa, ido](size_t x, size_t i) + { return wa[i-1+x*(ido-1)]; }; + + if (ido==1) + for (size_t k=0; k(t4); + PM(CH(0,k,0),CH(0,k,2),t2,t3); + PM(CH(0,k,1),CH(0,k,3),t1,t4); + } + else + for (size_t k=0; k(t4); + PM(CH(0,k,0),CH(0,k,2),t2,t3); + PM(CH(0,k,1),CH(0,k,3),t1,t4); + } + for (size_t i=1; i(t4); + CH(i,k,0) = t2+t3; + special_mul(t1+t4,WA(0,i),CH(i,k,1)); + special_mul(t2-t3,WA(1,i),CH(i,k,2)); + special_mul(t1-t4,WA(2,i),CH(i,k,3)); + } + } + } + +#define POCKETFFT_PREP5(idx) \ + T t0 = CC(idx,0,k), t1, t2, t3, t4; \ + PM (t1,t4,CC(idx,1,k),CC(idx,4,k)); \ + PM (t2,t3,CC(idx,2,k),CC(idx,3,k)); \ + CH(idx,k,0).r=t0.r+t1.r+t2.r; \ + CH(idx,k,0).i=t0.i+t1.i+t2.i; + +#define POCKETFFT_PARTSTEP5a(u1,u2,twar,twbr,twai,twbi) \ + { \ + T ca,cb; \ + ca.r=t0.r+twar*t1.r+twbr*t2.r; \ + ca.i=t0.i+twar*t1.i+twbr*t2.i; \ + cb.i=twai*t4.r twbi*t3.r; \ + cb.r=-(twai*t4.i twbi*t3.i); \ + PM(CH(0,k,u1),CH(0,k,u2),ca,cb); \ + } + +#define POCKETFFT_PARTSTEP5b(u1,u2,twar,twbr,twai,twbi) \ + { \ + T ca,cb,da,db; \ + ca.r=t0.r+twar*t1.r+twbr*t2.r; \ + ca.i=t0.i+twar*t1.i+twbr*t2.i; \ + cb.i=twai*t4.r twbi*t3.r; \ + cb.r=-(twai*t4.i twbi*t3.i); \ + special_mul(ca+cb,WA(u1-1,i),CH(i,k,u1)); \ + special_mul(ca-cb,WA(u2-1,i),CH(i,k,u2)); \ + } +template void pass5 (size_t ido, size_t l1, + const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const cmplx * POCKETFFT_RESTRICT wa) const + { + constexpr T0 tw1r= T0(0.3090169943749474241022934171828191L), + tw1i= (fwd ? -1: 1) * T0(0.9510565162951535721164393333793821L), + tw2r= T0(-0.8090169943749474241022934171828191L), + tw2i= (fwd ? -1: 1) * T0(0.5877852522924731291687059546390728L); + + auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+l1*c)]; }; + auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+5*c)]; }; + auto WA = [wa, ido](size_t x, size_t i) + { return wa[i-1+x*(ido-1)]; }; + + if (ido==1) + for (size_t k=0; k(da,WA(u1-1,i),CH(i,k,u1)); \ + special_mul(db,WA(u2-1,i),CH(i,k,u2)); \ + } + +template void pass7(size_t ido, size_t l1, + const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const cmplx * POCKETFFT_RESTRICT wa) const + { + constexpr T0 tw1r= T0(0.6234898018587335305250048840042398L), + tw1i= (fwd ? -1 : 1) * T0(0.7818314824680298087084445266740578L), + tw2r= T0(-0.2225209339563144042889025644967948L), + tw2i= (fwd ? -1 : 1) * T0(0.9749279121818236070181316829939312L), + tw3r= T0(-0.9009688679024191262361023195074451L), + tw3i= (fwd ? -1 : 1) * T0(0.433883739117558120475768332848359L); + + auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+l1*c)]; }; + auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+7*c)]; }; + auto WA = [wa, ido](size_t x, size_t i) + { return wa[i-1+x*(ido-1)]; }; + + if (ido==1) + for (size_t k=0; k void ROTX45(T &a) const + { + constexpr T0 hsqt2=T0(0.707106781186547524400844362104849L); + if (fwd) + { auto tmp_=a.r; a.r=hsqt2*(a.r+a.i); a.i=hsqt2*(a.i-tmp_); } + else + { auto tmp_=a.r; a.r=hsqt2*(a.r-a.i); a.i=hsqt2*(a.i+tmp_); } + } +template void ROTX135(T &a) const + { + constexpr T0 hsqt2=T0(0.707106781186547524400844362104849L); + if (fwd) + { auto tmp_=a.r; a.r=hsqt2*(a.i-a.r); a.i=hsqt2*(-tmp_-a.i); } + else + { auto tmp_=a.r; a.r=hsqt2*(-a.r-a.i); a.i=hsqt2*(tmp_-a.i); } + } + +template void pass8 (size_t ido, size_t l1, + const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const cmplx * POCKETFFT_RESTRICT wa) const + { + auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+l1*c)]; }; + auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+8*c)]; }; + auto WA = [wa, ido](size_t x, size_t i) + { return wa[i-1+x*(ido-1)]; }; + + if (ido==1) + for (size_t k=0; k(a3); + + ROTX90(a7); + PMINPLACE(a5,a7); + ROTX45(a5); + ROTX135(a7); + + PM(a0,a4,CC(0,0,k),CC(0,4,k)); + PM(a2,a6,CC(0,2,k),CC(0,6,k)); + PM(CH(0,k,0),CH(0,k,4),a0+a2,a1); + PM(CH(0,k,2),CH(0,k,6),a0-a2,a3); + ROTX90(a6); + PM(CH(0,k,1),CH(0,k,5),a4+a6,a5); + PM(CH(0,k,3),CH(0,k,7),a4-a6,a7); + } + else + for (size_t k=0; k(a3); + + ROTX90(a7); + PMINPLACE(a5,a7); + ROTX45(a5); + ROTX135(a7); + + PM(a0,a4,CC(0,0,k),CC(0,4,k)); + PM(a2,a6,CC(0,2,k),CC(0,6,k)); + PM(CH(0,k,0),CH(0,k,4),a0+a2,a1); + PM(CH(0,k,2),CH(0,k,6),a0-a2,a3); + ROTX90(a6); + PM(CH(0,k,1),CH(0,k,5),a4+a6,a5); + PM(CH(0,k,3),CH(0,k,7),a4-a6,a7); + } + for (size_t i=1; i(a7); + PMINPLACE(a1,a3); + ROTX90(a3); + PMINPLACE(a5,a7); + ROTX45(a5); + ROTX135(a7); + PM(a0,a4,CC(i,0,k),CC(i,4,k)); + PM(a2,a6,CC(i,2,k),CC(i,6,k)); + PMINPLACE(a0,a2); + CH(i,k,0) = a0+a1; + special_mul(a0-a1,WA(3,i),CH(i,k,4)); + special_mul(a2+a3,WA(1,i),CH(i,k,2)); + special_mul(a2-a3,WA(5,i),CH(i,k,6)); + ROTX90(a6); + PMINPLACE(a4,a6); + special_mul(a4+a5,WA(0,i),CH(i,k,1)); + special_mul(a4-a5,WA(4,i),CH(i,k,5)); + special_mul(a6+a7,WA(2,i),CH(i,k,3)); + special_mul(a6-a7,WA(6,i),CH(i,k,7)); + } + } + } + + +#define POCKETFFT_PREP11(idx) \ + T t1 = CC(idx,0,k), t2, t3, t4, t5, t6, t7, t8, t9, t10, t11; \ + PM (t2,t11,CC(idx,1,k),CC(idx,10,k)); \ + PM (t3,t10,CC(idx,2,k),CC(idx, 9,k)); \ + PM (t4,t9 ,CC(idx,3,k),CC(idx, 8,k)); \ + PM (t5,t8 ,CC(idx,4,k),CC(idx, 7,k)); \ + PM (t6,t7 ,CC(idx,5,k),CC(idx, 6,k)); \ + CH(idx,k,0).r=t1.r+t2.r+t3.r+t4.r+t5.r+t6.r; \ + CH(idx,k,0).i=t1.i+t2.i+t3.i+t4.i+t5.i+t6.i; + +#define POCKETFFT_PARTSTEP11a0(u1,u2,x1,x2,x3,x4,x5,y1,y2,y3,y4,y5,out1,out2) \ + { \ + T ca = t1 + t2*x1 + t3*x2 + t4*x3 + t5*x4 +t6*x5, \ + cb; \ + cb.i=y1*t11.r y2*t10.r y3*t9.r y4*t8.r y5*t7.r; \ + cb.r=-(y1*t11.i y2*t10.i y3*t9.i y4*t8.i y5*t7.i ); \ + PM(out1,out2,ca,cb); \ + } +#define POCKETFFT_PARTSTEP11a(u1,u2,x1,x2,x3,x4,x5,y1,y2,y3,y4,y5) \ + POCKETFFT_PARTSTEP11a0(u1,u2,x1,x2,x3,x4,x5,y1,y2,y3,y4,y5,CH(0,k,u1),CH(0,k,u2)) +#define POCKETFFT_PARTSTEP11(u1,u2,x1,x2,x3,x4,x5,y1,y2,y3,y4,y5) \ + { \ + T da,db; \ + POCKETFFT_PARTSTEP11a0(u1,u2,x1,x2,x3,x4,x5,y1,y2,y3,y4,y5,da,db) \ + special_mul(da,WA(u1-1,i),CH(i,k,u1)); \ + special_mul(db,WA(u2-1,i),CH(i,k,u2)); \ + } + +template void pass11 (size_t ido, size_t l1, + const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const cmplx * POCKETFFT_RESTRICT wa) const + { + constexpr T0 tw1r= T0(0.8412535328311811688618116489193677L), + tw1i= (fwd ? -1 : 1) * T0(0.5406408174555975821076359543186917L), + tw2r= T0(0.4154150130018864255292741492296232L), + tw2i= (fwd ? -1 : 1) * T0(0.9096319953545183714117153830790285L), + tw3r= T0(-0.1423148382732851404437926686163697L), + tw3i= (fwd ? -1 : 1) * T0(0.9898214418809327323760920377767188L), + tw4r= T0(-0.6548607339452850640569250724662936L), + tw4i= (fwd ? -1 : 1) * T0(0.7557495743542582837740358439723444L), + tw5r= T0(-0.9594929736144973898903680570663277L), + tw5i= (fwd ? -1 : 1) * T0(0.2817325568414296977114179153466169L); + + auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+l1*c)]; }; + auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+11*c)]; }; + auto WA = [wa, ido](size_t x, size_t i) + { return wa[i-1+x*(ido-1)]; }; + + if (ido==1) + for (size_t k=0; k void passg (size_t ido, size_t ip, + size_t l1, T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const cmplx * POCKETFFT_RESTRICT wa, + const cmplx * POCKETFFT_RESTRICT csarr) const + { + const size_t cdim=ip; + size_t ipph = (ip+1)/2; + size_t idl1 = ido*l1; + + auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+l1*c)]; }; + auto CC = [cc,ido,cdim](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+cdim*c)]; }; + auto CX = [cc, ido, l1](size_t a, size_t b, size_t c) -> T& + { return cc[a+ido*(b+l1*c)]; }; + auto CX2 = [cc, idl1](size_t a, size_t b) -> T& + { return cc[a+idl1*b]; }; + auto CH2 = [ch, idl1](size_t a, size_t b) -> const T& + { return ch[a+idl1*b]; }; + + arr> wal(ip); + wal[0] = cmplx(1., 0.); + for (size_t i=1; i(csarr[i].r,fwd ? -csarr[i].i : csarr[i].i); + + for (size_t k=0; kip) iwal-=ip; + cmplx xwal=wal[iwal]; + iwal+=l; if (iwal>ip) iwal-=ip; + cmplx xwal2=wal[iwal]; + for (size_t ik=0; ikip) iwal-=ip; + cmplx xwal=wal[iwal]; + for (size_t ik=0; ik(x1,wa[idij],CX(i,k,j)); + idij=(jc-1)*(ido-1)+i-1; + special_mul(x2,wa[idij],CX(i,k,jc)); + } + } + } + } + +template void pass_all(T c[], T0 fct) const + { + if (length==1) { c[0]*=fct; return; } + size_t l1=1; + arr ch(length); + T *p1=c, *p2=ch.data(); + + for(size_t k1=0; k1 (ido, l1, p1, p2, fact[k1].tw); + else if(ip==8) + pass8(ido, l1, p1, p2, fact[k1].tw); + else if(ip==2) + pass2(ido, l1, p1, p2, fact[k1].tw); + else if(ip==3) + pass3 (ido, l1, p1, p2, fact[k1].tw); + else if(ip==5) + pass5 (ido, l1, p1, p2, fact[k1].tw); + else if(ip==7) + pass7 (ido, l1, p1, p2, fact[k1].tw); + else if(ip==11) + pass11 (ido, l1, p1, p2, fact[k1].tw); + else + { + passg(ido, ip, l1, p1, p2, fact[k1].tw, fact[k1].tws); + std::swap(p1,p2); + } + std::swap(p1,p2); + l1=l2; + } + if (p1!=c) + { + if (fct!=1.) + for (size_t i=0; i void exec(T c[], T0 fct, bool fwd) const + { fwd ? pass_all(c, fct) : pass_all(c, fct); } + + private: + POCKETFFT_NOINLINE void factorize() + { + size_t len=length; + while ((len&7)==0) + { add_factor(8); len>>=3; } + while ((len&3)==0) + { add_factor(4); len>>=2; } + if ((len&1)==0) + { + len>>=1; + // factor 2 should be at the front of the factor list + add_factor(2); + std::swap(fact[0].fct, fact.back().fct); + } + for (size_t divisor=3; divisor*divisor<=len; divisor+=2) + while ((len%divisor)==0) + { + add_factor(divisor); + len/=divisor; + } + if (len>1) add_factor(len); + } + + size_t twsize() const + { + size_t twsize=0, l1=1; + for (size_t k=0; k11) + twsize+=ip; + l1*=ip; + } + return twsize; + } + + void comp_twiddle() + { + sincos_2pibyn twiddle(length); + size_t l1=1; + size_t memofs=0; + for (size_t k=0; k11) + { + fact[k].tws=mem.data()+memofs; + memofs+=ip; + for (size_t j=0; j class rfftp + { + private: + struct fctdata + { + size_t fct; + T0 *tw, *tws; + }; + + size_t length; + arr mem; + std::vector fact; + + void add_factor(size_t factor) + { fact.push_back({factor, nullptr, nullptr}); } + +/* (a+ib) = conj(c+id) * (e+if) */ +template inline void MULPM + (T1 &a, T1 &b, T2 c, T2 d, T3 e, T3 f) const + { a=c*e+d*f; b=c*f-d*e; } + +template void radf2 (size_t ido, size_t l1, + const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const T0 * POCKETFFT_RESTRICT wa) const + { + auto WA = [wa,ido](size_t x, size_t i) { return wa[i+x*(ido-1)]; }; + auto CC = [cc,ido,l1](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+l1*c)]; }; + auto CH = [ch,ido](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+2*c)]; }; + + for (size_t k=0; k void radf3(size_t ido, size_t l1, + const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const T0 * POCKETFFT_RESTRICT wa) const + { + constexpr T0 taur=-0.5, taui=T0(0.8660254037844386467637231707529362L); + + auto WA = [wa,ido](size_t x, size_t i) { return wa[i+x*(ido-1)]; }; + auto CC = [cc,ido,l1](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+l1*c)]; }; + auto CH = [ch,ido](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+3*c)]; }; + + for (size_t k=0; k void radf4(size_t ido, size_t l1, + const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const T0 * POCKETFFT_RESTRICT wa) const + { + constexpr T0 hsqt2=T0(0.707106781186547524400844362104849L); + + auto WA = [wa,ido](size_t x, size_t i) { return wa[i+x*(ido-1)]; }; + auto CC = [cc,ido,l1](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+l1*c)]; }; + auto CH = [ch,ido](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+4*c)]; }; + + for (size_t k=0; k void radf5(size_t ido, size_t l1, + const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const T0 * POCKETFFT_RESTRICT wa) const + { + constexpr T0 tr11= T0(0.3090169943749474241022934171828191L), + ti11= T0(0.9510565162951535721164393333793821L), + tr12= T0(-0.8090169943749474241022934171828191L), + ti12= T0(0.5877852522924731291687059546390728L); + + auto WA = [wa,ido](size_t x, size_t i) { return wa[i+x*(ido-1)]; }; + auto CC = [cc,ido,l1](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+l1*c)]; }; + auto CH = [ch,ido](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+5*c)]; }; + + for (size_t k=0; k void radfg(size_t ido, size_t ip, size_t l1, + T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const T0 * POCKETFFT_RESTRICT wa, const T0 * POCKETFFT_RESTRICT csarr) const + { + const size_t cdim=ip; + size_t ipph=(ip+1)/2; + size_t idl1 = ido*l1; + + auto CC = [cc,ido,cdim](size_t a, size_t b, size_t c) -> T& + { return cc[a+ido*(b+cdim*c)]; }; + auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> const T& + { return ch[a+ido*(b+l1*c)]; }; + auto C1 = [cc,ido,l1] (size_t a, size_t b, size_t c) -> T& + { return cc[a+ido*(b+l1*c)]; }; + auto C2 = [cc,idl1] (size_t a, size_t b) -> T& + { return cc[a+idl1*b]; }; + auto CH2 = [ch,idl1] (size_t a, size_t b) -> T& + { return ch[a+idl1*b]; }; + + if (ido>1) + { + for (size_t j=1, jc=ip-1; j=ip) iang-=ip; + T0 ar1=csarr[2*iang], ai1=csarr[2*iang+1]; + iang+=l; if (iang>=ip) iang-=ip; + T0 ar2=csarr[2*iang], ai2=csarr[2*iang+1]; + iang+=l; if (iang>=ip) iang-=ip; + T0 ar3=csarr[2*iang], ai3=csarr[2*iang+1]; + iang+=l; if (iang>=ip) iang-=ip; + T0 ar4=csarr[2*iang], ai4=csarr[2*iang+1]; + for (size_t ik=0; ik=ip) iang-=ip; + T0 ar1=csarr[2*iang], ai1=csarr[2*iang+1]; + iang+=l; if (iang>=ip) iang-=ip; + T0 ar2=csarr[2*iang], ai2=csarr[2*iang+1]; + for (size_t ik=0; ik=ip) iang-=ip; + T0 ar=csarr[2*iang], ai=csarr[2*iang+1]; + for (size_t ik=0; ik void radb2(size_t ido, size_t l1, + const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const T0 * POCKETFFT_RESTRICT wa) const + { + auto WA = [wa,ido](size_t x, size_t i) { return wa[i+x*(ido-1)]; }; + auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+2*c)]; }; + auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+l1*c)]; }; + + for (size_t k=0; k void radb3(size_t ido, size_t l1, + const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const T0 * POCKETFFT_RESTRICT wa) const + { + constexpr T0 taur=-0.5, taui=T0(0.8660254037844386467637231707529362L); + + auto WA = [wa,ido](size_t x, size_t i) { return wa[i+x*(ido-1)]; }; + auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+3*c)]; }; + auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+l1*c)]; }; + + for (size_t k=0; k void radb4(size_t ido, size_t l1, + const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const T0 * POCKETFFT_RESTRICT wa) const + { + constexpr T0 sqrt2=T0(1.414213562373095048801688724209698L); + + auto WA = [wa,ido](size_t x, size_t i) { return wa[i+x*(ido-1)]; }; + auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+4*c)]; }; + auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+l1*c)]; }; + + for (size_t k=0; k void radb5(size_t ido, size_t l1, + const T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const T0 * POCKETFFT_RESTRICT wa) const + { + constexpr T0 tr11= T0(0.3090169943749474241022934171828191L), + ti11= T0(0.9510565162951535721164393333793821L), + tr12= T0(-0.8090169943749474241022934171828191L), + ti12= T0(0.5877852522924731291687059546390728L); + + auto WA = [wa,ido](size_t x, size_t i) { return wa[i+x*(ido-1)]; }; + auto CC = [cc,ido](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+5*c)]; }; + auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+l1*c)]; }; + + for (size_t k=0; k void radbg(size_t ido, size_t ip, size_t l1, + T * POCKETFFT_RESTRICT cc, T * POCKETFFT_RESTRICT ch, + const T0 * POCKETFFT_RESTRICT wa, const T0 * POCKETFFT_RESTRICT csarr) const + { + const size_t cdim=ip; + size_t ipph=(ip+1)/ 2; + size_t idl1 = ido*l1; + + auto CC = [cc,ido,cdim](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+cdim*c)]; }; + auto CH = [ch,ido,l1](size_t a, size_t b, size_t c) -> T& + { return ch[a+ido*(b+l1*c)]; }; + auto C1 = [cc,ido,l1](size_t a, size_t b, size_t c) -> const T& + { return cc[a+ido*(b+l1*c)]; }; + auto C2 = [cc,idl1](size_t a, size_t b) -> T& + { return cc[a+idl1*b]; }; + auto CH2 = [ch,idl1](size_t a, size_t b) -> T& + { return ch[a+idl1*b]; }; + + for (size_t k=0; kip) iang-=ip; + T0 ar1=csarr[2*iang], ai1=csarr[2*iang+1]; + iang+=l; if(iang>ip) iang-=ip; + T0 ar2=csarr[2*iang], ai2=csarr[2*iang+1]; + iang+=l; if(iang>ip) iang-=ip; + T0 ar3=csarr[2*iang], ai3=csarr[2*iang+1]; + iang+=l; if(iang>ip) iang-=ip; + T0 ar4=csarr[2*iang], ai4=csarr[2*iang+1]; + for (size_t ik=0; ikip) iang-=ip; + T0 ar1=csarr[2*iang], ai1=csarr[2*iang+1]; + iang+=l; if(iang>ip) iang-=ip; + T0 ar2=csarr[2*iang], ai2=csarr[2*iang+1]; + for (size_t ik=0; ikip) iang-=ip; + T0 war=csarr[2*iang], wai=csarr[2*iang+1]; + for (size_t ik=0; ik void copy_and_norm(T *c, T *p1, T0 fct) const + { + if (p1!=c) + { + if (fct!=1.) + for (size_t i=0; i void exec(T c[], T0 fct, bool r2hc) const + { + if (length==1) { c[0]*=fct; return; } + size_t nf=fact.size(); + arr ch(length); + T *p1=c, *p2=ch.data(); + + if (r2hc) + for(size_t k1=0, l1=length; k1>=2; } + if ((len%2)==0) + { + len>>=1; + // factor 2 should be at the front of the factor list + add_factor(2); + std::swap(fact[0].fct, fact.back().fct); + } + for (size_t divisor=3; divisor*divisor<=len; divisor+=2) + while ((len%divisor)==0) + { + add_factor(divisor); + len/=divisor; + } + if (len>1) add_factor(len); + } + + size_t twsize() const + { + size_t twsz=0, l1=1; + for (size_t k=0; k5) twsz+=2*ip; + l1*=ip; + } + return twsz; + } + + void comp_twiddle() + { + sincos_2pibyn twid(length); + size_t l1=1; + T0 *ptr=mem.data(); + for (size_t k=0; k5) // special factors required by *g functions + { + fact[k].tws=ptr; ptr+=2*ip; + fact[k].tws[0] = 1.; + fact[k].tws[1] = 0.; + for (size_t i=2, ic=2*ip-2; i<=ic; i+=2, ic-=2) + { + fact[k].tws[i ] = twid[i/2*(length/ip)].r; + fact[k].tws[i+1] = twid[i/2*(length/ip)].i; + fact[k].tws[ic] = twid[i/2*(length/ip)].r; + fact[k].tws[ic+1] = -twid[i/2*(length/ip)].i; + } + } + l1*=ip; + } + } + + public: + POCKETFFT_NOINLINE rfftp(size_t length_) + : length(length_) + { + if (length==0) throw std::runtime_error("zero-length FFT requested"); + if (length==1) return; + factorize(); + mem.resize(twsize()); + comp_twiddle(); + } +}; + +// +// complex Bluestein transforms +// + +template class fftblue + { + private: + size_t n, n2; + cfftp plan; + arr> mem; + cmplx *bk, *bkf; + + template void fft(cmplx c[], T0 fct) const + { + arr> akf(n2); + + /* initialize a_k and FFT it */ + for (size_t m=0; m(c[m],bk[m],akf[m]); + auto zero = akf[0]*T0(0); + for (size_t m=n; m(bkf[0]); + for (size_t m=1; m<(n2+1)/2; ++m) + { + akf[m] = akf[m].template special_mul(bkf[m]); + akf[n2-m] = akf[n2-m].template special_mul(bkf[m]); + } + if ((n2&1)==0) + akf[n2/2] = akf[n2/2].template special_mul(bkf[n2/2]); + + /* inverse FFT */ + plan.exec (akf.data(),1.,false); + + /* multiply by b_k */ + for (size_t m=0; m(bk[m])*fct; + } + + public: + POCKETFFT_NOINLINE fftblue(size_t length) + : n(length), n2(util::good_size_cmplx(n*2-1)), plan(n2), mem(n+n2/2+1), + bk(mem.data()), bkf(mem.data()+n) + { + /* initialize b_k */ + sincos_2pibyn tmp(2*n); + bk[0].Set(1, 0); + + size_t coeff=0; + for (size_t m=1; m=2*n) coeff-=2*n; + bk[m] = tmp[coeff]; + } + + /* initialize the zero-padded, Fourier transformed b_k. Add normalisation. */ + arr> tbkf(n2); + T0 xn2 = T0(1)/T0(n2); + tbkf[0] = bk[0]*xn2; + for (size_t m=1; m void exec(cmplx c[], T0 fct, bool fwd) const + { fwd ? fft(c,fct) : fft(c,fct); } + + template void exec_r(T c[], T0 fct, bool fwd) + { + arr> tmp(n); + if (fwd) + { + auto zero = T0(0)*c[0]; + for (size_t m=0; m(tmp.data(),fct); + c[0] = tmp[0].r; + std::copy_n (&tmp[1].r, n-1, &c[1]); + } + else + { + tmp[0].Set(c[0],c[0]*0); + std::copy_n (c+1, n-1, &tmp[1].r); + if ((n&1)==0) tmp[n/2].i=T0(0)*c[0]; + for (size_t m=1; 2*m(tmp.data(),fct); + for (size_t m=0; m class pocketfft_c + { + private: + std::unique_ptr> packplan; + std::unique_ptr> blueplan; + size_t len; + + public: + POCKETFFT_NOINLINE pocketfft_c(size_t length) + : len(length) + { + if (length==0) throw std::runtime_error("zero-length FFT requested"); + size_t tmp = (length<50) ? 0 : util::largest_prime_factor(length); + if (tmp*tmp <= length) + { + packplan=std::unique_ptr>(new cfftp(length)); + return; + } + double comp1 = util::cost_guess(length); + double comp2 = 2*util::cost_guess(util::good_size_cmplx(2*length-1)); + comp2*=1.5; /* fudge factor that appears to give good overall performance */ + if (comp2>(new fftblue(length)); + else + packplan=std::unique_ptr>(new cfftp(length)); + } + + template POCKETFFT_NOINLINE void exec(cmplx c[], T0 fct, bool fwd) const + { packplan ? packplan->exec(c,fct,fwd) : blueplan->exec(c,fct,fwd); } + + size_t length() const { return len; } + }; + +// +// flexible (FFTPACK/Bluestein) real-valued 1D transform +// + +template class pocketfft_r + { + private: + std::unique_ptr> packplan; + std::unique_ptr> blueplan; + size_t len; + + public: + POCKETFFT_NOINLINE pocketfft_r(size_t length) + : len(length) + { + if (length==0) throw std::runtime_error("zero-length FFT requested"); + size_t tmp = (length<50) ? 0 : util::largest_prime_factor(length); + if (tmp*tmp <= length) + { + packplan=std::unique_ptr>(new rfftp(length)); + return; + } + double comp1 = 0.5*util::cost_guess(length); + double comp2 = 2*util::cost_guess(util::good_size_cmplx(2*length-1)); + comp2*=1.5; /* fudge factor that appears to give good overall performance */ + if (comp2>(new fftblue(length)); + else + packplan=std::unique_ptr>(new rfftp(length)); + } + + template POCKETFFT_NOINLINE void exec(T c[], T0 fct, bool fwd) const + { packplan ? packplan->exec(c,fct,fwd) : blueplan->exec_r(c,fct,fwd); } + + size_t length() const { return len; } + }; + + +// +// sine/cosine transforms +// + +template class T_dct1 + { + private: + pocketfft_r fftplan; + + public: + POCKETFFT_NOINLINE T_dct1(size_t length) + : fftplan(2*(length-1)) {} + + template POCKETFFT_NOINLINE void exec(T c[], T0 fct, bool ortho, + int /*type*/, bool /*cosine*/) const + { + constexpr T0 sqrt2=T0(1.414213562373095048801688724209698L); + size_t N=fftplan.length(), n=N/2+1; + if (ortho) + { c[0]*=sqrt2; c[n-1]*=sqrt2; } + arr tmp(N); + tmp[0] = c[0]; + for (size_t i=1; i class T_dst1 + { + private: + pocketfft_r fftplan; + + public: + POCKETFFT_NOINLINE T_dst1(size_t length) + : fftplan(2*(length+1)) {} + + template POCKETFFT_NOINLINE void exec(T c[], T0 fct, + bool /*ortho*/, int /*type*/, bool /*cosine*/) const + { + size_t N=fftplan.length(), n=N/2-1; + arr tmp(N); + tmp[0] = tmp[n+1] = c[0]*0; + for (size_t i=0; i class T_dcst23 + { + private: + pocketfft_r fftplan; + std::vector twiddle; + + public: + POCKETFFT_NOINLINE T_dcst23(size_t length) + : fftplan(length), twiddle(length) + { + sincos_2pibyn tw(4*length); + for (size_t i=0; i POCKETFFT_NOINLINE void exec(T c[], T0 fct, bool ortho, + int type, bool cosine) const + { + constexpr T0 sqrt2=T0(1.414213562373095048801688724209698L); + size_t N=length(); + size_t NS2 = (N+1)/2; + if (type==2) + { + if (!cosine) + for (size_t k=1; k class T_dcst4 + { + private: + size_t N; + std::unique_ptr> fft; + std::unique_ptr> rfft; + arr> C2; + + public: + POCKETFFT_NOINLINE T_dcst4(size_t length) + : N(length), + fft((N&1) ? nullptr : new pocketfft_c(N/2)), + rfft((N&1)? new pocketfft_r(N) : nullptr), + C2((N&1) ? 0 : N/2) + { + if ((N&1)==0) + { + sincos_2pibyn tw(16*N); + for (size_t i=0; i POCKETFFT_NOINLINE void exec(T c[], T0 fct, + bool /*ortho*/, int /*type*/, bool cosine) const + { + size_t n2 = N/2; + if (!cosine) + for (size_t k=0, kc=N-1; k y(N); + { + size_t i=0, m=n2; + for (; mexec(y.data(), fct, true); + { + auto SGN = [](size_t i) + { + constexpr T0 sqrt2=T0(1.414213562373095048801688724209698L); + return (i&2) ? -sqrt2 : sqrt2; + }; + c[n2] = y[0]*SGN(n2+1); + size_t i=0, i1=1, k=1; + for (; k> y(n2); + for(size_t i=0; iexec(y.data(), fct, true); + for(size_t i=0, ic=n2-1; i std::shared_ptr get_plan(size_t length) + { +#if POCKETFFT_CACHE_SIZE==0 + return std::make_shared(length); +#else + constexpr size_t nmax=POCKETFFT_CACHE_SIZE; + static std::array, nmax> cache; + static std::array last_access{{0}}; + static size_t access_counter = 0; + static std::mutex mut; + + auto find_in_cache = [&]() -> std::shared_ptr + { + for (size_t i=0; ilength()==length)) + { + // no need to update if this is already the most recent entry + if (last_access[i]!=access_counter) + { + last_access[i] = ++access_counter; + // Guard against overflow + if (access_counter == 0) + last_access.fill(0); + } + return cache[i]; + } + + return nullptr; + }; + + { + std::lock_guard lock(mut); + auto p = find_in_cache(); + if (p) return p; + } + auto plan = std::make_shared(length); + { + std::lock_guard lock(mut); + auto p = find_in_cache(); + if (p) return p; + + size_t lru = 0; + for (size_t i=1; i class cndarr: public arr_info + { + protected: + const char *d; + + public: + cndarr(const void *data_, const shape_t &shape_, const stride_t &stride_) + : arr_info(shape_, stride_), + d(reinterpret_cast(data_)) {} + const T &operator[](ptrdiff_t ofs) const + { return *reinterpret_cast(d+ofs); } + }; + +template class ndarr: public cndarr + { + public: + ndarr(void *data_, const shape_t &shape_, const stride_t &stride_) + : cndarr::cndarr(const_cast(data_), shape_, stride_) + {} + T &operator[](ptrdiff_t ofs) + { return *reinterpret_cast(const_cast(cndarr::d+ofs)); } + }; + +template class multi_iter + { + private: + shape_t pos; + const arr_info &iarr, &oarr; + ptrdiff_t p_ii, p_i[N], str_i, p_oi, p_o[N], str_o; + size_t idim, rem; + + void advance_i() + { + for (int i_=int(pos.size())-1; i_>=0; --i_) + { + auto i = size_t(i_); + if (i==idim) continue; + p_ii += iarr.stride(i); + p_oi += oarr.stride(i); + if (++pos[i] < iarr.shape(i)) + return; + pos[i] = 0; + p_ii -= ptrdiff_t(iarr.shape(i))*iarr.stride(i); + p_oi -= ptrdiff_t(oarr.shape(i))*oarr.stride(i); + } + } + + public: + multi_iter(const arr_info &iarr_, const arr_info &oarr_, size_t idim_) + : pos(iarr_.ndim(), 0), iarr(iarr_), oarr(oarr_), p_ii(0), + str_i(iarr.stride(idim_)), p_oi(0), str_o(oarr.stride(idim_)), + idim(idim_), rem(iarr.size()/iarr.shape(idim)) + { + auto nshares = threading::num_threads(); + if (nshares==1) return; + if (nshares==0) throw std::runtime_error("can't run with zero threads"); + auto myshare = threading::thread_id(); + if (myshare>=nshares) throw std::runtime_error("impossible share requested"); + size_t nbase = rem/nshares; + size_t additional = rem%nshares; + size_t lo = myshare*nbase + ((myshare=0; --i_) + { + auto i = size_t(i_); + p += arr.stride(i); + if (++pos[i] < arr.shape(i)) + return; + pos[i] = 0; + p -= ptrdiff_t(arr.shape(i))*arr.stride(i); + } + } + ptrdiff_t ofs() const { return p; } + size_t remaining() const { return rem; } + }; + +class rev_iter + { + private: + shape_t pos; + const arr_info &arr; + std::vector rev_axis; + std::vector rev_jump; + size_t last_axis, last_size; + shape_t shp; + ptrdiff_t p, rp; + size_t rem; + + public: + rev_iter(const arr_info &arr_, const shape_t &axes) + : pos(arr_.ndim(), 0), arr(arr_), rev_axis(arr_.ndim(), 0), + rev_jump(arr_.ndim(), 1), p(0), rp(0) + { + for (auto ax: axes) + rev_axis[ax]=1; + last_axis = axes.back(); + last_size = arr.shape(last_axis)/2 + 1; + shp = arr.shape(); + shp[last_axis] = last_size; + rem=1; + for (auto i: shp) + rem *= i; + } + void advance() + { + --rem; + for (int i_=int(pos.size())-1; i_>=0; --i_) + { + auto i = size_t(i_); + p += arr.stride(i); + if (!rev_axis[i]) + rp += arr.stride(i); + else + { + rp -= arr.stride(i); + if (rev_jump[i]) + { + rp += ptrdiff_t(arr.shape(i))*arr.stride(i); + rev_jump[i] = 0; + } + } + if (++pos[i] < shp[i]) + return; + pos[i] = 0; + p -= ptrdiff_t(shp[i])*arr.stride(i); + if (rev_axis[i]) + { + rp -= ptrdiff_t(arr.shape(i)-shp[i])*arr.stride(i); + rev_jump[i] = 1; + } + else + rp -= ptrdiff_t(shp[i])*arr.stride(i); + } + } + ptrdiff_t ofs() const { return p; } + ptrdiff_t rev_ofs() const { return rp; } + size_t remaining() const { return rem; } + }; + +template struct VTYPE {}; +template using vtype_t = typename VTYPE::type; + +#ifndef POCKETFFT_NO_VECTORS +template<> struct VTYPE + { + using type = float __attribute__ ((vector_size (VLEN::val*sizeof(float)))); + }; +template<> struct VTYPE + { + using type = double __attribute__ ((vector_size (VLEN::val*sizeof(double)))); + }; +template<> struct VTYPE + { + using type = long double __attribute__ ((vector_size (VLEN::val*sizeof(long double)))); + }; +#endif + +template arr alloc_tmp(const shape_t &shape, + size_t axsize, size_t elemsize) + { + auto othersize = util::prod(shape)/axsize; + auto tmpsize = axsize*((othersize>=VLEN::val) ? VLEN::val : 1); + return arr(tmpsize*elemsize); + } +template arr alloc_tmp(const shape_t &shape, + const shape_t &axes, size_t elemsize) + { + size_t fullsize=util::prod(shape); + size_t tmpsize=0; + for (size_t i=0; i=VLEN::val) ? VLEN::val : 1); + if (sz>tmpsize) tmpsize=sz; + } + return arr(tmpsize*elemsize); + } + +template void copy_input(const multi_iter &it, + const cndarr> &src, cmplx> *POCKETFFT_RESTRICT dst) + { + for (size_t i=0; i void copy_input(const multi_iter &it, + const cndarr &src, vtype_t *POCKETFFT_RESTRICT dst) + { + for (size_t i=0; i void copy_input(const multi_iter &it, + const cndarr &src, T *POCKETFFT_RESTRICT dst) + { + if (dst == &src[it.iofs(0)]) return; // in-place + for (size_t i=0; i void copy_output(const multi_iter &it, + const cmplx> *POCKETFFT_RESTRICT src, ndarr> &dst) + { + for (size_t i=0; i void copy_output(const multi_iter &it, + const vtype_t *POCKETFFT_RESTRICT src, ndarr &dst) + { + for (size_t i=0; i void copy_output(const multi_iter &it, + const T *POCKETFFT_RESTRICT src, ndarr &dst) + { + if (src == &dst[it.oofs(0)]) return; // in-place + for (size_t i=0; i struct add_vec { using type = vtype_t; }; +template struct add_vec> + { using type = cmplx>; }; +template using add_vec_t = typename add_vec::type; + +template +POCKETFFT_NOINLINE void general_nd(const cndarr &in, ndarr &out, + const shape_t &axes, T0 fct, size_t nthreads, const Exec & exec, + const bool allow_inplace=true) + { + std::shared_ptr plan; + + for (size_t iax=0; iaxlength())) + plan = get_plan(len); + + threading::thread_map( + util::thread_count(nthreads, in.shape(), axes[iax], VLEN::val), + [&] { + constexpr auto vlen = VLEN::val; + auto storage = alloc_tmp(in.shape(), len, sizeof(T)); + const auto &tin(iax==0? in : out); + multi_iter it(tin, out, axes[iax]); +#ifndef POCKETFFT_NO_VECTORS + if (vlen>1) + while (it.remaining()>=vlen) + { + it.advance(vlen); + auto tdatav = reinterpret_cast *>(storage.data()); + exec(it, tin, out, tdatav, *plan, fct); + } +#endif + while (it.remaining()>0) + { + it.advance(1); + auto buf = allow_inplace && it.stride_out() == sizeof(T) ? + &out[it.oofs(0)] : reinterpret_cast(storage.data()); + exec(it, tin, out, buf, *plan, fct); + } + }); // end of parallel region + fct = T0(1); // factor has been applied, use 1 for remaining axes + } + } + +struct ExecC2C + { + bool forward; + + template void operator () ( + const multi_iter &it, const cndarr> &in, + ndarr> &out, T * buf, const pocketfft_c &plan, T0 fct) const + { + copy_input(it, in, buf); + plan.exec(buf, fct, forward); + copy_output(it, buf, out); + } + }; + +template void copy_hartley(const multi_iter &it, + const vtype_t *POCKETFFT_RESTRICT src, ndarr &dst) + { + for (size_t j=0; j void copy_hartley(const multi_iter &it, + const T *POCKETFFT_RESTRICT src, ndarr &dst) + { + dst[it.oofs(0)] = src[0]; + size_t i=1, i1=1, i2=it.length_out()-1; + for (i=1; i void operator () ( + const multi_iter &it, const cndarr &in, ndarr &out, + T * buf, const pocketfft_r &plan, T0 fct) const + { + copy_input(it, in, buf); + plan.exec(buf, fct, true); + copy_hartley(it, buf, out); + } + }; + +struct ExecDcst + { + bool ortho; + int type; + bool cosine; + + template + void operator () (const multi_iter &it, const cndarr &in, + ndarr &out, T * buf, const Tplan &plan, T0 fct) const + { + copy_input(it, in, buf); + plan.exec(buf, fct, ortho, type, cosine); + copy_output(it, buf, out); + } + }; + +template POCKETFFT_NOINLINE void general_r2c( + const cndarr &in, ndarr> &out, size_t axis, bool forward, T fct, + size_t nthreads) + { + auto plan = get_plan>(in.shape(axis)); + size_t len=in.shape(axis); + threading::thread_map( + util::thread_count(nthreads, in.shape(), axis, VLEN::val), + [&] { + constexpr auto vlen = VLEN::val; + auto storage = alloc_tmp(in.shape(), len, sizeof(T)); + multi_iter it(in, out, axis); +#ifndef POCKETFFT_NO_VECTORS + if (vlen>1) + while (it.remaining()>=vlen) + { + it.advance(vlen); + auto tdatav = reinterpret_cast *>(storage.data()); + copy_input(it, in, tdatav); + plan->exec(tdatav, fct, true); + for (size_t j=0; j0) + { + it.advance(1); + auto tdata = reinterpret_cast(storage.data()); + copy_input(it, in, tdata); + plan->exec(tdata, fct, true); + out[it.oofs(0)].Set(tdata[0]); + size_t i=1, ii=1; + if (forward) + for (; i POCKETFFT_NOINLINE void general_c2r( + const cndarr> &in, ndarr &out, size_t axis, bool forward, T fct, + size_t nthreads) + { + auto plan = get_plan>(out.shape(axis)); + size_t len=out.shape(axis); + threading::thread_map( + util::thread_count(nthreads, in.shape(), axis, VLEN::val), + [&] { + constexpr auto vlen = VLEN::val; + auto storage = alloc_tmp(out.shape(), len, sizeof(T)); + multi_iter it(in, out, axis); +#ifndef POCKETFFT_NO_VECTORS + if (vlen>1) + while (it.remaining()>=vlen) + { + it.advance(vlen); + auto tdatav = reinterpret_cast *>(storage.data()); + for (size_t j=0; jexec(tdatav, fct, false); + copy_output(it, tdatav, out); + } +#endif + while (it.remaining()>0) + { + it.advance(1); + auto tdata = reinterpret_cast(storage.data()); + tdata[0]=in[it.iofs(0)].r; + { + size_t i=1, ii=1; + if (forward) + for (; iexec(tdata, fct, false); + copy_output(it, tdata, out); + } + }); // end of parallel region + } + +struct ExecR2R + { + bool r2h, forward; + + template void operator () ( + const multi_iter &it, const cndarr &in, ndarr &out, T * buf, + const pocketfft_r &plan, T0 fct) const + { + copy_input(it, in, buf); + if ((!r2h) && forward) + for (size_t i=2; i void c2c(const shape_t &shape, const stride_t &stride_in, + const stride_t &stride_out, const shape_t &axes, bool forward, + const std::complex *data_in, std::complex *data_out, T fct, + size_t nthreads=1) + { + if (util::prod(shape)==0) return; + util::sanity_check(shape, stride_in, stride_out, data_in==data_out, axes); + cndarr> ain(data_in, shape, stride_in); + ndarr> aout(data_out, shape, stride_out); + general_nd>(ain, aout, axes, fct, nthreads, ExecC2C{forward}); + } + +template void dct(const shape_t &shape, + const stride_t &stride_in, const stride_t &stride_out, const shape_t &axes, + int type, const T *data_in, T *data_out, T fct, bool ortho, size_t nthreads=1) + { + if ((type<1) || (type>4)) throw std::invalid_argument("invalid DCT type"); + if (util::prod(shape)==0) return; + util::sanity_check(shape, stride_in, stride_out, data_in==data_out, axes); + cndarr ain(data_in, shape, stride_in); + ndarr aout(data_out, shape, stride_out); + const ExecDcst exec{ortho, type, true}; + if (type==1) + general_nd>(ain, aout, axes, fct, nthreads, exec); + else if (type==4) + general_nd>(ain, aout, axes, fct, nthreads, exec); + else + general_nd>(ain, aout, axes, fct, nthreads, exec); + } + +template void dst(const shape_t &shape, + const stride_t &stride_in, const stride_t &stride_out, const shape_t &axes, + int type, const T *data_in, T *data_out, T fct, bool ortho, size_t nthreads=1) + { + if ((type<1) || (type>4)) throw std::invalid_argument("invalid DST type"); + if (util::prod(shape)==0) return; + util::sanity_check(shape, stride_in, stride_out, data_in==data_out, axes); + cndarr ain(data_in, shape, stride_in); + ndarr aout(data_out, shape, stride_out); + const ExecDcst exec{ortho, type, false}; + if (type==1) + general_nd>(ain, aout, axes, fct, nthreads, exec); + else if (type==4) + general_nd>(ain, aout, axes, fct, nthreads, exec); + else + general_nd>(ain, aout, axes, fct, nthreads, exec); + } + +template void r2c(const shape_t &shape_in, + const stride_t &stride_in, const stride_t &stride_out, size_t axis, + bool forward, const T *data_in, std::complex *data_out, T fct, + size_t nthreads=1) + { + if (util::prod(shape_in)==0) return; + util::sanity_check(shape_in, stride_in, stride_out, false, axis); + cndarr ain(data_in, shape_in, stride_in); + shape_t shape_out(shape_in); + shape_out[axis] = shape_in[axis]/2 + 1; + ndarr> aout(data_out, shape_out, stride_out); + general_r2c(ain, aout, axis, forward, fct, nthreads); + } + +template void r2c(const shape_t &shape_in, + const stride_t &stride_in, const stride_t &stride_out, const shape_t &axes, + bool forward, const T *data_in, std::complex *data_out, T fct, + size_t nthreads=1) + { + if (util::prod(shape_in)==0) return; + util::sanity_check(shape_in, stride_in, stride_out, false, axes); + r2c(shape_in, stride_in, stride_out, axes.back(), forward, data_in, data_out, + fct, nthreads); + if (axes.size()==1) return; + + shape_t shape_out(shape_in); + shape_out[axes.back()] = shape_in[axes.back()]/2 + 1; + auto newaxes = shape_t{axes.begin(), --axes.end()}; + c2c(shape_out, stride_out, stride_out, newaxes, forward, data_out, data_out, + T(1), nthreads); + } + +template void c2r(const shape_t &shape_out, + const stride_t &stride_in, const stride_t &stride_out, size_t axis, + bool forward, const std::complex *data_in, T *data_out, T fct, + size_t nthreads=1) + { + if (util::prod(shape_out)==0) return; + util::sanity_check(shape_out, stride_in, stride_out, false, axis); + shape_t shape_in(shape_out); + shape_in[axis] = shape_out[axis]/2 + 1; + cndarr> ain(data_in, shape_in, stride_in); + ndarr aout(data_out, shape_out, stride_out); + general_c2r(ain, aout, axis, forward, fct, nthreads); + } + +template void c2r(const shape_t &shape_out, + const stride_t &stride_in, const stride_t &stride_out, const shape_t &axes, + bool forward, const std::complex *data_in, T *data_out, T fct, + size_t nthreads=1) + { + if (util::prod(shape_out)==0) return; + if (axes.size()==1) + return c2r(shape_out, stride_in, stride_out, axes[0], forward, + data_in, data_out, fct, nthreads); + util::sanity_check(shape_out, stride_in, stride_out, false, axes); + auto shape_in = shape_out; + shape_in[axes.back()] = shape_out[axes.back()]/2 + 1; + auto nval = util::prod(shape_in); + stride_t stride_inter(shape_in.size()); + stride_inter.back() = sizeof(cmplx); + for (int i=int(shape_in.size())-2; i>=0; --i) + stride_inter[size_t(i)] = + stride_inter[size_t(i+1)]*ptrdiff_t(shape_in[size_t(i+1)]); + arr> tmp(nval); + auto newaxes = shape_t{axes.begin(), --axes.end()}; + c2c(shape_in, stride_in, stride_inter, newaxes, forward, data_in, tmp.data(), + T(1), nthreads); + c2r(shape_out, stride_inter, stride_out, axes.back(), forward, + tmp.data(), data_out, fct, nthreads); + } + +template void r2r_fftpack(const shape_t &shape, + const stride_t &stride_in, const stride_t &stride_out, const shape_t &axes, + bool real2hermitian, bool forward, const T *data_in, T *data_out, T fct, + size_t nthreads=1) + { + if (util::prod(shape)==0) return; + util::sanity_check(shape, stride_in, stride_out, data_in==data_out, axes); + cndarr ain(data_in, shape, stride_in); + ndarr aout(data_out, shape, stride_out); + general_nd>(ain, aout, axes, fct, nthreads, + ExecR2R{real2hermitian, forward}); + } + +template void r2r_separable_hartley(const shape_t &shape, + const stride_t &stride_in, const stride_t &stride_out, const shape_t &axes, + const T *data_in, T *data_out, T fct, size_t nthreads=1) + { + if (util::prod(shape)==0) return; + util::sanity_check(shape, stride_in, stride_out, data_in==data_out, axes); + cndarr ain(data_in, shape, stride_in); + ndarr aout(data_out, shape, stride_out); + general_nd>(ain, aout, axes, fct, nthreads, ExecHartley{}, + false); + } + +template void r2r_genuine_hartley(const shape_t &shape, + const stride_t &stride_in, const stride_t &stride_out, const shape_t &axes, + const T *data_in, T *data_out, T fct, size_t nthreads=1) + { + if (util::prod(shape)==0) return; + if (axes.size()==1) + return r2r_separable_hartley(shape, stride_in, stride_out, axes, data_in, + data_out, fct, nthreads); + util::sanity_check(shape, stride_in, stride_out, data_in==data_out, axes); + shape_t tshp(shape); + tshp[axes.back()] = tshp[axes.back()]/2+1; + arr> tdata(util::prod(tshp)); + stride_t tstride(shape.size()); + tstride.back()=sizeof(std::complex); + for (size_t i=tstride.size()-1; i>0; --i) + tstride[i-1]=tstride[i]*ptrdiff_t(tshp[i]); + r2c(shape, stride_in, tstride, axes, true, data_in, tdata.data(), fct, nthreads); + cndarr> atmp(tdata.data(), tshp, tstride); + ndarr aout(data_out, shape, stride_out); + simple_iter iin(atmp); + rev_iter iout(aout, axes); + while(iin.remaining()>0) + { + auto v = atmp[iin.ofs()]; + aout[iout.ofs()] = v.r+v.i; + aout[iout.rev_ofs()] = v.r-v.i; + iin.advance(); iout.advance(); + } + } + +} // namespace detail + +using detail::FORWARD; +using detail::BACKWARD; +using detail::shape_t; +using detail::stride_t; +using detail::c2c; +using detail::c2r; +using detail::r2c; +using detail::r2r_fftpack; +using detail::r2r_separable_hartley; +using detail::r2r_genuine_hartley; +using detail::dct; +using detail::dst; + +} // namespace pocketfft + +#undef POCKETFFT_NOINLINE +#undef POCKETFFT_RESTRICT + +#endif // POCKETFFT_HDRONLY_H diff --git a/gemmi_gph/gemmi/third_party/tinydir.h b/gemmi_gph/gemmi/third_party/tinydir.h new file mode 100644 index 00000000..0b536fa4 --- /dev/null +++ b/gemmi_gph/gemmi/third_party/tinydir.h @@ -0,0 +1,838 @@ +/* +Copyright (c) 2013-2021, tinydir authors: +- Cong Xu +- Lautis Sun +- Baudouin Feildel +- Andargor +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef TINYDIR_H +#define TINYDIR_H + +#ifdef __cplusplus +extern "C" { +#endif + +#if ((defined _UNICODE) && !(defined UNICODE)) +#define UNICODE +#endif + +#if ((defined UNICODE) && !(defined _UNICODE)) +#define _UNICODE +#endif + +#include +#include +#include +#ifdef _MSC_VER +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include +# include +# pragma warning(push) +# pragma warning (disable : 4996) +#else +# include +# include +# include +# include +#endif +#ifdef __MINGW32__ +# include +#endif + + +/* types */ + +/* Windows UNICODE wide character support */ +#if defined _MSC_VER || defined __MINGW32__ +# define _tinydir_char_t TCHAR +# define TINYDIR_STRING(s) _TEXT(s) +# define _tinydir_strlen _tcslen +# define _tinydir_strcpy _tcscpy +# define _tinydir_strcat _tcscat +# define _tinydir_strcmp _tcscmp +# define _tinydir_strrchr _tcsrchr +# define _tinydir_strncmp _tcsncmp +#else +# define _tinydir_char_t char +# define TINYDIR_STRING(s) s +# define _tinydir_strlen strlen +# define _tinydir_strcpy strcpy +# define _tinydir_strcat strcat +# define _tinydir_strcmp strcmp +# define _tinydir_strrchr strrchr +# define _tinydir_strncmp strncmp +#endif + +#if (defined _MSC_VER || defined __MINGW32__) +# include +# define _TINYDIR_PATH_MAX MAX_PATH +#elif defined __linux__ +# include +# ifdef PATH_MAX +# define _TINYDIR_PATH_MAX PATH_MAX +# endif +#elif defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) +# include +# if defined(BSD) +# include +# ifdef PATH_MAX +# define _TINYDIR_PATH_MAX PATH_MAX +# endif +# endif +#endif + +#ifndef _TINYDIR_PATH_MAX +#define _TINYDIR_PATH_MAX 4096 +#endif + +#ifdef _MSC_VER +/* extra chars for the "\\*" mask */ +# define _TINYDIR_PATH_EXTRA 2 +#else +# define _TINYDIR_PATH_EXTRA 0 +#endif + +#define _TINYDIR_FILENAME_MAX 256 + +#if (defined _MSC_VER || defined __MINGW32__) +#define _TINYDIR_DRIVE_MAX 3 +#endif + +#ifdef _MSC_VER +# define _TINYDIR_FUNC static __inline +#elif !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L +# define _TINYDIR_FUNC static __inline__ +#elif defined(__cplusplus) +# define _TINYDIR_FUNC static inline +#elif defined(__GNUC__) +/* Suppress unused function warning */ +# define _TINYDIR_FUNC __attribute__((unused)) static +#else +# define _TINYDIR_FUNC static +#endif + +/* readdir_r usage; define TINYDIR_USE_READDIR_R to use it (if supported) */ +#ifdef TINYDIR_USE_READDIR_R + +/* readdir_r is a POSIX-only function, and may not be available under various + * environments/settings, e.g. MinGW. Use readdir fallback */ +#if _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _BSD_SOURCE || _SVID_SOURCE ||\ + _POSIX_SOURCE +# define _TINYDIR_HAS_READDIR_R +#endif +#if _POSIX_C_SOURCE >= 200112L +# define _TINYDIR_HAS_FPATHCONF +# include +#endif +#if _BSD_SOURCE || _SVID_SOURCE || \ + (_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700) +# define _TINYDIR_HAS_DIRFD +# include +#endif +#if defined _TINYDIR_HAS_FPATHCONF && defined _TINYDIR_HAS_DIRFD &&\ + defined _PC_NAME_MAX +# define _TINYDIR_USE_FPATHCONF +#endif +#if defined __MINGW32__ || !defined _TINYDIR_HAS_READDIR_R ||\ + !(defined _TINYDIR_USE_FPATHCONF || defined NAME_MAX) +# define _TINYDIR_USE_READDIR +#endif + +/* Use readdir by default */ +#else +# define _TINYDIR_USE_READDIR +#endif + +/* MINGW32 has two versions of dirent, ASCII and UNICODE*/ +#ifndef _MSC_VER +#if (defined __MINGW32__) && (defined _UNICODE) +#define _TINYDIR_DIR _WDIR +#define _tinydir_dirent _wdirent +#define _tinydir_opendir _wopendir +#define _tinydir_readdir _wreaddir +#define _tinydir_closedir _wclosedir +#else +#define _TINYDIR_DIR DIR +#define _tinydir_dirent dirent +#define _tinydir_opendir opendir +#define _tinydir_readdir readdir +#define _tinydir_closedir closedir +#endif +#endif + +/* Allow user to use a custom allocator by defining _TINYDIR_MALLOC and _TINYDIR_FREE. */ +#if defined(_TINYDIR_MALLOC) && defined(_TINYDIR_FREE) +#elif !defined(_TINYDIR_MALLOC) && !defined(_TINYDIR_FREE) +#else +#error "Either define both alloc and free or none of them!" +#endif + +#if !defined(_TINYDIR_MALLOC) + #define _TINYDIR_MALLOC(_size) malloc(_size) + #define _TINYDIR_FREE(_ptr) free(_ptr) +#endif /* !defined(_TINYDIR_MALLOC) */ + +typedef struct tinydir_file +{ + _tinydir_char_t path[_TINYDIR_PATH_MAX]; + _tinydir_char_t name[_TINYDIR_FILENAME_MAX]; + _tinydir_char_t *extension; + int is_dir; + int is_reg; + +#ifndef _MSC_VER +#ifdef __MINGW32__ + struct _stat _s; +#else + struct stat _s; +#endif +#endif +} tinydir_file; + +typedef struct tinydir_dir +{ + _tinydir_char_t path[_TINYDIR_PATH_MAX]; + int has_next; + size_t n_files; + + tinydir_file *_files; +#ifdef _MSC_VER + HANDLE _h; + WIN32_FIND_DATA _f; +#else + _TINYDIR_DIR *_d; + struct _tinydir_dirent *_e; +#ifndef _TINYDIR_USE_READDIR + struct _tinydir_dirent *_ep; +#endif +#endif +} tinydir_dir; + + +/* declarations */ + +_TINYDIR_FUNC +int tinydir_open(tinydir_dir *dir, const _tinydir_char_t *path); +_TINYDIR_FUNC +int tinydir_open_sorted(tinydir_dir *dir, const _tinydir_char_t *path); +_TINYDIR_FUNC +void tinydir_close(tinydir_dir *dir); + +_TINYDIR_FUNC +int tinydir_next(tinydir_dir *dir); +_TINYDIR_FUNC +int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file); +_TINYDIR_FUNC +int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i); +_TINYDIR_FUNC +int tinydir_open_subdir_n(tinydir_dir *dir, size_t i); + +_TINYDIR_FUNC +int tinydir_file_open(tinydir_file *file, const _tinydir_char_t *path); +_TINYDIR_FUNC +void _tinydir_get_ext(tinydir_file *file); +_TINYDIR_FUNC +int _tinydir_file_cmp(const void *a, const void *b); +#ifndef _MSC_VER +#ifndef _TINYDIR_USE_READDIR +_TINYDIR_FUNC +size_t _tinydir_dirent_buf_size(_TINYDIR_DIR *dirp); +#endif +#endif + + +/* definitions*/ + +_TINYDIR_FUNC +int tinydir_open(tinydir_dir *dir, const _tinydir_char_t *path) +{ +#ifndef _MSC_VER +#ifndef _TINYDIR_USE_READDIR + int error; + int size; /* using int size */ +#endif +#else + _tinydir_char_t path_buf[_TINYDIR_PATH_MAX]; +#endif + _tinydir_char_t *pathp; + + if (dir == NULL || path == NULL || _tinydir_strlen(path) == 0) + { + errno = EINVAL; + return -1; + } + if (_tinydir_strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX) + { + errno = ENAMETOOLONG; + return -1; + } + + /* initialise dir */ + dir->_files = NULL; +#ifdef _MSC_VER + dir->_h = INVALID_HANDLE_VALUE; +#else + dir->_d = NULL; +#ifndef _TINYDIR_USE_READDIR + dir->_ep = NULL; +#endif +#endif + tinydir_close(dir); + + _tinydir_strcpy(dir->path, path); + /* Remove trailing slashes */ + pathp = &dir->path[_tinydir_strlen(dir->path) - 1]; + while (pathp != dir->path && (*pathp == TINYDIR_STRING('\\') || *pathp == TINYDIR_STRING('/'))) + { + *pathp = TINYDIR_STRING('\0'); + pathp++; + } +#ifdef _MSC_VER + _tinydir_strcpy(path_buf, dir->path); + _tinydir_strcat(path_buf, TINYDIR_STRING("\\*")); +#if (defined WINAPI_FAMILY) && (WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) + dir->_h = FindFirstFileEx(path_buf, FindExInfoStandard, &dir->_f, FindExSearchNameMatch, NULL, 0); +#else + dir->_h = FindFirstFile(path_buf, &dir->_f); +#endif + if (dir->_h == INVALID_HANDLE_VALUE) + { + errno = ENOENT; +#else + dir->_d = _tinydir_opendir(path); + if (dir->_d == NULL) + { +#endif + goto bail; + } + + /* read first file */ + dir->has_next = 1; +#ifndef _MSC_VER +#ifdef _TINYDIR_USE_READDIR + dir->_e = _tinydir_readdir(dir->_d); +#else + /* allocate dirent buffer for readdir_r */ + size = _tinydir_dirent_buf_size(dir->_d); /* conversion to int */ + if (size == -1) return -1; + dir->_ep = (struct _tinydir_dirent*)_TINYDIR_MALLOC(size); + if (dir->_ep == NULL) return -1; + + error = readdir_r(dir->_d, dir->_ep, &dir->_e); + if (error != 0) return -1; +#endif + if (dir->_e == NULL) + { + dir->has_next = 0; + } +#endif + + return 0; + +bail: + tinydir_close(dir); + return -1; +} + +_TINYDIR_FUNC +int tinydir_open_sorted(tinydir_dir *dir, const _tinydir_char_t *path) +{ + /* Count the number of files first, to pre-allocate the files array */ + size_t n_files = 0; + if (tinydir_open(dir, path) == -1) + { + return -1; + } + while (dir->has_next) + { + n_files++; + if (tinydir_next(dir) == -1) + { + goto bail; + } + } + tinydir_close(dir); + + if (n_files == 0 || tinydir_open(dir, path) == -1) + { + return -1; + } + + dir->n_files = 0; + dir->_files = (tinydir_file *)_TINYDIR_MALLOC(sizeof *dir->_files * n_files); + if (dir->_files == NULL) + { + goto bail; + } + while (dir->has_next) + { + tinydir_file *p_file; + dir->n_files++; + + p_file = &dir->_files[dir->n_files - 1]; + if (tinydir_readfile(dir, p_file) == -1) + { + goto bail; + } + + if (tinydir_next(dir) == -1) + { + goto bail; + } + + /* Just in case the number of files has changed between the first and + second reads, terminate without writing into unallocated memory */ + if (dir->n_files == n_files) + { + break; + } + } + + qsort(dir->_files, dir->n_files, sizeof(tinydir_file), _tinydir_file_cmp); + + return 0; + +bail: + tinydir_close(dir); + return -1; +} + +_TINYDIR_FUNC +void tinydir_close(tinydir_dir *dir) +{ + if (dir == NULL) + { + return; + } + + memset(dir->path, 0, sizeof(dir->path)); + dir->has_next = 0; + dir->n_files = 0; + _TINYDIR_FREE(dir->_files); + dir->_files = NULL; +#ifdef _MSC_VER + if (dir->_h != INVALID_HANDLE_VALUE) + { + FindClose(dir->_h); + } + dir->_h = INVALID_HANDLE_VALUE; +#else + if (dir->_d) + { + _tinydir_closedir(dir->_d); + } + dir->_d = NULL; + dir->_e = NULL; +#ifndef _TINYDIR_USE_READDIR + _TINYDIR_FREE(dir->_ep); + dir->_ep = NULL; +#endif +#endif +} + +_TINYDIR_FUNC +int tinydir_next(tinydir_dir *dir) +{ + if (dir == NULL) + { + errno = EINVAL; + return -1; + } + if (!dir->has_next) + { + errno = ENOENT; + return -1; + } + +#ifdef _MSC_VER + if (FindNextFile(dir->_h, &dir->_f) == 0) +#else +#ifdef _TINYDIR_USE_READDIR + dir->_e = _tinydir_readdir(dir->_d); +#else + if (dir->_ep == NULL) + { + return -1; + } + if (readdir_r(dir->_d, dir->_ep, &dir->_e) != 0) + { + return -1; + } +#endif + if (dir->_e == NULL) +#endif + { + dir->has_next = 0; +#ifdef _MSC_VER + if (GetLastError() != ERROR_SUCCESS && + GetLastError() != ERROR_NO_MORE_FILES) + { + tinydir_close(dir); + errno = EIO; + return -1; + } +#endif + } + + return 0; +} + +_TINYDIR_FUNC +int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file) +{ + const _tinydir_char_t *filename; + if (dir == NULL || file == NULL) + { + errno = EINVAL; + return -1; + } +#ifdef _MSC_VER + if (dir->_h == INVALID_HANDLE_VALUE) +#else + if (dir->_e == NULL) +#endif + { + errno = ENOENT; + return -1; + } + filename = +#ifdef _MSC_VER + dir->_f.cFileName; +#else + dir->_e->d_name; +#endif + if (_tinydir_strlen(dir->path) + + _tinydir_strlen(filename) + 1 + _TINYDIR_PATH_EXTRA >= + _TINYDIR_PATH_MAX) + { + /* the path for the file will be too long */ + errno = ENAMETOOLONG; + return -1; + } + if (_tinydir_strlen(filename) >= _TINYDIR_FILENAME_MAX) + { + errno = ENAMETOOLONG; + return -1; + } + + _tinydir_strcpy(file->path, dir->path); + if (_tinydir_strcmp(dir->path, TINYDIR_STRING("/")) != 0) + _tinydir_strcat(file->path, TINYDIR_STRING("/")); + _tinydir_strcpy(file->name, filename); + _tinydir_strcat(file->path, filename); +#ifndef _MSC_VER +#ifdef __MINGW32__ + if (_tstat( +#elif (defined _BSD_SOURCE) || (defined _DEFAULT_SOURCE) \ + || ((defined _XOPEN_SOURCE) && (_XOPEN_SOURCE >= 500)) \ + || ((defined _POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 200112L)) \ + || ((defined __APPLE__) && (defined __MACH__)) \ + || (defined BSD) + if (lstat( +#else + if (stat( +#endif + file->path, &file->_s) == -1) + { + return -1; + } +#endif + _tinydir_get_ext(file); + + file->is_dir = +#ifdef _MSC_VER + !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); +#else + S_ISDIR(file->_s.st_mode); +#endif + file->is_reg = +#ifdef _MSC_VER + !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NORMAL) || + ( + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DEVICE) && + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) && +#ifdef FILE_ATTRIBUTE_INTEGRITY_STREAM + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_INTEGRITY_STREAM) && +#endif +#ifdef FILE_ATTRIBUTE_NO_SCRUB_DATA + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NO_SCRUB_DATA) && +#endif + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE) && + !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY)); +#else + S_ISREG(file->_s.st_mode); +#endif + + return 0; +} + +_TINYDIR_FUNC +int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i) +{ + if (dir == NULL || file == NULL) + { + errno = EINVAL; + return -1; + } + if (i >= dir->n_files) + { + errno = ENOENT; + return -1; + } + + memcpy(file, &dir->_files[i], sizeof(tinydir_file)); + _tinydir_get_ext(file); + + return 0; +} + +_TINYDIR_FUNC +int tinydir_open_subdir_n(tinydir_dir *dir, size_t i) +{ + _tinydir_char_t path[_TINYDIR_PATH_MAX]; + if (dir == NULL) + { + errno = EINVAL; + return -1; + } + if (i >= dir->n_files || !dir->_files[i].is_dir) + { + errno = ENOENT; + return -1; + } + + _tinydir_strcpy(path, dir->_files[i].path); + tinydir_close(dir); + if (tinydir_open_sorted(dir, path) == -1) + { + return -1; + } + + return 0; +} + +/* Open a single file given its path */ +_TINYDIR_FUNC +int tinydir_file_open(tinydir_file *file, const _tinydir_char_t *path) +{ + tinydir_dir dir; + int result = 0; + int found = 0; + _tinydir_char_t dir_name_buf[_TINYDIR_PATH_MAX]; + _tinydir_char_t file_name_buf[_TINYDIR_PATH_MAX]; + _tinydir_char_t *dir_name; + _tinydir_char_t *base_name; +#if (defined _MSC_VER || defined __MINGW32__) + _tinydir_char_t drive_buf[_TINYDIR_PATH_MAX]; + _tinydir_char_t ext_buf[_TINYDIR_FILENAME_MAX]; +#endif + + if (file == NULL || path == NULL || _tinydir_strlen(path) == 0) + { + errno = EINVAL; + return -1; + } + if (_tinydir_strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX) + { + errno = ENAMETOOLONG; + return -1; + } + + /* Get the parent path */ +#if (defined _MSC_VER || defined __MINGW32__) +#if ((defined _MSC_VER) && (_MSC_VER >= 1400)) + errno = _tsplitpath_s( + path, + drive_buf, _TINYDIR_DRIVE_MAX, + dir_name_buf, _TINYDIR_FILENAME_MAX, + file_name_buf, _TINYDIR_FILENAME_MAX, + ext_buf, _TINYDIR_FILENAME_MAX); +#else + _tsplitpath( + path, + drive_buf, + dir_name_buf, + file_name_buf, + ext_buf); +#endif + + if (errno) + { + return -1; + } + +/* _splitpath_s not work fine with only filename and widechar support */ +#ifdef _UNICODE + if (drive_buf[0] == L'\xFEFE') + drive_buf[0] = '\0'; + if (dir_name_buf[0] == L'\xFEFE') + dir_name_buf[0] = '\0'; +#endif + + /* Emulate the behavior of dirname by returning "." for dir name if it's + empty */ + if (drive_buf[0] == '\0' && dir_name_buf[0] == '\0') + { + _tinydir_strcpy(dir_name_buf, TINYDIR_STRING(".")); + } + /* Concatenate the drive letter and dir name to form full dir name */ + _tinydir_strcat(drive_buf, dir_name_buf); + dir_name = drive_buf; + /* Concatenate the file name and extension to form base name */ + _tinydir_strcat(file_name_buf, ext_buf); + base_name = file_name_buf; +#else + _tinydir_strcpy(dir_name_buf, path); + dir_name = dirname(dir_name_buf); + _tinydir_strcpy(file_name_buf, path); + base_name = basename(file_name_buf); +#endif + + /* Special case: if the path is a root dir, open the parent dir as the file */ +#if (defined _MSC_VER || defined __MINGW32__) + if (_tinydir_strlen(base_name) == 0) +#else + if ((_tinydir_strcmp(base_name, TINYDIR_STRING("/"))) == 0) +#endif + { + memset(file, 0, sizeof * file); + file->is_dir = 1; + file->is_reg = 0; + _tinydir_strcpy(file->path, dir_name); + file->extension = file->path + _tinydir_strlen(file->path); + return 0; + } + + /* Open the parent directory */ + if (tinydir_open(&dir, dir_name) == -1) + { + return -1; + } + + /* Read through the parent directory and look for the file */ + while (dir.has_next) + { + if (tinydir_readfile(&dir, file) == -1) + { + result = -1; + goto bail; + } + if (_tinydir_strcmp(file->name, base_name) == 0) + { + /* File found */ + found = 1; + break; + } + tinydir_next(&dir); + } + if (!found) + { + result = -1; + errno = ENOENT; + } + +bail: + tinydir_close(&dir); + return result; +} + +_TINYDIR_FUNC +void _tinydir_get_ext(tinydir_file *file) +{ + _tinydir_char_t *period = _tinydir_strrchr(file->name, TINYDIR_STRING('.')); + if (period == NULL) + { + file->extension = &(file->name[_tinydir_strlen(file->name)]); + } + else + { + file->extension = period + 1; + } +} + +_TINYDIR_FUNC +int _tinydir_file_cmp(const void *a, const void *b) +{ + const tinydir_file *fa = (const tinydir_file *)a; + const tinydir_file *fb = (const tinydir_file *)b; + if (fa->is_dir != fb->is_dir) + { + return -(fa->is_dir - fb->is_dir); + } + return _tinydir_strncmp(fa->name, fb->name, _TINYDIR_FILENAME_MAX); +} + +#ifndef _MSC_VER +#ifndef _TINYDIR_USE_READDIR +/* +The following authored by Ben Hutchings +from https://womble.decadent.org.uk/readdir_r-advisory.html +*/ +/* Calculate the required buffer size (in bytes) for directory * +* entries read from the given directory handle. Return -1 if this * +* this cannot be done. * +* * +* This code does not trust values of NAME_MAX that are less than * +* 255, since some systems (including at least HP-UX) incorrectly * +* define it to be a smaller value. */ +_TINYDIR_FUNC +size_t _tinydir_dirent_buf_size(_TINYDIR_DIR *dirp) +{ + long name_max; + size_t name_end; + /* parameter may be unused */ + (void)dirp; + +#if defined _TINYDIR_USE_FPATHCONF + name_max = fpathconf(dirfd(dirp), _PC_NAME_MAX); + if (name_max == -1) +#if defined(NAME_MAX) + name_max = (NAME_MAX > 255) ? NAME_MAX : 255; +#else + return (size_t)(-1); +#endif +#elif defined(NAME_MAX) + name_max = (NAME_MAX > 255) ? NAME_MAX : 255; +#else +#error "buffer size for readdir_r cannot be determined" +#endif + name_end = (size_t)offsetof(struct _tinydir_dirent, d_name) + name_max + 1; + return (name_end > sizeof(struct _tinydir_dirent) ? + name_end : sizeof(struct _tinydir_dirent)); +} +#endif +#endif + +#ifdef __cplusplus +} +#endif + +# if defined (_MSC_VER) +# pragma warning(pop) +# endif + +#endif diff --git a/gemmi_gph/gemmi/to_chemcomp.hpp b/gemmi_gph/gemmi/to_chemcomp.hpp new file mode 100644 index 00000000..d49ece5d --- /dev/null +++ b/gemmi_gph/gemmi/to_chemcomp.hpp @@ -0,0 +1,108 @@ +// Copyright 2022 Global Phasing Ltd. +// +// Create cif::Block with monomer library _chem_comp* categories +// from struct ChemComp. + +#ifndef GEMMI_TO_CHEMCOMP_HPP_ +#define GEMMI_TO_CHEMCOMP_HPP_ + +#include "chemcomp.hpp" // for ChemComp +#include "sprintf.hpp" // for to_str + +namespace gemmi { + +inline void add_chemcomp_to_block(const ChemComp& cc, cif::Block& block, + const std::vector& acedrg_types = {}, + bool no_angles = false) { + { + std::vector tags = + {"comp_id", "atom_id", "type_symbol", "type_energy", "charge"}; + // Use external acedrg_types if provided, otherwise check if atoms have acedrg_type set + bool use_external_types = !acedrg_types.empty() && acedrg_types.size() == cc.atoms.size(); + bool has_stored_types = false; + if (!use_external_types) { + for (const ChemComp::Atom& a : cc.atoms) + if (!a.acedrg_type.empty()) { + has_stored_types = true; + break; + } + } + bool has_acedrg_type = use_external_types || has_stored_types; + if (has_acedrg_type) + tags.push_back("atom_type"); + if (cc.has_coordinates) + for (char c = 'x'; c <= 'z'; ++c) + tags.emplace_back(1, c); + cif::Table tab = block.find_or_add("_chem_comp_atom.", tags); + tab.ensure_loop(); + size_t pos = tab.length(); + cif::Loop& loop = tab.loop_item->loop; + loop.values.resize(loop.values.size() + loop.width() * cc.atoms.size(), "."); + size_t idx = 0; + for (const ChemComp::Atom& a : cc.atoms) { + cif::Table::Row row = tab[pos++]; + size_t col = 0; + row[col++] = cc.name; + row[col++] = a.id; + row[col++] = a.el.name(); + row[col++] = cif::quote(a.chem_type); + row[col++] = std::to_string(iround(a.charge)); + if (has_acedrg_type) + row[col++] = use_external_types ? acedrg_types[idx] : a.acedrg_type; + if (cc.has_coordinates) { + row[col++] = to_str(a.xyz.x); + row[col++] = to_str(a.xyz.y); + row[col++] = to_str(a.xyz.z); + } + ++idx; + } + } + { + cif::Table tab = block.find_or_add("_chem_comp_bond.", + {"comp_id", "atom_id_1", "atom_id_2", "type", "aromatic", + "value_dist", "value_dist_esd", "value_dist_nucleus", "value_dist_nucleus_esd"}); + for (const Restraints::Bond& a : cc.rt.bonds) + tab.append_row({cc.name, a.id1.atom, a.id2.atom, bond_type_to_string(a.type), + std::string(1, a.aromatic ? 'y' : 'n'), + to_str(a.value), to_str(a.esd), + to_str(a.value_nucleus), to_str(a.esd_nucleus)}); + } + if (!no_angles) { + cif::Table tab = block.find_or_add("_chem_comp_angle.", + {"comp_id", "atom_id_1", "atom_id_2", "atom_id_3", + "value_angle", "value_angle_esd"}); + for (const Restraints::Angle& a : cc.rt.angles) + tab.append_row({cc.name, a.id1.atom, a.id2.atom, a.id3.atom, + to_str(a.value), to_str(a.esd)}); + } + { + { + cif::Table tab = block.find_or_add("_chem_comp_tor.", + {"comp_id", "id", "atom_id_1", "atom_id_2", "atom_id_3", "atom_id_4", + "value_angle", "value_angle_esd", "period"}); + for (const Restraints::Torsion& a : cc.rt.torsions) + tab.append_row({cc.name, a.label, a.id1.atom, a.id2.atom, a.id3.atom, a.id4.atom, + to_str(a.value), to_str(a.esd), std::to_string(a.period)}); + } + { + cif::Table tab = block.find_or_add("_chem_comp_chir.", + {"comp_id", "id", "atom_id_centre", "atom_id_1", "atom_id_2", "atom_id_3", + "volume_sign"}); + for (const Restraints::Chirality& a : cc.rt.chirs) { + std::string label = "chir_" + std::to_string(tab.length() + 1); + tab.append_row({cc.name, label, a.id_ctr.atom, a.id1.atom, a.id2.atom, a.id3.atom, + chirality_to_string(a.sign)}); + } + } + { + cif::Table tab = block.find_or_add("_chem_comp_plane_atom.", + {"comp_id", "plane_id", "atom_id", "dist_esd"}); + for (const Restraints::Plane& p : cc.rt.planes) + for (const Restraints::AtomId& atom_id : p.ids) + tab.append_row({cc.name, p.label, atom_id.atom, to_str(p.esd)}); + } + } +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/to_cif.hpp b/gemmi_gph/gemmi/to_cif.hpp new file mode 100644 index 00000000..09ef32ad --- /dev/null +++ b/gemmi_gph/gemmi/to_cif.hpp @@ -0,0 +1,279 @@ +// Copyright 2017 Global Phasing Ltd. + +// Writing cif::Document or its parts to std::ostream. + +#ifndef GEMMI_TO_CIF_HPP_ +#define GEMMI_TO_CIF_HPP_ + +#include +#include "cifdoc.hpp" + +namespace gemmi { +namespace cif { + +/// deprecated, use cif::WriteOptions instead +enum class Style { + Simple, + NoBlankLines, + PreferPairs, // write single-row loops as pairs + Pdbx, // PreferPairs + put '#' (empty comments) between categories + Indent35, // start values in pairs from 35th column + Aligned, // columns in tables are left-aligned +}; + +struct WriteOptions { + /// write single-row loops as pairs + bool prefer_pairs = false; + /// no blank lines between categories, only between blocks + bool compact = false; + /// put '#' (empty comments) before/after categories + bool misuse_hash = false; + /// width reserved for tags in pairs (e.g. 34 = value starts at 35th column) + std::uint16_t align_pairs = 0; + /// if non-zero, determines max width of each column in a loop and aligns + /// all values to this width; the width is capped with the given value + std::uint16_t align_loops = 0; + + WriteOptions() {} + // implicit conversion from deprecated Style (for backward compatibility) + WriteOptions(Style style) { + switch (style) { + case Style::Simple: + break; + case Style::NoBlankLines: + compact = true; + break; + case Style::PreferPairs: + prefer_pairs = true; + break; + case Style::Pdbx: + prefer_pairs = true; + misuse_hash = true; + break; + case Style::Indent35: + align_pairs = 33; + break; + case Style::Aligned: + align_pairs = 33; + align_loops = 30; + break; + } + } + std::string str() const { + std::string s; + if (prefer_pairs) + s += "prefer_pairs,"; + if (compact) + s += "compact,"; + if (misuse_hash) + s += "misuse_hash,"; + if (align_pairs != 0) + s += "align_pairs=" + std::to_string(align_pairs) + ","; + if (align_loops != 0) + s += "align_loops=" + std::to_string(align_loops) + ","; + if (!s.empty()) + s.pop_back(); + return s; + } +}; + +/// std::ostream with buffering. C++ streams are so slow that even primitive +/// buffering makes it significantly more efficient. +class BufOstream { +public: + explicit BufOstream(std::ostream& os_) : os(os_), ptr(buf) {} + ~BufOstream() { flush(); } + void flush() { + os.write(buf, ptr - buf); + ptr = buf; + } + void write(const char* s, size_t len) { + constexpr int margin = sizeof(buf) - 512; + if (ptr - buf + len > margin) { + flush(); + if (len > margin) { + os.write(s, len); + return; + } + } + std::memcpy(ptr, s, len); + ptr += len; + } + void operator<<(const std::string& s) { + write(s.c_str(), s.size()); + } + // below we don't check the buffer boundary, these functions add <512 bytes + void put(char c) { + *ptr++ = c; + } + void pad(size_t n) { + std::memset(ptr, ' ', n); + ptr += n; + } +private: + std::ostream& os; + // increasing buffer to 8kb or 64kb doesn't make significant difference + char buf[4096]; + char* ptr; +}; + +// CIF files are read in binary mode. It makes difference only for text fields. +// If the text field with \r\n would be written as is in text mode on Windows +// \r would get duplicated. As a workaround, here we convert \r\n to \n. +// Hopefully \r that gets removed here is never meaningful. +inline void write_text_field(BufOstream& os, const std::string& value) { + for (size_t pos = 0, end = 0; end != std::string::npos; pos = end + 1) { + end = value.find("\r\n", pos); + size_t len = (end == std::string::npos ? value.size() : end) - pos; + os.write(value.c_str() + pos, len); + } +} + +inline void write_out_pair(BufOstream& os, const std::string& name, + const std::string& value, WriteOptions options) { + os << name; + if (is_text_field(value)) { + os.put('\n'); + write_text_field(os, value); + } else { + if (name.size() + value.size() > 120) { + os.put('\n'); + } else { + os.put(' '); + if (name.size() < options.align_pairs) + os.pad(options.align_pairs - name.size()); + } + os << value; + } + os.put('\n'); +} + +inline void write_out_loop(BufOstream& os, const Loop& loop, WriteOptions options) { + if (loop.values.empty()) + return; + if (options.prefer_pairs && loop.length() == 1) { + for (size_t i = 0; i != loop.tags.size(); ++i) + write_out_pair(os, loop.tags[i], loop.values[i], options); + return; + } + // tags + os.write("loop_", 5); + for (const std::string& tag : loop.tags) { + os.put('\n'); + os << tag; + } + // values + size_t ncol = loop.tags.size(); + + std::vector col_width(ncol, 0); + if (options.align_loops > 0) { + size_t col = 0; + for (const std::string& val : loop.values) { + if (!is_text_field(val)) + col_width[col] = std::max(col_width[col], val.size()); + if (++col == ncol) + col = 0; + } + for (size_t& w : col_width) + w = std::min(w, (size_t)options.align_loops); + } + + size_t col = 0; + bool need_new_line = true; + for (const std::string& val : loop.values) { + bool text_field = is_text_field(val); + os.put(need_new_line || text_field ? '\n' : ' '); + need_new_line = text_field; + if (text_field) + write_text_field(os, val); + else + os << val; + if (col != ncol - 1) { + if (val.size() < col_width[col]) + os.pad(col_width[col] - val.size()); + ++col; + } else { + col = 0; + need_new_line = true; + } + } + os.put('\n'); +} + +inline void write_out_item(BufOstream& os, const Item& item, WriteOptions options) { + switch (item.type) { + case ItemType::Pair: + write_out_pair(os, item.pair[0], item.pair[1], options); + break; + case ItemType::Loop: + write_out_loop(os, item.loop, options); + break; + case ItemType::Frame: + os.write("save_", 5); + os << item.frame.name; + os.put('\n'); + for (const Item& inner_item : item.frame.items) + write_out_item(os, inner_item, options); + os.write("save_\n", 6); + break; + case ItemType::Comment: + os << item.pair[1]; + os.put('\n'); + break; + case ItemType::Erased: + break; + } +} + +inline bool should_be_separated_(const Item& a, const Item& b) { + if (a.type == ItemType::Comment || b.type == ItemType::Comment) + return false; + if (a.type != ItemType::Pair || b.type != ItemType::Pair) + return true; + // check if we have mmcif-like tags from different categories + auto adot = a.pair[0].find('.'); + if (adot == std::string::npos) + return false; + auto bdot = b.pair[0].find('.'); + return adot != bdot || a.pair[0].compare(0, adot, b.pair[0], 0, adot) != 0; +} + +inline void write_cif_block_to_stream(std::ostream& os_, const Block& block, + WriteOptions options=WriteOptions()) { + BufOstream os(os_); + os.write("data_", 5); + os << block.name; + os.put('\n'); + if (options.misuse_hash) + os.write("#\n", 2); + const Item* prev = nullptr; + for (const Item& item : block.items) { + if (item.type == ItemType::Erased) + continue; + if (prev && !options.compact && should_be_separated_(*prev, item)) { + if (options.misuse_hash) + os.put('#'); + os.put('\n'); + } + write_out_item(os, item, options); + prev = &item; + } + if (options.misuse_hash) + os.write("#\n", 2); +} + +inline void write_cif_to_stream(std::ostream& os, const Document& doc, + WriteOptions options=WriteOptions()) { + bool first = true; + for (const Block& block : doc.blocks) { + if (!first) + os.put('\n'); // extra blank line for readability + write_cif_block_to_stream(os, block, options); + first = false; + } +} + +} // namespace cif +} // namespace gemmi + +#endif diff --git a/gemmi_gph/gemmi/to_json.hpp b/gemmi_gph/gemmi/to_json.hpp new file mode 100644 index 00000000..3915cdd0 --- /dev/null +++ b/gemmi_gph/gemmi/to_json.hpp @@ -0,0 +1,53 @@ +// Copyright 2017 Global Phasing Ltd. + +// Writing cif::Document or its parts as JSON (mmJSON, CIF-JSON, etc). + +#ifndef GEMMI_TO_JSON_HPP_ +#define GEMMI_TO_JSON_HPP_ +#include // for ostream +#include "cifdoc.hpp" + +namespace gemmi { +namespace cif { + +struct JsonWriteOptions { + bool as_comcifs = false; // conform to the COMCIFS CIF-JSON draft + bool group_ddl2_categories = false; // for mmJSON + bool with_data_keyword = false; // for mmJSON + bool bare_tags = false; // "tag" instead of "_tag" + bool values_as_arrays = false; // "_tag": ["value"] + bool lowercase_names = true; // write case-insensitive names as lower case + int quote_numbers = 1; // 0=never (no s.u.), 1=mix, 2=always + std::string cif_dot = "null"; // how to convert '.' from CIF + + static JsonWriteOptions comcifs() { + JsonWriteOptions opt; + opt.as_comcifs = true; + opt.values_as_arrays = true; + opt.quote_numbers = 2; + opt.cif_dot = "false"; + return opt; + } + + static JsonWriteOptions mmjson() { + JsonWriteOptions opt; + opt.group_ddl2_categories = true; + opt.with_data_keyword = true; + opt.bare_tags = true; + opt.values_as_arrays = true; + opt.lowercase_names = false; + opt.quote_numbers = 0; + return opt; + } +}; + +GEMMI_DLL void write_json_to_stream(std::ostream& os, const Document& doc, + const JsonWriteOptions& options); + +inline void write_mmjson_to_stream(std::ostream& os, const Document& doc) { + write_json_to_stream(os, doc, JsonWriteOptions::mmjson()); +} + +} // namespace cif +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/to_mmcif.hpp b/gemmi_gph/gemmi/to_mmcif.hpp new file mode 100644 index 00000000..9beecfef --- /dev/null +++ b/gemmi_gph/gemmi/to_mmcif.hpp @@ -0,0 +1,75 @@ +// Copyright 2017 Global Phasing Ltd. +// +// Create cif::Document (for PDBx/mmCIF file) from Structure. + +#ifndef GEMMI_TO_MMCIF_HPP_ +#define GEMMI_TO_MMCIF_HPP_ + +#include "model.hpp" +#include "cifdoc.hpp" + +namespace gemmi { + +struct MmcifOutputGroups { + bool atoms:1; + bool block_name:1; + bool entry:1; + bool database_status:1; + bool author:1; + bool cell:1; + bool symmetry:1; + bool entity:1; + bool entity_poly:1; + bool struct_ref:1; + bool chem_comp:1; + bool exptl:1; + bool diffrn:1; + bool reflns:1; + bool refine:1; + bool title_keywords:1; + bool ncs:1; + bool struct_asym:1; + bool origx:1; + bool struct_conf:1; + bool struct_sheet:1; + bool struct_biol:1; + bool assembly:1; + bool conn:1; + bool cis:1; + bool modres:1; + bool scale:1; + bool atom_type:1; + bool entity_poly_seq:1; + bool tls:1; + bool software:1; + bool group_pdb:1; // include _atom_site.group_PDB + bool auth_all:1; // include _atom_site.auth_atom_id and auth_comp_id + + explicit MmcifOutputGroups(bool all) + : atoms(all), block_name(all), entry(all), database_status(all), + author(all), cell(all), symmetry(all), entity(all), entity_poly(all), + struct_ref(all), chem_comp(all), exptl(all), diffrn(all), + reflns(all), refine(all), title_keywords(all), ncs(all), + struct_asym(all), origx(all), struct_conf(all), struct_sheet(all), + struct_biol(all), assembly(all), conn(all), cis(all), modres(all), + scale(all), atom_type(all), entity_poly_seq(all), tls(all), + software(all), group_pdb(all), auth_all(false) {} +}; + +GEMMI_DLL void update_mmcif_block(const Structure& st, cif::Block& block, + MmcifOutputGroups groups=MmcifOutputGroups(true)); +GEMMI_DLL cif::Document make_mmcif_document(const Structure& st, + MmcifOutputGroups groups=MmcifOutputGroups(true)); +GEMMI_DLL cif::Block make_mmcif_block(const Structure& st, + MmcifOutputGroups groups=MmcifOutputGroups(true)); +GEMMI_DLL cif::Block make_mmcif_headers(const Structure& st); +GEMMI_DLL void add_minimal_mmcif_data(const Structure& st, cif::Block& block); + +// temporarily we use it in crd.cpp +GEMMI_DLL void write_ncs_oper(const Structure& st, cif::Block& block); +GEMMI_DLL void write_struct_conn(const Structure& st, cif::Block& block); +GEMMI_DLL void write_cispeps(const Structure& st, cif::Block& block); + +} // namespace gemmi + +#endif diff --git a/gemmi_gph/gemmi/to_pdb.hpp b/gemmi_gph/gemmi/to_pdb.hpp new file mode 100644 index 00000000..f0ef0ce0 --- /dev/null +++ b/gemmi_gph/gemmi/to_pdb.hpp @@ -0,0 +1,67 @@ +// Copyright 2017 Global Phasing Ltd. +// +// Writing PDB file format (Structure -> pdb file). + +#ifndef GEMMI_TO_PDB_HPP_ +#define GEMMI_TO_PDB_HPP_ + +#include "model.hpp" +#include + +namespace gemmi { + +struct PdbWriteOptions { + bool minimal_file = false; // disable many records not listed below (HEADER, TITLE, ...) + bool atom_records = true; // write atomic models (set to false for headers only) + bool seqres_records = true; // write SEQRES + bool ssbond_records = true; // write SSBOND + bool link_records = true; // write LINK + bool cispep_records = true; // write CISPEP + bool cryst1_record = true; // write CRYST1 + bool ter_records = true; // write TER records + bool conect_records = false; // write CONECT - matters only if add_conect() was used + bool end_record = true; // write END + bool numbered_ter = true; // TER record gets own serial number + bool ter_ignores_type = false; // put TER after last atom in Chain (even if it's water) + bool use_linkr = false; // use non-standard Refmac LINKR record instead of LINK + bool use_link_id = false; // write Link::link_id instead of distanc in th LINK record + // (when link_id) is not empty. Implied by use_linkr. + bool preserve_serial = false; // use serial numbers from Atom.serial + // end of snippet for mol.rst + + static PdbWriteOptions minimal() { + PdbWriteOptions opt; + opt.minimal_file = true; + opt.seqres_records = false; + opt.ssbond_records = false; + opt.link_records = false; + opt.cispep_records = false; + opt.end_record = false; + return opt; + } + static PdbWriteOptions headers_only() { + PdbWriteOptions opt; + opt.atom_records = false; + opt.end_record = false; + return opt; + } +}; + +/// record type ATOM/HETATM to use for writing given residue +GEMMI_DLL bool use_hetatm(const Residue& res); + +GEMMI_DLL void write_pdb(const Structure& st, std::ostream& os, PdbWriteOptions opt={}); +GEMMI_DLL std::string make_pdb_string(const Structure& st, PdbWriteOptions opt={}); + +// deprecated +inline void write_minimal_pdb(const Structure& st, std::ostream& os) { + write_pdb(st, os, PdbWriteOptions::minimal()); +} +// deprecated +inline std::string make_pdb_headers(const Structure& st) { + return make_pdb_string(st, PdbWriteOptions::headers_only()); +} + +} // namespace gemmi + +#endif diff --git a/gemmi_gph/gemmi/topo.hpp b/gemmi_gph/gemmi/topo.hpp new file mode 100644 index 00000000..c9b0d837 --- /dev/null +++ b/gemmi_gph/gemmi/topo.hpp @@ -0,0 +1,307 @@ +// Copyright 2018 Global Phasing Ltd. +// +// Topo(logy) - restraints (from a monomer library) applied to a model. + +#ifndef GEMMI_TOPO_HPP_ +#define GEMMI_TOPO_HPP_ + +#include // for multimap +#include // for unique_ptr +#include // for unordered_map +#include "chemcomp.hpp" // for ChemComp +#include "monlib.hpp" // for MonLib +#include "model.hpp" // for Residue, Atom +#include "calculate.hpp" // for calculate_angle, calculate_dihedral +#include "logger.hpp" // for Logger + +namespace gemmi { + +enum class HydrogenChange { + NoChange, Shift, Remove, ReAdd, ReAddButWater, ReAddKnown +}; + +struct GEMMI_DLL Topo { + // We have internal pointers in this class (pointers setup in + // apply_restraints() that point to ResInfo::chemcomp.rt), + // disable copying this class. + Topo() = default; + Topo(Topo const&) = delete; + Topo& operator=(Topo const&) = delete; + + struct Bond { + const Restraints::Bond* restr; + std::array atoms; + Asu asu; + double calculate() const { + return asu != Asu::Different ? atoms[0]->pos.dist(atoms[1]->pos) : NAN; + } + double calculate_z_(double d) const { return std::abs(d - restr->value) / restr->esd; } + double calculate_z() const { return calculate_z_(calculate()); } + }; + struct Angle { + const Restraints::Angle* restr; + std::array atoms; + double calculate() const { + return calculate_angle(atoms[0]->pos, atoms[1]->pos, atoms[2]->pos); + } + double calculate_z() const { return angle_z(calculate(), *restr); } + }; + struct Torsion { + const Restraints::Torsion* restr; + std::array atoms; + double calculate() const { + return calculate_dihedral(atoms[0]->pos, atoms[1]->pos, + atoms[2]->pos, atoms[3]->pos); + } + double calculate_z() const { + return angle_z(calculate(), *restr, 360. / std::max(1, restr->period)); + } + }; + struct Chirality { + const Restraints::Chirality* restr; + std::array atoms; + double calculate() const { + return calculate_chiral_volume(atoms[0]->pos, atoms[1]->pos, + atoms[2]->pos, atoms[3]->pos); + } + double calculate_z(double ideal_abs_vol, double esd) const { + double calc = calculate(); + if (restr->sign == ChiralityType::Negative || + (restr->sign == ChiralityType::Both && calc < 0)) + ideal_abs_vol *= -1; + return std::abs(calc - ideal_abs_vol) / esd; + } + bool check() const { return !restr->is_wrong(calculate()); } + }; + struct Plane { + const Restraints::Plane* restr; + std::vector atoms; + bool has(const Atom* atom) const { + return in_vector(const_cast(atom), atoms); + } + }; + + enum class RKind { Bond, Angle, Torsion, Chirality, Plane }; + struct Rule { + RKind rkind; + size_t index; // index in the respective vector (bonds, ...) in Topo + }; + + struct Link { + std::string link_id; + Residue* res1 = nullptr; + Residue* res2 = nullptr; + std::vector link_rules; + char alt1 = '\0'; + char alt2 = '\0'; + Asu asu = Asu::Any; // used only in Links in ChainInfo::extras + bool is_cis = false; // helper field for CISPEP record generation + + // helper fields used in Topo::find_polymer_link() + int atom1_name_id = 0; + int atom2_name_id = 0; + + // aliasing1/2 points to vector element in ChemComp::aliases. + // The pointers should stay valid even if a ChemComp is moved. + const ChemComp::Aliasing* aliasing1 = nullptr; + const ChemComp::Aliasing* aliasing2 = nullptr; + + // only for polymer links, res1 and res2 must be in the same vector (Chain) + std::ptrdiff_t res_distance() const { return res1 - res2; } + }; + + struct Mod { + std::string id; // id of ChemMod from the dictionary (MonLib) + ChemComp::Group alias; // alias to be used when applying the modification + char altloc; // \0 = all conformers + + bool operator==(const Mod& o) const { + return id == o.id && alias == o.alias && altloc == o.altloc; + } + }; + + struct FinalChemComp { + char altloc; // Restraints apply to this conformer + const ChemComp* cc; + }; + + struct ResInfo { + Residue* res; + // in case of microheterogeneity we may have 2+ previous residues + std::vector prev; + std::vector mods; + // Pointer to ChemComp in MonLib::monomers. + const ChemComp* orig_chemcomp = nullptr; + // Pointer to restraints with modifications applied (if any). + std::vector chemcomps; + std::vector monomer_rules; + // lowest-energy hydrogen bonds for DSSP + std::array donors = {nullptr, nullptr}; + std::array acceptors = {nullptr, nullptr}; + std::array donor_energies = {0.0, 0.0}; + std::array acceptor_energies = {0.0, 0.0}; + + ResInfo(Residue* r) : res(r) {} + void add_mod(const std::string& m, const ChemComp::Aliasing* aliasing, char altloc) { + if (!m.empty()) { + auto alias_group = aliasing ? aliasing->group : ChemComp::Group::Null; + Mod mod{m, alias_group, altloc}; + if (!in_vector(mod, mods)) + mods.push_back(mod); + } + } + + const ChemComp& get_final_chemcomp(char altloc) const { + if (chemcomps.size() == 1) + return *chemcomps[0].cc; + assert(!chemcomps.empty()); + for (const FinalChemComp& it : chemcomps) + if (it.altloc == altloc) + return *it.cc; + return *chemcomps[0].cc; + } + }; + + // corresponds to a sub-chain + struct ChainInfo { + const Chain& chain_ref; + std::string subchain_name; + std::string entity_id; + bool polymer; + PolymerType polymer_type; + std::vector res_infos; + + ChainInfo(ResidueSpan& subchain, const Chain& chain, const Entity* ent); + using iterator = std::vector::iterator; + iterator group_end(iterator b) const { + auto e = b + 1; + while (e != res_infos.end() && e->res->group_key() == b->res->group_key()) + ++e; + return e; + } + }; + + template + static int has_atom(const Atom* a, const T& t) { + for (int i = 0; (size_t) i != t.atoms.size(); ++i) + if (t.atoms[i] == a) + return i; + return -1; + } + + Logger logger{}; + bool only_bonds = false; // an internal flag for apply_restraints() + std::vector chain_infos; + std::vector extras; + + // Restraints applied to Model + std::vector bonds; + std::vector angles; + std::vector torsions; + std::vector chirs; + std::vector planes; + + std::multimap bond_index; // indexes both atoms + std::multimap angle_index; // only middle atom + std::multimap torsion_index; // two middle atoms + std::multimap plane_index; // all atoms + + ResInfo* find_resinfo(const Residue* res) { + for (ChainInfo& ci : chain_infos) + for (ResInfo& ri : ci.res_infos) + if (ri.res == res) + return &ri; + return nullptr; + } + + Bond* first_bond_in_link(const Link& link) { + for (const Rule& rule : link.link_rules) + if (rule.rkind == RKind::Bond) + return &bonds[rule.index]; + return nullptr; + } + + const Restraints::Bond* take_bond(const Atom* a, const Atom* b) const { + auto range = bond_index.equal_range(a); + for (auto i = range.first; i != range.second; ++i) { + const Bond* bond = i->second; + if ((bond->atoms[0] == b && bond->atoms[1] == a) || + (bond->atoms[1] == b && bond->atoms[0] == a)) + return bond->restr; + } + return nullptr; + } + + const Restraints::Angle* take_angle(const Atom* a, + const Atom* b, + const Atom* c) const { + auto range = angle_index.equal_range(b); + for (auto i = range.first; i != range.second; ++i) { + const Angle* ang = i->second; + if ((ang->atoms[0] == a && ang->atoms[2] == c) || + (ang->atoms[0] == c && ang->atoms[2] == a)) + return ang->restr; + } + return nullptr; + } + + const Chirality* get_chirality(const Atom* ctr) const { + for (const Chirality& chir : chirs) + if (chir.atoms[0] == ctr) + return &chir; + return nullptr; + } + + double ideal_chiral_abs_volume(const Chirality &ch) const; + + std::vector apply_restraints(const Restraints& rt, + Residue& res, Residue* res2, Asu asu, + char altloc1, char altloc2, bool require_alt); + void apply_restraints_from_link(Link& link, const MonLib& monlib); + + // Structure is non-const b/c connections may have link_id assigned. + // Model is non-const b/c we store non-const pointers to residues in Topo. + // Because of the pointers, don't add or remove residues after this step. + // Monlib may get modified by addition of extra links from the model. + void initialize_refmac_topology(Structure& st, Model& model0, + MonLib& monlib, bool ignore_unknown_links=false); + + // This step stores pointers to gemmi::Atom's from model0, + // so after this step don't add or remove atoms. + // monlib is needed only for links. + void apply_all_restraints(const MonLib& monlib); + + // prepare bond_index, angle_index, torsion_index, plane_index + void create_indices(); + + // Searches for matching Link in ResInfo::prev lists. + Link* find_polymer_link(const AtomAddress& a1, const AtomAddress& a2); + + void set_cispeps_in_structure(Structure& st); + +private: + // storage for link restraints modified by aliases + std::vector> rt_storage; + // cache for ChemComps after applying modifications + std::unordered_map> cc_cache; + // storage for ad-hoc ChemComps (placeholders for those missing in MonLib) + std::vector> cc_storage; + + void setup_connection(Connection& conn, Model& model0, MonLib& monlib, + bool ignore_unknown_links); +}; + +GEMMI_DLL std::unique_ptr +prepare_topology(Structure& st, MonLib& monlib, size_t model_index, + HydrogenChange h_change, bool reorder, + const Logger& logger={}, bool ignore_unknown_links=false, + bool use_cispeps=false); + + +GEMMI_DLL std::unique_ptr make_chemcomp_with_restraints(const Residue& res); + +GEMMI_DLL std::vector find_missing_atoms(const Topo& topo, + bool including_hydrogen=false); + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/twin.hpp b/gemmi_gph/gemmi/twin.hpp new file mode 100644 index 00000000..27222779 --- /dev/null +++ b/gemmi_gph/gemmi/twin.hpp @@ -0,0 +1,226 @@ +// Copyright 2022 Global Phasing Ltd. +// +// Twinning laws. + +#ifndef GEMMI_TWIN_HPP_ +#define GEMMI_TWIN_HPP_ + +#include // for int8_t +#include // for sort +#include // for pair +#include "symmetry.hpp" // for Op +#include "unitcell.hpp" // for UnitCell +#include "cellred.hpp" // for GruberVector + +namespace gemmi { + +namespace impl { + +// Determination of the lattice symmetry is based on P.H. Zwart et al (2006) +// http://legacy.ccp4.ac.uk/newsletters/newsletter44/articles/explore_metric_symmetry.html +// which in turn is based on ideas from +// Le Page (1982) https://doi.org/10.1107/S0021889882011959 +// Lebedev et al. (2006) https://doi.org/10.1107/S0907444905036759 + +struct TwoFoldData { + std::int8_t matrix[9]; + std::int8_t ds_axis[3]; // 2-fold axis direction in direct space + std::int8_t rs_axis[3]; // 2-fold axis direction in reciprocal space +}; + +// The template wrapper here is only to substitute C++17 inline variables +// https://stackoverflow.com/questions/38043442/how-do-inline-variables-work +template struct TwoFold_ { + static const TwoFoldData table[81]; +}; + +// Two-fold twinning operators generated by cctbx/examples/reduced_cell_two_folds.py +// "Enumeration of the 81 2-fold symmetry operations possible for reduced cells." +template +const TwoFoldData TwoFold_::table[81] = { +{{-1, -1, -1, 0, 0, 1, 0, 1, 0}, {-1, 1, 1}, {0, 1, 1}}, +{{-1, -1, 0, 0, 1, 0, 0, -1, -1}, {1, -2, 1}, {0, 1, 0}}, +{{-1, -1, 0, 0, 1, 0, 0, 0, -1}, {-1, 2, 0}, {0, 1, 0}}, +{{-1, -1, 0, 0, 1, 0, 0, 1, -1}, {-1, 2, 1}, {0, 1, 0}}, +{{-1, -1, 1, 0, 0, -1, 0, -1, 0}, {1, -1, 1}, {0, -1, 1}}, +{{-1, 0, -1, 0, -1, -1, 0, 0, 1}, {-1, -1, 2}, {0, 0, 1}}, +{{-1, 0, -1, 0, -1, 0, 0, 0, 1}, {-1, 0, 2}, {0, 0, 1}}, +{{-1, 0, -1, 0, -1, 1, 0, 0, 1}, {-1, 1, 2}, {0, 0, 1}}, +{{-1, 0, 0, -1, 0, -1, 1, -1, 0}, {0, -1, 1}, {1, -1, 1}}, +{{-1, 0, 0, -1, 0, 1, -1, 1, 0}, {0, 1, 1}, {-1, 1, 1}}, +{{-1, 0, 0, -1, 1, -1, 0, 0, -1}, {0, 1, 0}, {1, -2, 1}}, +{{-1, 0, 0, -1, 1, 0, 0, 0, -1}, {0, 1, 0}, {-1, 2, 0}}, +{{-1, 0, 0, -1, 1, 1, 0, 0, -1}, {0, 1, 0}, {-1, 2, 1}}, +{{-1, 0, 0, 0, -1, -1, 0, 0, 1}, {0, -1, 2}, {0, 0, 1}}, +{{-1, 0, 0, 0, -1, 0, -1, -1, 1}, {0, 0, 1}, {-1, -1, 2}}, +{{-1, 0, 0, 0, -1, 0, -1, 0, 1}, {0, 0, 1}, {-1, 0, 2}}, +{{-1, 0, 0, 0, -1, 0, -1, 1, 1}, {0, 0, 1}, {-1, 1, 2}}, +{{-1, 0, 0, 0, -1, 0, 0, -1, 1}, {0, 0, 1}, {0, -1, 2}}, +{{-1, 0, 0, 0, -1, 0, 0, 0, 1}, {0, 0, 1}, {0, 0, 1}}, +{{-1, 0, 0, 0, -1, 0, 0, 1, 1}, {0, 0, 1}, {0, 1, 2}}, +{{-1, 0, 0, 0, -1, 0, 1, -1, 1}, {0, 0, 1}, {1, -1, 2}}, +{{-1, 0, 0, 0, -1, 0, 1, 0, 1}, {0, 0, 1}, {1, 0, 2}}, +{{-1, 0, 0, 0, -1, 0, 1, 1, 1}, {0, 0, 1}, {1, 1, 2}}, +{{-1, 0, 0, 0, -1, 1, 0, 0, 1}, {0, 1, 2}, {0, 0, 1}}, +{{-1, 0, 0, 0, 0, -1, 0, -1, 0}, {0, -1, 1}, {0, -1, 1}}, +{{-1, 0, 0, 0, 0, 1, 0, 1, 0}, {0, 1, 1}, {0, 1, 1}}, +{{-1, 0, 0, 0, 1, -1, 0, 0, -1}, {0, 1, 0}, {0, -2, 1}}, +{{-1, 0, 0, 0, 1, 0, 0, -1, -1}, {0, -2, 1}, {0, 1, 0}}, +{{-1, 0, 0, 0, 1, 0, 0, 0, -1}, {0, 1, 0}, {0, 1, 0}}, +{{-1, 0, 0, 0, 1, 0, 0, 1, -1}, {0, 2, 1}, {0, 1, 0}}, +{{-1, 0, 0, 0, 1, 1, 0, 0, -1}, {0, 1, 0}, {0, 2, 1}}, +{{-1, 0, 0, 1, 0, -1, -1, -1, 0}, {0, -1, 1}, {-1, -1, 1}}, +{{-1, 0, 0, 1, 0, 1, 1, 1, 0}, {0, 1, 1}, {1, 1, 1}}, +{{-1, 0, 0, 1, 1, -1, 0, 0, -1}, {0, 1, 0}, {-1, -2, 1}}, +{{-1, 0, 0, 1, 1, 0, 0, 0, -1}, {0, 1, 0}, {1, 2, 0}}, +{{-1, 0, 0, 1, 1, 1, 0, 0, -1}, {0, 1, 0}, {1, 2, 1}}, +{{-1, 0, 1, 0, -1, -1, 0, 0, 1}, {1, -1, 2}, {0, 0, 1}}, +{{-1, 0, 1, 0, -1, 0, 0, 0, 1}, {1, 0, 2}, {0, 0, 1}}, +{{-1, 0, 1, 0, -1, 1, 0, 0, 1}, {1, 1, 2}, {0, 0, 1}}, +{{-1, 1, -1, 0, 0, -1, 0, -1, 0}, {-1, -1, 1}, {0, -1, 1}}, +{{-1, 1, 0, 0, 1, 0, 0, -1, -1}, {-1, -2, 1}, {0, 1, 0}}, +{{-1, 1, 0, 0, 1, 0, 0, 0, -1}, {1, 2, 0}, {0, 1, 0}}, +{{-1, 1, 0, 0, 1, 0, 0, 1, -1}, {1, 2, 1}, {0, 1, 0}}, +{{-1, 1, 1, 0, 0, 1, 0, 1, 0}, {1, 1, 1}, {0, 1, 1}}, +{{0, -1, -1, -1, 0, 1, 0, 0, -1}, {-1, 1, 0}, {-1, 1, 1}}, +{{0, -1, -1, 0, -1, 0, -1, 1, 0}, {-1, 0, 1}, {-1, 1, 1}}, +{{0, -1, 0, -1, 0, 0, -1, 1, -1}, {-1, 1, 1}, {-1, 1, 0}}, +{{0, -1, 0, -1, 0, 0, 0, 0, -1}, {-1, 1, 0}, {-1, 1, 0}}, +{{0, -1, 0, -1, 0, 0, 1, -1, -1}, {1, -1, 1}, {-1, 1, 0}}, +{{0, -1, 1, -1, 0, -1, 0, 0, -1}, {-1, 1, 0}, {1, -1, 1}}, +{{0, -1, 1, 0, -1, 0, 1, -1, 0}, {1, 0, 1}, {1, -1, 1}}, +{{0, 0, -1, -1, -1, 1, -1, 0, 0}, {-1, 1, 1}, {-1, 0, 1}}, +{{0, 0, -1, 0, -1, 0, -1, 0, 0}, {-1, 0, 1}, {-1, 0, 1}}, +{{0, 0, -1, 1, -1, -1, -1, 0, 0}, {-1, -1, 1}, {-1, 0, 1}}, +{{0, 0, 1, -1, -1, -1, 1, 0, 0}, {1, -1, 1}, {1, 0, 1}}, +{{0, 0, 1, 0, -1, 0, 1, 0, 0}, {1, 0, 1}, {1, 0, 1}}, +{{0, 0, 1, 1, -1, 1, 1, 0, 0}, {1, 1, 1}, {1, 0, 1}}, +{{0, 1, -1, 0, -1, 0, -1, -1, 0}, {-1, 0, 1}, {-1, -1, 1}}, +{{0, 1, -1, 1, 0, -1, 0, 0, -1}, {1, 1, 0}, {-1, -1, 1}}, +{{0, 1, 0, 1, 0, 0, -1, -1, -1}, {-1, -1, 1}, {1, 1, 0}}, +{{0, 1, 0, 1, 0, 0, 0, 0, -1}, {1, 1, 0}, {1, 1, 0}}, +{{0, 1, 0, 1, 0, 0, 1, 1, -1}, {1, 1, 1}, {1, 1, 0}}, +{{0, 1, 1, 0, -1, 0, 1, 1, 0}, {1, 0, 1}, {1, 1, 1}}, +{{0, 1, 1, 1, 0, 1, 0, 0, -1}, {1, 1, 0}, {1, 1, 1}}, +{{1, -1, -1, 0, -1, 0, 0, 0, -1}, {1, 0, 0}, {-2, 1, 1}}, +{{1, -1, 0, 0, -1, 0, 0, 0, -1}, {1, 0, 0}, {-2, 1, 0}}, +{{1, -1, 1, 0, -1, 0, 0, 0, -1}, {1, 0, 0}, {2, -1, 1}}, +{{1, 0, -1, 0, -1, 0, 0, 0, -1}, {1, 0, 0}, {-2, 0, 1}}, +{{1, 0, 0, -1, -1, 0, -1, 0, -1}, {-2, 1, 1}, {1, 0, 0}}, +{{1, 0, 0, -1, -1, 0, 0, 0, -1}, {-2, 1, 0}, {1, 0, 0}}, +{{1, 0, 0, -1, -1, 0, 1, 0, -1}, {2, -1, 1}, {1, 0, 0}}, +{{1, 0, 0, 0, -1, 0, -1, 0, -1}, {-2, 0, 1}, {1, 0, 0}}, +{{1, 0, 0, 0, -1, 0, 0, 0, -1}, {1, 0, 0}, {1, 0, 0}}, +{{1, 0, 0, 0, -1, 0, 1, 0, -1}, {2, 0, 1}, {1, 0, 0}}, +{{1, 0, 0, 1, -1, 0, -1, 0, -1}, {-2, -1, 1}, {1, 0, 0}}, +{{1, 0, 0, 1, -1, 0, 0, 0, -1}, {2, 1, 0}, {1, 0, 0}}, +{{1, 0, 0, 1, -1, 0, 1, 0, -1}, {2, 1, 1}, {1, 0, 0}}, +{{1, 0, 1, 0, -1, 0, 0, 0, -1}, {1, 0, 0}, {2, 0, 1}}, +{{1, 1, -1, 0, -1, 0, 0, 0, -1}, {1, 0, 0}, {-2, -1, 1}}, +{{1, 1, 0, 0, -1, 0, 0, 0, -1}, {1, 0, 0}, {2, 1, 0}}, +{{1, 1, 1, 0, -1, 0, 0, 0, -1}, {1, 0, 0}, {2, 1, 1}}, +}; + +using TwoFold = TwoFold_; + +} // namespace impl + +using OpObliquity = std::pair; + +// Obliquity calculated here is the same as Le Page delta in cctbx. +// (tested against output of lebedev_2005_perturbation.py from cctbx) +inline double calculate_cos_obliquity(const UnitCell& reduced_cell, + const Vec3& d_axis, const Vec3& r_axis) { + // From the Le Page paper: + // tan(delta) = |t x tau| / |t . tau| (i.e. delta is angle 0...90deg) + // t = L U where U is direct-space axis + // tau = (L^T)^-1 h where h is reciprocal-space axis + // L is orthogonalization matrix, although in a different order + Vec3 t = reduced_cell.orth.mat.multiply(d_axis); + Vec3 tau = reduced_cell.frac.mat.left_multiply(r_axis); + // it's faster to calculate cos(delta) than tan(delta) + return std::min(1.0, std::fabs(t.cos_angle(tau))); +} + +// Reduced cell can be from GruberVector::get_cell() after Niggli reduction. +// max_obliq is max obliquity (delta) in degrees as defined in Le Page (1982). +inline std::vector find_lattice_2fold_ops(const UnitCell& reduced_cell, + double max_obliq) { + std::vector ret; + const double cos_max_obliq = std::cos(rad(max_obliq)); + for (const impl::TwoFoldData& row : impl::TwoFold::table) { + Vec3 d_axis(row.ds_axis[0], row.ds_axis[1], row.ds_axis[2]); + Vec3 r_axis(row.rs_axis[0], row.rs_axis[1], row.rs_axis[2]); + double cos_delta = calculate_cos_obliquity(reduced_cell, d_axis, r_axis); + if (cos_delta > cos_max_obliq) { + constexpr int D = Op::DEN; + Op op{Op::Rot{{{{row.matrix[0] * D, row.matrix[1] * D, row.matrix[2] * D}}, + {{row.matrix[3] * D, row.matrix[4] * D, row.matrix[5] * D}}, + {{row.matrix[6] * D, row.matrix[7] * D, row.matrix[8] * D}}}}, + Op::Tran{{0, 0, 0}}, 'x'}; + ret.emplace_back(op, deg(std::acos(cos_delta))); + } + } + std::sort(ret.begin(), ret.end(), + [](const OpObliquity& a, const OpObliquity& b) { return a.second < b.second; }); + return ret; +} + +// Reduced cell can be from GruberVector::get_cell() after Niggli reduction. +// max_obliq is max obliquity (delta) in degrees as defined in Le Page (1982). +// Returns lattice symmetry except inversion. +inline GroupOps find_lattice_symmetry_r(const UnitCell& reduced_cell, double max_obliq) { + std::vector gen = find_lattice_2fold_ops(reduced_cell, max_obliq); + std::vector genops; + genops.reserve(gen.size()); + for (const OpObliquity& op_obl : gen) + genops.push_back(op_obl.first); + GroupOps go; + go.sym_ops.push_back(Op::identity()); + go.cen_ops.push_back({0,0,0}); + // cf. GroupOps::add_missing_elements() + if (!gen.empty()) + go.sym_ops.push_back(gen[0].first); + // no need to try operator^2, we know it must be identity + go.add_missing_elements_part2(genops, 24, true); + return go; +} + +// Returns lattice symmetry, but without inversion. +inline GroupOps find_lattice_symmetry(const UnitCell& cell, char centring, + double max_obliq) { + GruberVector gv(cell, centring, true); + gv.niggli_reduce(); + UnitCell reduced = gv.get_cell(); + GroupOps gops = find_lattice_symmetry_r(reduced, max_obliq); + gops.change_basis_forward(*gv.change_of_basis); + return gops; +} + +// Determine potential twinning operators. +// Returns all operators or only unique ones (coset representatives). +inline std::vector find_twin_laws(const UnitCell& cell, + const SpaceGroup* sg, + double max_obliq, + bool all_ops) { + if (sg == nullptr) + sg = &get_spacegroup_p1(); + GroupOps go = sg->operations(); + GroupOps lat_go = find_lattice_symmetry(cell, sg->centring_type(), max_obliq); + if (!go.has_same_centring(lat_go)) + fail("find_twin_laws(): internal error"); + std::vector ops; + size_t sg_symop_count = go.sym_ops.size(); + for (const Op& op : lat_go.sym_ops) + if (!go.find_by_rotation(op.rot) && + !go.find_by_rotation(op.negated_rot())) { + ops.push_back(op); + if (!all_ops) + for (size_t i = 1; i < sg_symop_count; ++i) + go.sym_ops.push_back(op * go.sym_ops[i]); + } + return ops; +} + +} // namespace gemmi +#endif diff --git a/gemmi_gph/gemmi/version.hpp b/gemmi_gph/gemmi/version.hpp new file mode 100644 index 00000000..9d9daa83 --- /dev/null +++ b/gemmi_gph/gemmi/version.hpp @@ -0,0 +1,10 @@ +// Copyright 2017 Global Phasing Ltd. + +// Version number. + +#ifndef GEMMI_VERSION_HPP_ +#define GEMMI_VERSION_HPP_ + +#define GEMMI_VERSION "0.7.5" + +#endif diff --git a/gemmi_gph/gemmi/xds2mtz.hpp b/gemmi_gph/gemmi/xds2mtz.hpp new file mode 100644 index 00000000..af142a4e --- /dev/null +++ b/gemmi_gph/gemmi/xds2mtz.hpp @@ -0,0 +1,167 @@ +// Copyright Global Phasing Ltd. +// +// Convert XDS_ASCII to MTZ. + +#ifndef GEMMI_XDS2MTZ_HPP_ +#define GEMMI_XDS2MTZ_HPP_ + +#include +#include "mtz.hpp" // for Mtz +#include "xds_ascii.hpp" // for XdsAscii +#include "intensit.hpp" // for Intensities + +namespace gemmi { + +inline Mtz xds_to_mtz(XdsAscii& xds) { + if (xds.is_merged()) { + Intensities intensities; + intensities.import_xds(xds); + return intensities.prepare_merged_mtz(/*with_nobs=*/false); + } + Mtz mtz; + mtz.cell.set_from_array(xds.cell_constants); + mtz.spacegroup = find_spacegroup_by_number(xds.spacegroup_number); + mtz.add_base(); + const char* pxd[3] = {"XDSproject", "XDScrystal", "XDSdataset"}; + if (xds.isets.empty()) { + mtz.datasets.push_back({1, pxd[0], pxd[1], pxd[2], mtz.cell, xds.wavelength}); + } else { + for (XdsAscii::Iset& iset : xds.isets) { + double wavelength = iset.wavelength != 0 ? iset.wavelength : xds.wavelength; + UnitCell cell; + cell.set_from_array(iset.cell_constants[0] != 0 ? iset.cell_constants + : xds.cell_constants); + mtz.datasets.push_back({iset.id, pxd[0], pxd[1], pxd[2], cell, wavelength}); + } + } + mtz.add_column("M/ISYM", 'Y', 0, -1, false); + mtz.add_column("BATCH", 'B', 0, -1, false); + mtz.add_column("I", 'J', 0, -1, false); + mtz.add_column("SIGI", 'Q', 0, -1, false); + mtz.add_column("XDET", 'R', 0, -1, false); + mtz.add_column("YDET", 'R', 0, -1, false); + mtz.add_column("ROT", 'R', 0, -1, false); + if (xds.read_columns >= 11) { + mtz.add_column("FRACTIONCALC", 'R', 0, -1, false); + mtz.add_column("LP", 'R', 0, -1, false); + mtz.add_column("CORR", 'R', 0, -1, false); + if (xds.read_columns > 11) + mtz.add_column("MAXC", 'I', 0, -1, false); + } + mtz.add_column("FLAG", 'I', 0, -1, false); + mtz.nreflections = (int) xds.data.size(); + mtz.data.resize(mtz.columns.size() * xds.data.size()); + UnmergedHklMover hkl_mover(mtz.spacegroup); + int max_frame = 0; + for (const XdsAscii::Refl& refl : xds.data) + max_frame = std::max(max_frame, refl.frame()); + int iset_offset = (max_frame + 11000) / 10000 * 10000; + // iset,frame -> batch + std::map, int> frames; + size_t k = 0; + for (const XdsAscii::Refl& refl : xds.data) { + auto hkl = refl.hkl; + int isym = hkl_mover.move_to_asu(hkl); + for (size_t j = 0; j != 3; ++j) + mtz.data[k++] = (float) hkl[j]; + mtz.data[k++] = (float) isym; + int frame = refl.frame(); + int batch = frame + iset_offset * std::max(refl.iset - 1, 0); + frames.emplace(std::make_pair(refl.iset, frame), batch); + mtz.data[k++] = (float) batch; + mtz.data[k++] = (float) refl.iobs; // I + mtz.data[k++] = (float) std::fabs(refl.sigma); // SIGI + mtz.data[k++] = (float) refl.xd; + mtz.data[k++] = (float) refl.yd; + mtz.data[k++] = (float) xds.rot_angle(refl); // ROT + if (xds.read_columns >= 11) { + mtz.data[k++] = float(0.01 * refl.peak); // FRACTIONCALC + mtz.data[k++] = (float) refl.rlp; + mtz.data[k++] = float(0.01 * refl.corr); + if (xds.read_columns > 11) + mtz.data[k++] = (float) refl.maxc; + } + mtz.data[k++] = refl.sigma < 0 ? 64.f : 0.f; // FLAG + } + // Prepare a similar batch header as Pointless. + Mtz::Batch batch; + batch.set_dataset_id(1); + + // We don't set lbcell (refinement flags for unit cell), + // because it's probably not used by any program anyway. + // batch.ints[4] to [9] = left unset + + // We also skip jumpax, which is defined as: + // reciprocal axis closest to principle goniostat axis E1 + // batch.ints[11] = left unset + + // We assume one crystal, one goniostat axis, one detector. + batch.ints[12] = 1; // ncryst + batch.ints[14] = 2; // ldtype 3D + batch.ints[15] = 1; // jsaxs - goniostat scan axis number + batch.ints[17] = 1; // ngonax - number of goniostat axes + batch.ints[19] = 1; // ndet + + batch.set_cell(mtz.cell); // batch.floats[0] to [5] + Vec3 s0(-1, 0, 0); // will be re-set if we have geometry info + try { + Mat33 Q = xds.calculate_conversion_from_cambridge().inverse(); + s0 = -Q.multiply(xds.get_s0_direction()); + // Orientation matrix U. It is calculated differently in Pointless, + // so the results are slightly different (due to limited precision + // of numbers in XDS file). + Mat33 U = Q.multiply(xds.get_orientation()); + for (int i = 0; i < 3; ++i) + for (int j = 0; j < 3; ++j) + batch.floats[6 + 3*i + j] = (float) U[j][i]; + } catch (std::runtime_error& e) { + // if some of the headers are absent, U is not set + // and s0 is set to "idealised" s0 + std::fprintf(stderr, "Note: %s, orientation matrix U not set.\n", e.what()); + } + batch.floats[21] = float(xds.reflecting_range_esd); // crydat(0) + // In the so-called "Cambridge" frame (as used by Mosflm), + // the principal rotation axis is along z + // and the incident beam S0 is along x. + // Therefore, we set the rotation axis phi (scanax) to: + batch.floats[38+2] = 1.f; // scanax = [0, 0, 1] + batch.floats[47] = float(xds.oscillation_range); // phi range + // E1,E2,E3 vectors are the goniostat axes in Cambridge laboratory frame. + // E1 is set to rotation axis. E2 and E3 are not set for ngonax==1. + batch.floats[59+2] = 1.f; // e1 = scanax + + // Idealised source vector is -x ([-1 0 0]), antiparallel to beam. + batch.floats[80+0] = -1.f; // source[0] + + // s0 source vector (including tilts), CMtz::MTZBAT::so in libccp4 + batch.floats[83] = (float) s0.x; + batch.floats[84] = (float) s0.y; + batch.floats[85] = (float) s0.z; + + batch.set_wavelength((float)xds.wavelength); // batch.floats[86] + // or batch.set_wavelength(iset.wavelength); + // Detector geometry. + batch.floats[111] = (float) xds.detector_distance; // dx[0] + batch.floats[113] = 1.f; // detlm[0][0][0] + batch.floats[114] = (float) xds.nx; + batch.floats[115] = 1.f; + batch.floats[116] = (float) xds.ny; + batch.axes.push_back("PHI"); // gonlab[0] + + for (auto& t : frames) { + batch.set_dataset_id(t.first.first); + batch.number = t.second; + int frame = t.first.second; + double phistt = xds.starting_angle + + xds.oscillation_range * (frame - xds.starting_frame); + batch.floats[36] = float(phistt); + batch.floats[37] = float(phistt + xds.oscillation_range); // phiend + mtz.batches.push_back(batch); + } + mtz.sort(5); + return mtz; +} + + +} // namespace gemmi +#endif diff --git a/gemmi_gph/pdb.cpp b/gemmi_gph/pdb.cpp new file mode 100644 index 00000000..cca0e8a9 --- /dev/null +++ b/gemmi_gph/pdb.cpp @@ -0,0 +1,1269 @@ +// Copyright 2019 Global Phasing Ltd. + +#include "gemmi/pdb.hpp" +#include // for isalpha +#include // for atoi, strtol +#include // for memcpy, strstr, strchr, strcmp +#include // for min, swap +#include // for invalid_argument +#include +#include "gemmi/atof.hpp" // for fast_from_chars +#include "gemmi/atox.hpp" // for is_space, is_digit +#include "gemmi/input.hpp" +#include "gemmi/metadata.hpp" // for Metadata +#include "gemmi/model.hpp" // for Structure, impl::find_or_add +#include "gemmi/polyheur.hpp" // for assign_subchains +#include "gemmi/util.hpp" // for trim_str, alpha_up, istarts_with + +namespace gemmi { + +namespace { + +int read_int(const char* p, int field_length) { + return string_to_int(p, false, field_length); +} + +double read_double(const char* p, int field_length) { + double d = 0.; + // we don't check for errors here + fast_from_chars(p, p + field_length, d); + return d; +} + +std::string read_string(const char* p, int field_length) { + // left trim + while (field_length != 0 && is_space(*p)) { + ++p; + --field_length; + } + // EOL/EOF ends the string + for (int i = 0; i < field_length; ++i) + if (p[i] == '\n' || p[i] == '\r' || p[i] == '\0') { + field_length = i; + break; + } + // right trim + while (field_length != 0 && is_space(p[field_length-1])) + --field_length; + return std::string(p, field_length); +} + +template int read_base36(const char* p) { + char zstr[N+1] = {0}; + std::memcpy(zstr, p, N); + return std::strtol(zstr, nullptr, 36); +} + +// The standard charge format is 2+, but some files have +2. +signed char read_charge(char digit, char sign) { + if (sign == ' ' && digit == ' ') // by far the most common case + return 0; + if (sign >= '0' && sign <= '9') + std::swap(digit, sign); + if (digit >= '0' && digit <= '9') { + if (sign != '+' && sign != '-' && sign != '\0' && !is_space(sign)) + fail("Wrong format for charge: " + + std::string(1, digit) + std::string(1, sign)); + return (digit - '0') * (sign == '-' ? -1 : 1); + } + // if we are here the field should be blank, but maybe better not to check + return 0; +} + +int read_matrix(Transform& t, const char* line, size_t len) { + if (len < 46) + return 0; + char n = line[5] - '0'; + if (n >= 1 && n <= 3) { + t.mat[n-1][0] = read_double(line+10, 10); + t.mat[n-1][1] = read_double(line+20, 10); + t.mat[n-1][2] = read_double(line+30, 10); + t.vec.at(n-1) = read_double(line+45, 10); + } + return n; +} + +SeqId read_seq_id(const char* str) { + SeqId seqid; + if (str[4] != '\r' && str[4] != '\n') + seqid.icode = str[4]; + // We support hybrid-36 extension, although it is never used in practice + // as 9999 residues per chain are enough. + if (str[0] < 'A') { + for (int i = 4; i != 0; --i, ++str) + if (!is_space(*str)) { + seqid.num = read_int(str, i); + break; + } + } else { + seqid.num = read_base36<4>(str) - 466560 + 10000; + } + return seqid; +} + +ResidueId read_res_id(const char* seq_id, const char* name) { + return {read_seq_id(seq_id), {}, read_string(name, 3)}; +} + +char read_altloc(char c) { return c == ' ' ? '\0' : c; } + +int read_serial(const char* ptr) { + return ptr[0] < 'A' ? read_int(ptr, 5) + : read_base36<5>(ptr) - 16796160 + 100000; +} + +El infer_element_from_padded_name(const char* name) { + // Old versions of the PDB format had hydrogen names such as "1HB ". + // Some MD files use similar names for other elements ("1C4A" -> C). + if (name[0] == ' ' || is_digit(name[0])) + return impl::find_single_letter_element(name[1]); + // ... or it can be "C210" + if (is_digit(name[1])) + return impl::find_single_letter_element(name[0]); + if (name[3] != ' ') { + // Atom names HXXX are ambiguous, but Hg, He, Hf, Ho and Hs (almost) + // never have 4-character names, so H is assumed. + if (alpha_up(name[0]) == 'H') + return El::H; + // Similarly Deuterium (DXXX), but here alternatives are Dy, Db and Ds. + // Only Dysprosium is present in the PDB - in a single entry as of 2022. + if (alpha_up(name[0]) == 'D') + return El::D; + // Don't try harder for now. We don't recognize names such as CG11 as C + // (which we could; there is no Cg in the periodic table), but + // a name such as CL20 can be either Cl (in WGW) or C (in WQH) ¯\_(ツ)_/¯ + } + return find_element(name); +} + +bool element_from_padded_name_is_ambiguous(const char* name) { + return name[0] != ' ' && name[3] != ' ' && !is_digit(name[0]) && !is_digit(name[1]); +} + +// "28-MAR-07" -> "2007-03-28" +// (we also accept less standard format "28-Mar-2007" as used by BUSTER) +// We do not check if the date is correct. +// The returned value is one of: +// DDDD-DD-DD - possibly correct date, +// DDDD-xx-DD - unrecognized month, +// empty string - the digits were not there. +std::string pdb_date_format_to_iso(const std::string& date) { + const char months[] = "JAN01FEB02MAR03APR04MAY05JUN06" + "JUL07AUG08SEP09OCT10NOV11DEC122222"; + if (date.size() < 9 || !is_digit(date[0]) || !is_digit(date[1]) || + !is_digit(date[7]) || !is_digit(date[8])) + return std::string(); + std::string iso = "xxxx-xx-xx"; + if (date.size() >= 11 && is_digit(date[9]) && is_digit(date[10])) { + std::memcpy(&iso[0], &date[7], 4); + } else { + std::memcpy(&iso[0], (date[7] > '6' ? "19" : "20"), 2); + std::memcpy(&iso[2], &date[7], 2); + } + char month[4] = {alpha_up(date[3]), alpha_up(date[4]), alpha_up(date[5]), '\0'}; + if (const char* m = std::strstr(months, month)) + std::memcpy(&iso[5], m + 3, 2); + std::memcpy(&iso[8], &date[0], 2); + return iso; +} + + +bool is_double(const char* p) { + while (is_space(*p)) ++p; + if (*p == '-' || *p == '+') ++p; + while (is_digit(*p)) ++p; + if (*p == '.') { + ++p; + while (is_digit(*++p)) ++p; + } + while (is_space(*p)) ++p; + return *p == '\0'; +} + +template +bool same_str(const std::string& s, const char (&literal)[N]) { + return s.size() == N - 1 && std::strcmp(s.c_str(), literal) == 0; +} + +bool is_tls_item(const std::string& key) { + return key.size() == 3 && + (key[0] == 'T' || key[0] == 'L' || key[0] == 'S') && + (key[1] == '1' || key[1] == '2' || key[1] == '3') && + (key[2] == '1' || key[2] == '2' || key[2] == '3'); +} + +// Usually we have one program per line: +// XDS +// XDS VERSION NOVEMBER 3, 2014 +// AIMLESS 0.5.17 +// but it can also be a list of programs: +// autoPROC (Version 1.3.0), AIMLESS, STARANISO +// autoPROC, XDS (VERSION Jan 26, 2018) +// We assume that: +// - the name has only one word (apologies to Queen of Spades, +// Force Field X, APEX 2 and Insight II). +// - comma not followed by a digit separates programs +// - brackets and the word VERSION are to be removed from version +// Additionally, if version has format: "something (DATE)" where +// the DATE format is either 28-MAR-07 or 28-Mar-2007, then DATE +// is put into _software.date. +void add_software(Metadata& meta, SoftwareItem::Classification type, const std::string& name) { + for (size_t start = 0, end = 0; end != std::string::npos; start = end + 1) { + end = name.find(',', start); + while (end != std::string::npos && + name[end+1] == ' ' && is_digit(name[end+2])) + end = name.find(',', end + 1); + meta.software.emplace_back(); + SoftwareItem& item = meta.software.back(); + item.name = trim_str(name.substr(start, end - start)); + size_t sep = item.name.find(' '); + if (sep != std::string::npos) { + size_t ver_start = item.name.find_first_not_of(" (", sep + 1); + if (ver_start == std::string::npos) { + item.name.resize(sep); + item.classification = type; + continue; + } + item.version = item.name.substr(ver_start); + item.name.resize(sep); + if (!item.version.empty() && item.version.back() == ')') { + size_t open_br = item.version.find('('); + if (open_br == std::string::npos) { + item.version.pop_back(); + } else if (open_br + 11 == item.version.size() || + open_br + 13 == item.version.size()) { + item.date = pdb_date_format_to_iso(item.version.substr(open_br + 1)); + if (item.date.size() == 10 && item.date[5] != 'x') { + size_t last = item.version.find_last_not_of(' ', open_br - 1); + item.version.resize(last + 1); + } else { + item.date.clear(); + } + } + } + if (istarts_with(item.version, "version ")) + item.version.erase(0, 8); + } + item.classification = type; + } +} + +// REMARK 3 TERM COUNT WEIGHT FUNCTION. +// REMARK 3 BOND LENGTHS : 5760 ; 2.000 ; HARMONIC +void add_restraint_count_weight(RefinementInfo& ref_info, const char* key, const char* value) { + if (*value == 'N') // NULL instead of number + return; + ref_info.restr_stats.emplace_back(key); + RefinementInfo::Restr& restr = ref_info.restr_stats.back(); + const char* endptr; + restr.count = no_sign_atoi(value, &endptr); + if (const char* sep = std::strchr(endptr, ';')) + restr.weight = fast_atof(sep + 1, &endptr); + if (const char* sep = std::strchr(endptr, ';')) + restr.function = read_string(sep+1, 50); +} + +void read_remark3_line(const char* line, Metadata& meta, + std::string*& possibly_unfinished_remark3) { + // Based on: + // www.wwpdb.org/documentation/file-format-content/format23/remark3.html + // and analysis of PDB files. + // In special cases, such as joint X-ray and neutron refinement 5MOO, + // PDB file can have two REMARK 3 blocks. + // Generally, after "REMARK 3" we have either a header-like sentence + // or a key:value pair with a colon, or a continuation of text from the + // previous line. + const char* key_start = skip_blank(line + 10); + const char* colon = std::strchr(key_start, ':'); + const char* key_end = rtrim_cstr(key_start, colon); + std::string key(key_start, key_end); + + // multi-line continuation requires special handling + if (possibly_unfinished_remark3) { + if (key_start > line + 17) { + *possibly_unfinished_remark3 += ' '; + possibly_unfinished_remark3->append(key); + return; + } + possibly_unfinished_remark3 = nullptr; + } + + if (colon) { + const char* value = skip_blank(colon + 1); + const char* end = rtrim_cstr(value); + if (end - value == 4 && std::strncmp(value, "NULL", 4) == 0) + return; + if (same_str(key, "PROGRAM")) + add_software(meta, SoftwareItem::Refinement, std::string(value, end)); + if (meta.refinement.empty()) + return; + RefinementInfo& ref_info = meta.refinement.back(); + if (same_str(key, "RESOLUTION RANGE HIGH (ANGSTROMS)")) { + ref_info.resolution_high = fast_atof(value); + } else if (same_str(key, "RESOLUTION RANGE LOW (ANGSTROMS)")) { + ref_info.resolution_low = fast_atof(value); + } else if (same_str(key, "COMPLETENESS FOR RANGE (%)")) { + ref_info.completeness = fast_atof(value); + } else if (same_str(key, "NUMBER OF REFLECTIONS")) { + ref_info.reflection_count = std::atoi(value); + } else if (same_str(key, "CROSS-VALIDATION METHOD")) { + ref_info.cross_validation_method = std::string(value, end); + } else if (same_str(key, "FREE R VALUE TEST SET SELECTION")) { + ref_info.rfree_selection_method = std::string(value, end); + } else if (same_str(key, "R VALUE (WORKING + TEST SET)")) { + ref_info.r_all = fast_atof(value); + } else if (same_str(key, "R VALUE (WORKING SET)")) { + ref_info.r_work = fast_atof(value); + } else if (same_str(key, "FREE R VALUE")) { + ref_info.r_free = fast_atof(value); + } else if (same_str(key, "FREE R VALUE TEST SET COUNT")) { + ref_info.rfree_set_count = atoi(value); + } else if (same_str(key, "TOTAL NUMBER OF BINS USED")) { + ref_info.bin_count = std::atoi(value); + } else if (same_str(key, "BIN RESOLUTION RANGE HIGH (A)")) { + if (!ref_info.bins.empty()) + ref_info.bins.back().resolution_high = fast_atof(value); + } else if (same_str(key, "BIN RESOLUTION RANGE LOW (A)")) { + if (!ref_info.bins.empty()) + ref_info.bins.back().resolution_low = fast_atof(value); + } else if (same_str(key, "BIN COMPLETENESS (WORKING+TEST) (%)")) { + if (!ref_info.bins.empty()) + ref_info.bins.back().completeness = fast_atof(value); + } else if (same_str(key, "REFLECTIONS IN BIN (WORKING+TEST)")) { + if (!ref_info.bins.empty()) + ref_info.bins.back().reflection_count = std::atoi(value); + } else if (same_str(key, "BIN R VALUE (WORKING+TEST)")) { + if (!ref_info.bins.empty()) + ref_info.bins.back().r_all = fast_atof(value); + } else if (same_str(key, "REFLECTIONS IN BIN (WORKING SET)")) { + if (!ref_info.bins.empty()) + ref_info.bins.back().work_set_count = std::atoi(value); + } else if (same_str(key, "BIN R VALUE (WORKING SET)")) { + if (!ref_info.bins.empty()) + ref_info.bins.back().r_work = fast_atof(value); + } else if (same_str(key, "BIN FREE R VALUE")) { + if (!ref_info.bins.empty()) + ref_info.bins.back().r_free = fast_atof(value); + } else if (same_str(key, "BIN FREE R VALUE TEST SET COUNT")) { + if (!ref_info.bins.empty()) + ref_info.bins.back().rfree_set_count = std::atoi(value); + } else if (same_str(key, "FROM WILSON PLOT (A**2)")) { + // TODO + // exper.b_wilson = fast_atof(value); + } else if (same_str(key, "MEAN B VALUE (OVERALL, A**2)")) { + ref_info.mean_b = fast_atof(value); + } else if (same_str(key, "B11 (A**2)")) { + ref_info.aniso_b.u11 = fast_atof(value); + } else if (same_str(key, "B22 (A**2)")) { + ref_info.aniso_b.u22 = fast_atof(value); + } else if (same_str(key, "B33 (A**2)")) { + ref_info.aniso_b.u33 = fast_atof(value); + } else if (same_str(key, "B12 (A**2)")) { + ref_info.aniso_b.u12 = fast_atof(value); + } else if (same_str(key, "B13 (A**2)")) { + ref_info.aniso_b.u13 = fast_atof(value); + } else if (same_str(key, "B23 (A**2)")) { + ref_info.aniso_b.u23 = fast_atof(value); + } else if (same_str(key, "ESD FROM LUZZATI PLOT (A)")) { + ref_info.luzzati_error = fast_atof(value); + } else if (same_str(key, "DPI (BLOW EQ-10) BASED ON R VALUE (A)")) { + ref_info.dpi_blow_r = fast_atof(value); + } else if (same_str(key, "DPI (BLOW EQ-9) BASED ON FREE R VALUE (A)")) { + ref_info.dpi_blow_rfree = fast_atof(value); + } else if (same_str(key, "DPI (CRUICKSHANK) BASED ON R VALUE (A)")) { + ref_info.dpi_cruickshank_r = fast_atof(value); + } else if (same_str(key, "DPI (CRUICKSHANK) BASED ON FREE R VALUE (A)")) { + ref_info.dpi_cruickshank_rfree = fast_atof(value); + } else if (same_str(key, "CORRELATION COEFFICIENT FO-FC")) { + ref_info.cc_fo_fc_work = fast_atof(value); + } else if (same_str(key, "CORRELATION COEFFICIENT FO-FC FREE")) { + ref_info.cc_fo_fc_free = fast_atof(value); + } else if (same_str(key, "BOND LENGTHS")) { + add_restraint_count_weight(ref_info, "t_bond_d", value); + } else if (same_str(key, "BOND ANGLES")) { + add_restraint_count_weight(ref_info, "t_angle_deg", value); + } else if (same_str(key, "TORSION ANGLES")) { + add_restraint_count_weight(ref_info, "t_dihedral_angle_d", value); + } else if (same_str(key, "TRIGONAL CARBON PLANES")) { + add_restraint_count_weight(ref_info, "t_trig_c_planes", value); + } else if (same_str(key, "GENERAL PLANES")) { + add_restraint_count_weight(ref_info, "t_gen_planes", value); + } else if (same_str(key, "ISOTROPIC THERMAL FACTORS")) { + add_restraint_count_weight(ref_info, "t_it", value); + } else if (same_str(key, "BAD NON-BONDED CONTACTS")) { + add_restraint_count_weight(ref_info, "t_nbd", value); + } else if (same_str(key, "IMPROPER TORSIONS")) { + add_restraint_count_weight(ref_info, "t_improper_torsion", value); + } else if (same_str(key, "CHIRAL IMPROPER TORSION")) { + add_restraint_count_weight(ref_info, "t_chiral_improper_torsion", value); + } else if (same_str(key, "SUM OF OCCUPANCIES")) { + add_restraint_count_weight(ref_info, "t_sum_occupancies", value); + } else if (same_str(key, "UTILITY DISTANCES")) { + add_restraint_count_weight(ref_info, "t_utility_distance", value); + } else if (same_str(key, "UTILITY ANGLES")) { + add_restraint_count_weight(ref_info, "t_utility_angle", value); + } else if (same_str(key, "UTILITY TORSION")) { + add_restraint_count_weight(ref_info, "t_utility_torsion", value); + } else if (same_str(key, "IDEAL-DIST CONTACT TERM")) { + add_restraint_count_weight(ref_info, "t_ideal_dist_contact", value); + } else if (same_str(key, "BOND LENGTHS (A)")) { + impl::find_or_add(ref_info.restr_stats, "t_bond_d").dev_ideal + = read_double(value, 50); + } else if (same_str(key, "BOND ANGLES (DEGREES)")) { + impl::find_or_add(ref_info.restr_stats, "t_angle_deg").dev_ideal + = read_double(value, 50); + } else if (same_str(key, "PEPTIDE OMEGA TORSION ANGLES (DEGREES)")) { + impl::find_or_add(ref_info.restr_stats, "t_omega_torsion").dev_ideal + = read_double(value, 50); + } else if (same_str(key, "OTHER TORSION ANGLES (DEGREES)")) { + impl::find_or_add(ref_info.restr_stats, "t_other_torsion").dev_ideal + = read_double(value, 50); + } else if (same_str(key, "TLS GROUP")) { + ref_info.tls_groups.emplace_back(); + TlsGroup& tls_group = ref_info.tls_groups.back(); + tls_group.id = std::string(value, end); + tls_group.num_id = (short) no_sign_atoi(tls_group.id.c_str()); + } else if (same_str(key, "SET") || + // "REMARK 3 SELECTION:" -> TLS + // "REMARK 3 SELECTION :" -> NCS + (same_str(key, "SELECTION") && colon == line + 23)) { + if (!ref_info.tls_groups.empty()) { + TlsGroup& group = ref_info.tls_groups.back(); + group.selections.emplace_back(); + group.selections.back().details = std::string(value, end); + possibly_unfinished_remark3 = &group.selections.back().details; + } + } else if (same_str(key, "RESIDUE RANGE")) { + if (!ref_info.tls_groups.empty() && end > colon+21) { + TlsGroup& group = ref_info.tls_groups.back(); + group.selections.emplace_back(); + TlsGroup::Selection& sel = group.selections.back(); + sel.chain = read_string(colon+1, 5); + if (sel.chain == read_string(colon+16, 5)) { + try { + sel.res_begin = SeqId(read_string(colon+6, 6)); + sel.res_end = SeqId(read_string(colon+21, 6)); + } catch (std::invalid_argument&) { + group.selections.pop_back(); + } + } else { // unexpected -- TLS group should be in one chain + group.selections.pop_back(); + } + } + } else if (same_str(key, "ORIGIN FOR THE GROUP (A)")) { + std::vector xyz = split_str_multi(std::string(value, end)); + if (ref_info.tls_groups.empty() || xyz.size() != 3) + return; + Position& origin = ref_info.tls_groups.back().origin; + origin.x = fast_atof(xyz[0].c_str()); + origin.y = fast_atof(xyz[1].c_str()); + origin.z = fast_atof(xyz[2].c_str()); + } else if (is_tls_item(key)) { + if (ref_info.tls_groups.empty()) + return; + TlsGroup& tls = ref_info.tls_groups.back(); + std::vector tokens = split_str_multi(key_start); + for (size_t i = 0; i + 1 < tokens.size(); i += 2) { + std::string& k = tokens[i]; + if (k.size() == 4 && k[3] == ':') + k.resize(3); + if (is_tls_item(k)) { + int x = k[1] - '1'; + int y = k[2] - '1'; + double v = fast_atof(tokens[i+1].c_str()); + if (k[0] == 'S') { + tls.S[x][y] = v; + } else { + SMat33& tensor = k[0] == 'T' ? tls.T : tls.L; + tensor.unchecked_ref(x, y) = v; + } + } + } + } + } else { + if (same_str(key, "DATA USED IN REFINEMENT.")) { + meta.refinement.emplace_back(); + meta.refinement.back().id = std::to_string(meta.refinement.size()); + } else if (same_str(key, "FIT IN THE HIGHEST RESOLUTION BIN.")) { + if (!meta.refinement.empty()) + meta.refinement.back().bins.emplace_back(); + } + } +} + +void read_remark_200_230_240(const char* line, Metadata& meta, std::string*& cryst_desc) { + // multi-line continuation requires special handling + if (cryst_desc) { + if (line[10] == ' ' && line[11] == ' ') { + const char* start = line + 11; + cryst_desc->append(start, rtrim_cstr(start) - start); + return; + } + cryst_desc = nullptr; + } + + const char* key_start = skip_blank(line + 10); + const char* colon = std::strchr(key_start, ':'); + const char* key_end = rtrim_cstr(key_start, colon); + std::string key(key_start, key_end); + if (colon) { + const char* value = skip_blank(colon + 1); + const char* end = rtrim_cstr(value); + if (end - value == 4 && std::strncmp(value, "NULL", 4) == 0) + return; + if (same_str(key, "INTENSITY-INTEGRATION SOFTWARE")) { + add_software(meta, SoftwareItem::DataReduction, std::string(value, end)); + } else if (same_str(key, "DATA SCALING SOFTWARE")) { + add_software(meta, SoftwareItem::DataScaling, std::string(value, end)); + } else if (same_str(key, "SOFTWARE USED")) { + add_software(meta, SoftwareItem::Phasing, std::string(value, end)); + } else if (same_str(key, "METHOD USED TO DETERMINE THE STRUCTURE")) { + meta.solved_by = std::string(value, end); + } else if (same_str(key, "STARTING MODEL")) { + meta.starting_model = std::string(value, end); + } else if (!meta.experiments.empty()) { + ExperimentInfo& exper = meta.experiments.back(); + DiffractionInfo& diffr = meta.crystals.back().diffractions[0]; + if (same_str(key, "EXPERIMENT TYPE")) { + exper.method = std::string(value, end); + } else if (same_str(key, "NUMBER OF CRYSTALS USED")) { + exper.number_of_crystals = std::atoi(value); + } else if (same_str(key, "PH")) { + if (is_double(value)) + meta.crystals.back().ph = fast_atof(value); + else + meta.crystals.back().ph_range = std::string(value, end); + } else if (same_str(key, "DATE OF DATA COLLECTION")) { + diffr.collection_date = pdb_date_format_to_iso(std::string(value, end)); + } else if (same_str(key, "TEMPERATURE (KELVIN)")) { + diffr.temperature = fast_atof(value); + } else if (same_str(key, "SYNCHROTRON (Y/N)")) { + if (*value == 'Y') + diffr.source = "SYNCHROTRON"; + } else if (same_str(key, "RADIATION SOURCE")) { + if (same_str(diffr.source, "SYNCHROTRON")) + diffr.synchrotron = std::string(value, end); + else + diffr.source = std::string(value, end); + } else if (same_str(key, "NEUTRON SOURCE")) { + diffr.source = std::string(value, end); + } else if (same_str(key, "BEAMLINE")) { + diffr.beamline = std::string(value, end); + if (!diffr.synchrotron.empty() && diffr.source_type.empty()) + diffr.source_type = diffr.synchrotron + " BEAMLINE " + diffr.beamline; + } else if (same_str(key, "X-RAY GENERATOR MODEL")) { + diffr.source_type = std::string(value, end); + } else if (same_str(key, "MONOCHROMATIC OR LAUE (M/L)")) { + diffr.mono_or_laue = *value; + } else if (same_str(key, "WAVELENGTH OR RANGE (A)")) { + diffr.wavelengths = std::string(value, end); + } else if (same_str(key, "MONOCHROMATOR")) { + diffr.monochromator = std::string(value, end); + } else if (same_str(key, "OPTICS")) { + diffr.optics = std::string(value, end); + } else if (same_str(key, "DETECTOR TYPE")) { + diffr.detector = std::string(value, end); + } else if (same_str(key, "DETECTOR MANUFACTURER")) { + diffr.detector_make = std::string(value, end); + } else if (same_str(key, "NUMBER OF UNIQUE REFLECTIONS")) { + exper.unique_reflections = std::atoi(value); + } else if (same_str(key, "RESOLUTION RANGE HIGH (A)")) { + exper.reflections.resolution_high = fast_atof(value); + } else if (same_str(key, "RESOLUTION RANGE LOW (A)")) { + exper.reflections.resolution_low = fast_atof(value); + } else if (same_str(key, "COMPLETENESS FOR RANGE (%)")) { + exper.reflections.completeness = fast_atof(value); + } else if (same_str(key, "DATA REDUNDANCY")) { + exper.reflections.redundancy = fast_atof(value); + } else if (same_str(key, "R MERGE (I)")) { + exper.reflections.r_merge = fast_atof(value); + } else if (same_str(key, "R SYM (I)")) { + exper.reflections.r_sym = fast_atof(value); + } else if (same_str(key, " FOR THE DATA SET")) { + exper.reflections.mean_I_over_sigma = fast_atof(value); + } else if (same_str(key, "REMARK")) { + cryst_desc = &meta.crystals.back().description; + *cryst_desc = std::string(value, end); + } else if (!exper.shells.empty()) { + if (same_str(key, "HIGHEST RESOLUTION SHELL, RANGE HIGH (A)")) { + exper.shells.back().resolution_high = fast_atof(value); + } else if (same_str(key, "HIGHEST RESOLUTION SHELL, RANGE LOW (A)")) { + exper.shells.back().resolution_low = fast_atof(value); + } else if (same_str(key, "COMPLETENESS FOR SHELL (%)")) { + exper.shells.back().completeness = fast_atof(value); + } else if (same_str(key, "DATA REDUNDANCY IN SHELL")) { + exper.shells.back().redundancy = fast_atof(value); + } else if (same_str(key, "R MERGE FOR SHELL (I)")) { + exper.shells.back().r_merge = fast_atof(value); + } else if (same_str(key, "R SYM FOR SHELL (I)")) { + exper.shells.back().r_sym = fast_atof(value); + } else if (same_str(key, " FOR SHELL")) { + exper.shells.back().mean_I_over_sigma = fast_atof(value); + } + } + } + } else { + if (same_str(key, "EXPERIMENTAL DETAILS")) { + meta.crystals.emplace_back(); + CrystalInfo& c = meta.crystals.back(); + c.id = std::to_string(meta.crystals.size()); + c.diffractions.emplace_back(); + c.diffractions[0].id = c.id; + meta.experiments.emplace_back(); + meta.experiments.back().diffraction_ids.push_back(c.id); + if (line[8] == '0' && line[9] == '0') + c.diffractions[0].scattering_type = "x-ray"; + else if (line[8] == '3' && line[9] == '0') + c.diffractions[0].scattering_type = "neutron"; + else if (line[8] == '4' && line[9] == '0') + c.diffractions[0].scattering_type = "electron"; + } + if (same_str(key, "IN THE HIGHEST RESOLUTION SHELL.")) { + if (!meta.experiments.empty()) + meta.experiments.back().shells.emplace_back(); + } + } +} + +// Atom name and altloc are not provided in the SSBOND record. +// Usually it is SG (cysteine), but other disulfide bonds are also possible. +// If it's not SG, we pick the first sulfur atom in the residue. +const Residue* complete_ssbond_atom(AtomAddress& ad, const Model& mdl) { + ad.atom_name = "SG"; + const_CRA cra = mdl.find_cra(ad); + if (cra.residue && (!cra.atom || cra.atom->element != El::S)) + if (const Atom* a = cra.residue->find_by_element(El::S)) { + ad.atom_name = a->name; + ad.altloc = a->altloc; + } + return cra.residue; +} +void complete_ssbond(Connection& con, const Model& mdl, const UnitCell& cell) { + const Residue* res1 = complete_ssbond_atom(con.partner1, mdl); + const Residue* res2 = complete_ssbond_atom(con.partner2, mdl); + if (res1 && res2 && (con.partner1.altloc != '\0' || con.partner2.altloc != '\0')) { + // pick a pair of atoms in the shortest distance + double min_dist_sq = INFINITY; + for (const Atom& a1 : const_cast(res1)->get(con.partner1.atom_name)) + for (const Atom& a2 : const_cast(res2)->get(con.partner2.atom_name)) + if (a2.same_conformer(a1)) { + double dist_sq = cell.find_nearest_image(a1.pos, a2.pos, con.asu).dist_sq; + if (dist_sq < min_dist_sq) { + con.partner1.altloc = a1.altloc; + con.partner2.altloc = a2.altloc; + min_dist_sq = dist_sq; + } + } + } +} + +Asu compare_link_symops(const std::string& record, short* reported_sym) { + if (record.size() < 72) + return Asu::Any; // it could be interpreted as Same + std::string s1 = read_string(&record[59], 6); + std::string s2 = read_string(&record[66], 6); + if (s1 == s2) + return Asu::Same; + size_t len1 = s1.length(); + size_t len2 = s2.length(); + if (len1 >= 4 && len1 < 6 && len2 >= 4 && len2 < 6) { + // for 5 digits, we assume here that two digits are for sym_idx + if (s1[0] == '1' && len1 == 4) // symop1 is usually 1555 + reported_sym[0] = (short) read_int(s2.c_str(), len2 - 3); + else + reported_sym[0] = 99; + for (size_t i = 1; i <= 3; ++i) + reported_sym[i] = s2[len2 - 4 + i] - s1[len1 - 4 + i]; + } + return Asu::Different; +} + +void process_conn(Structure& st, const std::vector& conn_records) { + int disulf_count = 0; + int covale_count = 0; + int metalc_count = 0; + for (const std::string& record : conn_records) { + if (record[0] == 'S' || record[0] == 's') { // SSBOND + if (record.length() < 32) + continue; + Connection c; + c.name = "disulf" + std::to_string(++disulf_count); + c.type = Connection::Disulf; + const char* r = record.c_str(); + c.partner1.chain_name = read_string(r + 14, 2); + c.partner1.res_id = read_res_id(r + 17, r + 11); + c.partner2.chain_name = read_string(r + 28, 2); + char res_id2[5] = {' ', ' ', ' ', ' ', ' '}; + std::memcpy(res_id2, r + 31, std::min((size_t)5, record.length() - 31)); + c.partner2.res_id = read_res_id(res_id2, r + 25); + c.asu = compare_link_symops(record, c.reported_sym); + if (record.length() > 73) + c.reported_distance = read_double(r + 73, 5); + complete_ssbond(c, st.first_model(), st.cell); + st.connections.emplace_back(c); + } else if (record[0] == 'L' || record[0] == 'l') { // LINK + if (record.length() < 57) + continue; + Connection c; + for (int i : {0, 1}) { + const char* t = record.c_str() + 30 * i; + AtomAddress& ad = (i == 0 ? c.partner1 : c.partner2); + ad.chain_name = read_string(t + 20, 2); + ad.res_id = read_res_id(t + 22, t + 17); + ad.atom_name = read_string(t + 12, 4); + ad.altloc = read_altloc(t[16]); + } + auto get_elem = [&](const char* name, const AtomAddress& ad) { + if (element_from_padded_name_is_ambiguous(name)) { + const_CRA cra = st.first_model().find_cra(ad); + if (cra.atom) + return cra.atom->element.elem; + } + return infer_element_from_padded_name(name); + }; + // emulating names used in wwPDB mmCIFs (covaleN and metalcN) + if (is_metal(get_elem(&record[12], c.partner1)) || + is_metal(get_elem(&record[42], c.partner2))) { + c.name = "metalc" + std::to_string(++metalc_count); + c.type = Connection::MetalC; + } else { + c.name = "covale" + std::to_string(++covale_count); + c.type = Connection::Covale; + } + c.asu = compare_link_symops(record, c.reported_sym); + if (record.length() > 73) { + if (record[4] == 'R') + c.link_id = read_string(&record[72], 8); + else + c.reported_distance = read_double(&record[73], 5); + } + st.connections.emplace_back(c); + } else if (record[0] == 'C' || record[0] == 'c') { // CISPEP + if (record.length() < 22) + continue; + const char* r = record.c_str(); + CisPep cispep; + cispep.partner_c.chain_name = read_string(r + 14, 2); + cispep.partner_c.res_id = read_res_id(r + 17, r + 11); + cispep.partner_n.chain_name = read_string(r + 28, 2); + cispep.partner_n.res_id = read_res_id(r + 31, r + 25); + // In files with a single model in the PDB CISPEP modNum is 0, + // but _struct_mon_prot_cis.pdbx_PDB_model_num is 1. + cispep.model_num = st.models.size() == 1 ? st.models[0].num : read_int(r + 43, 3); + cispep.reported_angle = read_double(r + 53, 6); + st.cispeps.push_back(cispep); + } + } +} + +// move initials after comma, as in mmCIF (A.-B.DOE -> DOE, A.-B.), see +// https://www.wwpdb.org/documentation/file-format-content/format33/sect2.html#AUTHOR +void change_author_name_format_to_mmcif(std::string& name) { + // If the AUTHOR record has comma followed by space we get leading space here + while (name[0] == ' ') + name.erase(name.begin()); + size_t pos = 0; + // Initials may have multiple letters (e.g. JU. or PON.) + // but should not have space after dot. + for (size_t i = 1; i < pos+4 && i+1 < name.size(); ++i) + if (name[i] == '.' && name[i+1] != ' ') + pos = i+1; + if (pos > 0) + name = name.substr(pos) + ", " + name.substr(0, pos); +} + +// interprets subset of REMARKs from raw_remarks, filling in Metadata. +void read_metadata_from_remarks(Structure& st) { + std::string* possibly_unfinished_remark3 = nullptr; + std::string* cr_desc = nullptr; + Transform matrix; + for (const std::string& remark : st.raw_remarks) { + if (remark.size() <= 11) + continue; + const char* line = remark.c_str(); + int num = read_int(line + 7, 3); + switch (num) { + case 2: + if (st.resolution == 0.0 && std::strstr(line, "ANGSTROM")) + st.resolution = read_double(line + 23, 7); + break; + case 3: + read_remark3_line(line, st.meta, possibly_unfinished_remark3); + break; + case 200: + case 230: + case 240: + read_remark_200_230_240(line, st.meta, cr_desc); + break; + case 300: + if (!st.meta.remark_300_detail.empty()) { + st.meta.remark_300_detail += '\n'; + st.meta.remark_300_detail += rtrim_str(remark.substr(11)); + } else if (remark.compare(11, 7, "REMARK:") == 0) { + st.meta.remark_300_detail = trim_str(remark.substr(18)); + } + break; + case 350: { + const char* colon = std::strchr(line+11, ':'); + if (colon == line+22 && starts_with(line+11, "BIOMOLECULE")) { + st.assemblies.emplace_back(read_string(line+23, 20)); + continue; + } + if (st.assemblies.empty()) + continue; + Assembly& assembly = st.assemblies.back(); + auto r350_key = [&](int cpos, const char* text) { + return colon == line + cpos && starts_with(line+11, text); + }; + if (starts_with(line+11, " BIOMT")) { + if (read_matrix(matrix, line+13, remark.size()-13) == 3) + if (!assembly.generators.empty()) { + auto& opers = assembly.generators.back().operators; + opers.emplace_back(); + opers.back().name = read_string(line+20, 3); + opers.back().transform = matrix; + matrix.set_identity(); + } + } else if (r350_key(44, "AUTHOR DETERMINED")) { + assembly.author_determined = true; + assembly.oligomeric_details = read_string(line+45, 35); + } else if (r350_key(51, "SOFTWARE DETERMINED")) { + assembly.software_determined = true; + assembly.oligomeric_details = read_string(line+52, 28); + } else if (r350_key(24, "SOFTWARE USED")) { + assembly.software_name = read_string(line+25, 55); + } else if (r350_key(36, "TOTAL BURIED SURFACE AREA")) { + assembly.absa = read_double(line+37, 12); + } else if (r350_key(38, "SURFACE AREA OF THE COMPLEX")) { + assembly.ssa = read_double(line+39, 12); + } else if (r350_key(40, "CHANGE IN SOLVENT FREE ENERGY")) { + assembly.more = read_double(line+41, 12); + } else if (r350_key(40, "APPLY THE FOLLOWING TO CHAINS") || + r350_key(40, " AND CHAINS")) { + if (line[11] == 'A') // first line - APPLY ... + assembly.generators.emplace_back(); + else if (assembly.generators.empty()) + continue; + split_str_into_multi(read_string(line+41, 39), ", ", + assembly.generators.back().chains); + } + } + } + // if REMARK 2 was missing, try resolution from REMARK 3 + if (st.resolution == 0.0) { + for (const RefinementInfo& ref_info : st.meta.refinement) + if (!std::isnan(ref_info.resolution_high) && ref_info.resolution_high != 0.) { + st.resolution = ref_info.resolution_high; + break; + } + } + } +} + +} // anonymous namespace + +void populate_structure_from_pdb_stream(AnyStream& line_reader, const std::string& source, + Structure& st, + PdbReadOptions options) { + if (options.max_line_length <= 0 || options.max_line_length > 120) + options.max_line_length = 120; + st.input_format = CoorFormat::Pdb; + st.name = path_basename(source, {".gz", ".pdb"}); + Transform matrix; + std::vector conn_records; + std::unordered_map resmap; + Model *model = nullptr; + Chain *chain = nullptr; + Residue *resi = nullptr; + char line[122] = {0}; + int line_num = 0; + bool after_ter = false; + auto wrong = [&line_num](const std::string& msg) { + fail("Problem in line ", std::to_string(line_num), ": ", msg); + }; + while (size_t len = line_reader.copy_line(line, options.max_line_length+1)) { + ++line_num; + if (options.check_non_ascii && st.non_ascii_line == 0) + for (size_t i = 0; i < len; ++i) + if (static_cast(line[i]) >= 0x80) { + st.non_ascii_line = line_num; + break; + } + if (is_record_type4(line, "ATOM") || is_record_type4(line, "HETATM")) { + if (len < 55) + wrong("The line is too short to be correct:\n" + std::string(line)); + std::string chain_name = read_string(line+20, 2); + ResidueId rid = read_res_id(line+22, line+17); + + if (!chain || chain_name != chain->name) { + if (!model) { + // A single model usually doesn't have the MODEL record. Also, + // MD trajectories may have frames separated by ENDMDL without MODEL. + int num = (int) st.models.size() + 1; + if (st.find_model(num)) + wrong("ATOM/HETATM between models"); + st.models.emplace_back(num); + model = &st.models.back(); + } + const Chain* prev_part = model->find_chain(chain_name); + after_ter = prev_part && + prev_part->residues[0].entity_type == EntityType::Polymer; + model->chains.emplace_back(chain_name); + chain = &model->chains.back(); + resmap.clear(); + resi = nullptr; + } + // Non-standard but widely used 4-character segment identifier. + // Left-justified, and may include a space in the middle. + // The segment may be a portion of a chain or a complete chain. + if (len > 72) + rid.segment = read_string(line+72, 4); + if (!resi || !resi->matches(rid)) { + auto it = resmap.find(rid); + // In normal PDB files it is fast enough to use + // resi = chain->find_residue(rid); + // but in pseudo-PDB files (such as MD files where millions + // of residues are in the same "chain") it is too slow. + if (it == resmap.end()) { + resmap.emplace(rid, (int) chain->residues.size()); + chain->residues.emplace_back(rid); + resi = &chain->residues.back(); + + resi->het_flag = line[0] & ~0x20; + if (after_ter) + resi->entity_type = resi->is_water() ? EntityType::Water + : EntityType::NonPolymer; + } else { + resi = &chain->residues[it->second]; + } + } + + Atom atom; + atom.serial = read_serial(line+6); + atom.name = read_string(line+12, 4); + atom.altloc = read_altloc(line[16]); + atom.pos.x = read_double(line+30, 8); + atom.pos.y = read_double(line+38, 8); + atom.pos.z = read_double(line+46, 8); + if (len > 58) + atom.occ = (float) read_double(line+54, 6); + if (len > 64) + atom.b_iso = (float) read_double(line+60, 6); + if (len > 76 && (std::isalpha(line[76]) || std::isalpha(line[77]))) + atom.element = Element(line + 76); + else + atom.element = infer_element_from_padded_name(line+12); + atom.charge = (len > 78 ? read_charge(line[78], line[79]) : 0); + resi->atoms.emplace_back(atom); + + } else if (is_record_type4(line, "ANISOU")) { + if (!model || !chain || !resi || resi->atoms.empty()) + wrong("ANISOU record not directly after ATOM/HETATM."); + // We assume that ANISOU refers to the last atom. + // Can it not be the case? + Atom &atom = resi->atoms.back(); + if (atom.aniso.u11 != 0.) + wrong("Duplicated ANISOU record or not directly after ATOM/HETATM."); + atom.aniso.u11 = read_int(line+28, 7) * 1e-4f; + atom.aniso.u22 = read_int(line+35, 7) * 1e-4f; + atom.aniso.u33 = read_int(line+42, 7) * 1e-4f; + atom.aniso.u12 = read_int(line+49, 7) * 1e-4f; + atom.aniso.u13 = read_int(line+56, 7) * 1e-4f; + atom.aniso.u23 = read_int(line+63, 7) * 1e-4f; + + } else if (is_record_type4(line, "REMARK")) { + if (line[len-1] == '\n') + --len; + if (line[len-1] == '\r') + --len; + st.raw_remarks.emplace_back(line, line+len); + + } else if (is_record_type4(line, "CONECT")) { + int serial = read_serial(line+6); + if (len >= 11 && serial != 0) { + std::vector& bonded_atoms = st.conect_map[serial]; + int limit = std::min(27, (int)len - 1); + for (int offset = 11; offset <= limit; offset += 5) { + int n = read_serial(line+offset); + if (n != 0) + bonded_atoms.push_back(n); + } + } + + } else if (is_record_type4(line, "SEQRES")) { + std::string chain_name = read_string(line+10, 2); + Entity& ent = impl::find_or_add(st.entities, chain_name); + ent.entity_type = EntityType::Polymer; + for (int i = 19; i < 68 && i < (int)len; i += 4) { + std::string res_name = read_string(line+i, 3); + if (!res_name.empty()) + ent.full_sequence.emplace_back(res_name); + } + + } else if (is_record_type4(line, "HELIX")) { + if (len < 40) + continue; + Helix helix; + helix.start.chain_name = read_string(line+18, 2); + helix.start.res_id = read_res_id(line+21, line+15); + helix.end.chain_name = read_string(line+30, 2); + helix.end.res_id = read_res_id(line+33, line+27); + helix.set_helix_class_as_int(read_int(line+38, 2)); + if (len > 72) + helix.length = read_int(line+72, 5); + st.helices.emplace_back(helix); + + } else if (is_record_type4(line, "SHEET")) { + if (len < 40) + continue; + std::string sheet_id = read_string(line+11, 3); + Sheet& sheet = impl::find_or_add(st.sheets, sheet_id); + sheet.strands.emplace_back(); + Sheet::Strand& strand = sheet.strands.back(); + strand.start.chain_name = read_string(line+20, 2); + strand.start.res_id = read_res_id(line+22, line+17); + strand.end.chain_name = read_string(line+31, 2); + strand.end.res_id = read_res_id(line+33, line+28); + strand.sense = read_int(line+38, 2); + if (len > 67) { + // the SHEET record has no altloc for atoms of hydrogen bond + strand.hbond_atom2.atom_name = read_string(line+41, 4); + strand.hbond_atom2.chain_name = read_string(line+48, 2); + strand.hbond_atom2.res_id = read_res_id(line+50, line+45); + strand.hbond_atom1.atom_name = read_string(line+56, 4); + strand.hbond_atom1.chain_name = read_string(line+63, 2); + strand.hbond_atom1.res_id = read_res_id(line+65, line+60); + } + + } else if (is_record_type4(line, "SSBOND") || + is_record_type4(line, "LINK") || + is_record_type4(line, "CISPEP")) { + conn_records.emplace_back(line); + + } else if (is_record_type3(line, "TER") && !options.ignore_ter) { // finishes polymer chains + if (!chain || st.ter_status == 'e') + continue; + st.ter_status = 'y'; + if (options.split_chain_on_ter) { + chain = nullptr; + // split_chain_on_ter is used for AMBER files that can have TER records + // in various places. So in such case TER doesn't imply entity_type. + continue; + } + // If we have 2+ TER records in one chain, they are used in non-standard + // way and should be better ignored (in all the chains). + if (after_ter) { + st.ter_status = 'e'; // all entity_types will be later set to Unknown + continue; + } + for (Residue& res : chain->residues) { + res.entity_type = EntityType::Polymer; + // Sanity check: water should not be marked as a polymer. + if GEMMI_UNLIKELY(res.is_water()) + st.ter_status = 'e'; // all entity_types will be later set to Unknown + } + after_ter = true; + + } else if (is_record_type4(line, "MODRES")) { + ModRes modres; + modres.chain_name = read_string(line + 15, 2); + modres.res_id = read_res_id(line + 18, line + 12); + modres.parent_comp_id = read_string(line + 24, 3); + if (len >= 30) + // this field is named comment in PDB spec, but details in mmCIF + modres.details = read_string(line + 29, 41); + // Refmac's extension: 73-80 mod_id + // Check for spaces to make sure it's not an overflowed comment + if (len >= 73 && line[70] == ' ' && line[71] == ' ') + modres.mod_id = read_string(line + 72, 8); + st.mod_residues.push_back(modres); + + } else if (is_record_type4(line, "HETNAM")) { + if (len > 71 && line[70] == ' ') { + std::string full_code = read_string(line + 71, 8); + if (!full_code.empty()) + st.shortened_ccd_codes.emplace_back(full_code, read_string(line + 11, 3)); + } + + } else if (is_record_type4(line, "DBREF")) { // DBREF or DBREF1 or DBREF2 + std::string chain_name = read_string(line+11, 2); + Entity& ent = impl::find_or_add(st.entities, chain_name); + ent.entity_type = EntityType::Polymer; + if (line[5] == ' ' || line[5] == '1') + ent.dbrefs.emplace_back(); + else if (ent.dbrefs.empty()) // DBREF2 without DBREF1? + break; + Entity::DbRef& dbref = ent.dbrefs.back(); + if (line[5] == ' ' || line[5] == '1') { + dbref.seq_begin = read_seq_id(line+14); + dbref.seq_end = read_seq_id(line+20); + dbref.db_name = read_string(line+26, 6); + if (line[5] == ' ') { + dbref.accession_code = read_string(line+33, 8); + dbref.id_code = read_string(line+42, 12); + dbref.db_begin.num = read_int(line+55, 5); + dbref.db_begin.icode = line[60]; + dbref.db_end.num = read_int(line+62, 5); + dbref.db_end.icode = line[67]; + } else { // line[5] == '1' + dbref.id_code = read_string(line+47, 20); + } + } else if (line[5] == '2') { + dbref.accession_code = read_string(line+18, 22); + dbref.db_begin.num = read_int(line+45, 10); + dbref.db_end.num = read_int(line+57, 10); + } + + } else if (is_record_type4(line, "HEADER")) { + if (len > 50) + st.info["_struct_keywords.pdbx_keywords"] = rtrim_str(std::string(line+10, 40)); + if (len > 59) { // date in PDB has format 28-MAR-07 + std::string date = pdb_date_format_to_iso(std::string(line+50, 9)); + if (!date.empty()) + st.info["_pdbx_database_status.recvd_initial_deposition_date"] = date; + } + if (len > 66) { + std::string entry_id = rtrim_str(std::string(line+62, 4)); + if (!entry_id.empty()) + st.info["_entry.id"] = entry_id; + } + + } else if (is_record_type4(line, "TITLE")) { + if (len > 10) + st.info["_struct.title"] += rtrim_str(std::string(line+10, len-10-1)); + + } else if (is_record_type4(line, "KEYWDS")) { + if (len > 10) + st.info["_struct_keywords.text"] += rtrim_str(std::string(line+10, len-10-1)); + + } else if (is_record_type4(line, "EXPDTA")) { + if (len > 10) + st.info["_exptl.method"] += trim_str(std::string(line+10, len-10-1)); + + } else if (is_record_type4(line, "AUTHOR") && len > 10) { + std::string last; + if (!st.meta.authors.empty()) { + last = st.meta.authors.back(); + st.meta.authors.pop_back(); + } + size_t prev_size = st.meta.authors.size(); + const char* start = skip_blank(line+10); + const char* end = rtrim_cstr(start, line+len); + split_str_into(std::string(start, end), ',', st.meta.authors); + if (!last.empty() && st.meta.authors.size() > prev_size) { + // the spaces were trimmed, we may need a space between words + if (last.back() != '-' && last.back() != '.') + last += ' '; + st.meta.authors[prev_size].insert(0, last); + } + + } else if (is_record_type4(line, "SCALEn")) { + if (read_matrix(matrix, line, len) == 3) { + st.cell.set_matrices_from_fract(matrix); + matrix.set_identity(); + } + + } else if (is_record_type4(line, "ORIGX")) { + st.has_origx = true; + read_matrix(st.origx, line, len); + + } else if (is_record_type4(line, "CRYST1")) { + if (len > 54) + st.cell.set(read_double(line+6, 9), + read_double(line+15, 9), + read_double(line+24, 9), + read_double(line+33, 7), + read_double(line+40, 7), + read_double(line+47, 7)); + if (len > 56) + st.spacegroup_hm = read_string(line+55, 11); + if (len > 67) { + std::string z = read_string(line+66, 4); + if (!z.empty()) + st.info["_cell.Z_PDB"] = z; + } + + } else if (is_record_type4(line, "MTRIXn")) { + if (read_matrix(matrix, line, len) == 3) { + std::string id = read_string(line+7, 3); + if (matrix.is_identity()) { + // store only ID that will be used when writing to file + st.info["_struct_ncs_oper.id"] = id; + } else { + bool given = len > 59 && line[59] == '1'; + st.ncs.push_back({id, given, matrix}); + matrix.set_identity(); + } + } + } else if (is_record_type4(line, "MODEL")) { + if (model && chain) + wrong("MODEL without ENDMDL?"); + int num = read_int(line+6, 8); + model = &st.find_or_add_model(num); + if (!model->chains.empty()) + wrong("duplicate MODEL number: " + std::to_string(num)); + chain = nullptr; + + } else if (is_record_type4(line, "ENDMDL")) { + model = nullptr; + chain = nullptr; + + } else if (is_record_type3(line, "END")) { + break; + } else if (is_record_type4(line, "data")) { + if (line[4] == '_' && !model) + fail("Incorrect file format (perhaps it is cif not pdb?): " + source); + } else if (is_record_type4(line, "{\"da")) { + if (ialpha3_id(line+4) == ialpha3_id("ta_") && !model) + fail("Incorrect file format (perhaps it is mmJSON not pdb?): " + source); + } + } + // If we read a PDB header (they can be downloaded from RSCB) we have no + // models. User's code may not expect this. Usually, empty model will be + // handled more gracefully than no models. + if (st.models.empty()) + st.models.emplace_back(1); + + if (st.ter_status == 'e') + remove_entity_types(st); + + // Here we assign Residue::subchain, but only for chains with all + // Residue::entity_type assigned, i.e. for chains with TER. + assign_subchains(st, /*force=*/false, /*fail_if_unknown=*/false); + + for (Chain& ch : st.models[0].chains) + if (Entity* entity = st.get_entity(ch.name)) + if (auto polymer = ch.get_polymer()) + entity->subchains.emplace_back(polymer.subchain_id()); + + st.setup_cell_images(); + + process_conn(st, conn_records); + + for (std::string& name : st.meta.authors) + change_author_name_format_to_mmcif(name); + + if (!options.skip_remarks) + read_metadata_from_remarks(st); + + restore_full_ccd_codes(st); +} + +std::vector read_remark_290(const std::vector& raw_remarks) { + std::vector ops; + // we only check triplet notation: + // REMARK 290 NNNMMM OPERATOR + // REMARK 290 1555 X,Y,Z + for (const std::string& remark : raw_remarks) + if (remark.size() > 25 && std::memcmp(&remark[7], "290", 3) == 0 && + std::memcmp(&remark[10], " ", 5) == 0 && + std::memcmp(&remark[18], "555 ", 6) == 0) { + if (read_int(remark.c_str() + 15, 3) != (int)ops.size() + 1) + fail("Symmetry operators not in order?: " + remark); + Op op = parse_triplet(read_string(remark.c_str() + 24, 56)); + ops.push_back(op); + } + return ops; +} + +} // namespace gemmi diff --git a/gemmi_gph/polyheur.cpp b/gemmi_gph/polyheur.cpp new file mode 100644 index 00000000..af55fdb5 --- /dev/null +++ b/gemmi_gph/polyheur.cpp @@ -0,0 +1,463 @@ +// Copyright 2017-2022 Global Phasing Ltd. + +#include +#include // for find_tabulated_residue +#include // for rename_residues +#include // for no_sign_atoi + +namespace gemmi { + +PolymerType check_polymer_type(const ConstResidueSpan& span, bool ignore_entity_type) { + if (span.empty()) + return PolymerType::Unknown; + size_t counts[(int)ResidueKind::ELS+1] = {0}; + size_t aa = 0; + size_t na = 0; + size_t total = 0; + bool has_atom_record = false; + for (const Residue& r : span) + if (ignore_entity_type || + r.entity_type == EntityType::Unknown || + r.entity_type == EntityType::Polymer) { + if (r.het_flag == 'A') + has_atom_record = true; + const ResidueInfo& info = find_tabulated_residue(r.name); + if (info.found()) { + // Exclude water and ions - it can make difference + // if this function is called for the whole chain. + // Components w/o hydrogens are often ions and always non-polymers + // (and almost never are in a polymer - except PO4, PO2 and AZI (N3) + // which in a few PDB entries are included in polymers - but it + // doesn't matter here). + if (info.kind == ResidueKind::HOH || info.hydrogen_count == 0) + continue; + if (info.is_peptide_linking()) + ++aa; + if (info.is_na_linking()) + ++na; + counts[(int)info.kind]++; + } else if (r.get_ca()) { + ++aa; + } else if (r.get_p()) { + ++na; + } + ++total; + } + if (total == 0) + return PolymerType::Unknown; + // One residue is not a polymer, but it may happen that only a single residue + // of a chain is modelled. OTOH, a single non-standard residue is usually + // a ligand. + if (total == 1 && !has_atom_record) + return PolymerType::Unknown; + // ATOM records suggest a polymer, so weaken the condition for AA/NA polymers. + size_t bonus = has_atom_record ? 1 : 0; + if (2 * aa + bonus > total) + return counts[(int)ResidueKind::AA] >= counts[(int)ResidueKind::AAD] + ? PolymerType::PeptideL : PolymerType::PeptideD; + if (2 * na + bonus > total) { + if (counts[(int)ResidueKind::DNA] == 0) + return PolymerType::Rna; + if (counts[(int)ResidueKind::RNA] == 0) + return PolymerType::Dna; + return PolymerType::DnaRnaHybrid; + } + return PolymerType::Unknown; +} + +std::string make_one_letter_sequence(const ConstResidueSpan& polymer) { + std::string seq; + const Residue* prev = nullptr; + PolymerType ptype = check_polymer_type(polymer); + for (const Residue& residue : polymer.first_conformer()) { + ResidueInfo info = find_tabulated_residue(residue.name); + if (prev && !are_connected2(*prev, residue, ptype)) + seq += '-'; + seq += (info.one_letter_code != ' ' ? info.one_letter_code : 'X'); + prev = &residue; + } + return seq; +} + +static std::vector::iterator infer_polymer_end(Chain& chain, PolymerType ptype) { + auto b = chain.residues.begin(); + auto e = chain.residues.end(); + + // find the last residue w/ record type ATOM + auto last_a = e; + for (auto it = e; it-- != b;) + if (it->het_flag == 'A') + last_a = it; + + for (auto it = b; it != e; ++it) { + ResidueInfo info = find_tabulated_residue(it->name); + if (info.found()) { + if (info.is_water()) { + e = it; + break; + } + bool maybe_linking = (is_polypeptide(ptype) && info.is_peptide_linking()) + || (is_polynucleotide(ptype) && info.is_na_linking()); + // The first residue could be non-polymer. + if (!maybe_linking && b != chain.residues.begin()) { + e = it; + break; + } + // If a standard residue is HETATM, it should be in the buffer. Although + // it could happen that a non-standard residue was mutated to a standard + // one, but the record type was not updated, so it's not 100% reliable. + if (info.is_standard() && it->het_flag == 'H' && it > last_a) { + e = it; + break; + } + if (maybe_linking && info.is_standard()) + b = it; + } + } + if (b == e || b + 1 == e) + return e; + // Ligands are often separated by a significant gap in the sequence ID numeration. + // But such gap can also mean that part of the chain is not modelled. + auto last = std::min(e, chain.residues.end() - 1); + for (auto it = b; it < last; ++it) { + int gap = *(it+1)->seqid.num - *it->seqid.num; + // The gap should be non-negative, but you can find exceptions in the PDB. + if (gap < -1 || gap > 10) + return it+1; + // Usually polymers are longer than 1-2 residues, although there are + // exceptions (example: 1-residue polymers in 5N22), so we can't be sure. + // OTOH a protein can be capped with monomers different from amino-acid + // and are_connected2() may return false negative. So if there is no gap + // in numbering, it seems better to assume the polymer didn't end yet. + if (gap == 1 && it - chain.residues.begin() < 2) + continue; + if (!are_connected2(*it, *(it+1), ptype)) + return it+1; + } + return e; +} + +void add_entity_types(Chain& chain, bool overwrite) { + if (!overwrite && + std::all_of(chain.residues.begin(), chain.residues.end(), + [](const Residue& r) { return r.entity_type != EntityType::Unknown; })) + return; + PolymerType ptype = check_polymer_type(chain.whole(), /*ignore_entity_type=*/overwrite); + auto it = chain.residues.begin(); + if (ptype != PolymerType::Unknown) { + auto polymer_end = infer_polymer_end(chain, ptype); + for (; it != polymer_end; ++it) + if (overwrite || it->entity_type == EntityType::Unknown) + it->entity_type = EntityType::Polymer; + } + for (; it != chain.residues.end(); ++it) + if (overwrite || it->entity_type == EntityType::Unknown) + it->entity_type = it->is_water() ? EntityType::Water + : EntityType::NonPolymer; +} + +void add_entity_types(Structure& st, bool overwrite) { + for (Model& model : st.models) + for (Chain& chain : model.chains) + add_entity_types(chain, overwrite); +} + +void remove_entity_types(Structure& st) { + for (Model& model : st.models) + for (Chain& chain : model.chains) + for (Residue& res : chain.residues) + res.entity_type = EntityType::Unknown; +} + +void add_entity_ids(Structure& st, bool overwrite) { + for (Model& model : st.models) + for (Chain& chain : model.chains) + for (ResidueSpan& sub : chain.subchains()) { + if (Entity* ent = st.get_entity_of(sub)) { + for (Residue& res : sub) + if (overwrite || res.entity_id.empty()) + res.entity_id = ent->name; + } else if (overwrite) { + for (Residue& res : sub) + res.entity_id.clear(); + } + } +} + +void assign_subchain_names(Chain& chain, int& nonpolymer_counter) { + for (Residue& res : chain.residues) { + res.subchain = chain.name; + // We'd use '-' as a separator (A-p or B-4 is more clear), but although + // such names are valid in mmCIF, OneDep refuses to accept them. + res.subchain += "x"; + switch (res.entity_type) { + case EntityType::Polymer: + res.subchain += 'p'; + break; + case EntityType::NonPolymer: + ++nonpolymer_counter; + // to keep the name short use base36 for 2+ digit numbers: + // 1, 2, ..., 9, 00, 01, ..., 09, 0A, 0B, ..., 0Z, 10, ... + if (nonpolymer_counter < 10) { + res.subchain += char('0' + nonpolymer_counter); + } else { + const char base36[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + int n = nonpolymer_counter - 10; + if (n < 36) + res.subchain += '0'; + size_t pos = res.subchain.size(); + while (n != 0) { + res.subchain.insert(res.subchain.begin() + pos, base36[n % 36]); + n /= 36; + } + } + break; + case EntityType::Water: + res.subchain += 'w'; + break; + // In the wwPDB branched are kept each in separate auth/label chain. + // So we have one subchain in chain. + case EntityType::Branched: + res.subchain += 'b'; + break; + case EntityType::Unknown: + break; + } + } +} + +static std::pair has_entity_types_and_subchains(const Chain& chain) { + bool has_entity_types = true; + bool has_subchains = true; + for (const Residue& res : chain.residues) { + if (res.subchain.empty()) + has_subchains = false; + if (res.entity_type == EntityType::Unknown) + has_entity_types = false; + } + return {has_entity_types, has_subchains}; +} + +void assign_subchains(Structure& st, bool force, bool fail_if_unknown) { + for (Model& model : st.models) { + std::map counters; + for (Chain& chain : model.chains) { + auto has = has_entity_types_and_subchains(chain); + if (force || !has.second) { + if (has.first) // all chain's residues have known entity_type + assign_subchain_names(chain, counters[chain.name]); + else if (fail_if_unknown) + fail("assign_subchains(): missing entity_type in chain " + chain.name); + } + } + } +} + +void ensure_entities(Structure& st) { + for (Model& model : st.models) + for (Chain& chain : model.chains) + for (ResidueSpan& sub : chain.subchains()) { + Entity* ent = st.get_entity_of(sub); + if (!ent) { + EntityType etype = sub[0].entity_type; + std::string name = sub[0].entity_id; + if (name.empty()) { + if (etype == EntityType::Polymer || etype == EntityType::Branched) + name = chain.name; + else if (etype == EntityType::NonPolymer) + name = sub[0].name + "!"; + else if (etype == EntityType::Water) + name = "water"; + } + if (!name.empty()) { + ent = &impl::find_or_add(st.entities, name); + ent->entity_type = etype; + ent->subchains.push_back(sub.subchain_id()); + } + } + // ensure we have polymer_type set where needed + if (ent && ent->entity_type == EntityType::Polymer && + ent->polymer_type == PolymerType::Unknown) + ent->polymer_type = check_polymer_type(sub); + } +} + +static bool operator==(const Entity::DbRef& a, const Entity::DbRef& b) { + return a.db_name == b.db_name && + a.id_code == b.id_code && + a.isoform == b.isoform && + a.seq_begin == b.seq_begin && a.seq_end == b.seq_end && + a.db_begin == b.db_begin && a.db_end == b.db_end; +} + +void deduplicate_entities(Structure& st) { + for (auto i = st.entities.begin(); i != st.entities.end(); ++i) + if (!i->full_sequence.empty()) + for (auto j = i + 1; j != st.entities.end(); ++j) + if (j->polymer_type == i->polymer_type && + j->full_sequence == i->full_sequence && + j->dbrefs == i->dbrefs) { + vector_move_extend(i->subchains, std::move(j->subchains)); + st.entities.erase(j--); + } +} + +char recommended_het_flag(const Residue& res) { + if (res.entity_type == EntityType::Unknown) + return '\0'; + if (res.entity_type == EntityType::Polymer && + find_tabulated_residue(res.name).is_standard()) + return 'A'; + return 'H'; +} + +bool trim_to_alanine(Residue& res) { + static const std::pair ala_atoms[6] = { + {"N", El::N}, {"CA", El::C}, {"C", El::C}, {"O", El::O}, {"CB", El::C}, + {"OXT", El::O} + }; + if (res.get_ca() == nullptr) + return false; + vector_remove_if(res.atoms, [](const Atom& a) { + for (const auto& name_el : ala_atoms) + if (a.name == name_el.first && a.element == name_el.second) + return false; + return true; + }); + // if non-standard polymer residue was mutated, update het_flag + if (res.entity_type == EntityType::Polymer && res.het_flag == 'H') + res.het_flag = 'A'; + return true; +} + +template +static bool in_vector_at(T1& x, std::vector& v) { + for (const auto& el : v) + if (std::get(el) == x) + return true; + return false; +} + +void shorten_ccd_codes(Structure& st) { + // find all long residue names in both models and sequences + for (Model& model : st.models) + for (Chain& chain : model.chains) + for (Residue& res : chain.residues) + if (res.name.size() > 3 && !in_vector_at<0>(res.name, st.shortened_ccd_codes)) + st.shortened_ccd_codes.emplace_back(res.name, ""); + for (const Entity& ent : st.entities) + for (const std::string& mon_ids : ent.full_sequence) { + for (size_t start = 0;;) { + size_t end = mon_ids.find(',', start); + size_t len = std::min(end, mon_ids.size()) - start; + if (len > 3) { + std::string s(mon_ids, start, len); + if (!in_vector_at<0>(s, st.shortened_ccd_codes)) + st.shortened_ccd_codes.emplace_back(s, ""); + } + if (end == std::string::npos) + break; + start = end + 1; + } + } + // the first try on renaming: ABCDE -> ~DE + for (auto& old_new : st.shortened_ccd_codes) { + const std::string& old = old_new.first; + char short_code[4] = {'~', *(old.end()-2), *(old.end()-1), '\0'}; + if (!in_vector_at<1>(short_code, st.shortened_ccd_codes)) + old_new.second = short_code; + } + // pick a new residue name and call rename_residues() + int i = -1; + for (auto& old_new : st.shortened_ccd_codes) { + // If ~DE was not unique, use ~00, ~01, ... + // After ~99, the middle character will be punctuation or letter. + // After ~Z9 (430+ names), we give up and the names will be empty. + while (old_new.second.empty() && ++i < 'Z'*10) { + char short_code[4] = {'~', char('0' + i/10), char('0' + i%10), '\0'}; + if (!in_vector_at<1>(short_code, st.shortened_ccd_codes)) + old_new.second = short_code; + } + rename_residues(st, old_new.first, old_new.second); + } +} + +void restore_full_ccd_codes(Structure& st) { + for (const auto& item : st.shortened_ccd_codes) + rename_residues(st, item.second, item.first); + st.shortened_ccd_codes.clear(); +} + +// Unlike _entity_poly_seq, SEQRES doesn't contain alternative residue names. +// This function adds the alternative names to full_sequence. +static void add_microhetero_to_sequence(Entity& ent, ConstResidueSpan polymer) { + ent.reflects_microhetero = false; + if (ent.full_sequence.empty()) + return; + int max_n = -1; // max label_seq seen so far + for (const Residue& res : polymer) { + int n = *res.label_seq; + if (size_t(n-1) > ent.full_sequence.size()) + return; + std::string& seq_item = ent.full_sequence[n-1]; + if (n > max_n) { + if (!is_in_list(res.name, seq_item)) + return; + max_n = n; + } else { // n < max_n shouldn't happen + if (!is_in_list(res.name, seq_item)) + cat_to(seq_item, ',', res.name); + } + } + ent.reflects_microhetero = true; +} + +void add_microhetero_to_sequences(Structure& st, bool overwrite) { + if (st.models.empty()) + return; + for (Entity& ent : st.entities) { + if (ent.subchains.empty()) + continue; + ConstResidueSpan polymer = st.models[0].get_subchain(ent.subchains[0]); + if (!polymer || !polymer.front().label_seq) + continue; + if (overwrite || !ent.reflects_microhetero) + add_microhetero_to_sequence(ent, polymer); + } +} + +void add_tls_group_ids(Structure& st) { + std::vector* tls_groups = st.meta.get_tls_groups(); + if (!tls_groups) + return; + bool has_ids = false; + for (const Model& model : st.models) + for (const Chain& chain : model.chains) + for (const Residue& res : chain.residues) + for (const Atom& atom : res.atoms) + if (atom.tls_group_id >= 0) + has_ids = true; + if (has_ids) + return; + for (const TlsGroup& tls : *tls_groups) { + // assuming that _pdbx_refine_tls.id is a non-negative number + const char* endptr; + short tls_id = (short) no_sign_atoi(tls.id.c_str(), &endptr); + if (endptr == tls.id.c_str() || *endptr != '\0') + continue; + for (const TlsGroup::Selection& sel : tls.selections) { + // for now we don't use selection_details, only chains and sequence ids + for (Model& model : st.models) + for (Chain& chain : model.chains) + if (chain.name == sel.chain) { + for (Residue& res : chain.residues) + if (sel.res_begin <= res.seqid && res.seqid <= sel.res_end) { + for (Atom& atom : res.atoms) + atom.tls_group_id = tls_id; + } + } + } + } +} + +} // namespace gemmi diff --git a/gemmi_gph/resinfo.cpp b/gemmi_gph/resinfo.cpp new file mode 100644 index 00000000..264e0f54 --- /dev/null +++ b/gemmi_gph/resinfo.cpp @@ -0,0 +1,884 @@ +/// Copyright 2018-2022 Global Phasing Ltd. + +#include +#include // for is_space + +namespace gemmi { + +using RI = ResidueKind; + + + + size_t find_tabulated_residue_idx(const std::string& name) { + if (name.size() == 3) { + +#define ID(s) (((s)[0] << 16 | (s)[1] << 8 | (s)[2]) & ~0x202020) + //printf(">>> %x %x %x\n", ID(name.c_str()), ID("ALA"), ID("GLX")); + switch (ID(name.c_str())) { + case ID("ALA"): return 0; + case ID("ARG"): return 1; + case ID("ASN"): return 2; + case ID("ABA"): return 3; + case ID("ASP"): return 4; + case ID("ASX"): return 5; + case ID("CYS"): return 6; + case ID("CSH"): return 7; + case ID("GLN"): return 8; + case ID("GLU"): return 9; + case ID("GLX"): return 10; + case ID("GLY"): return 11; + case ID("HIS"): return 12; + case ID("ILE"): return 13; + case ID("LEU"): return 14; + case ID("LYS"): return 15; + case ID("MET"): return 16; + case ID("MSE"): return 17; + case ID("ORN"): return 18; + case ID("PHE"): return 19; + case ID("PRO"): return 20; + case ID("SER"): return 21; + case ID("THR"): return 22; + case ID("TRY"): + case ID("TRP"): return 23; + case ID("TYR"): return 24; + case ID("UNK"): return 25; + case ID("VAL"): return 26; + case ID("SEC"): return 27; + case ID("PYL"): return 28; + case ID("SEP"): return 29; + case ID("TPO"): return 30; + case ID("PCA"): return 31; + case ID("CSO"): return 32; + case ID("PTR"): return 33; + case ID("KCX"): return 34; + case ID("CSD"): return 35; + case ID("LLP"): return 36; + case ID("CME"): return 37; + case ID("MLY"): return 38; + case ID("DAL"): return 39; + case ID("TYS"): return 40; + case ID("OCS"): return 41; + case ID("M3L"): return 42; + case ID("FME"): return 43; + case ID("ALY"): return 44; + case ID("HYP"): return 45; + case ID("CAS"): return 46; + case ID("CRO"): return 47; + case ID("CSX"): return 48; + case ID("DPR"): return 49; + case ID("DGL"): return 50; + case ID("DVA"): return 51; + case ID("CSS"): return 52; + case ID("DPN"): return 53; + case ID("DSN"): return 54; + case ID("DLE"): return 55; + case ID("HIC"): return 56; + case ID("NLE"): return 57; + case ID("MVA"): return 58; + case ID("MLZ"): return 59; + case ID("CR2"): return 60; + case ID("SAR"): return 61; + case ID("DAR"): return 62; + case ID("DLY"): return 63; + case ID("YCM"): return 64; + case ID("NRQ"): return 65; + case ID("CGU"): return 66; + case ID("0TD"): return 67; + case ID("MLE"): return 68; + case ID("DAS"): return 69; + case ID("DTR"): return 70; + case ID("CXM"): return 71; + case ID("TPQ"): return 72; + case ID("DCY"): return 73; + case ID("DSG"): return 74; + case ID("DTY"): return 75; + case ID("DHI"): return 76; + case ID("MEN"): return 77; + case ID("DTH"): return 78; + case ID("SAC"): return 79; + case ID("DGN"): return 80; + case ID("AIB"): return 81; + case ID("SMC"): return 82; + case ID("IAS"): return 83; + case ID("CIR"): return 84; + case ID("BMT"): return 85; + case ID("DIL"): return 86; + case ID("FGA"): return 87; + case ID("PHI"): return 88; + case ID("CRQ"): return 89; + case ID("SME"): return 90; + case ID("GHP"): return 91; + case ID("MHO"): return 92; + case ID("NEP"): return 93; + case ID("TRQ"): return 94; + case ID("TOX"): return 95; + case ID("ALC"): return 96; + case ID("3FG"): return 97; + case ID("SCH"): return 98; + case ID("MDO"): return 99; + case ID("MAA"): return 100; + case ID("GYS"): return 101; + case ID("MK8"): return 102; + case ID("CR8"): return 103; + case ID("KPI"): return 104; + case ID("SCY"): return 105; + case ID("DHA"): return 106; + case ID("OMY"): return 107; + case ID("CAF"): return 108; + case ID("0AF"): return 109; + case ID("SNN"): return 110; + case ID("MHS"): return 111; + case ID("MLU"): return 112; + case ID("SNC"): return 113; + case ID("PHD"): return 114; + case ID("B3E"): return 115; + case ID("MEA"): return 116; + case ID("MED"): return 117; + case ID("OAS"): return 118; + case ID("GL3"): return 119; + case ID("FVA"): return 120; + case ID("PHL"): return 121; + case ID("CRF"): return 122; + case ID("OMZ"): return 123; + case ID("BFD"): return 124; + case ID("MEQ"): return 125; + case ID("DAB"): return 126; + case ID("AGM"): return 127; + case ID("PSU"): return 128; + case ID("5MU"): return 129; + case ID("7MG"): return 130; + case ID("OMG"): return 131; + case ID("UR3"): return 132; + case ID("OMC"): return 133; + case ID("2MG"): return 134; + case ID("H2U"): return 135; + case ID("4SU"): return 136; + case ID("OMU"): return 137; + case ID("4OC"): return 138; + case ID("MA6"): return 139; + case ID("M2G"): return 140; + case ID("1MA"): return 141; + case ID("6MZ"): return 142; + case ID("CCC"): return 143; + case ID("2MA"): return 144; + case ID("1MG"): return 145; + case ID("5BU"): return 146; + case ID("MIA"): return 147; + case ID("DOC"): return 148; + case ID("8OG"): return 149; + case ID("5CM"): return 150; + case ID("3DR"): return 151; + case ID("BRU"): return 152; + case ID("CBR"): return 153; + case ID("WAT"): + case ID("H2O"): + case ID("HOH"): return 154; + case ID("DOD"): return 155; + case ID("HEM"): return 156; + case ID("SO4"): return 157; + case ID("GOL"): return 158; + case ID("EDO"): return 159; + case ID("NAG"): return 160; + case ID("PO4"): return 161; + case ID("ACT"): return 162; + case ID("PEG"): return 163; + case ID("MAN"): return 164; + case ID("FAD"): return 165; + case ID("BMA"): return 166; + case ID("ADP"): return 167; + case ID("DMS"): return 168; + case ID("ACE"): return 169; + case ID("NH2"): return 170; + case ID("MPD"): return 171; + case ID("MES"): return 172; + case ID("NAD"): return 173; + case ID("NAP"): return 174; + case ID("TRS"): return 175; + case ID("ATP"): return 176; + case ID("PG4"): return 177; + case ID("GDP"): return 178; + case ID("FUC"): return 179; + case ID("FMT"): return 180; + case ID("GAL"): return 181; + case ID("PGE"): return 182; + case ID("FMN"): return 183; + case ID("PLP"): return 184; + case ID("EPE"): return 185; + case ID("SF4"): return 186; + case ID("BME"): return 187; + case ID("CIT"): return 188; + case ID("BE7"): return 189; + case ID("MRD"): return 190; + case ID("MHA"): return 191; + case ID("BU3"): return 192; + case ID("PGO"): return 193; + case ID("BU2"): return 194; + case ID("PDO"): return 195; + case ID("BU1"): return 196; + case ID("PG6"): return 197; + case ID("1BO"): return 198; + case ID("PE7"): return 199; + case ID("PG5"): return 200; + case ID("TFP"): return 201; + case ID("DHD"): return 202; + case ID("PEU"): return 203; + case ID("TAU"): return 204; + case ID("SBT"): return 205; + case ID("SAL"): return 206; + case ID("IOH"): return 207; + case ID("IPA"): return 208; + case ID("PIG"): return 209; + case ID("B3P"): return 210; + case ID("BTB"): return 211; + case ID("NHE"): return 212; + case ID("C8E"): return 213; + case ID("OTE"): return 214; + case ID("PE4"): return 215; + case ID("XPE"): return 216; + case ID("PE8"): return 217; + case ID("P33"): return 218; + case ID("N8E"): return 219; + case ID("2OS"): return 220; + case ID("1PS"): return 221; + case ID("CPS"): return 222; + case ID("DMX"): return 223; + case ID("MPO"): return 224; + case ID("GCD"): return 225; + case ID("DXG"): return 226; + case ID("CM5"): return 227; + case ID("ACA"): return 228; + case ID("ACN"): return 229; + case ID("CCN"): return 230; + case ID("GLC"): return 231; + case ID("DR6"): return 232; + case ID("NH4"): return 233; + case ID("AZI"): return 234; + case ID("BNG"): return 235; + case ID("BOG"): return 236; + case ID("BGC"): return 237; + case ID("BCN"): return 238; + case ID("BRO"): return 239; + case ID("CAC"): return 240; + case ID("CBX"): return 241; + case ID("ACY"): return 242; + case ID("CBM"): return 243; + case ID("CLO"): return 244; + case ID("3CO"): return 245; + case ID("NCO"): return 246; + case ID("CU1"): return 247; + case ID("CYN"): return 248; + case ID("MA4"): return 249; + case ID("TAR"): return 250; + case ID("GLO"): return 251; + case ID("MTL"): return 252; + case ID("SOR"): return 253; + case ID("DMU"): return 254; + case ID("DDQ"): return 255; + case ID("DMF"): return 256; + case ID("DIO"): return 257; + case ID("DOX"): return 258; + case ID("12P"): return 259; + case ID("SDS"): return 260; + case ID("LMT"): return 261; + case ID("EOH"): return 262; + case ID("EEE"): return 263; + case ID("EGL"): return 264; + case ID("FLO"): return 265; + case ID("TRT"): return 266; + case ID("FCY"): return 267; + case ID("FRU"): return 268; + case ID("GBL"): return 269; + case ID("GPX"): return 270; + case ID("HTO"): return 271; + case ID("HTG"): return 272; + case ID("B7G"): return 273; + case ID("C10"): return 274; + case ID("16D"): return 275; + case ID("HEZ"): return 276; + case ID("IOD"): return 277; + case ID("IDO"): return 278; + case ID("ICI"): return 279; + case ID("ICT"): return 280; + case ID("TLA"): return 281; + case ID("LAT"): return 282; + case ID("LBT"): return 283; + case ID("LDA"): return 284; + case ID("MN3"): return 285; + case ID("MRY"): return 286; + case ID("MOH"): return 287; + case ID("BEQ"): return 288; + case ID("C15"): return 289; + case ID("MG8"): return 290; + case ID("POL"): return 291; + case ID("NO3"): return 292; + case ID("JEF"): return 293; + case ID("P4C"): return 294; + case ID("CE1"): return 295; + case ID("DIA"): return 296; + case ID("CXE"): return 297; + case ID("IPH"): return 298; + case ID("PIN"): return 299; + case ID("15P"): return 300; + case ID("CRY"): return 301; + case ID("PGR"): return 302; + case ID("PGQ"): return 303; + case ID("SPD"): return 304; + case ID("SPK"): return 305; + case ID("SPM"): return 306; + case ID("SUC"): return 307; + case ID("TBU"): return 308; + case ID("TMA"): return 309; + case ID("TEP"): return 310; + case ID("SCN"): return 311; + case ID("TRE"): return 312; + case ID("ETF"): return 313; + case ID("144"): return 314; + case ID("UMQ"): return 315; + case ID("URE"): return 316; + case ID("YT3"): return 317; + case ID("ZN2"): return 318; + case ID("FE2"): return 319; + case ID("3NI"): return 320; + case ID("SIA"): return 321; + case ID("XYP"): return 322; + case ID("A2G"): return 323; + case ID("GLA"): return 324; + case ID("NDG"): return 325; + case ID("NGA"): return 326; +#undef ID + }} else if (name.size() == 1) { + switch (name[0]& ~0x20) { + case 'A': return 327; + case 'C': return 328; + case 'G': return 329; + case 'I': return 330; + case 'U': return 331; + case 'N': return 332; + case 'F': return 333; + case 'K': return 334; + } + } else if (name.size() == 2) { + if (name[0] == 'D' || name[0] == '+') + switch (name[1]) { + case 'A': return 335; + case 'C': return 336; + case 'G': return 337; + case 'I': return 338; + case 'T': return 339; + case 'U': return 340; + case 'N': return 341; + } +#define ID(s) ((s)[0] << 8 | (s)[1]) + switch (ID(name.c_str())) { + case ID("AG"): return 342; + case ID("AL"): return 343; + case ID("BA"): return 344; + case ID("BR"): return 345; + case ID("CA"): return 346; + case ID("CD"): return 347; + case ID("CL"): return 348; + case ID("CM"): return 349; + case ID("CN"): return 350; + case ID("CO"): return 351; + case ID("CS"): return 352; + case ID("CU"): return 353; + case ID("FE"): return 354; + case ID("HG"): return 355; + case ID("LI"): return 356; + case ID("MG"): return 357; + case ID("MN"): return 358; + case ID("NA"): return 359; + case ID("NI"): return 360; + case ID("NO"): return 361; + case ID("PB"): return 362; + case ID("RB"): return 363; + case ID("SR"): return 364; + case ID("Y1"): return 365; + case ID("ZN"): return 366; + } +#undef ID + } + return 367; + } + +ResidueInfo& get_residue_info(size_t idx) { + static ResidueInfo array[368] = { + // hydrogen_count needs to be verified + {"ALA", RI::AA, 1, 'A', 7, 89.0932f }, + {"ARG", RI::AA, 1, 'R', 15, 175.209f }, + {"ASN", RI::AA, 1, 'N', 8, 132.118f }, + {"ABA", RI::AA, 1, 'a', 9, 103.120f }, + {"ASP", RI::AA, 1, 'D', 7, 133.103f }, + {"ASX", RI::AA, 1, 'B', 6, 100.096f }, + {"CYS", RI::AA, 1, 'C', 7, 121.158f }, // also BUF + {"CSH", RI::AA, 1, 's', 17, 283.284f }, + {"GLN", RI::AA, 1, 'Q', 10, 146.144f }, + {"GLU", RI::AA, 1, 'E', 9, 147.129f }, + + {"GLX", RI::AA, 1, 'Z', 8, 114.123f }, + {"GLY", RI::AA, 1, 'G', 5, 75.0666f }, // also BUF + {"HIS", RI::AA, 1, 'H', 10, 156.162f }, + {"ILE", RI::AA, 1, 'I', 13, 131.173f }, + {"LEU", RI::AA, 1, 'L', 13, 131.173f }, + {"LYS", RI::AA, 1, 'K', 15, 147.196f }, + {"MET", RI::AA, 1, 'M', 11, 149.211f }, + {"MSE", RI::AA, 1, 'm', 11, 196.106f }, + {"ORN", RI::AA, 1, 'a', 12, 132.161f }, + {"PHE", RI::AA, 1, 'F', 11, 165.189f }, + //20 + + {"PRO", RI::AA, 1, 'P', 9, 115.130f }, + {"SER", RI::AA, 1, 'S', 7, 105.093f }, + {"THR", RI::AA, 1, 'T', 9, 119.119f }, + {"TRP", RI::AA, 1, 'W', 12, 204.225f }, + {"TYR", RI::AA, 1, 'Y', 11, 181.189f }, + {"UNK", RI::AA, 1, 'X', 9, 103.120f }, + {"VAL", RI::AA, 1, 'V', 11, 117.146f }, + {"SEC", RI::AA, 1, 'U', 7, 168.053f }, + {"PYL", RI::AA, 1, 'O', 21, 255.313f }, + {"SEP", RI::AA, 1, 's', 8, 185.072f }, + // 30 + + {"TPO", RI::AA, 1, 't', 10, 199.099f }, + {"PCA", RI::AA, 1, 'e', 7, 129.114f }, + {"CSO", RI::AA, 1, 'c', 7, 137.158f }, + {"PTR", RI::AA, 1, 'y', 12, 261.168f }, + {"KCX", RI::AA, 1, 'k', 14, 190.197f }, + {"CSD", RI::AA, 1, 'c', 7, 153.157f }, + {"LLP", RI::AA, 1, 'k', 22, 375.314f }, + {"CME", RI::AA, 1, 'c', 11, 197.276f }, + {"MLY", RI::AA, 1, 'k', 18, 174.241f }, + {"DAL", RI::AAD, 1, 'a', 7, 89.0932f }, + {"TYS", RI::AA, 1, 'y', 11, 261.252f }, + {"OCS", RI::AA, 1, 'c', 7, 169.156f }, + // 40 + + {"M3L", RI::AA, 1, 'k', 21, 189.275f }, + {"FME", RI::AA, 1, 'm', 11, 177.221f }, + {"ALY", RI::AA, 1, 'k', 16, 188.224f }, + {"HYP", RI::AA, 1, 'p', 9, 131.130f }, + {"CAS", RI::AA, 1, 'c', 12, 225.141f }, + {"CRO", RI::AA, 1, 't', 17, 319.313f }, + {"CSX", RI::AA, 1, 'c', 7, 137.158f }, + {"DPR", RI::AAD, 1, 'p', 9, 115.130f }, // also BUF + {"DGL", RI::AAD, 1, 'e', 9, 147.129f }, + {"DVA", RI::AAD, 1, 'v', 11, 117.146f }, + {"CSS", RI::AA, 1, 'c', 7, 153.223f }, + {"DPN", RI::AAD, 1, 'f', 11, 165.189f }, + {"DSN", RI::AAD, 1, 's', 7, 105.093f }, + // 50 + + {"DLE", RI::AAD, 1, 'l', 13, 131.173f }, + {"HIC", RI::AA, 1, 'h', 11, 169.181f }, + {"NLE", RI::AA, 1, 'l', 13, 131.173f }, + {"MVA", RI::AA, 1, 'v', 13, 131.173f }, + {"MLZ", RI::AA, 1, 'k', 16, 160.214f }, + {"CR2", RI::AA, 1, 'g', 13, 275.260f }, + {"SAR", RI::AA, 1, 'g', 7, 89.0932f }, + {"DAR", RI::AAD, 1, 'r', 15, 175.209f }, + {"DLY", RI::AAD, 1, 'k', 14, 146.188f }, + {"YCM", RI::AA, 1, 'c', 10, 178.209f }, + // 60 + + {"NRQ", RI::AA, 1, 'm', 17, 347.389f }, + {"CGU", RI::AA, 1, 'e', 9, 191.139f }, + {"0TD", RI::AA, 1, 'd', 9, 179.194f }, + {"MLE", RI::AA, 1, 'l', 15, 145.200f }, + {"DAS", RI::AAD, 1, 'd', 7, 133.103f }, + {"DTR", RI::AAD, 1, 'w', 12, 204.225f }, + {"CXM", RI::AA, 1, 'm', 11, 193.221f }, + {"TPQ", RI::AA, 1, 'y', 9, 211.171f }, + {"DCY", RI::AAD, 1, 'c', 7, 121.158f }, + {"DSG", RI::AAD, 1, 'n', 8, 132.118f }, + {"DTY", RI::AAD, 1, 'y', 11, 181.189f }, + // 70 + + {"DHI", RI::AAD, 1, 'h', 10, 156.162f }, + {"MEN", RI::AA, 1, 'n', 10, 146.144f }, + {"DTH", RI::AAD, 1, 't', 9, 119.119f }, + {"SAC", RI::AA, 1, 's', 9, 147.129f }, + {"DGN", RI::AAD, 1, 'q', 10, 146.144f }, + {"AIB", RI::AA, 1, 'a', 9, 103.120f }, + {"SMC", RI::AA, 1, 'c', 9, 135.185f }, + {"IAS", RI::AA, 1, 'd', 7, 133.103f }, + {"CIR", RI::AA, 1, 'r', 13, 175.186f }, + {"BMT", RI::AA, 1, 't', 19, 201.263f }, + {"DIL", RI::AAD, 1, 'i', 13, 131.173f }, + {"FGA", RI::AA, 1, 'e', 9, 147.129f }, + //80 + + {"PHI", RI::AA, 1, 'f', 10, 291.086f }, + {"CRQ", RI::AA, 1, 'q', 16, 344.322f }, + {"SME", RI::AA, 1, 'm', 11, 165.211f }, + {"GHP", RI::AA, 1, 'g', 9, 167.162f }, // d-peptide in CCD + {"MHO", RI::AA, 1, 'm', 11, 165.211f }, + {"NEP", RI::AA, 1, 'h', 10, 235.134f }, + {"TRQ", RI::AA, 1, 'w', 10, 234.208f }, + {"TOX", RI::AA, 1, 'w', 12, 236.224f }, + {"ALC", RI::AA, 1, 'a', 17, 171.237f }, + {"3FG", RI::AA, 1, ' ', 9, 183.161f }, + {"SCH", RI::AA, 1, 'c', 9, 167.250f }, + {"MDO", RI::AA, 1, 'a', 11, 197.191f }, + {"MAA", RI::AA, 1, 'a', 9, 103.120f }, + //90 + + {"GYS", RI::AA, 1, 's', 15, 305.286f }, + {"MK8", RI::AA, 1, 'l', 15, 145.200f }, + {"CR8", RI::AA, 1, 'h', 16, 354.340f }, + {"KPI", RI::AA, 1, 'k', 16, 216.234f }, + {"SCY", RI::AA, 1, 'c', 9, 163.195f }, + {"DHA", RI::AA, 1, 's', 5, 87.0773f }, + {"OMY", RI::AA, 1, 'y', 10, 231.633f }, + {"CAF", RI::AA, 1, 'c', 12, 241.140f }, + {"0AF", RI::AA, 1, 'w', 12, 220.225f }, + {"SNN", RI::AA, 1, 'n', 6, 114.103f }, + // 100 + + {"MHS", RI::AA, 1, 'h', 11, 169.181f }, + {"MLU", RI::AAD, 1, ' ', 15, 145.200f }, + {"SNC", RI::AA, 1, 'c', 6, 150.156f }, + {"PHD", RI::AA, 1, 'd', 8, 213.083f }, + {"B3E", RI::AA, 1, 'e', 11, 161.156f }, + {"MEA", RI::AA, 1, 'f', 13, 179.216f }, + {"MED", RI::AAD, 1, 'm', 11, 149.211f }, + {"OAS", RI::AA, 1, 's', 9, 147.129f }, + {"GL3", RI::AA, 1, 'g', 5, 91.1322f }, + {"FVA", RI::AA, 1, 'v', 11, 145.156f }, + // 110 + + {"PHL", RI::AA, 1, 'f', 13, 151.206f }, + {"CRF", RI::AA, 1, 't', 18, 342.349f }, + {"OMZ", RI::AA, 1, ' ', 10, 231.633f }, // d-peptide in CCD + {"BFD", RI::AA, 1, 'd', 6, 198.102f }, + {"MEQ", RI::AA, 1, 'q', 12, 160.171f }, + {"DAB", RI::AA, 1, 'a', 10, 118.134f }, + {"AGM", RI::AA, 1, 'r', 17, 189.235f }, + {"PSU", RI::RNA, 2, 'u', 13, 324.181f }, + {"5MU", RI::RNA, 2, 'u', 15, 338.208f }, + {"7MG", RI::RNA, 2, 'g', 18, 379.263f }, + // 120 + + {"OMG", RI::RNA, 2, 'g', 16, 377.247f }, + {"UR3", RI::RNA, 2, 'u', 15, 338.208f }, + {"OMC", RI::RNA, 2, 'c', 16, 337.223f }, + {"2MG", RI::RNA, 2, 'g', 16, 377.247f }, + {"H2U", RI::RNA, 2, 'u', 15, 326.197f }, + {"4SU", RI::RNA, 2, 'u', 13, 340.247f }, + {"OMU", RI::RNA, 2, 'u', 15, 338.208f }, + {"4OC", RI::RNA, 2, 'c', 18, 351.250f }, + {"MA6", RI::RNA, 2, 'a', 18, 375.274f }, + {"M2G", RI::RNA, 2, 'g', 18, 391.274f }, + // 130 + + {"1MA", RI::RNA, 2, 'a', 16, 361.248f }, + {"6MZ", RI::RNA, 2, 'a', 16, 361.248f }, + {"CCC", RI::RNA, 2, 'c', 13, 385.161f }, + {"2MA", RI::RNA, 2, 'a', 16, 361.248f }, + {"1MG", RI::RNA, 2, 'g', 16, 377.247f }, + {"5BU", RI::RNA, 2, 'u', 12, 403.077f }, + {"MIA", RI::RNA, 2, 'a', 24, 461.430f }, + {"DOC", RI::DNA, 2, 'c', 14, 291.198f }, + {"8OG", RI::DNA, 2, 'g', 14, 363.221f }, + {"5CM", RI::DNA, 2, 'c', 16, 321.224f }, + // 140 + + {"3DR", RI::DNA, 2, ' ', 11, 198.111f }, + {"BRU", RI::DNA, 2, 'u', 12, 387.078f }, + {"CBR", RI::DNA, 2, 'c', 13, 386.093f }, + {"HOH", RI::HOH, 0, ' ', 2, 18.0153f }, + {"DOD", RI::HOH, 0, ' ', 2, 20.0276f }, + {"HEM", RI::ELS, 0, ' ', 32, 616.487f }, + {"SO4", RI::BUF, 0, ' ', 0, 96.0626f }, + {"GOL", RI::BUF, 0, ' ', 8, 92.0938f }, + {"EDO", RI::BUF, 0, ' ', 6, 62.0678f }, + {"NAG", RI::PYR, 0, ' ', 15, 221.208f }, + // 150 + + {"PO4", RI::ELS, 0, ' ', 0, 94.9714f }, + {"ACT", RI::BUF, 0, ' ', 3, 59.0440f }, + {"PEG", RI::ELS, 0, ' ', 10, 106.120f }, + {"MAN", RI::PYR, 0, ' ', 12, 180.156f }, // also BUF + {"FAD", RI::ELS, 0, ' ', 33, 785.550f }, + {"BMA", RI::PYR, 0, ' ', 12, 180.156f }, // also BUF + {"ADP", RI::ELS, 0, ' ', 15, 427.201f }, + {"DMS", RI::BUF, 0, ' ', 6, 78.1334f }, + {"ACE", RI::ELS, 1, ' ', 4, 44.0526f }, + {"NH2", RI::ELS, 1, ' ', 2, 16.0226f }, // ? + {"MPD", RI::BUF, 0, ' ', 14, 118.174f }, + {"MES", RI::ELS, 0, ' ', 13, 195.237f }, + // 160 + + {"NAD", RI::ELS, 0, ' ', 27, 663.425f }, + {"NAP", RI::ELS, 0, ' ', 28, 743.405f }, + {"TRS", RI::BUF, 0, ' ', 12, 122.143f }, + {"ATP", RI::ELS, 0, ' ', 16, 507.181f }, + {"PG4", RI::ELS, 0, ' ', 18, 194.226f }, + {"GDP", RI::ELS, 2, 'g', 15, 443.201f }, // RNA in CCD + {"FUC", RI::PYR, 0, ' ', 12, 164.156f }, + {"FMT", RI::BUF, 0, ' ', 2, 46.0254f }, + {"GAL", RI::PYR, 0, ' ', 12, 180.156f }, + {"PGE", RI::BUF, 0, ' ', 14, 150.173f }, + {"FMN", RI::ELS, 0, ' ', 21, 456.344f }, + // 170 + + {"PLP", RI::ELS, 0, ' ', 10, 247.142f }, + {"EPE", RI::ELS, 0, ' ', 18, 238.305f }, + {"SF4", RI::ELS, 0, ' ', 0, 351.640f }, + {"BME", RI::ELS, 0, ' ', 6, 78.1334f }, + {"CIT", RI::BUF, 0, ' ', 8, 192.124f }, + {"BE7", RI::BUF, 0, ' ', 5, 357.156f }, + {"MRD", RI::BUF, 0, ' ', 14, 118.174f }, + {"MHA", RI::BUF, 0, ' ', 10, 190.154f }, + {"BU3", RI::BUF, 0, ' ', 10, 90.1210f }, + {"PGO", RI::BUF, 0, ' ', 8, 76.0944f }, + {"BU2", RI::BUF, 0, ' ', 10, 90.1210f }, + // 180 + + {"PDO", RI::BUF, 0, ' ', 8, 76.0944f }, + {"BU1", RI::BUF, 0, ' ', 10, 90.1210f }, + {"PG6", RI::BUF, 0, ' ', 26, 266.331f }, + {"1BO", RI::BUF, 0, ' ', 10, 74.1216f }, + {"PE7", RI::BUF, 0, ' ', 30, 342.449f }, + {"PG5", RI::BUF, 0, ' ', 18, 178.226f }, + {"TFP", RI::BUF, 0, ' ', 24, 407.496f }, + {"DHD", RI::BUF, 0, ' ', 4, 160.082f }, + {"PEU", RI::BUF, 0, ' ', 112, 1221.46f }, + {"TAU", RI::BUF, 0, ' ', 7, 125.147f }, + {"SBT", RI::BUF, 0, ' ', 10, 74.1216f }, + // 180 + + {"SAL", RI::BUF, 0, ' ', 6, 138.121f }, + {"IOH", RI::BUF, 0, ' ', 8, 60.0950f }, + {"IPA", RI::BUF, 0, ' ', 8, 60.0950f }, + {"PIG", RI::BUF, 0, ' ', 14, 150.173f }, + {"B3P", RI::BUF, 0, ' ', 26, 282.334f }, + {"BTB", RI::BUF, 0, ' ', 19, 209.240f }, + {"NHE", RI::BUF, 0, ' ', 17, 207.290f }, + {"C8E", RI::BUF, 0, ' ', 34, 306.438f }, + {"OTE", RI::BUF, 0, ' ', 34, 306.438f }, + {"PE4", RI::BUF, 0, ' ', 34, 354.436f }, + {"XPE", RI::BUF, 0, ' ', 42, 458.541f }, + // 200 + + {"PE8", RI::BUF, 0, ' ', 34, 370.436f }, + {"P33", RI::BUF, 0, ' ', 30, 326.383f }, + {"N8E", RI::BUF, 0, ' ', 38, 350.491f }, + {"2OS", RI::BUF, 0, ' ', 36, 468.493f }, + {"1PS", RI::BUF, 0, ' ', 11, 201.243f }, + {"CPS", RI::BUF, 0, ' ', 58, 614.877f }, + {"DMX", RI::BUF, 0, ' ', 19, 257.349f }, + {"MPO", RI::BUF, 0, ' ', 15, 209.263f }, + {"GCD", RI::PYR, 0, ' ', 8, 176.124f }, + {"DXG", RI::BUF, 0, ' ', 8, 192.124f }, + {"CM5", RI::BUF, 0, ' ', 42, 494.573f }, + // 210 + + {"ACA", RI::BUF, 1, ' ', 13, 131.173f }, // peptide linking + {"ACN", RI::BUF, 0, ' ', 6, 58.0791f }, + {"CCN", RI::BUF, 0, ' ', 3, 41.0519f }, + {"GLC", RI::PYR, 0, ' ', 12, 180.156f }, + {"DR6", RI::BUF, 0, ' ', 142, 1527.90f }, + {"NH4", RI::BUF, 0, ' ', 4, 18.0385f }, + {"AZI", RI::BUF, 0, ' ', 0, 42.0201f }, + {"BNG", RI::PYR, 0, ' ', 30, 306.395f }, + {"BOG", RI::PYR, 0, ' ', 28, 292.369f }, + {"BGC", RI::PYR, 0, ' ', 12, 180.156f }, + {"BCN", RI::BUF, 0, ' ', 13, 163.172f }, + // 220 + + {"BRO", RI::BUF, 0, ' ', 0, 79.9040f }, + {"CAC", RI::BUF, 0, ' ', 6, 136.989f }, + {"CBX", RI::BUF, 0, ' ', 2, 46.0254f }, + {"ACY", RI::BUF, 0, ' ', 4, 60.0520f }, + {"CBM", RI::BUF, 0, ' ', 4, 60.0520f }, + {"CLO", RI::BUF, 0, ' ', 0, 35.4530f }, + {"3CO", RI::BUF, 0, ' ', 0, 58.9332f }, + {"NCO", RI::BUF, 0, ' ', 18, 161.116f }, + {"CU1", RI::BUF, 0, ' ', 0, 63.5460f }, + {"CYN", RI::BUF, 0, ' ', 0, 26.0174f }, + {"MA4", RI::BUF, 0, ' ', 44, 508.600f }, + // 230 + + {"TAR", RI::BUF, 0, ' ', 6, 150.087f }, + {"GLO", RI::BUF, 0, ' ', 12, 180.156f }, // d-saccharide + {"MTL", RI::BUF, 0, ' ', 14, 182.172f }, + {"SOR", RI::BUF, 0, ' ', 14, 182.172f }, + {"DMU", RI::BUF, 0, ' ', 42, 482.562f }, // d-saccharide + {"DDQ", RI::BUF, 0, ' ', 27, 201.349f }, + {"DMF", RI::BUF, 0, ' ', 7, 73.0938f }, + {"DIO", RI::BUF, 0, ' ', 8, 88.1051f }, + {"DOX", RI::BUF, 0, ' ', 8, 88.1051f }, + {"12P", RI::BUF, 0, ' ', 50, 546.646f }, + {"SDS", RI::BUF, 0, ' ', 26, 266.397f }, + // 240 + + {"LMT", RI::BUF, 0, ' ', 46, 510.615f }, // d-saccharide + {"EOH", RI::BUF, 0, ' ', 6, 46.0684f }, + {"EEE", RI::BUF, 0, ' ', 8, 88.1051f }, + {"EGL", RI::BUF, 0, ' ', 6, 62.0678f }, + {"FLO", RI::BUF, 0, ' ', 0, 18.9984f }, + {"TRT", RI::BUF, 0, ' ', 36, 352.508f }, + {"FCY", RI::BUF, 0, ' ', 7, 121.158f }, + {"FRU", RI::BUF, 0, ' ', 12, 180.156f }, // saccharide + {"GBL", RI::BUF, 0, ' ', 6, 86.0892f }, + {"GPX", RI::BUF, 0, ' ', 14, 505.165f }, + {"HTO", RI::BUF, 0, ' ', 16, 148.200f }, + // 250 + + {"HTG", RI::BUF, 0, ' ', 26, 294.408f }, + {"B7G", RI::BUF, 0, ' ', 26, 278.342f }, + {"C10", RI::BUF, 0, ' ', 46, 422.596f }, + {"16D", RI::BUF, 0, ' ', 16, 116.205f }, + {"HEZ", RI::BUF, 0, ' ', 14, 118.174f }, + {"IOD", RI::BUF, 0, ' ', 0, 126.904f }, + {"IDO", RI::BUF, 0, ' ', 0, 126.904f }, + {"ICI", RI::BUF, 0, ' ', 8, 192.124f }, + {"ICT", RI::BUF, 0, ' ', 8, 192.124f }, + {"TLA", RI::BUF, 0, ' ', 6, 150.087f }, + {"LAT", RI::BUF, 0, ' ', 22, 342.296f }, // saccharide + // 260 + + {"LBT", RI::BUF, 0, ' ', 22, 342.296f }, // saccharide + {"LDA", RI::BUF, 0, ' ', 31, 229.402f }, + {"MN3", RI::BUF, 0, ' ', 0, 54.9380f }, + {"MRY", RI::BUF, 0, ' ', 10, 122.120f }, + {"MOH", RI::BUF, 0, ' ', 4, 32.0419f }, + {"BEQ", RI::BUF, 0, ' ', 38, 342.517f }, + {"C15", RI::BUF, 0, ' ', 38, 336.554f }, + {"MG8", RI::BUF, 0, ' ', 31, 321.410f }, + {"POL", RI::BUF, 0, ' ', 8, 60.0950f }, + {"NO3", RI::BUF, 0, ' ', 0, 62.0049f }, + {"JEF", RI::BUF, 0, ' ', 63, 597.822f }, + // 270 + + {"P4C", RI::BUF, 0, ' ', 28, 324.367f }, + {"CE1", RI::BUF, 0, ' ', 58, 538.755f }, + {"DIA", RI::BUF, 0, ' ', 20, 144.258f }, + {"CXE", RI::BUF, 0, ' ', 42, 378.544f }, + {"IPH", RI::BUF, 0, ' ', 6, 94.1112f }, + {"PIN", RI::BUF, 0, ' ', 18, 302.368f }, + {"15P", RI::BUF, 0, ' ', 140, 1529.83f }, + {"CRY", RI::BUF, 0, ' ', 8, 92.0938f }, + {"PGR", RI::BUF, 0, ' ', 8, 76.0944f }, + {"PGQ", RI::BUF, 0, ' ', 8, 76.0944f }, + {"SPD", RI::BUF, 0, ' ', 19, 145.246f }, + // 270 + + {"SPK", RI::BUF, 0, ' ', 30, 206.372f }, + {"SPM", RI::BUF, 0, ' ', 26, 202.340f }, + {"SUC", RI::PYR, 0, ' ', 22, 342.296f }, + {"TBU", RI::BUF, 0, ' ', 10, 74.1216f }, + {"TMA", RI::BUF, 0, ' ', 12, 74.1448f }, + {"TEP", RI::BUF, 0, ' ', 8, 180.164f }, + {"SCN", RI::BUF, 0, ' ', 0, 58.0824f }, + {"TRE", RI::PYR, 0, ' ', 22, 342.296f }, + {"ETF", RI::BUF, 0, ' ', 3, 100.040f }, + {"144", RI::BUF, 0, ' ', 12, 122.143f }, + {"UMQ", RI::BUF, 0, ' ', 44, 496.589f }, + // 280 + + {"URE", RI::BUF, 0, ' ', 4, 60.0553f }, + {"YT3", RI::BUF, 0, ' ', 0, 88.9059f }, + {"ZN2", RI::BUF, 0, ' ', 0, 65.3800f }, + {"FE2", RI::BUF, 0, ' ', 0, 55.8450f }, + {"3NI", RI::BUF, 0, ' ', 0, 58.6934f }, + {"SIA", RI::PYR, 0, ' ', 0, 0.0f }, + {"XYP", RI::PYR, 0, ' ', 0, 0.0f }, + {"A2G", RI::PYR, 0, ' ', 0, 0.0f }, + {"GLA", RI::PYR, 0, ' ', 0, 0.0f }, + {"NDG", RI::PYR, 0, ' ', 0, 0.0f }, + // 290 + {"NGA", RI::PYR, 0, ' ', 0, 0.0f }, + {"A", RI::RNA, 2, 'A', 14, 347.221f }, + {"C", RI::RNA, 2, 'C', 14, 323.197f }, + {"G", RI::RNA, 2, 'G', 14, 363.221f }, + {"I", RI::RNA, 2, 'I', 13, 348.206f }, + {"U", RI::RNA, 2, 'U', 13, 324.181f }, + {"N", RI::RNA, 2, 'N', 11, 214.11f }, + + {"F", RI::BUF, 0, ' ', 0, 18.9984f }, + {"K", RI::BUF, 0, ' ', 0, 39.0983f }, + {"DA", RI::DNA, 2, 'A', 14, 331.222f }, + // 300 + {"DC", RI::DNA, 2, 'C', 14, 307.197f }, + {"DG", RI::DNA, 2, 'G', 14, 347.221f }, + {"DI", RI::DNA, 2, 'I', 13, 332.207f }, + {"DT", RI::DNA, 2, 'T', 15, 322.208f }, + {"DU", RI::DNA, 2, 'U', 13, 308.182f }, + {"DN", RI::DNA, 2, 'N', 14, 198.111f }, // unknown DNA + {"AG", RI::BUF, 0, ' ', 0, 107.868f }, + {"AL", RI::BUF, 0, ' ', 0, 26.9815f }, + // 308 + + {"BA", RI::BUF, 0, ' ', 0, 137.327f }, + {"BR", RI::BUF, 0, ' ', 0, 79.9040f }, + {"CA", RI::BUF, 0, ' ', 0, 40.0780f }, + {"CD", RI::BUF, 0, ' ', 0, 112.411f }, + {"CL", RI::BUF, 0, ' ', 0, 35.4530f }, + {"CM", RI::BUF, 0, ' ', 4, 60.0520f }, + {"CN", RI::BUF, 0, ' ', 0, 27.0253f }, + {"CO", RI::BUF, 0, ' ', 0, 58.9332f }, + {"CS", RI::BUF, 0, ' ', 0, 132.905f }, + {"CU", RI::BUF, 0, ' ', 0, 63.5460f }, + {"FE", RI::BUF, 0, ' ', 0, 55.8450f }, + // 318 + + {"HG", RI::BUF, 0, ' ', 0, 200.590f }, + {"LI", RI::BUF, 0, ' ', 0, 6.94100f }, + {"MG", RI::BUF, 0, ' ', 0, 24.3050f }, + {"MN", RI::BUF, 0, ' ', 0, 54.9380f }, + {"NA", RI::BUF, 0, ' ', 0, 22.9898f }, + {"NI", RI::BUF, 0, ' ', 0, 58.6934f }, + {"NO", RI::ELS, 0, ' ', 0, 30.0061f }, + {"PB", RI::BUF, 0, ' ', 0, 207.200f }, + {"RB", RI::BUF, 0, ' ', 0, 85.4678f }, + {"SR", RI::BUF, 0, ' ', 0, 87.6200f }, + {"Y1", RI::BUF, 0, ' ', 0, 88.9059f }, + // 328 + + {"ZN", RI::BUF, 0, ' ', 0, 65.3800f }, + {"", RI::UNKNOWN, 0, ' ', 0, 0.0f } + }; + return array[idx]; +} + +ResidueInfo& find_tabulated_residue(const std::string& name) { + size_t idx = find_tabulated_residue_idx(name); + return get_residue_info(idx); +} + + +std::vector expand_one_letter_sequence(const std::string& seq, + ResidueKind kind) { + + std::vector r; + r.reserve(seq.size()); + auto kind_str = [&]() { + switch (kind) { + case ResidueKind::AA: return "peptide"; + case ResidueKind::RNA: return "RNA"; + case ResidueKind::DNA: return "DNA"; + default: return "unknown"; + } + }; + for (size_t i = 0; i != seq.size(); ++i) { + char c = seq[i]; + if (is_space(c)) + continue; + if (c == '(') { // special case, e.g. (MSE) + size_t start = i + 1; + i = seq.find(')', start); + if (i == std::string::npos) + gemmi::fail("unmatched '(' in sequence"); + r.emplace_back(seq, start, i - start); + } else { + const char* str = gemmi::expand_one_letter(c, kind); + if (str == nullptr) + gemmi::fail("unexpected letter in ", kind_str(), " sequence: ", c, + " (", std::to_string(int(c)), ')'); + r.emplace_back(str); + } + } + return r; +} +} -- 2.54.0 From 4c95a289b6d312af6c97db0985ceab50366c87ff Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Sun, 12 Jul 2026 21:08:59 +0200 Subject: [PATCH 02/17] scale_merge: harden R-free flag assignment (Bijvoet-safe, deterministic, stratified) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The R-free flags were drawn with a per-reflection std::bernoulli_distribution off a shared std::mt19937(12345). Two problems: in anomalous mode I(+) and I(-) are separate merged rows, so a Bijvoet pair could be split across the work and free sets (biasing R-free); and the draw order depended on merge/call order, so it was not reproducible or parallel-safe. The resolution-shell grouping was cosmetic — it still drew an independent Bernoulli per row, so it neither tied mates together nor guaranteed a per-shell quota. New AssignRfreeFlags() (own file RfreeFlags.{h,cpp}) replaces both call sites (stills MergeOnTheFly, rotation RotationScaleMerge). It: - keys each reflection by its Friedel-merged (Laue) ASU index, so all symmetry- and Friedel-equivalent reflections (including a Bijvoet pair) share one flag and are never split; - selects the free set by a deterministic splitmix64 hash of that key (same idiom as the CC1/2 half-set split), so it is reproducible run-to-run and order-independent; - stratifies by resolution: exactly round(fraction * n) of the distinct reflections in each of 20 shells is flagged free. Verified on an anomalous lyso merge (29536 refl, 5% free): 13548 Bijvoet pairs, 0 split; free fraction 4.6-5.4% in every resolution shell. Co-Authored-By: Claude Opus 4.8 (1M context) --- image_analysis/scale_merge/CMakeLists.txt | 2 + image_analysis/scale_merge/Merge.cpp | 38 +-------- image_analysis/scale_merge/RfreeFlags.cpp | 79 +++++++++++++++++++ image_analysis/scale_merge/RfreeFlags.h | 18 +++++ .../scale_merge/RotationScaleMerge.cpp | 23 +----- 5 files changed, 103 insertions(+), 57 deletions(-) create mode 100644 image_analysis/scale_merge/RfreeFlags.cpp create mode 100644 image_analysis/scale_merge/RfreeFlags.h diff --git a/image_analysis/scale_merge/CMakeLists.txt b/image_analysis/scale_merge/CMakeLists.txt index dcac4989..ea3e655a 100644 --- a/image_analysis/scale_merge/CMakeLists.txt +++ b/image_analysis/scale_merge/CMakeLists.txt @@ -13,6 +13,8 @@ ADD_LIBRARY(JFJochScaleMerge ResolutionCutoff.h HKLKey.cpp HKLKey.h + RfreeFlags.cpp + RfreeFlags.h ScalingResult.h ScalingResult.cpp) TARGET_LINK_LIBRARIES(JFJochScaleMerge Ceres::ceres Eigen3::Eigen JFJochCommon) diff --git a/image_analysis/scale_merge/Merge.cpp b/image_analysis/scale_merge/Merge.cpp index 9228fb67..9bfa9b62 100644 --- a/image_analysis/scale_merge/Merge.cpp +++ b/image_analysis/scale_merge/Merge.cpp @@ -17,6 +17,7 @@ #include "../../common/ResolutionShells.h" #include "../../common/Definitions.h" #include "HKLKey.h" +#include "RfreeFlags.h" namespace { // Deterministic CC1/2 half-set assignment: a splitmix64 bit-mix of the image's stable index. @@ -438,9 +439,6 @@ bool MergeOnTheFly::Mask(const IntegrationOutcome &outcome, bool cc_mask) { std::vector MergeOnTheFly::ExportReflections() { std::unique_lock ul(merged_mutex); - float d_min = std::numeric_limits::max(); - float d_max = 0.0f; - std::vector out; out.reserve(accumulator.size()); for (const auto &accum: accumulator | std::views::values) { @@ -470,42 +468,10 @@ std::vector MergeOnTheFly::ExportReflections() { if (!std::isfinite(accum.d) || accum.d <= 0.0f) continue; - d_min = std::min(d_min, accum.d); - d_max = std::max(d_max, accum.d); - out.emplace_back(mr); } - const double rfree_fraction = scaling_settings.GetRfreeFraction(); - if (rfree_fraction > 0.0 && !out.empty()) { - if (d_min < d_max && d_min > 0.0f) { - constexpr int n_shells = 20; - - const float d_min_pad = d_min * 0.999f; - const float d_max_pad = d_max * 1.001f; - - ResolutionShells shells(d_min_pad, d_max_pad, n_shells); - std::vector> shell_groups(n_shells); - - for (size_t i = 0; i < out.size(); ++i) { - const auto shell = shells.GetShell(out[i].d); - if (!shell.has_value()) - continue; - - const int s = *shell; - if (s >= 0 && s < n_shells) - shell_groups[s].push_back(i); - } - - std::mt19937 rfree_rng(12345u); - std::bernoulli_distribution rfree_dist(rfree_fraction); - - for (const auto &group: shell_groups) { - for (const size_t idx: group) - out[idx].rfree_flag = rfree_dist(rfree_rng); - } - } - } + AssignRfreeFlags(out, space_group_number, scaling_settings.GetRfreeFraction()); return out; } diff --git a/image_analysis/scale_merge/RfreeFlags.cpp b/image_analysis/scale_merge/RfreeFlags.cpp new file mode 100644 index 00000000..e039113f --- /dev/null +++ b/image_analysis/scale_merge/RfreeFlags.cpp @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#include "RfreeFlags.h" + +#include +#include +#include +#include + +#include "HKLKey.h" +#include "../../common/ResolutionShells.h" + +namespace { + // splitmix64 bit-mix of a key -> uniform double in [0, 1). Same key -> same value, so all + // mates of a reflection (which share the Laue-ASU key) get the same draw. Same idiom as the + // CC1/2 half-set split (HalfForImage in Merge.cpp). + double UniformFromKey(uint64_t key) { + uint64_t z = key + 0x9e3779b97f4a7c15ULL; + z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9ULL; + z = (z ^ (z >> 27)) * 0x94d049bb133111ebULL; + z = z ^ (z >> 31); + return static_cast(z >> 11) * (1.0 / 9007199254740992.0); + } +} + +void AssignRfreeFlags(std::vector &merged, int32_t space_group_number, + double rfree_fraction) { + for (auto &r : merged) + r.rfree_flag = false; + if (rfree_fraction <= 0.0 || merged.empty()) + return; + + // Friedel-merged (Laue) ASU key: I(+)/I(-) and every symmetry mate collapse to one key, so a + // reflection's free/work status is a pure function of this key -> mates never split. + const HKLKeyGenerator laue_key(/*merge_friedel=*/true, space_group_number); + + float d_min = std::numeric_limits::max(), d_max = 0.0f; + for (const auto &r : merged) + if (std::isfinite(r.d) && r.d > 0.0f) { + d_min = std::min(d_min, r.d); + d_max = std::max(d_max, r.d); + } + + // No usable resolution: fall back to a global deterministic hash (still mate-consistent). + if (!(d_min < d_max && d_min > 0.0f)) { + for (auto &r : merged) + r.rfree_flag = UniformFromKey(laue_key(r).pack()) < rfree_fraction; + return; + } + + // Stratify by resolution: within each shell take exactly round(fraction * n) of the distinct + // Laue reflections as the free set, chosen by hash rank. This guarantees ~fraction of the data + // is free in every shell, deterministically and without splitting any Bijvoet/symmetry set. + constexpr int n_shells = 20; + ResolutionShells shells(d_min * 0.999f, d_max * 1.001f, n_shells); + std::vector>> shell_keys(n_shells); // (hash, key) + std::unordered_set seen; + seen.reserve(merged.size()); + for (const auto &r : merged) { + const uint64_t key = laue_key(r).pack(); + if (!seen.insert(key).second) // count each Laue set once (Bijvoet mates share a key) + continue; + const auto shell = shells.GetShell(r.d); + if (shell) + shell_keys[*shell].push_back({UniformFromKey(key), key}); + } + + std::unordered_set free_keys; + free_keys.reserve(static_cast(rfree_fraction * seen.size()) + 1); + for (auto &keys : shell_keys) { + std::sort(keys.begin(), keys.end()); + const auto n_free = static_cast(std::llround(rfree_fraction * keys.size())); + for (size_t i = 0; i < n_free; ++i) + free_keys.insert(keys[i].second); + } + for (auto &r : merged) + r.rfree_flag = free_keys.count(laue_key(r).pack()) > 0; +} diff --git a/image_analysis/scale_merge/RfreeFlags.h b/image_analysis/scale_merge/RfreeFlags.h new file mode 100644 index 00000000..6a51ed43 --- /dev/null +++ b/image_analysis/scale_merge/RfreeFlags.h @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#pragma once + +#include +#include + +#include "../../common/Reflection.h" + +// Assign R-free (test-set) flags to merged reflections. Two guarantees: +// - All symmetry- and Friedel-equivalent reflections share one flag (the flag is a pure function +// of the Friedel-merged Laue-ASU key), so a Bijvoet pair I(+)/I(-) is never split across sets, +// and the assignment is reproducible run-to-run and independent of merge/call order. +// - The free set is stratified across resolution: ~rfree_fraction of the distinct reflections in +// every resolution shell is flagged free. +void AssignRfreeFlags(std::vector &merged, int32_t space_group_number, + double rfree_fraction); diff --git a/image_analysis/scale_merge/RotationScaleMerge.cpp b/image_analysis/scale_merge/RotationScaleMerge.cpp index 1b479b1a..71ec34d9 100644 --- a/image_analysis/scale_merge/RotationScaleMerge.cpp +++ b/image_analysis/scale_merge/RotationScaleMerge.cpp @@ -16,6 +16,7 @@ #include #include "HKLKey.h" +#include "RfreeFlags.h" #include "ResolutionCutoff.h" #include "../../common/CorrelationCoefficient.h" #include "../../common/CrystalLattice.h" @@ -1297,28 +1298,8 @@ RotationScaleMerge::Result RotationScaleMerge::MergeAndStats(int n_groups, bool // the space-group search still sees the full range. const std::optional effective_d_min = ApplyResolutionCutoff( result.merged, d_min_limit, resolution_cutoff_method, resolution_cc_target, for_search, logger); - if (effective_d_min) { - // Recompute the merged resolution span for the R-free binning below over the trimmed set. - d_min = std::numeric_limits::max(); d_max = 0.0f; - for (const auto &m : result.merged) { - if (!std::isfinite(m.d) || m.d <= 0.0f) continue; - d_min = std::min(d_min, m.d); d_max = std::max(d_max, m.d); - } - } - if (rfree_fraction > 0.0 && !result.merged.empty() && d_min < d_max && d_min > 0.0f) { - constexpr int n_shells = 20; - ResolutionShells shells(d_min * 0.999f, d_max * 1.001f, n_shells); - std::vector> shell_groups(n_shells); - for (size_t i = 0; i < result.merged.size(); ++i) { - const auto shell = shells.GetShell(result.merged[i].d); - if (shell && *shell >= 0 && *shell < n_shells) shell_groups[*shell].push_back(i); - } - std::mt19937 rng(12345u); - std::bernoulli_distribution dist(rfree_fraction); - for (const auto &grp : shell_groups) - for (const size_t i : grp) result.merged[i].rfree_flag = dist(rng); - } + AssignRfreeFlags(result.merged, x.GetSpaceGroupNumber().value_or(1), rfree_fraction); if (reject_count > 0) logger.Info("Merge outlier rejection: dropped {} observations", reject_count); -- 2.54.0 From 87af0f419bd167bece314ea2c9b15e722cb8c7eb Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Sun, 12 Jul 2026 21:12:26 +0200 Subject: [PATCH 03/17] scale_merge: compute French-Wilson amplitudes at end of merge, write them alongside intensities Merged reflections carried only intensities; a naive sqrt(max(I,0)) turns every weak or negative measurement into a biased (or zero) amplitude. Add a French-Wilson step so the merged reflections themselves carry proper Bayesian amplitudes. New FrenchWilson.{h,cpp}: ApplyFrenchWilson() fills F and sigmaF on each MergedReflection with the posterior mean |F| given I and its sigma under the Wilson prior: - correct centric vs acentric prior (gemmi is_reflection_centric); - epsilon (symmetry-enhancement) multiplicity: the shell mean is and the prior mean for a reflection is eps * ; - numerically stable log-shift integration of the posterior; - strong reflections (I > 4 sigma) short-circuit to sqrt(I) where the FW bias is negligible; - unusable I/sigma falls back to sqrt(max(I,0)). It runs as the last step of the merge routine (both MergeOnTheFly and RotationScaleMerge), so F/sigmaF are part of the merged result and downstream consumers (file writing AND, later, map calculation) all use the same amplitudes rather than each recomputing FW and risking divergence. The writers just emit the fields; extended additively: - MTZ: F (F) + SIGF (Q) columns; - mmCIF: _refln.F_meas_au / _refln.F_meas_sigma_au; - HKL text: F sigmaF appended (rfree flag kept in place for back-compat). Existing intensity columns are unchanged, so current readers keep working while phenix.refine (etc.) can now refine against the amplitudes. Verified on lyso (1.3 A): all 173 negative-intensity reflections get F>0, strong reflections have F/sqrt(I)=1.000, no NaN, and the mmCIF _refln loop is well-formed. Co-Authored-By: Claude Opus 4.8 (1M context) --- common/Reflection.h | 2 + image_analysis/WriteReflections.cpp | 13 +- image_analysis/scale_merge/CMakeLists.txt | 2 + image_analysis/scale_merge/FrenchWilson.cpp | 131 ++++++++++++++++++ image_analysis/scale_merge/FrenchWilson.h | 24 ++++ image_analysis/scale_merge/Merge.cpp | 2 + .../scale_merge/RotationScaleMerge.cpp | 2 + 7 files changed, 174 insertions(+), 2 deletions(-) create mode 100644 image_analysis/scale_merge/FrenchWilson.cpp create mode 100644 image_analysis/scale_merge/FrenchWilson.h diff --git a/common/Reflection.h b/common/Reflection.h index 4f7b4251..bf78ee49 100644 --- a/common/Reflection.h +++ b/common/Reflection.h @@ -43,6 +43,8 @@ struct MergedReflection { float sigma_half[2] = {NAN, NAN}; float d = 0.0; bool rfree_flag = false; + float F = NAN; // French-Wilson amplitude |F| (filled by ApplyFrenchWilson at end of merge) + float sigmaF = NAN; // its sigma }; diff --git a/image_analysis/WriteReflections.cpp b/image_analysis/WriteReflections.cpp index c2e10d7d..b01bc0a7 100644 --- a/image_analysis/WriteReflections.cpp +++ b/image_analysis/WriteReflections.cpp @@ -181,6 +181,8 @@ void WriteMmcifReflections(const std::vector &reflections, out << "_refln.index_l\n"; out << "_refln.intensity_meas\n"; out << "_refln.intensity_sigma\n"; + out << "_refln.F_meas_au\n"; + out << "_refln.F_meas_sigma_au\n"; out << "_refln.status_free\n"; out << "_refln.status\n"; @@ -190,6 +192,8 @@ void WriteMmcifReflections(const std::vector &reflections, << std::setw(5) << r.l << " " << std::setw(14) << Fmt(r.I, 4) << " " << std::setw(14) << Fmt(r.sigma, 4) << " " + << std::setw(14) << Fmt(r.F, 4) << " " + << std::setw(14) << Fmt(r.sigmaF, 4) << " " << (r.rfree_flag ? 1 : 0) << " " << "o" // 'o' = observed << "\n"; @@ -224,13 +228,15 @@ void WriteMtzReflections(const std::vector &reflections, mtz.add_column("L", 'H', dataset_id, -1, false); mtz.add_column("IMEAN", 'J', dataset_id, -1, false); mtz.add_column("SIGIMEAN", 'Q', dataset_id, -1, false); + mtz.add_column("F", 'F', dataset_id, -1, false); // French-Wilson amplitude + mtz.add_column("SIGF", 'Q', dataset_id, -1, false); mtz.add_column("FreeR_flag", 'I', dataset_id, -1, false); // Number of rows mtz.nreflections = reflections.size(); // Allocate data table - mtz.data.reserve(reflections.size() * 6); + mtz.data.reserve(reflections.size() * 8); for (const auto& r : reflections) { mtz.data.push_back(static_cast(r.h)); @@ -238,6 +244,8 @@ void WriteMtzReflections(const std::vector &reflections, mtz.data.push_back(static_cast(r.l)); mtz.data.push_back(r.I); mtz.data.push_back(r.sigma); + mtz.data.push_back(r.F); + mtz.data.push_back(r.sigmaF); mtz.data.push_back(r.rfree_flag ? 1 : 0); } @@ -254,7 +262,8 @@ void WriteHKLReflections(const std::vector &reflections, for (const auto &r: reflections) hkl_file << r.h << " " << r.k << " " << r.l << " " << r.I << " " << r.sigma << " " - << (r.rfree_flag ? 1 : 0) << std::endl; + << (r.rfree_flag ? 1 : 0) << " " + << r.F << " " << r.sigmaF << std::endl; hkl_file.close(); } diff --git a/image_analysis/scale_merge/CMakeLists.txt b/image_analysis/scale_merge/CMakeLists.txt index ea3e655a..9eb12b27 100644 --- a/image_analysis/scale_merge/CMakeLists.txt +++ b/image_analysis/scale_merge/CMakeLists.txt @@ -15,6 +15,8 @@ ADD_LIBRARY(JFJochScaleMerge HKLKey.h RfreeFlags.cpp RfreeFlags.h + FrenchWilson.cpp + FrenchWilson.h ScalingResult.h ScalingResult.cpp) TARGET_LINK_LIBRARIES(JFJochScaleMerge Ceres::ceres Eigen3::Eigen JFJochCommon) diff --git a/image_analysis/scale_merge/FrenchWilson.cpp b/image_analysis/scale_merge/FrenchWilson.cpp new file mode 100644 index 00000000..0f3a5d8c --- /dev/null +++ b/image_analysis/scale_merge/FrenchWilson.cpp @@ -0,0 +1,131 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#include "FrenchWilson.h" + +#include +#include +#include +#include + +#include "../../common/ResolutionShells.h" +#include "gemmi/symmetry.hpp" + +namespace { + +struct Posterior { + double mean_I; // (posterior mean true intensity) + double mean_F; // <|F|> (posterior mean amplitude) +}; + +// Posterior moments of the true intensity J >= 0 given a measurement I +/- sigma and the Wilson +// prior with mean sigma_wilson. Integrated numerically over J in [0, I + 8 sigma] with a log-shift +// so the exponentials never overflow/underflow. acentric: p(J) ~ exp(-J/S); centric: +// p(J) ~ exp(-J/2S)/sqrt(J). +Posterior integrate_posterior(double I, double sigma, double sigma_wilson, bool centric, int npts) { + const double inv_2s2 = 1.0 / (2.0 * sigma * sigma); + const double j_max = std::max(I, 0.0) + 8.0 * sigma; + const double dj = j_max / npts; + + std::vector logw(npts); + double max_logw = -std::numeric_limits::infinity(); + for (int i = 0; i < npts; ++i) { + const double j = (i + 0.5) * dj; + const double diff = I - j; + const double log_prior = centric ? (-j / (2.0 * sigma_wilson) - 0.5 * std::log(j)) + : (-j / sigma_wilson); + logw[i] = log_prior - diff * diff * inv_2s2; + max_logw = std::max(max_logw, logw[i]); + } + + double sum_w = 0, sum_wI = 0, sum_wF = 0; + for (int i = 0; i < npts; ++i) { + const double j = (i + 0.5) * dj; + const double w = std::exp(logw[i] - max_logw); + if (!std::isfinite(w)) + continue; + sum_w += w; + sum_wI += w * j; + sum_wF += w * std::sqrt(j); + } + if (sum_w <= 0.0) { + const double j = std::max(I, 0.0); + return {j, std::sqrt(j)}; + } + return {sum_wI / sum_w, sum_wF / sum_w}; +} + +} // namespace + +void ApplyFrenchWilson(std::vector &merged, int32_t space_group_number, + const FrenchWilsonOptions &opts) { + auto naive = [](MergedReflection &r) { + const double ip = std::max(r.I, 0.0f); + r.F = static_cast(std::sqrt(ip)); + r.sigmaF = (ip > 0.0 && std::isfinite(r.sigma)) ? static_cast(r.sigma / (2.0 * std::sqrt(ip))) + : NAN; + }; + + const gemmi::SpaceGroup *sg = gemmi::find_spacegroup_by_number(space_group_number); + if (sg == nullptr || merged.empty()) { + for (auto &r : merged) naive(r); + return; + } + const gemmi::GroupOps gops = sg->operations(); + + float d_min = std::numeric_limits::max(), d_max = 0.0f; + for (const auto &r : merged) + if (std::isfinite(r.d) && r.d > 0.0f) { + d_min = std::min(d_min, r.d); + d_max = std::max(d_max, r.d); + } + if (!(d_min < d_max && d_min > 0.0f)) { + for (auto &r : merged) naive(r); + return; + } + + // Wilson mean intensity per resolution shell. + ResolutionShells shells(d_min * 0.999f, d_max * 1.001f, opts.num_shells); + std::vector shell_sum(opts.num_shells, 0.0); + std::vector shell_count(opts.num_shells, 0); + double global_sum = 0.0; + int global_count = 0; + auto epsilon = [&](const MergedReflection &r) { + return std::max(1, gops.epsilon_factor_without_centering({{r.h, r.k, r.l}})); + }; + for (const auto &r : merged) { + if (!std::isfinite(r.I) || !std::isfinite(r.sigma) || r.sigma <= 0.0f) + continue; + const double i_over_eps = r.I / epsilon(r); + global_sum += i_over_eps; + ++global_count; + if (const auto s = shells.GetShell(r.d)) { + shell_sum[*s] += i_over_eps; + ++shell_count[*s]; + } + } + const double global_mean = global_count > 0 ? std::max(global_sum / global_count, 1e-10) : 1.0; + std::vector shell_mean(opts.num_shells, global_mean); + for (int s = 0; s < opts.num_shells; ++s) + if (shell_count[s] >= opts.min_reflections_per_shell) + shell_mean[s] = std::max(shell_sum[s] / shell_count[s], 1e-10); + + for (auto &r : merged) { + if (!std::isfinite(r.I) || !std::isfinite(r.sigma) || r.sigma <= 0.0f) { + naive(r); + continue; + } + // Strong reflections: the FW correction is negligible, <|F|> = sqrt(I). + if (r.I > opts.strong_cutoff * r.sigma) { + naive(r); + continue; + } + const auto s = shells.GetShell(r.d); + const double sigma_wilson = epsilon(r) * (s ? shell_mean[*s] : global_mean); + const bool centric = gops.is_reflection_centric({{r.h, r.k, r.l}}); + const Posterior post = integrate_posterior(r.I, r.sigma, sigma_wilson, !centric, + opts.integration_points); + r.F = static_cast(post.mean_F); + r.sigmaF = static_cast(std::sqrt(std::max(0.0, post.mean_I - post.mean_F * post.mean_F))); + } +} diff --git a/image_analysis/scale_merge/FrenchWilson.h b/image_analysis/scale_merge/FrenchWilson.h new file mode 100644 index 00000000..9ea38350 --- /dev/null +++ b/image_analysis/scale_merge/FrenchWilson.h @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#pragma once + +#include +#include + +#include "../../common/Reflection.h" + +struct FrenchWilsonOptions { + int num_shells = 20; // resolution shells for the Wilson mean + int min_reflections_per_shell = 20; // below this, fall back to the overall mean for the shell + int integration_points = 400; // posterior integration steps (weak reflections only) + double strong_cutoff = 4.0; // I/sigma above which <|F|> = sqrt(I) (FW bias negligible) +}; + +// Fill F and sigmaF on every merged reflection with the French-Wilson estimate of the amplitude: +// the posterior mean |F| given the measured intensity I and its sigma under the Wilson prior. The +// prior uses the resolution-shell mean intensity, the correct centric/acentric form, and the +// reflection's epsilon (symmetry-enhancement) multiplicity. Strong reflections reduce to sqrt(I); +// reflections with an unusable I/sigma fall back to sqrt(max(I,0)) with propagated sigma. +void ApplyFrenchWilson(std::vector &merged, int32_t space_group_number, + const FrenchWilsonOptions &opts = {}); diff --git a/image_analysis/scale_merge/Merge.cpp b/image_analysis/scale_merge/Merge.cpp index 9bfa9b62..405a9b26 100644 --- a/image_analysis/scale_merge/Merge.cpp +++ b/image_analysis/scale_merge/Merge.cpp @@ -18,6 +18,7 @@ #include "../../common/Definitions.h" #include "HKLKey.h" #include "RfreeFlags.h" +#include "FrenchWilson.h" namespace { // Deterministic CC1/2 half-set assignment: a splitmix64 bit-mix of the image's stable index. @@ -472,6 +473,7 @@ std::vector MergeOnTheFly::ExportReflections() { } AssignRfreeFlags(out, space_group_number, scaling_settings.GetRfreeFraction()); + ApplyFrenchWilson(out, space_group_number); return out; } diff --git a/image_analysis/scale_merge/RotationScaleMerge.cpp b/image_analysis/scale_merge/RotationScaleMerge.cpp index 71ec34d9..af6db35d 100644 --- a/image_analysis/scale_merge/RotationScaleMerge.cpp +++ b/image_analysis/scale_merge/RotationScaleMerge.cpp @@ -17,6 +17,7 @@ #include "HKLKey.h" #include "RfreeFlags.h" +#include "FrenchWilson.h" #include "ResolutionCutoff.h" #include "../../common/CorrelationCoefficient.h" #include "../../common/CrystalLattice.h" @@ -1300,6 +1301,7 @@ RotationScaleMerge::Result RotationScaleMerge::MergeAndStats(int n_groups, bool result.merged, d_min_limit, resolution_cutoff_method, resolution_cc_target, for_search, logger); AssignRfreeFlags(result.merged, x.GetSpaceGroupNumber().value_or(1), rfree_fraction); + ApplyFrenchWilson(result.merged, x.GetSpaceGroupNumber().value_or(1)); if (reject_count > 0) logger.Info("Merge outlier rejection: dropped {} observations", reject_count); -- 2.54.0 From d6924aa90130234472ee4ef21708b5fd6522b9b0 Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Sun, 12 Jul 2026 21:22:37 +0200 Subject: [PATCH 04/17] rugnux: add --model for R-free and 2Fo-Fc / Fo-Fc maps from an atomic model New offline capability: `rugnux --model model.pdb` validates the merged intensities against a PDB model. It computes structure factors from the model on a grid (IT92 x-ray form factors, FFT), adds an optimized flat bulk solvent (SolventMasker + gemmi Scaling: k_sol, b_sol, overall k and anisotropic B) and a per-resolution-shell scale, then reports R-work / R-free and writes 2Fo-Fc and Fo-Fc CCP4 maps plus a map-coefficient MTZ (FP, FC, PHIC, FWT/PHWT, DELFWT/PHDELWT, FREE). No refinement of the structure itself; the model is only re-fractionalized into the data cell (a rigid cell adjustment). The observed amplitudes are the French-Wilson |F| already on the merged reflections (MergedReflection.F, computed at end of merge), so the R-free/maps use exactly the same amplitudes as the written reflection file. ModelValidation.{h,cpp} holds the logic; wired via --model (ProcessConfig.model_path) into both the full pipeline (Rugnux::Run write site) and the --scale re-merge path. Links the unified `gemmi` static target for the model / structure-factor / map headers. Verified on lyso_ref: R-free 0.346, mean 2Fo-Fc at atom centres +2.16 sigma, maps written. Co-Authored-By: Claude Opus 4.8 (1M context) --- rugnux/CMakeLists.txt | 4 +- rugnux/ModelValidation.cpp | 292 +++++++++++++++++++++++++++++++++++++ rugnux/ModelValidation.h | 40 +++++ rugnux/Rugnux.cpp | 7 + rugnux/Rugnux.h | 4 + rugnux/rugnux_cli.cpp | 13 ++ 6 files changed, 359 insertions(+), 1 deletion(-) create mode 100644 rugnux/ModelValidation.cpp create mode 100644 rugnux/ModelValidation.h diff --git a/rugnux/CMakeLists.txt b/rugnux/CMakeLists.txt index 4956f312..c0ef52ac 100644 --- a/rugnux/CMakeLists.txt +++ b/rugnux/CMakeLists.txt @@ -6,9 +6,11 @@ ADD_LIBRARY(Rugnux STATIC Rugnux.h RugnuxCommandLine.cpp RugnuxCommandLine.h + ModelValidation.cpp + ModelValidation.h ) -TARGET_LINK_LIBRARIES(Rugnux JFJochReader JFJochImageAnalysis JFJochWriter) +TARGET_LINK_LIBRARIES(Rugnux JFJochReader JFJochImageAnalysis JFJochWriter gemmi) # rugnux is the single offline analysis CLI: full pipeline by default, plus --azint-only # (azimuthal integration) and --scale (re-scale/merge stored reflections) modes. diff --git a/rugnux/ModelValidation.cpp b/rugnux/ModelValidation.cpp new file mode 100644 index 00000000..d4b81410 --- /dev/null +++ b/rugnux/ModelValidation.cpp @@ -0,0 +1,292 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#include "ModelValidation.h" + +#include +#include +#include +#include +#include +#include + +#include // read_pdb +#include // MaybeGzipped +#include // IT92 x-ray form factors +#include // DensityCalculator +#include // transform_map_to_f_phi, get_f_phi_on_grid, transform_f_phi_grid_to_map +#include // SolventMasker +#include // Scaling (bulk solvent + anisotropic B) +#include // Ccp4 map I/O +#include // Mtz (map-coefficient output) + +#include "../common/Logger.h" + +namespace { + +using Table = gemmi::IT92; + +// Stable key for a Miller index reduced into the ASU (indices are small, well within +/-512). +long hkl_key(const gemmi::Miller &h) { + return (h[0] + 512L) * 1048576 + (h[1] + 512L) * 1024 + (h[2] + 512L); +} + +// Write an FFT of ASU map coefficients as a CCP4 map; return its RMS (for reporting / sigma units). +double write_ccp4(gemmi::AsuData> &coef, const std::string &path) { + coef.ensure_sorted(); + std::array size = gemmi::get_size_for_hkl(coef, {{0, 0, 0}}, 3.0); + gemmi::Grid map = + gemmi::transform_f_phi_grid_to_map(gemmi::get_f_phi_on_grid(coef, size, true)); + gemmi::Ccp4 ccp4; + ccp4.grid = map; + ccp4.update_ccp4_header(2); + ccp4.write_ccp4_map(path); + return ccp4.hstats.rms; +} + +} // namespace + +ModelValidationResult ValidateAgainstModel(const std::vector &merged, + const UnitCell &cell, + const std::string &model_path, + const std::string &output_prefix, + Logger &logger) { + ModelValidationResult result; + + // --- read the atomic model --- + gemmi::Structure st; + try { + st = gemmi::read_pdb(gemmi::MaybeGzipped(model_path)); + } catch (const std::exception &e) { + logger.Error("Model validation: cannot read model {}: {}", model_path, e.what()); + return result; + } + if (st.models.empty() || !st.cell.is_crystal()) { + logger.Error("Model validation: model {} has no atoms or no unit cell", model_path); + return result; + } + const gemmi::SpaceGroup *sg = st.find_spacegroup(); + if (!sg) { + logger.Error("Model validation: model {} has no usable space group", model_path); + return result; + } + + // Resolution limit from the data (the merged set is already resolution-trimmed). + double d_min = 0.0; + for (const MergedReflection &r : merged) + if (r.d > 0 && (d_min == 0.0 || r.d < d_min)) + d_min = r.d; + if (d_min <= 0.0) { + logger.Error("Model validation: merged reflections carry no resolution"); + return result; + } + + // Re-fractionalize the model into the data cell (rigid cell adjustment; no refinement). + const gemmi::UnitCell data_cell = cell; // UnitCell -> gemmi::UnitCell + if (data_cell.is_crystal()) { + gemmi::UnitCell old = st.cell; + for (gemmi::Model &m : st.models) + for (gemmi::Chain &ch : m.chains) + for (gemmi::Residue &r : ch.residues) + for (gemmi::Atom &a : r.atoms) + a.pos = data_cell.orthogonalize(old.fractionalize(a.pos)); + st.cell = data_cell; + } + st.setup_cell_images(); + const gemmi::UnitCell &ucell = st.cell; + + logger.Info("Model validation: {} atoms, cell a={:.2f} b={:.2f} c={:.2f}, sg {}, to {:.2f} A", + gemmi::count_atom_sites(st.models[0]), ucell.a, ucell.b, ucell.c, sg->hm, d_min); + + // --- Fcalc (atomic) via electron density on a grid + FFT --- + gemmi::DensityCalculator dc; + dc.d_min = d_min; + dc.rate = 1.5; + dc.set_grid_cell_and_spacegroup(st); + dc.set_refmac_compatible_blur(st.models[0]); + dc.put_model_density_on_grid(st.models[0]); + gemmi::AsuData> fmodel = + gemmi::transform_map_to_f_phi(dc.grid, true).prepare_asu_data(dc.d_min, dc.blur, false, false, false); + + // --- flat bulk-solvent mask -> Fmask --- + // Refmac radii give a slightly lower R than the Cctbx set on our test cases, at no cost. + gemmi::SolventMasker masker(gemmi::AtomicRadiiSet::Refmac); + gemmi::Grid mask_grid; + mask_grid.unit_cell = dc.grid.unit_cell; + mask_grid.spacegroup = dc.grid.spacegroup; + mask_grid.set_size_from_spacing(dc.requested_grid_spacing(), gemmi::GridSizeRounding::Up); + masker.put_mask_on_grid(mask_grid, st.models[0]); + gemmi::AsuData> fmask = + gemmi::transform_map_to_f_phi(mask_grid, true).prepare_asu_data(dc.d_min, 0); + + // --- observed amplitudes into the model ASU, keyed by hkl (also remember free flag) --- + gemmi::GroupOps gops = sg->operations(); + gemmi::ReciprocalAsu asu(sg); + gemmi::AsuData> fobs; + fobs.unit_cell_ = ucell; + fobs.spacegroup_ = sg; + std::unordered_map> obs_by_hkl; // hkl -> (Fobs, is_free) + + // Observed amplitudes are the French-Wilson |F| already computed at the end of the merge + // (MergedReflection.F), so the model R-free / maps use exactly the same amplitudes as the + // written reflection file. + for (const MergedReflection &r : merged) { + if (std::isnan(r.F)) continue; + gemmi::Miller h{{r.h, r.k, r.l}}; + if (!asu.is_in(h)) h = asu.to_asu(h, gops).first; + fobs.v.push_back({h, {r.F, 1.0f}}); + obs_by_hkl[hkl_key(h)] = {r.F, r.rfree_flag}; + } + fobs.ensure_asu(); + fobs.ensure_sorted(); + + // --- scale Fmodel(+solvent) to Fobs: k_overall, anisotropic B, k_sol, b_sol --- + gemmi::Scaling scaling(ucell, sg); + scaling.use_solvent = true; + scaling.prepare_points(fmodel, fobs, &fmask); + scaling.fit_isotropic_b_approximately(); + scaling.fit_parameters(); + scaling.scale_data(fmodel, &fmask); // fmodel now holds the scaled, solvent-corrected Fmodel + + // --- per-resolution-shell scaling on top of the overall scaling --- + // Fit a smooth scale K(1/d^2) least-squares per shell (sum(Fo*Fc)/sum(Fc^2)) and apply it to + // every reflection. This mops up the residual radial Fobs/Fmodel mismatch that a single overall + // B leaves behind. The shells are fit on the work set only, so R-free stays untouched by the fit. + int n_shells = 0; + { + struct ShellPoint { double s2, fo, fc; }; + std::vector pts; + pts.reserve(fmodel.v.size()); + for (const auto &hv : fmodel.v) { + auto it = obs_by_hkl.find(hkl_key(hv.hkl)); + if (it == obs_by_hkl.end() || it->second.second) continue; // skip missing + free set + if (it->second.first <= 0) continue; + pts.push_back({ucell.calculate_1_d2(hv.hkl), it->second.first, std::abs(hv.value)}); + } + std::sort(pts.begin(), pts.end(), [](const ShellPoint &a, const ShellPoint &b) { return a.s2 < b.s2; }); + int nb = std::max(6, std::min(40, static_cast(pts.size() / 300))); // ~300 refl/shell + std::vector shell_s2, shell_k; + for (int b = 0; b < nb; ++b) { + size_t i0 = pts.size() * b / nb, i1 = pts.size() * (b + 1) / nb; + double sum_fofc = 0, sum_fc2 = 0, sum_s2 = 0; + int n = 0; + for (size_t i = i0; i < i1; ++i) { + sum_fofc += pts[i].fo * pts[i].fc; + sum_fc2 += pts[i].fc * pts[i].fc; + sum_s2 += pts[i].s2; + ++n; + } + if (sum_fc2 > 0 && n > 0) { shell_s2.push_back(sum_s2 / n); shell_k.push_back(sum_fofc / sum_fc2); } + } + auto scale_at = [&](double s2) -> double { + if (shell_s2.empty()) return 1.0; + if (s2 <= shell_s2.front()) return shell_k.front(); + if (s2 >= shell_s2.back()) return shell_k.back(); + for (size_t i = 1; i < shell_s2.size(); ++i) + if (s2 <= shell_s2[i]) { + double t = (s2 - shell_s2[i - 1]) / (shell_s2[i] - shell_s2[i - 1]); + return shell_k[i - 1] * (1 - t) + shell_k[i] * t; + } + return shell_k.back(); + }; + for (auto &hv : fmodel.v) + hv.value *= static_cast(scale_at(ucell.calculate_1_d2(hv.hkl))); + n_shells = static_cast(shell_k.size()); + } + + // --- R-work / R-free and map coefficients (2Fo-Fc and Fo-Fc, model phases) --- + gemmi::AsuData> map2fofc, mapfofc; + map2fofc.unit_cell_ = ucell; map2fofc.spacegroup_ = sg; + mapfofc.unit_cell_ = ucell; mapfofc.spacegroup_ = sg; + double num_w = 0, den_w = 0, num_f = 0, den_f = 0; + int n_w = 0, n_f = 0; + for (const auto &hv : fmodel.v) { + auto it = obs_by_hkl.find(hkl_key(hv.hkl)); + if (it == obs_by_hkl.end()) continue; + double Fo = it->second.first; + double Fc = std::abs(hv.value); + double phi = std::arg(hv.value); + if (it->second.second) { num_f += std::fabs(Fo - Fc); den_f += Fo; ++n_f; } + else { num_w += std::fabs(Fo - Fc); den_w += Fo; ++n_w; } + std::complex ph = std::polar(1.0f, static_cast(phi)); + map2fofc.v.push_back({hv.hkl, static_cast(2 * Fo - Fc) * ph}); + mapfofc.v.push_back({hv.hkl, static_cast(Fo - Fc) * ph}); + } + + result.r_work = den_w > 0 ? num_w / den_w : 0; + result.r_free = den_f > 0 ? num_f / den_f : 0; + result.n_work = n_w; + result.n_free = n_f; + result.k_sol = scaling.k_sol; + result.b_sol = scaling.b_sol; + result.k_overall = scaling.k_overall; + + // --- write the maps and score the 2Fo-Fc map at atom centres (a real map peaks there) --- + const std::string p2 = output_prefix + "_2fofc.ccp4"; + const std::string pd = output_prefix + "_fofc.ccp4"; + double rms2 = write_ccp4(map2fofc, p2); + write_ccp4(mapfofc, pd); + + // Recompute the 2Fo-Fc real-space grid once more to sample it at atom positions. + { + map2fofc.ensure_sorted(); + std::array size = gemmi::get_size_for_hkl(map2fofc, {{0, 0, 0}}, 3.0); + gemmi::Grid grid = + gemmi::transform_f_phi_grid_to_map(gemmi::get_f_phi_on_grid(map2fofc, size, true)); + double s = 0; int n = 0; + for (gemmi::Model &m : st.models) + for (gemmi::Chain &ch : m.chains) + for (gemmi::Residue &r : ch.residues) + for (gemmi::Atom &a : r.atoms) { s += grid.interpolate_value(a.pos); ++n; } + result.mean_atom_density_sigma = (n > 0 && rms2 > 0) ? (s / n) / rms2 : 0; + } + + // --- MTZ of map coefficients so the maps can be re-opened / rebuilt in Coot etc. --- + try { + gemmi::Mtz mtz(true); + mtz.spacegroup = sg; + mtz.set_cell_for_all(ucell); + mtz.add_dataset("model_validation"); + mtz.add_column("FP", 'F', -1, -1, false); + mtz.add_column("FC", 'F', -1, -1, false); + mtz.add_column("PHIC", 'P', -1, -1, false); + mtz.add_column("FWT", 'F', -1, -1, false); + mtz.add_column("PHWT", 'P', -1, -1, false); + mtz.add_column("DELFWT", 'F', -1, -1, false); + mtz.add_column("PHDELWT", 'P', -1, -1, false); + mtz.add_column("FREE", 'I', -1, -1, false); + std::vector data; + int nref = 0; + for (const auto &hv : fmodel.v) { + auto it = obs_by_hkl.find(hkl_key(hv.hkl)); + if (it == obs_by_hkl.end()) continue; + double Fo = it->second.first; + double Fc = std::abs(hv.value); + double phi_deg = gemmi::phase_in_angles(hv.value); + data.insert(data.end(), {static_cast(hv.hkl[0]), static_cast(hv.hkl[1]), + static_cast(hv.hkl[2]), + static_cast(Fo), static_cast(Fc), + static_cast(phi_deg), + static_cast(2 * Fo - Fc), static_cast(phi_deg), + static_cast(Fo - Fc), static_cast(phi_deg), + it->second.second ? 0.0f : 1.0f}); + ++nref; + } + mtz.nreflections = nref; + mtz.data = std::move(data); + mtz.write_to_file(output_prefix + "_maps.mtz"); + } catch (const std::exception &e) { + logger.Warning("Model validation: could not write map MTZ: {}", e.what()); + } + + result.ok = true; + result.maps_prefix = output_prefix; + logger.Info("Model validation: R-work={:.4f} ({} refl) R-free={:.4f} ({} refl) [overall + {}-shell scaling]", + result.r_work, result.n_work, result.r_free, result.n_free, n_shells); + logger.Info("Model validation: bulk solvent k_sol={:.3f} b_sol={:.1f}, k_overall={:.3f}", + result.k_sol, result.b_sol, result.k_overall); + logger.Info("Model validation: mean 2Fo-Fc density at atom centres = {:.2f} sigma", result.mean_atom_density_sigma); + logger.Info("Model validation: wrote {}_2fofc.ccp4, {}_fofc.ccp4, {}_maps.mtz", + output_prefix, output_prefix, output_prefix); + return result; +} diff --git a/rugnux/ModelValidation.h b/rugnux/ModelValidation.h new file mode 100644 index 00000000..5a6bc4ed --- /dev/null +++ b/rugnux/ModelValidation.h @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#pragma once + +#include +#include + +#include "../common/Reflection.h" // MergedReflection +#include "../common/UnitCell.h" + +class Logger; + +// Result of validating merged intensities against an atomic model. +struct ModelValidationResult { + bool ok = false; + double r_work = 0.0; + double r_free = 0.0; + double k_sol = 0.0; + double b_sol = 0.0; + double k_overall = 0.0; + int n_work = 0; + int n_free = 0; + double mean_atom_density_sigma = 0.0; // mean 2Fo-Fc value at atom centres, in sigma + std::string maps_prefix; // where the .ccp4 / _maps.mtz were written +}; + +// Given merged intensities and a PDB atomic model, scale the model structure factors (with an +// optimized flat bulk-solvent contribution and an overall anisotropic B) to the observed +// amplitudes, then report R-work / R-free and write 2Fo-Fc and Fo-Fc electron-density maps +// (CCP4) plus an MTZ of map coefficients next to output_prefix. +// +// No refinement of the structure itself is done. The model is only re-fractionalized into the +// data unit cell (a cheap rigid cell adjustment) so a deposited model with a slightly different +// cell still lines up with the processed data. Returns ok=false (and logs) on any failure. +ModelValidationResult ValidateAgainstModel(const std::vector &merged, + const UnitCell &cell, + const std::string &model_path, + const std::string &output_prefix, + Logger &logger); diff --git a/rugnux/Rugnux.cpp b/rugnux/Rugnux.cpp index 5e007736..92fada20 100644 --- a/rugnux/Rugnux.cpp +++ b/rugnux/Rugnux.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-3.0-only #include "Rugnux.h" +#include "ModelValidation.h" #include #include @@ -868,6 +869,12 @@ ProcessResult Rugnux::Run(RugnuxObserver *observer) { // partials, which carry the first-pass per-image scale. ScalingResult(indexer->GetIntegrationOutcome()).SaveToFile(config_.output_prefix); } + + if (result.consensus_cell && write_files && !config_.model_path.empty()) { + phase("Validating against model"); + ValidateAgainstModel(sm.merged, *result.consensus_cell, config_.model_path, + config_.output_prefix, logger); + } } if (writer) { diff --git a/rugnux/Rugnux.h b/rugnux/Rugnux.h index 58a7f167..1a317883 100644 --- a/rugnux/Rugnux.h +++ b/rugnux/Rugnux.h @@ -69,6 +69,10 @@ struct ProcessConfig { // Diagnostic: if set, the -P rot3d combine writes the unmerged fulls here (for comparison vs XDS). std::string observation_dump_path; + + // If set, after merging, validate the merged intensities against this atomic model (PDB): + // compute R-free with an optimized bulk solvent and write 2Fo-Fc / Fo-Fc maps (--model). + std::string model_path; }; struct ProcessResult { diff --git a/rugnux/rugnux_cli.cpp b/rugnux/rugnux_cli.cpp index de85e130..02ac869d 100644 --- a/rugnux/rugnux_cli.cpp +++ b/rugnux/rugnux_cli.cpp @@ -29,6 +29,7 @@ #include "../image_analysis/scale_merge/TwinningAnalysis.h" #include "../image_analysis/scale_merge/SearchSpaceGroup.h" #include "Rugnux.h" +#include "ModelValidation.h" // Default rot3d per-frame scale-G smoothing range (XDS DELPHI-like), in degrees of rotation. constexpr double SMOOTH_G_DEFAULT_DEG = 5.0; @@ -98,6 +99,7 @@ void print_usage() { std::cout << " --scaling-output Output format for scaling results mtz|cif|txt (default: cif)" << std::endl; std::cout << " -z, --reference-mtz Reference MTZ file" << std::endl; std::cout << " --reference-column