/** * get atom positions from cif * @author Tobias Weber * @date Jan-2019 * @license GPLv3, see 'LICENSE' file */ #ifndef __LOAD_CIF_H__ #define __LOAD_CIF_H__ #include #include //#include #include #include #include #include #include #include "libs/math_algos.h" using namespace m_ops; template struct Lattice { t_real a, b, c; t_real alpha, beta, gamma; }; /** * removes quotation marks around a string */ template void remove_quotes(t_str& str) { if(str.length() == 0) return; if(str[0] == '\'' || str[0] == '\"') str.erase(str.begin()); if(str[str.size()-1] == '\'' || str[str.size()-1] == '\"') str.erase(str.begin()+str.size()-1); } /** * gets the atom positions from the CIF */ template std::tuple, std::vector> get_cif_atoms(gemmi::cif::Block& block) { std::vector atoms; std::vector atomnames; // possible names for atom symbol const char* loopNames[] = { "_type_symbol", "_symbol" "_site_label", "_label", }; for(const char* loopName : loopNames) { atoms.clear(); atomnames.clear(); auto tabAtoms = block.find("_atom_site", {loopName, "_fract_x", "_fract_y", "_fract_z"}); for(std::size_t row=0; row(tabAtoms[row][1]); const t_real y = m::stoval(tabAtoms[row][2]); const t_real z = m::stoval(tabAtoms[row][3]); atoms.emplace_back(t_vec{{x, y, z}}); } // already found, no need to try another name if(tabAtoms.length()) break; } return std::make_tuple(atoms, atomnames); } /** * gets the symmetry operations from the CIF */ template std::vector get_cif_ops(gemmi::cif::Block& block) { std::vector ops; // possible names for symop loop const char* loopNames[] = { "_symmetry_equiv_pos_as_xyz", "_space_group_symop_operation_xyz", }; for(const char* loopName : loopNames) { ops.clear(); //std::cerr << "Trying symop loop name " << loopName << std::endl; auto colOps = block.find_values(loopName); for(std::size_t row=0; row({ std::get<0>(std::get<0>(M)), std::get<1>(std::get<0>(M)), std::get<2>(std::get<0>(M)), std::get<3>(std::get<0>(M)), std::get<0>(std::get<1>(M)), std::get<1>(std::get<1>(M)), std::get<2>(std::get<1>(M)), std::get<3>(std::get<1>(M)), std::get<0>(std::get<2>(M)), std::get<1>(std::get<2>(M)), std::get<2>(std::get<2>(M)), std::get<3>(std::get<2>(M)), std::get<0>(std::get<3>(M)), std::get<1>(std::get<3>(M)), std::get<2>(std::get<3>(M)), std::get<3>(std::get<3>(M)) }); ops.emplace_back(std::move(mat)); } // already found, no need to try another name if(colOps.length()) break; } return ops; } /** * gets the symmetry operations from the CIF's space group * (use m::equals_all to check if space group operations are the same) */ template std::vector get_cif_sg_ops(gemmi::cif::Block& block) { std::vector ops; if(auto val = block.find_values("_symmetry_space_group_name_H-M"); val.length()) { std::string sgname = boost::trim_copy(val[0]); remove_quotes(sgname); if(auto sg = gemmi::find_spacegroup_by_name(sgname)) { auto symops = sg->operations().all_ops_sorted(); for(const auto &op : symops) { auto M = op.float_seitz(); t_mat mat = m::create({ std::get<0>(std::get<0>(M)), std::get<1>(std::get<0>(M)), std::get<2>(std::get<0>(M)), std::get<3>(std::get<0>(M)), std::get<0>(std::get<1>(M)), std::get<1>(std::get<1>(M)), std::get<2>(std::get<1>(M)), std::get<3>(std::get<1>(M)), std::get<0>(std::get<2>(M)), std::get<1>(std::get<2>(M)), std::get<2>(std::get<2>(M)), std::get<3>(std::get<2>(M)), std::get<0>(std::get<3>(M)), std::get<1>(std::get<3>(M)), std::get<2>(std::get<3>(M)), std::get<3>(std::get<3>(M)) }); ops.emplace_back(std::move(mat)); } } } return ops; } /** * loads the lattice parameters and the atom positions from a CIF */ template std::tuple< std::string /* errors and warnings */, std::vector /* basic atom positions */, std::vector> /* all generated atoms */, std::vector /* atom names */, Lattice /* lattice */ , std::vector /* ops */ > load_cif(const std::string& filename, t_real eps=1e-6) { auto ifstr = std::ifstream(filename); if(!ifstr) return std::make_tuple("Cannot open CIF.", std::vector{}, std::vector>{}, std::vector{}, Lattice{}, std::vector{}); // load CIF auto cif = gemmi::cif::read_istream(ifstr, 4096, filename.c_str()); if(!cif.blocks.size()) return std::make_tuple("No blocks in CIF.", std::vector{}, std::vector>{}, std::vector{}, Lattice{}, std::vector{}); // get the block /*const*/ auto& block = cif.sole_block(); // lattice t_real a{}, b{}, c{}, alpha{}, beta{}, gamma{}; if(auto val = block.find_values("_cell_length_a"); val.length()) a = m::stoval(val[0]); if(auto val = block.find_values("_cell_length_b"); val.length()) b = m::stoval(val[0]); if(auto val = block.find_values("_cell_length_c"); val.length()) c = m::stoval(val[0]); if(auto val = block.find_values("_cell_angle_alpha"); val.length()) alpha = m::stoval(val[0]); if(auto val = block.find_values("_cell_angle_beta"); val.length()) beta = m::stoval(val[0]); if(auto val = block.find_values("_cell_angle_gamma"); val.length()) gamma = m::stoval(val[0]); Lattice latt{.a=a, .b=b, .c=c, .alpha=alpha, .beta=beta, .gamma=gamma}; // fractional atom positions auto [atoms, atomnames] = get_cif_atoms(block); // generate all atoms using symmetry ops std::ostringstream errstr; std::vector> generatedatoms; auto ops = get_cif_ops(block); if(!ops.size()) // if ops are not directly given, use standard ops from space group { errstr << "Warning: Could not find CIF symops, trying to use space group defaults instead.\n"; ops = get_cif_sg_ops(block); if(!ops.size()) errstr << "Warning: No symops found!\n"; } for(t_vec atom : atoms) { // make homogeneuous 4-vector if(atom.size() == 3) atom.push_back(1); // if no ops are given, just output the raw atom position if(!ops.size()) { generatedatoms.push_back(std::vector{{atom}}); continue; } std::vector newatoms = m::apply_ops_hom(atom, ops, eps); generatedatoms.emplace_back(std::move(newatoms)); } return std::make_tuple(errstr.str(), atoms, generatedatoms, atomnames, latt, ops); } /** * gets space group description strings and symmetry operations */ template std::vector // symops >> get_sgs(bool bAddNr=true, bool bAddHall=true) { std::vector>> sgs; for(const auto &sg : gemmi::spacegroup_tables::main) { std::ostringstream ostrDescr; if(bAddNr) ostrDescr << "#" << sg.number << ": "; ostrDescr << sg.hm; if(bAddHall) ostrDescr << " (" << sg.hall << ")"; std::vector ops; for(const auto &op : sg.operations().all_ops_sorted()) { auto M = op.float_seitz(); t_mat mat = m::create({ std::get<0>(std::get<0>(M)), std::get<1>(std::get<0>(M)), std::get<2>(std::get<0>(M)), std::get<3>(std::get<0>(M)), std::get<0>(std::get<1>(M)), std::get<1>(std::get<1>(M)), std::get<2>(std::get<1>(M)), std::get<3>(std::get<1>(M)), std::get<0>(std::get<2>(M)), std::get<1>(std::get<2>(M)), std::get<2>(std::get<2>(M)), std::get<3>(std::get<2>(M)), std::get<0>(std::get<3>(M)), std::get<1>(std::get<3>(M)), std::get<2>(std::get<3>(M)), std::get<3>(std::get<3>(M)) }); ops.emplace_back(std::move(mat)); } sgs.emplace_back(std::make_tuple(sg.number, ostrDescr.str(), ops)); } std::stable_sort(sgs.begin(), sgs.end(), [](const auto& sg1, const auto& sg2) -> bool { return std::get<0>(sg1) < std::get<0>(sg2); }); return sgs; } /** * finds all space groups which transform the initial positions into the final ones */ template std::vector // symops >> find_matching_sgs( const std::vector& posInit, const std::vector& _posFinal, t_real eps=1e-6) { std::vector posFinal = m::keep_atoms_in_uc(_posFinal); std::vector>> matchingSGs; auto sgs = get_sgs(); // iterate spacegroups for(const auto& [sgNum, sgName, sgOps] : sgs) { // generate symmetry-equivalent positions std::vector generatedpos; for(const t_vec& pos : posInit) { std::vector newpos = m::apply_ops_hom(pos, sgOps, eps); generatedpos.insert(generatedpos.end(), newpos.begin(), newpos.end()); } //for(const auto& thepos : generatedpos) // std::cout << thepos << std::endl; // filter multiple occupancies in generatedpos generatedpos = m::remove_duplicates(generatedpos, eps); // no match if(!m::equals_all(generatedpos, posFinal, eps, 3)) continue; matchingSGs.emplace_back(std::make_tuple(sgNum, sgName, sgOps)); } return matchingSGs; } #endif