Coverage Report

Created: 2026-07-16 06:56

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/openbabel/src/ops/fillUC.cpp
Line
Count
Source
1
/**********************************************************************
2
fillUC.cpp - plugin to fill the unit cell from unique atom positions,
3
using unit cell & spacegroup operations
4
5
Copyright (C) 2009 Vincent Favre-Nicolin
6
7
This file is part of the Open Babel project.
8
For more information, see <http://openbabel.org/>
9
10
This program is free software; you can redistribute it and/or modify
11
it under the terms of the GNU General Public License as published by
12
the Free Software Foundation version 2 of the License.
13
14
This program is distributed in the hope that it will be useful,
15
but WITHOUT ANY WARRANTY; without even the implied warranty of
16
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
GNU General Public License for more details.
18
***********************************************************************/
19
#include <openbabel/babelconfig.h>
20
#include <openbabel/op.h>
21
#include <openbabel/mol.h>
22
#include <openbabel/atom.h>
23
#include <openbabel/obiter.h>
24
#include <openbabel/math/spacegroup.h>
25
#include <openbabel/generic.h>
26
#include <openbabel/obconversion.h>
27
#include <map>
28
#include <vector>
29
#include <iostream>
30
31
using namespace std;
32
namespace OpenBabel
33
{
34
35
class OpFillUC : public OBOp
36
{
37
public:
38
2
  OpFillUC(const char* ID) : OBOp(ID, false){
39
2
    OBConversion::RegisterOptionParam("fillUC", nullptr, 1, OBConversion::GENOPTIONS);
40
2
  }
41
0
  const char* Description() override { return "<param> Fill the unit cell (strict or keepconnect)\n"
42
0
    "using unique positions, unit cell and spacegroup"
43
0
    "<param> can be:\n"
44
0
    "   strict (keep only atoms inside the UC) => use \"--fillUC strict\"\n"
45
0
    "   keepconnect (fill the unit cell but keep the original connectivity => use \"--fillUC keepconnect\""; }
46
47
0
  bool WorksWith(OBBase* pOb) const override { return dynamic_cast<OBMol*>(pOb) != nullptr; }
48
  bool Do(OBBase* pOb, const char* OptionText=nullptr, OpMap* pOptions=nullptr,
49
      OBConversion* pConv=nullptr) override;
50
};
51
52
/////////////////////////////////////////////////////////////////
53
OpFillUC theOpFillUC("fillUC"); //Global instance
54
55
// Wrap coordinates in the unit cell with some fuzziness, i.e. when one
56
// coordinate is very very close to 1 (>= 0.999999), we wrap it to
57
// exactly zero.
58
vector3 fuzzyWrapFractionalCoordinate (vector3 coord)
59
0
{
60
0
    double x = fmod(coord.x(), 1);
61
0
    double y = fmod(coord.y(), 1);
62
0
    double z = fmod(coord.z(), 1);
63
0
    if (x < 0) x += 1;
64
0
    if (y < 0) y += 1;
65
0
    if (z < 0) z += 1;
66
67
0
#define LIMIT 0.999999
68
0
    if (x > LIMIT)
69
0
      x -= 1;
70
0
    if (y > LIMIT)
71
0
      y -= 1;
72
0
    if (z > LIMIT)
73
0
      z -= 1;
74
0
#undef LIMIT
75
76
    // Fuzzy logic from Francois-Xavier
77
0
#define EPSILON 1.0e-6
78
0
    if (x > 1 - EPSILON || x < EPSILON)
79
0
      x = 0.0;
80
0
    if (y > 1 - EPSILON || y < EPSILON)
81
0
      y = 0.0;
82
0
    if (z > 1 - EPSILON || z < EPSILON)
83
0
      z = 0.0;
84
0
#undef EPSILON
85
86
0
    return vector3(x, y, z);
87
0
}
88
89
// Whether two points (given in fractional coordinates) are close enough
90
// to be considered duplicates.
91
// This function is duplicate from generic.cpp, these should be merged
92
bool areDuplicateAtoms2(vector3 v1, vector3 v2)
93
0
{
94
0
  vector3 dr = fuzzyWrapFractionalCoordinate(v2)
95
0
    - fuzzyWrapFractionalCoordinate(v1);
96
97
0
  if (dr.x() < -0.5)
98
0
    dr.SetX(dr.x() + 1);
99
0
  if (dr.x() > 0.5)
100
0
    dr.SetX(dr.x() - 1);
101
0
  if (dr.y() < -0.5)
102
0
    dr.SetY(dr.y() + 1);
103
0
  if (dr.y() > 0.5)
104
0
    dr.SetY(dr.y() - 1);
105
0
  if (dr.z() < -0.5)
106
0
    dr.SetZ(dr.z() + 1);
107
0
  if (dr.z() > 0.5)
108
0
    dr.SetZ(dr.z() - 1);
109
110
0
  return (dr.length_2() < 1e-3);
111
0
}
112
113
114
/////////////////////////////////////////////////////////////////
115
bool OpFillUC::Do(OBBase* pOb, const char* OptionText, OpMap* pOptions, OBConversion* /*pConv*/)
116
0
{
117
0
  if (!OptionText)
118
0
    OptionText = "";
119
0
  OBMol* pmol = dynamic_cast<OBMol*>(pOb);
120
0
  if(!pmol)
121
0
    return false;
122
123
0
  if (!(pmol->HasData(OBGenericDataType::UnitCell)))
124
0
  {
125
0
    obErrorLog.ThrowError(__FUNCTION__, "Cannot fill unit cell without a unit cell !" , obWarning);
126
0
    return false;
127
0
  }
128
129
0
  OBUnitCell *pUC = (OBUnitCell*)pmol->GetData(OBGenericDataType::UnitCell);
130
0
  SpaceGroup spacegroup;
131
0
  const SpaceGroup* pSG;
132
0
  map<string,string>::const_iterator itr;
133
134
0
  if(pOptions && pOptions->find("transformations") != pOptions->end())
135
0
  {
136
0
    itr = pOptions->find("transformations");
137
0
    vector<string> vec;
138
0
    tokenize(vec, itr->second.c_str());
139
0
    for(vector<string>::iterator iter = vec.begin(); iter != vec.end(); ++ iter)
140
0
    {
141
0
      if (iter == vec.begin()) // Warn user about converting only once
142
0
        obErrorLog.ThrowError(__FUNCTION__, "Converting to P 1 cell using available symmetry transformations." , obWarning);
143
0
      spacegroup.AddTransform(iter->c_str());
144
0
    }
145
146
0
    pSG = &spacegroup;
147
0
  }
148
0
  else
149
0
  {
150
0
    pSG = pUC->GetSpaceGroup();
151
0
  }
152
153
0
  if (pSG == nullptr)
154
0
  {
155
0
    obErrorLog.ThrowError(__FUNCTION__, "Cannot fill unit cell without spacegroup information !" , obWarning);
156
0
    return false;
157
0
  }
158
  // Now loop over all symmetry operations, and generate symmetric atoms one at a time
159
  // Avoid creating overlapping atoms (duplicate), and bring back atoms within the unit cell
160
  // using two options:
161
  // "--fillUC strict": keep only atoms that are strictly inside the unit cell
162
  //                    (fractionnal coordinates 0<= <1)
163
  // "--fillUC keepconnect": generate symmetrics of the molecule, and translate
164
  //                         it back in the unit cell if necessary
165
166
0
  std::map<OBAtom*,std::vector<vector3> > vatoms;// key: original atoms, value=all generated symmetrics
167
0
  FOR_ATOMS_OF_MOL(atom, *pmol)
168
0
      vatoms[&(*atom)]=std::vector<vector3>();
169
170
0
  for(std::map<OBAtom*,std::vector<vector3> >:: iterator atom=vatoms.begin();
171
0
      atom!=vatoms.end();++atom){
172
0
    vector3 orig = atom->first->GetVector();
173
0
    orig = pUC->CartesianToFractional(orig);// To fractional coordinates
174
175
    // Loop over symmetry operators
176
0
    transform3dIterator ti;
177
0
    const transform3d *t = pSG->BeginTransform(ti);
178
0
    while(t){
179
0
      atom->second.push_back ( (transform3d)(*t) * orig);
180
0
      t = pSG->NextTransform(ti);
181
0
    }
182
0
  }
183
184
0
  if(0==strncasecmp(OptionText, "keepconnect", 11)){
185
    // First, bring back all symmetrical molecules back in the UC
186
0
    for(unsigned int i=0;i<vatoms.begin()->second.size();++i){
187
0
      vector3 ccoord(0,0,0);//geometrical center
188
0
      for(std::map<OBAtom*,std::vector<vector3> >:: iterator atom=vatoms.begin();
189
0
        atom!=vatoms.end();++atom){
190
0
        ccoord+=atom->second[i];
191
0
      }
192
0
      ccoord /=vatoms.size();
193
0
      ccoord=fuzzyWrapFractionalCoordinate(ccoord)-ccoord;
194
0
      for(std::map<OBAtom*,std::vector<vector3> >:: iterator atom=vatoms.begin();
195
0
        atom!=vatoms.end();++atom){
196
0
        atom->second[i]+=ccoord;
197
0
      }
198
0
    }
199
    // Now add atoms that are not duplicates
200
0
    for(std::map<OBAtom*,std::vector<vector3> >:: iterator atom=vatoms.begin();
201
0
        atom!=vatoms.end();++atom){
202
0
      for(unsigned int i=1;i<atom->second.size();++i){
203
0
        bool foundDuplicate = false;
204
0
        for(unsigned int j=0;j<i;++j){
205
0
          if(areDuplicateAtoms2(atom->second[i],atom->second[j])){
206
0
            foundDuplicate=true;
207
0
            break;
208
0
          }
209
0
        }
210
0
        if(!foundDuplicate){
211
0
          vector3 transformed = pUC->FractionalToCartesian(atom->second[i]);
212
          // let's make sure there isn't some *other* atom that's in this spot
213
0
          bool foundCartesianDuplicate = false;
214
0
          FOR_ATOMS_OF_MOL(a, *pmol) {
215
0
            vector3 diff = a->GetVector() - transformed;
216
0
            if (diff.length_2() < 1.0e-4) {
217
0
              foundCartesianDuplicate = true;
218
0
              break;
219
0
            }
220
0
          }
221
222
0
          if (!foundCartesianDuplicate) {
223
0
            OBAtom *newAtom = pmol->NewAtom();
224
0
            newAtom->Duplicate(atom->first);
225
0
            newAtom->SetVector( transformed );
226
0
          }
227
0
        }
228
0
      }
229
0
    }
230
0
  }
231
232
0
  else{
233
0
    if(0!=strncasecmp(OptionText, "strict", 6))
234
0
      obErrorLog.ThrowError(__FUNCTION__, "fillUC: lacking \"strict\n or \"keepconnect\" option, using strict" , obWarning);
235
0
    for(std::map<OBAtom*,std::vector<vector3> >:: iterator atom=vatoms.begin();
236
0
        atom!=vatoms.end();++atom){
237
      // Bring back within unit cell
238
0
      for(unsigned int i=0;i<atom->second.size();++i){
239
0
        atom->second[i]=fuzzyWrapFractionalCoordinate(atom->second[i]);
240
0
      }
241
0
      for(unsigned int i=1;i<atom->second.size();++i){
242
0
        bool foundDuplicate = false;
243
0
        for(unsigned int j=0;j<i;++j){
244
0
          if(areDuplicateAtoms2(atom->second[i],atom->second[j])){
245
0
            foundDuplicate=true;
246
0
            break;
247
0
          }
248
0
        }
249
0
        if(!foundDuplicate){
250
0
          vector3 transformed = pUC->FractionalToCartesian(atom->second[i]);
251
          // let's make sure there isn't some *other* atom that's in this spot
252
0
          bool foundCartesianDuplicate = false;
253
0
          FOR_ATOMS_OF_MOL(a, *pmol) {
254
0
            vector3 diff = a->GetVector() - transformed;
255
0
            if (diff.length_2() < 1.0e-4) {
256
0
              foundCartesianDuplicate = true;
257
0
              break;
258
0
            }
259
0
          }
260
261
0
          if (!foundCartesianDuplicate) {
262
0
            OBAtom *newAtom = pmol->NewAtom();
263
0
            newAtom->Duplicate(atom->first);
264
0
            newAtom->SetVector( transformed );
265
0
          }
266
0
        }
267
0
      }
268
0
    }
269
0
  }
270
271
  // Set spacegroup to P1, since we generated all symmetrics
272
0
  pUC->SetSpaceGroup("P1");
273
0
  return true;
274
0
}
275
}//namespace