`--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
1322 lines
59 KiB
C++
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
|