Files
leonarski_fandClaude Opus 5 28a58e94b9 model validation: write the model as it was placed
`--model` re-fractionalizes the model into the data cell and then places it as
one rigid body, but the placed coordinates never reached disk. On a lysozyme
sweep against a non-isomorphous deposited model the move is 3.058 deg and
1.035 A, so a user overlaying their input model on rugnux's maps was out by
exactly that, and no file on disk corresponded to the maps at all.

`<prefix>_model.cif` is that file: the input's chains, residues, ligands,
waters, B-factors, occupancies and anisotropic Us, at the coordinates the maps
were computed from. `<prefix>.cif` is already the merged reflections, hence the
suffix.

The cell and space group come from the same two values WriteReflections is
given - the unit cell and DiffractionExperiment::GetSpaceGroupOrP1() after
AdoptModelFrame has settled the enantiomorph - so the coordinate file and the
.mtz beside it always agree. Taking them from the input model would not: with
data merged in P4(1)2(1)2 and a P4(3)2(1)2 model, the written reflections take
the model's group, which is neither the data's original label nor, when the
model is rejected, the model's own.

Written whenever the maps are, not only where the rigid-body step was
committed. The model is re-fractionalized and may be relabelled whatever the
placement decided, so an unmoved model is still not the input file; and a model
the null rejected is scored, placed and mapped like any other - the negative
result, and the case where the density is most worth looking at.

Nothing in the tree could write coordinates: gemmi_gph declared to_mmcif.hpp
but src/to_mmcif.cpp had been trimmed from the vendored subset. Both it and
to_pdb.cpp (to_mmcif.cpp calls its use_hetatm) are vendored from the same
gemmi 0.7.5 the rest of gemmi_gph comes from, unmodified, and every header they
include was already there. Same package, same MPL-2.0, same LICENSE.txt already
collected into licenses/gemmi.txt and already listed against `gemmi_gph/` in
THIRD_PARTY_NOTICES.md, so no new row and no new licence text.

Verified end to end: read back with gemmi the file differs from the
re-fractionalized input by exactly the reported 3.058 deg / 1.035 A with 0.0000 A
rms about that rigid move, and REFMAC5 at zero cycles against rugnux's own .mtz
starts at R-free 0.3667 where rugnux reports 0.3826 - against 0.5832 for the
unplaced input model, where rugnux reports 0.5911 before the placement.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N
2026-09-02 14:47:38 +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