Files
Jungfraujoch/gemmi_gph/to_mmcif.cpp
T
leonarski_f 680c36c20d
Build Packages / Unit tests (push) Successful in 1h22m15s
Build Packages / build:windows:nocuda (push) Successful in 18m0s
Build Packages / build:windows:cuda (push) Successful in 20m30s
Build Packages / build:viewer-tgz:cpu (push) Successful in 10m32s
Build Packages / build:viewer-tgz:cuda (push) Successful in 11m39s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 8m55s
Build Packages / build:rugnux:windows (push) Successful in 11m25s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 20m6s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 16m27s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 20m19s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 15m34s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 20m25s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 19m36s
Build Packages / build:rpm (rocky8) (push) Successful in 17m43s
Build Packages / build:rpm (rocky9) (push) Successful in 13m34s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 21m28s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 18m19s
Build Packages / DIALS test (push) Successful in 12m36s
Build Packages / XDS test (durin plugin) (push) Successful in 6m56s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 6m48s
Build Packages / XDS test (neggia plugin) (push) Successful in 6m7s
Build Packages / Generate python client (push) Successful in 11s
Build Packages / Build documentation (push) Successful in 36s
Build Packages / Create release (push) Skipped
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 5m11s
v1.0.0-rc.166 (#76)
* `rugnux --mode calibration` writes `<prefix>.json` beside the `.poni`, whose `dataset_settings` member is a `jfjoch_broker` `dataset_settings` body as it stands.
* `rugnux` and `jfjoch_viewer` read PILATUS miniCBF sweeps natively, without conversion.
* Masters written by other facilities open, including Eiger 1.x and third-party NXmx variants.
* `rugnux` measures the beam centre on every run, and indexes with it when the file's value indexes nothing.
* A detector swung out on a 2theta arm is placed where the file says it stands, and the calibration can hold the tilt fixed.
* `rugnux` writes the unmerged MTZ by default, and a P1 merge beside it, so a wrong space group can be re-merged without reprocessing.
* Significant improvements to symmetry handling in `rugnux`: the lattice, the point group, the setting and the systematic absences.
* The `rugnux` report gives the resolution the CC1/2 fit reached, beside the range the reflections were written to.
* The `rugnux` report gives the twinning statistics measured before the space group was decided, beside the ones measured after.
* The `rugnux` report gives the strong-direction diffraction limit, and warns when CC1/2 is not monotone with resolution.
* `rugnux` ranks screw axes on the evidence their absences carry, rather than on how many control reflections a candidate happens to have.
* Twinning is no longer reported when the L-test contradicts it.
* The `rugnux` report gives the detector tilt, the measured tilt and the direct beam beside the beam centre, and a post-refined beam centre is judged against the run's own measurement rather than the file's.
* `--no-refine-tilt` holds the detector tilt at the value in the file, instead of zeroing it, when the calibration starts from the spots.
* The `jfjoch_viewer` grid scan view draws the cells in the proportion of the scan steps, so the map has the shape of the scanned area.

Reviewed-on: #76
Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
2026-09-02 21:17:31 +02:00

1322 lines
59 KiB
C++

// Copyright 2017-2023 Global Phasing Ltd.
#include <gemmi/to_mmcif.hpp>
#include <cassert>
#include <cmath> // for isnan
#include <set>
#include <string>
#include <utility> // std::pair
#include <gemmi/atox.hpp> // no_sign_atoi
#include <gemmi/sprintf.hpp>
#include <gemmi/enumstr.hpp> // for entity_type_to_string, ...
#include <gemmi/seqtools.hpp> // for pdbx_one_letter_code, ...
#include <gemmi/to_pdb.hpp> // for use_hetatm
namespace gemmi {
namespace {
inline std::string pdbx_icode(const SeqId& seqid) {
return std::string(1, seqid.has_icode() ? seqid.icode : '?');
}
inline std::string pdbx_icode(const ResidueId& rid) {
return pdbx_icode(rid.seqid);
}
inline std::string subchain_or_dot(const Residue& res) {
return res.subchain.empty() ? "." : cif::quote(res.subchain);
}
inline std::string number_or_dot(double d) {
return std::isnan(d) ? "." : to_str(d);
}
inline std::string number_or_qmark(double d) {
return std::isnan(d) ? "?" : to_str(d);
}
// for use with non-negative Metadata fields that use -1 for N/A
inline std::string int_or_dot(int n) {
return n == -1 ? "." : std::to_string(n);
}
inline std::string int_or_qmark(int n) {
return n == -1 ? "?" : std::to_string(n);
}
inline std::string string_or_dot(const std::string& s) {
return s.empty() ? "." : cif::quote(s);
}
inline std::string string_or_qmark(const std::string& s) {
return s.empty() ? "?" : cif::quote(s);
}
// Quote chain name or entity id if necessary. It is necessary
// only if the chain name is missing, which was OK in the past.
// Here we use '' rather than . or ?.
inline std::string qchain(const std::string& s) {
return cif::quote(s);
}
void add_cif_atoms(const Structure& st, cif::Block& block,
bool use_group_pdb, bool auth_all) {
// atom list
cif::Loop& atom_loop = block.init_mmcif_loop("_atom_site.", {
"id",
"type_symbol",
"label_atom_id",
"label_alt_id",
"label_comp_id",
"label_asym_id",
"label_entity_id",
"label_seq_id",
"pdbx_PDB_ins_code",
"Cartn_x",
"Cartn_y",
"Cartn_z",
"occupancy",
"B_iso_or_equiv",
"pdbx_formal_charge",
"auth_atom_id", // optional (tags[15] is removed if !auth_all)
"auth_comp_id", // optional (tags[16] is removed if !auth_all)
"auth_seq_id",
"auth_asym_id",
"pdbx_PDB_model_num"});
if (!auth_all)
atom_loop.tags.erase(atom_loop.tags.begin() + 15, atom_loop.tags.begin() + 17);
if (use_group_pdb)
atom_loop.tags.emplace(atom_loop.tags.begin(), "_atom_site.group_PDB");
bool has_calc_flag = false;
bool has_tls_group_id = false;
size_t atom_site_count = 0;
for (const Model& model : st.models)
for (const Chain& chain : model.chains)
for (const Residue& res : chain.residues)
for (const Atom& atom : res.atoms) {
++atom_site_count;
if (atom.calc_flag != CalcFlag::NotSet &&
atom.calc_flag != CalcFlag::NoHydrogen)
has_calc_flag = true;
if (atom.tls_group_id >= 0)
has_tls_group_id = true;
}
if (has_calc_flag)
atom_loop.tags.emplace_back("_atom_site.calc_flag");
if (has_tls_group_id)
atom_loop.tags.emplace_back("_atom_site.pdbx_tls_group_id");
if (st.has_d_fraction)
atom_loop.tags.emplace_back("_atom_site.ccp4_deuterium_fraction");
std::vector<std::string>& vv = atom_loop.values;
vv.reserve(atom_site_count * atom_loop.tags.size());
std::vector<std::tuple<int, int, const Atom*>> aniso;
int serial = 0;
for (const Model& model : st.models) {
for (const Chain& chain : model.chains) {
for (const Residue& res : chain.residues) {
bool as_het = use_hetatm(res);
std::string label_seq_id = res.label_seq.str('.');
std::string auth_seq_id = res.seqid.num.str();
std::string entity_id;
if (const Entity* ent = gemmi::find_entity_of_subchain(res.subchain, st.entities))
entity_id = cif::quote(ent->name);
else
entity_id = string_or_dot(res.entity_id);
for (const Atom& atom : res.atoms) {
if (use_group_pdb)
vv.emplace_back(as_het ? "HETATM" : "ATOM");
vv.emplace_back(std::to_string(++serial));
vv.emplace_back(atom.element.uname());
vv.emplace_back(cif::quote(atom.name));
vv.emplace_back(1, atom.altloc_or('.'));
vv.emplace_back(cif::quote(res.name));
vv.emplace_back(subchain_or_dot(res));
vv.emplace_back(entity_id);
vv.emplace_back(label_seq_id);
vv.emplace_back(pdbx_icode(res));
vv.emplace_back(to_str(atom.pos.x));
vv.emplace_back(to_str(atom.pos.y));
vv.emplace_back(to_str(atom.pos.z));
vv.emplace_back(to_str(atom.occ));
vv.emplace_back(to_str(atom.b_iso));
vv.emplace_back(atom.charge == 0 ? "?" : std::to_string(atom.charge));
if (auth_all) {
size_t atom_name_idx = vv.size() - 13;
vv.emplace_back(vv[atom_name_idx]); // auth_atom_id = label_atom_id
vv.emplace_back(vv[atom_name_idx + 2]); // auth_comp_id = label_comp_id
}
vv.emplace_back(auth_seq_id);
vv.emplace_back(qchain(chain.name));
vv.emplace_back(std::to_string(model.num));
if (has_calc_flag)
vv.emplace_back(&".\0.\0d\0c\0dum"[2 * (int) atom.calc_flag]);
if (has_tls_group_id)
vv.emplace_back(int_or_qmark(atom.tls_group_id));
if (st.has_d_fraction)
vv.emplace_back(to_str(atom.fraction));
if (atom.aniso.nonzero())
aniso.emplace_back(serial, model.num, &atom);
}
}
}
}
if (aniso.empty()) {
block.find_mmcif_category("_atom_site_anisotrop.").erase();
} else {
cif::Loop& aniso_loop = block.init_mmcif_loop("_atom_site_anisotrop.", {
"id", "type_symbol", "U[1][1]", "U[2][2]",
"U[3][3]", "U[1][2]", "U[1][3]", "U[2][3]"});
if (st.models.size() > 1)
aniso_loop.tags.push_back("_atom_site_anisotrop.pdbx_PDB_model_num");
std::vector<std::string>& aniso_val = aniso_loop.values;
aniso_val.reserve(aniso_loop.tags.size() * aniso.size());
for (const auto& a : aniso) {
aniso_val.emplace_back(std::to_string(std::get<0>(a)));
const Atom* atom = std::get<2>(a);
aniso_val.emplace_back(atom->element.uname());
aniso_val.emplace_back(to_str(atom->aniso.u11));
aniso_val.emplace_back(to_str(atom->aniso.u22));
aniso_val.emplace_back(to_str(atom->aniso.u33));
aniso_val.emplace_back(to_str(atom->aniso.u12));
aniso_val.emplace_back(to_str(atom->aniso.u13));
aniso_val.emplace_back(to_str(atom->aniso.u23));
if (st.models.size() > 1)
aniso_loop.values.push_back(std::to_string(std::get<1>(a)));
}
}
}
// the names are: monomeric, dimeric, ...meric, 21-meric, 22-meric, ...
int xmeric_to_number(const std::string& oligomeric) {
static const char names[20][10] = {
"mono", "di", "tri", "tetra", "penta",
"hexa", "hepta", "octa", "nona", "deca",
"undeca", "dodeca", "trideca", "tetradeca", "pentadeca",
"hexadeca", "heptadeca", "octadeca", "nonadeca", "eicosa"
};
size_t len = oligomeric.length();
const char* p = oligomeric.c_str();
for (int i = 0; i != 20; ++i)
if (len == std::strlen(names[i]) + 5 && strncmp(p, names[i], len-5) == 0)
return i + 1;
return no_sign_atoi(p);
}
void write_assemblies(const Structure& st, cif::Block& block) {
block.items.reserve(block.items.size() + 4); // avoid re-allocation
cif::Loop& a_loop = block.init_mmcif_loop("_pdbx_struct_assembly.",
{"id", "details", "method_details",
"oligomeric_details", "oligomeric_count"});
cif::Loop& prop_loop = block.init_mmcif_loop("_pdbx_struct_assembly_prop.",
{"biol_id", "type", "value"});
cif::Loop& gen_loop = block.init_mmcif_loop("_pdbx_struct_assembly_gen.",
{"assembly_id", "oper_expression", "asym_id_list"});
cif::Loop& oper_loop = block.init_mmcif_loop("_pdbx_struct_oper_list.",
{"id", "type",
"matrix[1][1]", "matrix[1][2]", "matrix[1][3]", "vector[1]",
"matrix[2][1]", "matrix[2][2]", "matrix[2][3]", "vector[2]",
"matrix[3][1]", "matrix[3][2]", "matrix[3][3]", "vector[3]"});
std::vector<const Assembly::Operator*> distinct_oper;
for (const Assembly& as : st.assemblies) {
std::string how_defined = "?";
if (as.author_determined && as.software_determined)
how_defined = "author_and_software_defined_assembly";
else if (as.author_determined)
how_defined = "author_defined_assembly";
else if (as.software_determined)
how_defined = "software_defined_assembly";
else if (as.special_kind == Assembly::SpecialKind::CompleteIcosahedral)
how_defined = "'complete icosahedral assembly'";
else if (as.special_kind == Assembly::SpecialKind::RepresentativeHelical)
how_defined = "'representative helical assembly'";
else if (as.special_kind == Assembly::SpecialKind::CompletePoint)
how_defined = "'complete point assembly'";
std::string oligomer = to_lower(as.oligomeric_details);
int nmer = as.oligomeric_count != 0 ? as.oligomeric_count
: xmeric_to_number(oligomer);
// _pdbx_struct_assembly
a_loop.add_row({as.name,
how_defined,
string_or_qmark(as.software_name),
string_or_qmark(oligomer),
nmer == 0 ? "?" : std::to_string(nmer)});
// _pdbx_struct_assembly_prop
if (!std::isnan(as.absa))
prop_loop.add_row({as.name, "'ABSA (A^2)'", to_str(as.absa)});
if (!std::isnan(as.ssa))
prop_loop.add_row({as.name, "'SSA (A^2)'", to_str(as.ssa)});
if (!std::isnan(as.more))
prop_loop.add_row({as.name, "MORE", to_str(as.more)});
// _pdbx_struct_assembly_gen and _pdbx_struct_oper_list
for (const Assembly::Gen& gen : as.generators) {
std::string subchain_str;
for (const std::string& name : gen.subchains)
string_append_sep(subchain_str, ',', name);
if (subchain_str.empty()) // chain names to subchain names
for (const Chain& chain : st.models[0].chains)
if (in_vector(chain.name, gen.chains))
for (const auto& sub : chain.subchains())
string_append_sep(subchain_str, ',', sub.front().subchain);
std::string oper_str;
for (const Assembly::Operator& oper : gen.operators) {
size_t k = 0;
for (; k != distinct_oper.size(); ++k)
if (distinct_oper[k]->transform.approx(oper.transform, 1e-9))
break;
string_append_sep(oper_str, ',', std::to_string(k+1));
if (k != distinct_oper.size())
continue;
distinct_oper.emplace_back(&oper);
oper_loop.values.emplace_back(std::to_string(k+1));
if (!oper.type.empty()) {
oper_loop.values.emplace_back(cif::quote(oper.type));
} else if (oper.transform.is_identity()) {
oper_loop.values.emplace_back("'identity operation'");
} else if (as.author_determined || as.software_determined) {
oper_loop.values.emplace_back("'crystal symmetry operation'");
} else {
oper_loop.values.emplace_back(".");
}
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j)
oper_loop.values.emplace_back(to_str(oper.transform.mat[i][j]));
oper_loop.values.emplace_back(to_str(oper.transform.vec.at(i)));
}
}
gen_loop.add_row({as.name,
oper_str.empty() ? "." : oper_str,
subchain_str.empty() ? "?" : subchain_str});
}
}
}
void write_cell_parameters(const UnitCell& cell, cif::ItemSpan& span) {
span.set_pair("_cell.length_a", to_str(cell.a));
span.set_pair("_cell.length_b", to_str(cell.b));
span.set_pair("_cell.length_c", to_str(cell.c));
span.set_pair("_cell.angle_alpha", to_str(cell.alpha));
span.set_pair("_cell.angle_beta", to_str(cell.beta));
span.set_pair("_cell.angle_gamma", to_str(cell.gamma));
}
bool is_valid_block_name(const std::string& name) {
return !name.empty() &&
std::all_of(name.begin(), name.end(), [](char c){ return c >= '!' && c <= '~'; });
}
int get_number_obs(const BasicRefinementInfo& ref) {
int nobs = ref.reflection_count;
if (nobs == -1 && ref.rfree_set_count >= 0 && ref.work_set_count >= 0)
nobs = ref.work_set_count + ref.rfree_set_count;
return nobs;
}
int get_number_work(const BasicRefinementInfo& ref) {
int nwork = ref.work_set_count;
if (nwork == -1 && ref.rfree_set_count >= 0 && ref.reflection_count >= 0)
nwork = ref.reflection_count - ref.rfree_set_count;
return nwork;
}
} // anonymous namespace
void write_ncs_oper(const Structure& st, cif::Block& block) {
// _struct_ncs_oper (MTRIX)
if (st.ncs.empty())
return;
cif::Loop& ncs_oper = block.init_mmcif_loop("_struct_ncs_oper.",
{"id", "code",
"matrix[1][1]", "matrix[1][2]", "matrix[1][3]", "vector[1]",
"matrix[2][1]", "matrix[2][2]", "matrix[2][3]", "vector[2]",
"matrix[3][1]", "matrix[3][2]", "matrix[3][3]", "vector[3]"});
auto add_op = [&ncs_oper](const NcsOp& op) {
ncs_oper.values.emplace_back(op.id);
ncs_oper.values.emplace_back(op.given ? "given" : "generate");
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j)
ncs_oper.values.emplace_back(to_str(op.tr.mat[i][j]));
ncs_oper.values.emplace_back(to_str(op.tr.vec.at(i)));
}
};
auto identity = st.info.find("_struct_ncs_oper.id");
if (identity != st.info.end() &&
!in_vector_f([&](const NcsOp& op) { return op.id == identity->second; }, st.ncs))
add_op(NcsOp{identity->second, true, {}});
for (const NcsOp& op : st.ncs)
add_op(op);
}
void write_struct_conn(const Structure& st, cif::Block& block) {
// example:
// disulf1 disulf A CYS 3 SG ? 3 ? 1_555 A CYS 18 SG ? 18 ? 1_555 ? 2.045
std::array<bool,(int)Connection::Type::Unknown+1> type_ids{};
bool use_ccp4_link_id = false;
for (const Connection& con : st.connections)
if (!con.link_id.empty())
use_ccp4_link_id = true;
cif::Loop& conn_loop = block.init_mmcif_loop("_struct_conn.",
{"id", "conn_type_id",
"ptnr1_label_asym_id", "ptnr1_label_comp_id", "ptnr1_label_seq_id",
"ptnr1_label_atom_id", "pdbx_ptnr1_label_alt_id", "ptnr1_auth_asym_id",
"ptnr1_auth_seq_id", "pdbx_ptnr1_PDB_ins_code", "ptnr1_symmetry",
"ptnr2_label_asym_id", "ptnr2_label_comp_id", "ptnr2_label_seq_id",
"ptnr2_label_atom_id", "pdbx_ptnr2_label_alt_id", "ptnr2_auth_asym_id",
"ptnr2_auth_seq_id", "pdbx_ptnr2_PDB_ins_code", "ptnr2_symmetry",
"details", "pdbx_dist_value"});
if (use_ccp4_link_id)
conn_loop.tags.push_back("_struct_conn.ccp4_link_id");
for (const Connection& con : st.connections) {
const_CRA cra1 = st.models[0].find_cra(con.partner1, true);
const_CRA cra2 = st.models[0].find_cra(con.partner2, true);
if (!cra1.residue || !cra2.residue)
continue;
const Atom* at1 = cra1.atom;
const Atom* at2 = cra2.atom;
std::string im_pdb_symbol = "?", im_dist_str = "?";
if (at1 && at2) {
NearestImage im = st.cell.find_nearest_image(at1->pos, at2->pos, con.asu);
im_pdb_symbol = im.symmetry_code(true);
im_dist_str = to_str_prec<4>(im.dist());
}
auto& v = conn_loop.values;
v.emplace_back(string_or_qmark(con.name)); // id
v.emplace_back(connection_type_to_string(con.type)); // conn_type_id
v.emplace_back(subchain_or_dot(*cra1.residue)); // ptnr1_label_asym_id
v.emplace_back(cra1.residue->name); // ptnr1_label_comp_id
v.emplace_back(cra1.residue->label_seq.str('.')); // ptnr1_label_seq_id
v.emplace_back(at1 ? cif::quote(at1->name) : "?"); // ptnr1_label_atom_id
v.emplace_back(1, at1 ? at1->altloc_or('?') : '?'); // pdbx_ptnr1_label_alt_id
v.emplace_back(qchain(con.partner1.chain_name)); // ptnr1_auth_asym_id
v.emplace_back(cra1.residue->seqid.num.str()); // ptnr1_auth_seq_id
v.emplace_back(pdbx_icode(con.partner1.res_id)); // ptnr1_PDB_ins_code
v.emplace_back("1_555"); // ptnr1_symmetry
v.emplace_back(subchain_or_dot(*cra2.residue)); // ptnr2_label_asym_id
v.emplace_back(cra2.residue->name); // ptnr2_label_comp_id
v.emplace_back(cra2.residue->label_seq.str('.')); // ptnr2_label_seq_id
v.emplace_back(at2 ? cif::quote(at2->name) : "?"); // ptnr2_label_atom_id
v.emplace_back(1, at2 ? at2->altloc_or('?') : '?'); // pdbx_ptnr2_label_alt_id
v.emplace_back(qchain(con.partner2.chain_name)); // ptnr2_auth_asym_id
v.emplace_back(cra2.residue->seqid.num.str()); // ptnr2_auth_seq_id
v.emplace_back(pdbx_icode(con.partner2.res_id)); // ptnr2_PDB_ins_code
v.emplace_back(im_pdb_symbol); // ptnr2_symmetry
v.emplace_back("?"); // details
v.emplace_back(im_dist_str); // pdbx_dist_value
if (use_ccp4_link_id)
v.emplace_back(string_or_qmark(con.link_id)); // ccp4_link_id
type_ids[int(con.type)] = true;
}
cif::Loop& type_loop = block.init_mmcif_loop("_struct_conn_type.", {"id"});
for (int i = 0; i < (int)type_ids.size() - 1; ++i)
if (type_ids[i])
type_loop.add_row({connection_type_to_string((Connection::Type)i)});
}
void write_cispeps(const Structure& st, cif::Block& block) {
cif::Loop* prot_cis_loop = nullptr;
int pdbx_id = 0;
for (const CisPep& cispep : st.cispeps) {
const Model* model = &st.models[0];
if (st.models.size() > 1) {
model = st.find_model(cispep.model_num);
if (!model)
continue;
}
const_CRA cra1 = model->find_cra(cispep.partner_c, true);
const_CRA cra2 = model->find_cra(cispep.partner_n, true);
if (!cra1.residue || !cra2.residue)
continue;
if (!prot_cis_loop)
prot_cis_loop = &block.init_mmcif_loop("_struct_mon_prot_cis.",
{"pdbx_id", "pdbx_PDB_model_num",
"label_asym_id", "label_seq_id", "label_comp_id",
"auth_asym_id", "auth_seq_id", "pdbx_PDB_ins_code",
"pdbx_label_asym_id_2", "pdbx_label_seq_id_2", "pdbx_label_comp_id_2",
"pdbx_auth_asym_id_2", "pdbx_auth_seq_id_2", "pdbx_PDB_ins_code_2",
"label_alt_id", "pdbx_omega_angle"});
auto& v = prot_cis_loop->values;
v.emplace_back(std::to_string(++pdbx_id)); // pdbx_id
v.emplace_back(std::to_string(model->num)); // pdbx_PDB_model_num
v.emplace_back(subchain_or_dot(*cra1.residue)); // label_asym_id
v.emplace_back(cra1.residue->label_seq.str('.')); // label_seq_id
v.emplace_back(cra1.residue->name); // label_comp_id
v.emplace_back(qchain(cispep.partner_c.chain_name)); // auth_asym_id
v.emplace_back(cispep.partner_c.res_id.seqid.num.str()); // auth_seq_id
v.emplace_back(pdbx_icode(cispep.partner_c.res_id)); // pdbx_PDB_ins_code
v.emplace_back(subchain_or_dot(*cra2.residue)); // pdbx_label_asym_id_2
v.emplace_back(cra2.residue->label_seq.str('.')); // pdbx_label_seq_id_2
v.emplace_back(cra2.residue->name); // pdbx_label_comp_id_2
v.emplace_back(qchain(cispep.partner_n.chain_name)); // pdbx_auth_asym_id_2
v.emplace_back(cispep.partner_n.res_id.seqid.num.str()); // pdbx_auth_seq_id_2
v.emplace_back(pdbx_icode(cispep.partner_n.res_id)); // pdbx_PDB_ins_code_2
v.emplace_back(1, cispep.only_altloc ? cispep.only_altloc : '.');
v.emplace_back(number_or_qmark(cispep.reported_angle));
}
}
void update_mmcif_block(const Structure& st, cif::Block& block, MmcifOutputGroups groups) {
if (st.models.empty())
return;
if (groups.block_name)
block.name = is_valid_block_name(st.name) ? st.name : "model";
auto e_id = st.info.find("_entry.id");
std::string id = cif::quote(e_id != st.info.end() ? e_id->second : block.name);
if (groups.entry)
block.set_pair("_entry.id", id);
else if (const std::string* val = block.find_value("_entry.id"))
id = *val;
if (groups.database_status) {
auto initial_date = st.info.find("_pdbx_database_status.recvd_initial_deposition_date");
if (initial_date != st.info.end() && !initial_date->second.empty()) {
cif::ItemSpan span(block.items, "_pdbx_database_status.");
span.set_pair("_pdbx_database_status.entry_id", id);
span.set_pair(initial_date->first, initial_date->second);
}
}
if (groups.author && !st.meta.authors.empty()) {
cif::Loop& loop = block.init_mmcif_loop("_audit_author.", {"pdbx_ordinal", "name"});
int n = 0;
for (const std::string& author : st.meta.authors)
loop.add_row({std::to_string(++n), cif::quote(author)});
}
if (groups.cell) {
cif::ItemSpan cell_span(block.items, "_cell.");
cell_span.set_pair("_cell.entry_id", id);
write_cell_parameters(st.cell, cell_span);
auto z_pdb = st.info.find("_cell.Z_PDB");
if (z_pdb != st.info.end())
cell_span.set_pair(z_pdb->first, z_pdb->second);
}
if (groups.symmetry) {
cif::ItemSpan span(block.items, "_symmetry.");
span.set_pair("_symmetry.entry_id", id);
span.set_pair("_symmetry.space_group_name_H-M",
cif::quote(st.spacegroup_hm));
if (const SpaceGroup* sg = st.find_spacegroup())
span.set_pair("_symmetry.Int_Tables_number", std::to_string(sg->number));
}
if (groups.entity) {
cif::Loop& entity_loop = block.init_mmcif_loop("_entity.", {"id", "type"});
for (const Entity& ent : st.entities)
entity_loop.add_row({qchain(ent.name),
entity_type_to_string(ent.entity_type)});
}
std::map<std::string, std::string> subs_to_strands;
if (groups.entity_poly || groups.struct_ref)
subs_to_strands = st.models[0].subchain_to_chain();
if (groups.entity_poly) {
// If the _entity_poly category is included when depositing to the PDB,
// it must contain entity_id, type, pdbx_seq_one_letter_code
// and pdbx_strand_id. The last one is not documented as required,
// but OneDep shows error when it's not included.
cif::Loop& ent_poly_loop = block.init_mmcif_loop("_entity_poly.",
{"entity_id", "type", "pdbx_strand_id", "pdbx_seq_one_letter_code"});
for (const Entity& ent : st.entities)
if (ent.entity_type == EntityType::Polymer) {
if (ent.polymer_type == PolymerType::Unknown)
continue; // not sure what to do here
ResidueKind kind = sequence_kind(ent.polymer_type);
std::string seq1 = pdbx_one_letter_code(ent.full_sequence, kind);
std::string strand_ids;
for (const std::string& sub : ent.subchains) {
auto strand_id = subs_to_strands.find(sub);
if (strand_id != subs_to_strands.end()) {
if (!strand_ids.empty())
strand_ids += ',';
strand_ids += strand_id->second;
}
}
ent_poly_loop.add_row({qchain(ent.name),
polymer_type_to_string(ent.polymer_type),
string_or_qmark(strand_ids),
string_or_qmark(seq1)});
}
}
if (groups.struct_ref) { // _struct_ref, _struct_ref_seq
block.items.reserve(block.items.size() + 2); // avoid re-allocation
cif::Loop& ref_loop = block.init_mmcif_loop("_struct_ref.",
{"id", "entity_id", "db_name", "db_code",
"pdbx_db_accession", "pdbx_db_isoform"});
cif::Loop& seq_loop = block.init_mmcif_loop("_struct_ref_seq.", {
"align_id", "ref_id", "pdbx_strand_id", "pdbx_PDB_id_code",
"seq_align_beg", "seq_align_end", "pdbx_db_accession",
"db_align_beg", "db_align_end",
"pdbx_auth_seq_align_beg", "pdbx_seq_align_beg_ins_code",
"pdbx_auth_seq_align_end", "pdbx_seq_align_end_ins_code"});
int counter = 0;
int counter2 = 0;
for (const Entity& ent : st.entities)
for (const Entity::DbRef& dbref : ent.dbrefs) {
ref_loop.add_row({std::to_string(++counter),
qchain(ent.name),
string_or_dot(dbref.db_name),
string_or_dot(dbref.id_code),
string_or_qmark(dbref.accession_code),
string_or_qmark(dbref.isoform)});
for (const std::string& subchain : ent.subchains) {
auto strand_id = subs_to_strands.find(subchain);
if (strand_id == subs_to_strands.end())
continue;
// DbRef::label_seq_begin/end (_struct_ref_seq.seq_align_beg/end) is
// not filled in when reading PDB file, so we check it here.
Residue::OptionalNum label_begin = dbref.label_seq_begin;
Residue::OptionalNum label_end = dbref.label_seq_end;
if (!label_begin || !label_end) {
ConstResidueSpan span = st.models[0].get_subchain(subchain);
try {
label_begin = span.auth_seq_id_to_label(dbref.seq_begin);
label_end = span.auth_seq_id_to_label(dbref.seq_end);
} catch (const std::out_of_range&) {}
}
SeqId begin = dbref.seq_begin;
SeqId end = dbref.seq_end;
if (!begin.num || !end.num) {
if (const Chain* chain = st.models[0].find_chain(strand_id->second))
if (ConstResidueGroup polymer = chain->get_polymer()) {
begin = polymer.label_seq_id_to_auth(dbref.label_seq_begin);
end = polymer.label_seq_id_to_auth(dbref.label_seq_end);
}
}
seq_loop.add_row({std::to_string(++counter2),
std::to_string(counter),
strand_id->second, // pdbx_strand_id
id,
label_begin.str(),
label_end.str(),
string_or_qmark(dbref.accession_code),
dbref.db_begin.num.str(),
dbref.db_end.num.str(),
begin.num.str(),
pdbx_icode(begin),
end.num.str(),
pdbx_icode(end)});
}
}
}
if (groups.chem_comp) {
std::set<std::string> resnames;
for (const Model& model : st.models)
for (const Chain& chain : model.chains)
for (const Residue& res : chain.residues)
resnames.insert(res.name);
for (const Entity& ent : st.entities)
for (const std::string& item : ent.full_sequence)
resnames.insert(Entity::first_mon(item));
cif::Loop& chem_comp_loop = block.init_mmcif_loop("_chem_comp.", {"id", "type"});
if (!st.shortened_ccd_codes.empty())
chem_comp_loop.tags.push_back("_chem_comp.three_letter_code");
for (const std::string& name : resnames) {
chem_comp_loop.values.push_back(cif::quote(name));
chem_comp_loop.values.push_back(".");
if (!st.shortened_ccd_codes.empty()) {
chem_comp_loop.values.push_back(cif::quote(name));
for (const auto& old_new : st.shortened_ccd_codes)
if (old_new.second == name)
chem_comp_loop.values.back() = old_new.first;
}
}
}
if (groups.exptl) {
// _exptl
if (!st.meta.experiments.empty()) {
cif::Loop& loop = block.init_mmcif_loop("_exptl.",
{"entry_id", "method", "crystals_number"});
for (const ExperimentInfo& exper : st.meta.experiments)
loop.add_row({id, cif::quote(exper.method),
int_or_qmark(exper.number_of_crystals)});
} else {
auto exptl_method = st.info.find("_exptl.method");
if (exptl_method != st.info.end()) {
cif::Loop& loop = block.init_mmcif_loop("_exptl.", {"entry_id", "method"});
for (const std::string& m : gemmi::split_str(exptl_method->second, "; "))
loop.add_row({id, cif::quote(m)});
}
}
// _exptl_crystal
if (!st.meta.crystals.empty()) {
cif::Loop& loop = block.init_mmcif_loop("_exptl_crystal.",
{"id", "description"});
for (const CrystalInfo& cryst : st.meta.crystals)
loop.add_row({cryst.id, string_or_qmark(cryst.description)});
}
// _exptl_crystal_grow
if (std::any_of(st.meta.crystals.begin(), st.meta.crystals.end(),
[](const CrystalInfo& c) { return !c.ph_range.empty() || !std::isnan(c.ph); })) {
cif::Loop& grow_loop = block.init_mmcif_loop("_exptl_crystal_grow.",
{"crystal_id", "pH", "pdbx_pH_range"});
for (const CrystalInfo& crystal : st.meta.crystals)
grow_loop.add_row({cif::quote(crystal.id),
number_or_qmark(crystal.ph),
string_or_qmark(crystal.ph_range)});
}
}
if (groups.diffrn &&
std::any_of(st.meta.crystals.begin(), st.meta.crystals.end(),
[](const CrystalInfo& c) { return !c.diffractions.empty(); })) {
cif::Loop& loop = block.init_mmcif_loop("_diffrn.", {"id", "crystal_id", "ambient_temp"});
for (const CrystalInfo& cryst : st.meta.crystals)
for (const DiffractionInfo& diffr : cryst.diffractions)
loop.add_row({diffr.id, cryst.id, number_or_qmark(diffr.temperature)});
// _diffrn_detector
cif::Loop& det_loop = block.init_mmcif_loop("_diffrn_detector.",
{"diffrn_id",
"pdbx_collection_date",
"detector",
"type",
"details"});
for (const CrystalInfo& cryst : st.meta.crystals)
for (const DiffractionInfo& diffr : cryst.diffractions)
det_loop.add_row({diffr.id,
string_or_qmark(diffr.collection_date),
string_or_qmark(diffr.detector),
string_or_qmark(diffr.detector_make),
string_or_qmark(diffr.optics)});
// _diffrn_radiation
cif::Loop& rad_loop = block.init_mmcif_loop("_diffrn_radiation.",
{"diffrn_id",
"pdbx_scattering_type",
"pdbx_monochromatic_or_laue_m_l",
"monochromator"});
for (const CrystalInfo& cryst : st.meta.crystals)
for (const DiffractionInfo& diffr : cryst.diffractions)
rad_loop.add_row({diffr.id,
string_or_qmark(diffr.scattering_type),
std::string(1, diffr.mono_or_laue ? diffr.mono_or_laue : '?'),
string_or_qmark(diffr.monochromator)});
// _diffrn_source
cif::Loop& source_loop = block.init_mmcif_loop("_diffrn_source.",
{"diffrn_id",
"source",
"type",
"pdbx_synchrotron_site",
"pdbx_synchrotron_beamline",
"pdbx_wavelength_list"});
for (const CrystalInfo& crystal : st.meta.crystals)
for (const DiffractionInfo& diffr : crystal.diffractions)
source_loop.add_row({diffr.id,
string_or_qmark(diffr.source),
string_or_qmark(diffr.source_type),
string_or_qmark(diffr.synchrotron),
string_or_qmark(diffr.beamline),
string_or_qmark(diffr.wavelengths)});
}
if (groups.reflns && !st.meta.experiments.empty()) {
// _reflns
cif::Loop& loop = block.init_mmcif_loop("_reflns.", {
"entry_id",
"pdbx_ordinal",
"pdbx_diffrn_id",
"number_obs",
"d_resolution_high",
"d_resolution_low",
"percent_possible_obs",
"pdbx_redundancy",
"pdbx_Rmerge_I_obs",
"pdbx_Rsym_value",
"pdbx_netI_over_sigmaI",
/*"B_iso_Wilson_estimate"*/});
int n = 0;
for (const ExperimentInfo& exper : st.meta.experiments)
loop.add_row({id,
std::to_string(++n),
string_or_dot(join_str(exper.diffraction_ids, ",")),
int_or_qmark(exper.unique_reflections),
number_or_qmark(exper.reflections.resolution_high),
number_or_qmark(exper.reflections.resolution_low),
number_or_qmark(exper.reflections.completeness),
number_or_qmark(exper.reflections.redundancy),
number_or_qmark(exper.reflections.r_merge),
number_or_qmark(exper.reflections.r_sym),
number_or_qmark(exper.reflections.mean_I_over_sigma),
/*number_or_qmark(exper.b_wilson)*/});
// _reflns_shell
cif::Loop* shell_loop = nullptr;
n = 0;
for (const ExperimentInfo& exper : st.meta.experiments) {
std::string diffrn_id =
string_or_dot(join_str(exper.diffraction_ids, ","));
for (const ReflectionsInfo& shell : exper.shells) {
if (!shell_loop)
shell_loop = &block.init_mmcif_loop("_reflns_shell.", {
"pdbx_ordinal",
"pdbx_diffrn_id",
"d_res_high",
"d_res_low",
"percent_possible_all",
"pdbx_redundancy",
"Rmerge_I_obs",
"pdbx_Rsym_value",
"meanI_over_sigI_obs"});
shell_loop->add_row({std::to_string(++n),
diffrn_id,
number_or_qmark(shell.resolution_high),
number_or_qmark(shell.resolution_low),
number_or_qmark(shell.completeness),
number_or_qmark(shell.redundancy),
number_or_qmark(shell.r_merge),
number_or_qmark(shell.r_sym),
number_or_qmark(shell.mean_I_over_sigma)});
}
}
}
if (groups.refine && !st.meta.refinement.empty()) {
block.items.reserve(block.items.size() + 4);
cif::Loop& loop = block.init_mmcif_loop("_refine.", {
"entry_id",
"pdbx_refine_id",
"ls_d_res_high",
"ls_d_res_low",
"ls_percent_reflns_obs",
"ls_number_reflns_obs",
"ls_number_reflns_R_work"});
cif::Loop& analyze_loop = block.init_mmcif_loop("_refine_analyze.", {
"entry_id",
"pdbx_refine_id",
"Luzzati_coordinate_error_obs"});
cif::Loop& restr_loop = block.init_mmcif_loop("_refine_ls_restr.", {
"pdbx_refine_id", "type",
"number", "weight", "pdbx_restraint_function", "dev_ideal"});
// _refine_ls_shell
std::vector<std::string> shell_tags = {
"pdbx_refine_id",
"d_res_high",
"d_res_low",
"percent_reflns_obs",
"number_reflns_obs",
"number_reflns_R_work",
"number_reflns_R_free",
"R_factor_obs",
"R_factor_R_work",
"R_factor_R_free"};
bool has_shell_fsc = false;
bool has_shell_ffcc = false;
bool has_shell_iicc = false;
for (const RefinementInfo& ref : st.meta.refinement)
for (const BasicRefinementInfo& bin : ref.bins) {
if (!std::isnan(bin.fsc_work) || !std::isnan(bin.fsc_free))
has_shell_fsc = true;
if (!std::isnan(bin.cc_fo_fc_work) || !std::isnan(bin.cc_fo_fc_free))
has_shell_ffcc = true;
if (!std::isnan(bin.cc_intensity_work) || !std::isnan(bin.cc_intensity_free))
has_shell_iicc = true;
}
if (has_shell_fsc) {
shell_tags.push_back("pdbx_fsc_work");
shell_tags.push_back("pdbx_fsc_free");
}
if (has_shell_ffcc) {
shell_tags.push_back("correlation_coeff_Fo_to_Fc");
shell_tags.push_back("correlation_coeff_Fo_to_Fc_free");
}
if (has_shell_iicc) {
shell_tags.push_back("correlation_coeff_I_to_Fcsqd_work");
shell_tags.push_back("correlation_coeff_I_to_Fcsqd_free");
}
cif::Loop& shell_loop = block.init_mmcif_loop("_refine_ls_shell.", shell_tags);
for (size_t i = 0; i != st.meta.refinement.size(); ++i) {
const RefinementInfo& ref = st.meta.refinement[i];
loop.add_values({id,
cif::quote(ref.id),
number_or_dot(ref.resolution_high),
number_or_dot(ref.resolution_low),
number_or_dot(ref.completeness),
int_or_dot(get_number_obs(ref)),
int_or_qmark(get_number_work(ref))});
auto add = [&](const std::string& tag, const std::string& val) {
if (i == 0)
loop.tags.push_back("_refine." + tag);
loop.values.push_back(val);
};
if (st.meta.has(&RefinementInfo::rfree_set_count))
add("ls_number_reflns_R_free", int_or_dot(ref.rfree_set_count));
if (st.meta.has(&RefinementInfo::r_all))
add("ls_R_factor_obs", number_or_qmark(ref.r_all));
if (st.meta.has(&RefinementInfo::r_work))
add("ls_R_factor_R_work", number_or_qmark(ref.r_work));
if (st.meta.has(&RefinementInfo::r_free))
add("ls_R_factor_R_free", number_or_qmark(ref.r_free));
if (st.meta.has(&RefinementInfo::cross_validation_method))
add("pdbx_ls_cross_valid_method",
string_or_qmark(ref.cross_validation_method));
if (st.meta.has(&RefinementInfo::rfree_selection_method))
add("pdbx_R_Free_selection_details",
string_or_qmark(ref.rfree_selection_method));
if (st.meta.has(&RefinementInfo::mean_b))
add("B_iso_mean", number_or_qmark(ref.mean_b));
if (st.meta.has(&RefinementInfo::aniso_b)) {
if (i == 0)
for (const char* index : {"[1][1]", "[2][2]", "[3][3]", "[1][2]", "[1][3]", "[2][3]"})
loop.tags.push_back(std::string("_refine.aniso_B") + index);
for (double d : ref.aniso_b.elements_pdb())
loop.values.push_back(number_or_qmark(d));
}
if (st.meta.has(&RefinementInfo::dpi_blow_r))
add("pdbx_overall_SU_R_Blow_DPI",
number_or_qmark(ref.dpi_blow_r));
if (st.meta.has(&RefinementInfo::dpi_blow_rfree))
add("pdbx_overall_SU_R_free_Blow_DPI",
number_or_qmark(ref.dpi_blow_rfree));
if (st.meta.has(&RefinementInfo::dpi_cruickshank_r))
add("overall_SU_R_Cruickshank_DPI",
number_or_qmark(ref.dpi_cruickshank_r));
if (st.meta.has(&RefinementInfo::dpi_cruickshank_rfree))
add("pdbx_overall_SU_R_free_Cruickshank_DPI",
number_or_qmark(ref.dpi_cruickshank_rfree));
if (st.meta.has(&RefinementInfo::cc_fo_fc_work))
add("correlation_coeff_Fo_to_Fc", number_or_qmark(ref.cc_fo_fc_work));
if (st.meta.has(&RefinementInfo::cc_fo_fc_free))
add("correlation_coeff_Fo_to_Fc_free",
number_or_qmark(ref.cc_fo_fc_free));
if (st.meta.has(&RefinementInfo::fsc_work))
add("pdbx_average_fsc_work", number_or_qmark(ref.fsc_work));
if (st.meta.has(&RefinementInfo::fsc_free))
add("pdbx_average_fsc_free", number_or_qmark(ref.fsc_free));
if (st.meta.has(&RefinementInfo::cc_intensity_work))
add("correlation_coeff_I_to_Fcsqd_work", number_or_qmark(ref.cc_intensity_work));
if (st.meta.has(&RefinementInfo::cc_intensity_free))
add("correlation_coeff_I_to_Fcsqd_free", number_or_qmark(ref.cc_intensity_free));
if (!st.meta.solved_by.empty())
add("pdbx_method_to_determine_struct", string_or_qmark(st.meta.solved_by));
if (!st.meta.starting_model.empty())
add("pdbx_starting_model", string_or_qmark(st.meta.starting_model));
if (!std::isnan(ref.luzzati_error))
analyze_loop.add_row({id,
cif::quote(ref.id),
number_or_qmark(ref.luzzati_error)});
for (const RefinementInfo::Restr& restr : ref.restr_stats)
restr_loop.add_row({cif::quote(ref.id),
cif::quote(restr.name),
int_or_qmark(restr.count),
number_or_qmark(restr.weight),
string_or_qmark(restr.function),
number_or_qmark(restr.dev_ideal)});
for (const BasicRefinementInfo& bin : ref.bins) {
shell_loop.add_values({cif::quote(ref.id),
number_or_dot(bin.resolution_high),
number_or_qmark(bin.resolution_low),
number_or_qmark(bin.completeness),
int_or_qmark(get_number_obs(bin)),
int_or_qmark(get_number_work(bin)),
int_or_qmark(bin.rfree_set_count),
number_or_qmark(bin.r_all),
number_or_qmark(bin.r_work),
number_or_qmark(bin.r_free)});
if (has_shell_fsc)
shell_loop.add_values({number_or_qmark(bin.fsc_work),
number_or_qmark(bin.fsc_free)});
if (has_shell_ffcc)
shell_loop.add_values({number_or_qmark(bin.cc_fo_fc_work),
number_or_qmark(bin.cc_fo_fc_free)});
if (has_shell_iicc)
shell_loop.add_values({number_or_qmark(bin.cc_intensity_work),
number_or_qmark(bin.cc_intensity_free)});
}
}
assert(shell_loop.values.size() % shell_loop.tags.size() == 0);
assert(loop.values.size() % loop.tags.size() == 0);
}
if (groups.title_keywords) {
auto title = st.info.find("_struct.title");
if (title != st.info.end()) {
cif::ItemSpan span(block.items, "_struct.");
span.set_pair("_struct.entry_id", id);
span.set_pair(title->first, cif::quote(title->second));
}
auto pdbx_keywords = st.info.find("_struct_keywords.pdbx_keywords");
auto keywords = st.info.find("_struct_keywords.text");
cif::ItemSpan span(block.items, "_struct_keywords.");
if (pdbx_keywords != st.info.end() || keywords != st.info.end())
span.set_pair("_struct_keywords.entry_id", id);
if (pdbx_keywords != st.info.end())
span.set_pair(pdbx_keywords->first, cif::quote(pdbx_keywords->second));
if (keywords != st.info.end())
span.set_pair(keywords->first, cif::quote(keywords->second));
}
if (groups.ncs)
write_ncs_oper(st, block);
if (groups.struct_asym) {
cif::Loop& asym_loop = block.init_mmcif_loop("_struct_asym.",
{"id", "entity_id"});
for (const Chain& chain : st.models[0].chains)
for (ConstResidueSpan& sub : chain.subchains()) {
const std::string& sub_id = sub.subchain_id();
if (!sub_id.empty()) {
const Entity* ent = find_entity_of_subchain(sub_id, st.entities);
asym_loop.add_row({sub_id, (ent ? qchain(ent->name) : "?")});
}
}
}
bool nontrivial_origx = st.has_origx && !st.origx.is_identity();
if (groups.origx && nontrivial_origx) { // _database_PDB_matrix (ORIGX)
cif::ItemSpan span(block.items, "_database_PDB_matrix.");
span.set_pair("_database_PDB_matrix.entry_id", id);
std::string tag_mat = "_database_PDB_matrix.origx[0][0]";
std::string tag_vec = "_database_PDB_matrix.origx_vector[0]";
for (int i = 0; i < 3; ++i) {
tag_mat[27] += 1; // origx[0] -> origx[1] -> origx[2]
tag_vec[34] += 1;
for (int j = 0; j < 3; ++j) {
tag_mat[30] = '1' + j;
span.set_pair(tag_mat, to_str(st.origx.mat[i][j]));
}
span.set_pair(tag_vec, to_str(st.origx.vec.at(i)));
}
}
if (groups.struct_conf && !st.helices.empty()) {
cif::Loop& struct_conf_loop = block.init_mmcif_loop("_struct_conf.",
{"conf_type_id", "id",
"beg_auth_asym_id", "beg_label_asym_id", "beg_label_comp_id",
"beg_label_seq_id", "beg_auth_seq_id", "pdbx_beg_PDB_ins_code",
"end_auth_asym_id", "end_label_asym_id", "end_label_comp_id",
"end_label_seq_id", "end_auth_seq_id", "pdbx_end_PDB_ins_code",
"pdbx_PDB_helix_class", "pdbx_PDB_helix_length"});
int count = 0;
for (const Helix& helix : st.helices) {
const_CRA cra1 = st.models[0].find_cra(helix.start);
const_CRA cra2 = st.models[0].find_cra(helix.end);
if (!cra1.residue || !cra2.residue)
continue;
struct_conf_loop.add_row({
"HELX_P", // conf_type_id
"H" + std::to_string(++count), // id
qchain(cra1.chain->name), // beg_auth_asym_id
subchain_or_dot(*cra1.residue), // beg_label_asym_id
cra1.residue->name, // beg_label_comp_id
cra1.residue->label_seq.str(), // beg_label_seq_id
cra1.residue->seqid.num.str(), // beg_auth_seq_id
pdbx_icode(*cra1.residue), // beg_PDB_ins_code
qchain(cra2.chain->name), // end_auth_asym_id
subchain_or_dot(*cra2.residue), // end_label_asym_id
cra2.residue->name, // end_label_comp_id
cra2.residue->label_seq.str(), // end_label_seq_id
cra2.residue->seqid.num.str(), // end_auth_seq_id
pdbx_icode(*cra2.residue), // end_PDB_ins_code
std::to_string((int)helix.pdb_helix_class), // pdbx_PDB_helix_class
int_or_qmark(helix.length) // pdbx_PDB_helix_length
});
}
if (count != 0)
block.set_pair("_struct_conf_type.id", "HELX_P");
}
// _struct_sheet*
if (groups.struct_sheet && !st.sheets.empty()) {
cif::Loop& sheet_loop = block.init_mmcif_loop("_struct_sheet.",
{"id", "number_strands"});
for (const Sheet& sheet : st.sheets)
sheet_loop.add_row({string_or_dot(sheet.name),
std::to_string(sheet.strands.size())});
cif::Loop& order_loop = block.init_mmcif_loop("_struct_sheet_order.",
{"sheet_id", "range_id_1", "range_id_2", "sense"});
for (const Sheet& sheet : st.sheets)
for (size_t i = 1; i < sheet.strands.size(); ++i) {
const Sheet::Strand& strand = sheet.strands[i];
if (strand.sense != 0)
order_loop.add_row({string_or_dot(sheet.name),
std::to_string(i), std::to_string(i+1),
strand.sense > 0 ? "parallel" : "anti-parallel"});
}
cif::Loop& range_loop = block.init_mmcif_loop("_struct_sheet_range.",
{"sheet_id", "id",
"beg_auth_asym_id", "beg_label_asym_id", "beg_label_comp_id",
"beg_label_seq_id", "beg_auth_seq_id", "pdbx_beg_PDB_ins_code",
"end_auth_asym_id", "end_label_asym_id", "end_label_comp_id",
"end_label_seq_id", "end_auth_seq_id", "pdbx_end_PDB_ins_code"});
for (const Sheet& sheet : st.sheets)
for (size_t i = 0; i < sheet.strands.size(); ++i) {
const Sheet::Strand& strand = sheet.strands[i];
const_CRA cra1 = st.models[0].find_cra(strand.start);
const_CRA cra2 = st.models[0].find_cra(strand.end);
if (!cra1.residue || !cra2.residue)
continue;
range_loop.add_row({
string_or_dot(sheet.name), // sheet_id
std::to_string(i+1), // id
qchain(cra1.chain->name), // beg_auth_asym_id
subchain_or_dot(*cra1.residue), // beg_label_asym_id
cra1.residue->name, // beg_label_comp_id
cra1.residue->label_seq.str(), // beg_label_seq_id
cra1.residue->seqid.num.str(), // beg_auth_seq_id
pdbx_icode(*cra1.residue), // beg_PDB_ins_code
qchain(cra2.chain->name), // end_auth_asym_id
subchain_or_dot(*cra2.residue), // end_label_asym_id
cra2.residue->name, // end_label_comp_id
cra2.residue->label_seq.str(), // end_label_seq_id
cra2.residue->seqid.num.str(), // end_auth_seq_id
pdbx_icode(*cra2.residue) // end_PDB_ins_code
});
}
cif::Loop& hbond_loop = block.init_mmcif_loop("_pdbx_struct_sheet_hbond.",
{"sheet_id", "range_id_1", "range_id_2",
"range_1_auth_asym_id", "range_1_label_asym_id",
"range_1_label_comp_id", "range_1_label_seq_id", "range_1_auth_seq_id",
"range_1_PDB_ins_code", "range_1_label_atom_id",
"range_2_auth_asym_id", "range_2_label_asym_id",
"range_2_label_comp_id", "range_2_label_seq_id", "range_2_auth_seq_id",
"range_2_PDB_ins_code", "range_2_label_atom_id"});
for (const Sheet& sheet : st.sheets)
for (size_t i = 1; i < sheet.strands.size(); ++i) {
const Sheet::Strand& strand = sheet.strands[i];
if (strand.hbond_atom2.atom_name.empty())
continue;
// hbond_atomN is not a full atom "address": altloc is missing
const_CRA cra1 = st.models[0].find_cra(strand.hbond_atom1);
const_CRA cra2 = st.models[0].find_cra(strand.hbond_atom2);
if (!cra1.residue || !cra2.residue)
continue;
hbond_loop.add_row({
string_or_dot(sheet.name), // sheet_id
std::to_string(i), // range_id_1
std::to_string(i+1), // range_id_2
qchain(cra1.chain->name), // range_1_auth_asym_id
subchain_or_dot(*cra1.residue), // range_1_label_asym_id
cra1.residue->name, // range_1_label_comp_id
cra1.residue->label_seq.str(), // range_1_label_seq_id
cra1.residue->seqid.num.str(), // range_1_auth_seq_id
pdbx_icode(*cra1.residue), // range_1_PDB_ins_code
cif::quote(strand.hbond_atom1.atom_name), // range_1_label_atom_id
qchain(cra2.chain->name), // range_2_auth_asym_id
subchain_or_dot(*cra2.residue), // range_2_label_asym_id
cra2.residue->name, // range_2_label_comp_id
cra2.residue->label_seq.str(), // range_2_label_seq_id
cra2.residue->seqid.num.str(), // range_2_auth_seq_id
pdbx_icode(*cra2.residue), // range_2_PDB_ins_code
cif::quote(strand.hbond_atom2.atom_name) // range_2_label_atom_id
});
}
}
// _pdbx_struct_assembly* and _struct_biol are REMARK 300/350 in PDB
if (groups.struct_biol && !st.meta.remark_300_detail.empty()) {
cif::ItemSpan span(block.items, "_struct_biol.");
span.set_pair("_struct_biol.id", "1");
span.set_pair("_struct_biol.details", cif::quote(st.meta.remark_300_detail));
}
if (groups.assembly && !st.assemblies.empty())
write_assemblies(st, block);
if (groups.conn)
write_struct_conn(st, block);
if (groups.cis) // _struct_mon_prot_cis
write_cispeps(st, block);
// _pdbx_struct_mod_residue (MODRES)
if (groups.modres && !st.mod_residues.empty()) {
bool use_ccp4_mod_id = false;
for (const ModRes& modres : st.mod_residues)
if (!modres.mod_id.empty())
use_ccp4_mod_id = true;
cif::Loop& loop = block.init_mmcif_loop("_pdbx_struct_mod_residue.",
{"id", "auth_asym_id", "auth_seq_id", "PDB_ins_code", "auth_comp_id",
"label_comp_id", "parent_comp_id", "details"});
if (use_ccp4_mod_id)
loop.tags.push_back("_pdbx_struct_mod_residue.ccp4_mod_id");
int counter = 0;
for (const ModRes& modres : st.mod_residues) {
loop.add_values({std::to_string(++counter),
qchain(modres.chain_name),
modres.res_id.seqid.num.str(),
pdbx_icode(modres.res_id),
string_or_dot(modres.res_id.name),
string_or_qmark(modres.res_id.name),
string_or_qmark(modres.parent_comp_id),
string_or_qmark(modres.details)});
if (use_ccp4_mod_id)
loop.values.push_back(string_or_qmark(modres.mod_id));
}
}
// _atom_sites (SCALE)
if (groups.scale && (nontrivial_origx || st.cell.explicit_matrices)) {
cif::ItemSpan span(block.items, "_atom_sites.");
span.set_pair("_atom_sites.entry_id", id);
std::string prefix = "_atom_sites.fract_transf_";
for (int i = 0; i < 3; ++i) {
std::string idx = "[" + std::to_string(i + 1) + "]";
const auto& frac = st.cell.frac;
std::string matrix_idx = prefix + "matrix";
matrix_idx += idx;
span.set_pair(matrix_idx + "[1]", to_str(frac.mat[i][0]));
span.set_pair(matrix_idx + "[2]", to_str(frac.mat[i][1]));
span.set_pair(matrix_idx + "[3]", to_str(frac.mat[i][2]));
span.set_pair(cat(prefix, "vector", idx), to_str(frac.vec.at(i)));
}
}
// _atom_type
if (groups.atom_type) {
std::array<bool, (int)El::END> types{};
for (const Model& model : st.models)
for (const Chain& chain : model.chains)
for (const Residue& res : chain.residues)
for (const Atom& atom : res.atoms)
types[atom.element.ordinal()] = true;
cif::Loop& atom_type_loop = block.init_mmcif_loop("_atom_type.", {"symbol"});
for (int i = 0; i < (int)El::END; ++i)
if (types[i])
atom_type_loop.add_row({Element((El)i).uname()});
}
if (groups.entity_poly_seq) {
cif::Loop& poly_loop = block.init_mmcif_loop("_entity_poly_seq.",
{"entity_id", "num", "mon_id", "hetero"});
for (const Entity& ent : st.entities)
if (ent.entity_type == EntityType::Polymer) {
// SEQRES from PDB doesn't record microheterogeneity.
std::string hetero_no = ent.reflects_microhetero ? "n" : "?";
for (size_t i = 0; i != ent.full_sequence.size(); ++i) {
const std::string& mon_ids = ent.full_sequence[i];
std::string num = std::to_string(i+1);
size_t start = 0, end;
while ((end = mon_ids.find(',', start)) != std::string::npos) {
poly_loop.add_row({qchain(ent.name), num,
mon_ids.substr(start, end-start), "y"});
start = end + 1;
}
poly_loop.add_row({qchain(ent.name), num, mon_ids.substr(start),
start == 0 ? hetero_no : "y"});
}
}
}
if (groups.atoms)
add_cif_atoms(st, block, groups.group_pdb, groups.auth_all);
if (groups.tls && st.meta.get_tls_groups() != nullptr) {
// pdbx_refine_id doesn't make sense here, but it's required
// by the mmCIF spec. In joint refinement, TLS constraints can't be
// specific to a dataset, because they constrain the shared model.
cif::Loop& loop = block.init_mmcif_loop("_pdbx_refine_tls.", {
"id", "pdbx_refine_id",
"origin_x", "origin_y", "origin_z",
"T[1][1]", "T[2][2]", "T[3][3]", "T[1][2]", "T[1][3]", "T[2][3]",
"L[1][1]", "L[2][2]", "L[3][3]", "L[1][2]", "L[1][3]", "L[2][3]",
"S[1][1]", "S[1][2]", "S[1][3]",
"S[2][1]", "S[2][2]", "S[2][3]",
"S[3][1]", "S[3][2]", "S[3][3]"});
for (const RefinementInfo& ref : st.meta.refinement)
for (const TlsGroup& tls : ref.tls_groups) {
const SMat33<double>& T = tls.T;
const SMat33<double>& L = tls.L;
const Mat33& S = tls.S;
auto q = number_or_qmark;
loop.add_row({string_or_dot(tls.id), cif::quote(ref.id),
q(tls.origin.x), q(tls.origin.y), q(tls.origin.z),
q(T.u11), q(T.u22), q(T.u33), q(T.u12), q(T.u13), q(T.u23),
q(L.u11), q(L.u22), q(L.u33), q(L.u12), q(L.u13), q(L.u23),
q(S[0][0]), q(S[0][1]), q(S[0][2]),
q(S[1][0]), q(S[1][1]), q(S[1][2]),
q(S[2][0]), q(S[2][1]), q(S[2][2])});
}
cif::Loop& group_loop = block.init_mmcif_loop("_pdbx_refine_tls_group.", {
"id", "refine_tls_id", "pdbx_refine_id",
"beg_auth_asym_id", "beg_auth_seq_id", "beg_PDB_ins_code",
"end_auth_asym_id", "end_auth_seq_id", "end_PDB_ins_code",
"selection_details"});
int counter = 1;
for (const RefinementInfo& ref : st.meta.refinement)
for (const TlsGroup& tls : ref.tls_groups)
for (const TlsGroup::Selection& sel : tls.selections)
group_loop.add_row({std::to_string(counter++),
string_or_dot(tls.id),
cif::quote(ref.id),
string_or_qmark(sel.chain),
sel.res_begin.num.str(),
pdbx_icode(sel.res_begin),
string_or_qmark(sel.chain),
sel.res_end.num.str(),
pdbx_icode(sel.res_end),
string_or_qmark(sel.details)});
}
if (groups.software && !st.meta.software.empty()) {
bool write_all_fields = false;
for (const SoftwareItem& item : st.meta.software)
if (!item.date.empty() || !item.description.empty() ||
!item.contact_author.empty() || !item.contact_author_email.empty())
write_all_fields = true;
cif::Loop& loop = block.init_mmcif_loop("_software.",
{"pdbx_ordinal", "classification", "name", "version"});
if (write_all_fields)
loop.tags.insert(loop.tags.end(),
{"_software.date", "_software.description",
"_software.contact_author", "_software.contact_author_email"});
int ordinal = 0;
for (const SoftwareItem& item : st.meta.software) {
loop.add_values({
std::to_string(++ordinal),
cif::quote(software_classification_to_string(item.classification)),
cif::quote(item.name),
string_or_dot(item.version)});
if (write_all_fields)
loop.add_values({
string_or_qmark(item.date),
string_or_qmark(item.description),
string_or_qmark(item.contact_author),
string_or_qmark(item.contact_author_email)});
}
}
}
cif::Document make_mmcif_document(const Structure& st, MmcifOutputGroups groups) {
cif::Document doc;
doc.blocks.resize(1);
update_mmcif_block(st, doc.blocks[0], groups);
return doc;
}
cif::Block make_mmcif_block(const Structure& st, MmcifOutputGroups groups) {
cif::Block block;
update_mmcif_block(st, block, groups);
return block;
}
cif::Block make_mmcif_headers(const Structure& st) {
MmcifOutputGroups groups(true);
groups.atoms = false;
return make_mmcif_block(st, groups);
}
void add_minimal_mmcif_data(const Structure& st, cif::Block& block) {
cif::ItemSpan cell_span(block.items, "_cell.");
write_cell_parameters(st.cell, cell_span);
block.set_pair("_symmetry.space_group_name_H-M", cif::quote(st.spacegroup_hm));
write_ncs_oper(st, block);
add_cif_atoms(st, block, /*use_group_pdb=*/false, /*auth_all=*/false);
}
} // namespace gemmi