Skip to content

Commit bf1f289

Browse files
drroeDaniel R. Roe
andauthored
Fix solvating with truncated octahedral cells (Amber-MD#1174)
* Truncated octahedron diagonal cut scaling fix suggested by Nikolai Skrynnikov and Stephan Schott. * Fix minimage action setup when no data set name specified * Try to implement toface keyword to check closest approach to unit cell face * Add a toface test * Add another toface test * Store face a string. Ensure negative frac coords are wrapped back into 0.0 to 1.0 range. * Fix up solvateoct diagonal check to be more like the original fixed code * Original test save calculated when LEAPPI active, update for regular PI * Add debug print of intermediate structures * Try to handle the trunc oct box better in nsolvent * Hide some debug info. Ensure all box lengths are properly enforced to be equal for truncoct * Do clipping just like the original solvate command - results in nicer looking trunc oct cells * Add the centering step * Hide some debug info. Clean up output * Remove unused code * Add ifdef, update depends * nsolvent now works with truncoct * Remove old include * Improve build manual entry * V7.5.0. Minor version bump for fixed truncated octahedral cell calculation (thanks to N. Skrynnikov for the fix). Also enables truncated octahedron for 'nsolvent' keyword. * Update manual PDF --------- Co-authored-by: Daniel R. Roe <daniel.roe@nih.gov>
1 parent 660fab8 commit bf1f289

13 files changed

Lines changed: 428 additions & 83 deletions

doc/CpptrajManual.pdf

-149 KB
Binary file not shown.

doc/DocumentChecksums.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
f6f8cb1a79951d80a9d2656fd9c30f55 CpptrajDevelopmentGuide.lyx
2-
3b89e9556662b1c6de2d0e38ab894588 cpptraj.lyx
2+
369e9afee9918ff0797c4a4bf08473fd cpptraj.lyx
33
5d9b5b5ed47a3ded57b6464df99b3585 CpptrajManual.lyx

doc/cpptraj.lyx

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7501,7 +7501,7 @@ Solvation/box:
75017501
\end_layout
75027502

75037503
\begin_layout Description
7504-
solvatebox|solvateoct Solvate the system in either an orthorhombic or truncated octadedral box.
7504+
solvatebox|solvateoct Solvate the system in either an orthorhombic or truncated octahedral box.
75057505
\end_layout
75067506

75077507
\begin_deeper
@@ -7551,8 +7551,7 @@ nsolvent
75517551
<#> EXPERIMENTAL.
75527552
Instead of a buffer,
75537553
specify a target number of waters to solvate the system with.
7554-
Currently only works for orthorhombic cells (i.e.
7555-
not truncated octahedron).
7554+
75567555
\end_layout
75577556

75587557
\end_deeper
@@ -7769,14 +7768,23 @@ fixatomorder
77697768
The build procedure is similar to that of LEaP,
77707769
but CPPTRAJ's build tends to be faster and more robust,
77717770
particularly for large systems and/or system containing CMAP terms.
7772-
Note that since LEaP uses an imprecise value of PI compared to CPPTRAJ/sander/pmemd,
7771+
7772+
\end_layout
7773+
7774+
\begin_layout Standard
7775+
Note that since LEaP uses an imprecise value of PI compared to CPPTRAJ/sander/pmemd,
77737776
the topologies produced by CPPTRAJ will have slight differences in any terms involving PI (e.g.
77747777
angles,
77757778
dihedrals,
77767779
etc).
77777780
For 1:1 direct comparison to LEaP topologies,
77787781
CPPTRAJ can be compiled using LEaP's value of PI by configuring with the '-leappi' configure flag,
7779-
which activates the CPPTRAJ_USE_LEAP_PI compiler define.
7782+
which activates the CPPTRAJ_USE_LEAP_PI compiler define;
7783+
7784+
\series bold
7785+
this should only be used to compare directly to LEaP
7786+
\series default
7787+
.
77807788
Note also that CPPTRAJ retains PDB information (chain ID,
77817789
original residue number,
77827790
Bfactor/Occupancy etc) which is printed to the output Topology.
@@ -7898,6 +7906,16 @@ addionsrand
78987906
ion2,
78997907
or both.
79007908
Note that currently CPPTRAJ does not support adding ions if no solvent has been added.
7909+
Either a
7910+
\series bold
7911+
buffer
7912+
\series default
7913+
size (in Angstroms) must be given,
7914+
or a target number of waters given with
7915+
\series bold
7916+
nsolvent
7917+
\series default
7918+
.
79017919
\end_layout
79027920

79037921
\begin_layout Enumerate

src/Action_MinImage.cpp

Lines changed: 117 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
Action_MinImage::Action_MinImage() :
1212
dist_(0),
1313
useMass_(true),
14-
calcUsingMask_(false)
14+
calcUsingMask_(false),
15+
toFace_(false) // HIDDEN, TODO make not hidden after testing
1516
{}
1617

1718
void Action_MinImage::Help() const {
@@ -25,28 +26,43 @@ Action::RetType Action_MinImage::Init(ArgList& actionArgs, ActionInit& init, int
2526
// Get Keywords
2627
useMass_ = !(actionArgs.hasKey("geom"));
2728
calcUsingMask_ = actionArgs.hasKey("maskcenter");
29+
toFace_ = actionArgs.hasKey("toface");
30+
if (calcUsingMask_ && toFace_) {
31+
mprinterr("Error: Specify either 'maskcenter' or 'toface', not both.\n");
32+
return Action::ERR;
33+
}
2834
DataFile* outfile = init.DFL().AddDataFile( actionArgs.GetStringKey("out"), actionArgs );
2935
// Get Masks
3036
std::string mask1 = actionArgs.GetMaskNext();
31-
std::string mask2 = actionArgs.GetMaskNext();
32-
if (mask1.empty() || mask2.empty()) {
33-
mprinterr("Error: Requires 2 masks\n");
34-
return Action::ERR;
35-
}
3637
if (Mask1_.SetMaskString(mask1)) return Action::ERR;
37-
if (Mask2_.SetMaskString(mask2)) return Action::ERR;
38+
std::string mask2;
39+
if (!toFace_) {
40+
mask2 = actionArgs.GetMaskNext();
41+
if (mask1.empty() || mask2.empty()) {
42+
mprinterr("Error: Requires 2 masks\n");
43+
return Action::ERR;
44+
}
45+
if (Mask2_.SetMaskString(mask2)) return Action::ERR;
46+
}
3847

3948
// Dataset to store distances
4049
MetaData md(actionArgs.GetStringNext());
4150
md.SetScalarMode( MetaData::M_DISTANCE );
4251
dist_ = init.DSL().AddSet(DataSet::DOUBLE, md, "MID");
4352
if (dist_==0) return Action::ERR;
53+
// Update metadata in case a default name was generated
54+
md.SetName( dist_->Meta().Name() );
4455
if (outfile != 0) outfile->AddDataSet( dist_ );
45-
if (!calcUsingMask_) {
56+
if (toFace_ || !calcUsingMask_) {
4657
md.SetAspect("A1");
4758
atom1_ = init.DSL().AddSet(DataSet::INTEGER, md);
48-
md.SetAspect("A2");
49-
atom2_ = init.DSL().AddSet(DataSet::INTEGER, md);
59+
if (toFace_) {
60+
md.SetAspect("FACE");
61+
atom2_ = init.DSL().AddSet(DataSet::STRING, md);
62+
} else {
63+
md.SetAspect("A2");
64+
atom2_ = init.DSL().AddSet(DataSet::INTEGER, md);
65+
}
5066
if (atom1_ == 0 || atom2_ == 0) return Action::ERR;
5167
if (outfile != 0) {
5268
outfile->AddDataSet( atom1_ );
@@ -66,7 +82,9 @@ Action::RetType Action_MinImage::Init(ArgList& actionArgs, ActionInit& init, int
6682
minAtom2_.resize( numthreads );
6783

6884
mprintf(" MINIMAGE: Looking for closest approach of");
69-
if (calcUsingMask_) {
85+
if (toFace_) {
86+
mprintf(" atoms in mask '%s' to unit cell faces.\n", Mask1_.MaskString());
87+
} else if (calcUsingMask_) {
7088
mprintf(" center of mask %s\n\tto images of center of mask %s\n",
7189
Mask1_.MaskString(), Mask2_.MaskString());
7290
if (useMass_)
@@ -92,12 +110,21 @@ Action::RetType Action_MinImage::Setup(ActionSetup& setup) {
92110
return Action::SKIP;
93111
}
94112
if (setup.Top().SetupIntegerMask( Mask1_ )) return Action::ERR;
95-
if (setup.Top().SetupIntegerMask( Mask2_ )) return Action::ERR;
96-
mprintf("\t%s (%i atoms) to %s (%i atoms)\n",Mask1_.MaskString(), Mask1_.Nselected(),
97-
Mask2_.MaskString(),Mask2_.Nselected());
98-
if (Mask1_.None() || Mask2_.None()) {
99-
mprintf("Warning: One or both masks have no atoms.\n");
100-
return Action::SKIP;
113+
if (!toFace_) {
114+
if (setup.Top().SetupIntegerMask( Mask2_ )) return Action::ERR;
115+
mprintf("\t%s (%i atoms) to %s (%i atoms)\n",
116+
Mask1_.MaskString(), Mask1_.Nselected(),
117+
Mask2_.MaskString(), Mask2_.Nselected());
118+
if (Mask1_.None() || Mask2_.None()) {
119+
mprintf("Warning: One or both masks have no atoms.\n");
120+
return Action::SKIP;
121+
}
122+
} else {
123+
mprintf("\t%s (%i atoms)\n", Mask1_.MaskString(), Mask1_.Nselected());
124+
if (Mask1_.None()) {
125+
mprintf("Warning: Mask has no atoms.\n");
126+
return Action::SKIP;
127+
}
101128
}
102129

103130
return Action::OK;
@@ -116,6 +143,75 @@ static void WriteMatrix(Matrix_3x3 const& ucell, PDBfile& pdbout, const char* na
116143
}
117144
*/
118145

146+
/** Distance of each atom in mask to each unit cell face. */
147+
double Action_MinImage::minDistToCellFace(AtomMask const& maskIn, Frame const& frameIn, int frameNum)
148+
{
149+
Box const& boxIn = frameIn.BoxCrd();
150+
// Store face coords in cartesian space
151+
static const char* faceStr[] = { "+X", "-X", "+Y", "-Y", "+Z", "-Z" };
152+
Vec3 Faces[6];
153+
Faces[0] = boxIn.UnitCell().TransposeMult( Vec3(1.0, 0.5, 0.5) ); // +X
154+
Faces[1] = boxIn.UnitCell().TransposeMult( Vec3(0.0, 0.5, 0.5) ); // -X
155+
Faces[2] = boxIn.UnitCell().TransposeMult( Vec3(0.5, 1.0, 0.5) ); // +Y
156+
Faces[3] = boxIn.UnitCell().TransposeMult( Vec3(0.5, 0.0, 0.5) ); // -Y
157+
Faces[4] = boxIn.UnitCell().TransposeMult( Vec3(0.5, 0.5, 1.0) ); // +Z
158+
Faces[5] = boxIn.UnitCell().TransposeMult( Vec3(0.5, 0.5, 0.0) ); // -Z
159+
160+
minDist_.assign(minDist_.size(), DBL_MAX);
161+
int mythread = 0;
162+
for (AtomMask::const_iterator at = maskIn.begin(); at != maskIn.end(); ++at)
163+
{
164+
// To fractional space
165+
Vec3 frac = boxIn.FracCell() * Vec3( frameIn.XYZ(*at) );
166+
// Bring back into main unit cell
167+
frac[0] = frac[0] - floor(frac[0]);
168+
frac[1] = frac[1] - floor(frac[1]);
169+
frac[2] = frac[2] - floor(frac[2]);
170+
if (frac[0] < 0.0) frac[0] += 1.0;
171+
if (frac[1] < 0.0) frac[1] += 1.0;
172+
if (frac[2] < 0.0) frac[2] += 1.0;
173+
// Back to Cartesian
174+
Vec3 cart = boxIn.UnitCell().TransposeMult( frac );
175+
// Distance to each face
176+
for (unsigned int idx = 0; idx < 6; idx++) {
177+
Vec3 dxyz = Faces[idx] - cart;
178+
double dist2 = dxyz.Magnitude2();
179+
// mprintf("DEBUG: %3i Dist from at %i to face %s is %g face={%g %g %g} cart={%g %g %g}\n", frameNum+1, *at+1, faceStr[idx], sqrt(dist2),
180+
// Faces[idx][0], Faces[idx][1], Faces[idx][2], cart[0], cart[1], cart[2]);
181+
//minDist2 = std::min(minDist2, dist2);
182+
if (dist2 < minDist_[mythread]) {
183+
minDist_[mythread] = dist2;
184+
minAtom1_[mythread] = *at;
185+
minAtom2_[mythread] = idx;
186+
//rprintf("DEBUG: New Min Dist: Atom %i to %i (%g)\n",
187+
// Mask1_[m1]+1,Mask2_[m2]+1,sqrt(Dist2));
188+
//pdbout_.WriteHET(1, minxyz_[0], minxyz_[1], minxyz_[2]);
189+
}
190+
}
191+
}
192+
// Find lowest minDist
193+
double globalMin = minDist_[0];
194+
int min1 = minAtom1_[0];
195+
int min2 = minAtom2_[0];
196+
for (unsigned int n = 1; n != minDist_.size(); n++)
197+
if (minDist_[n] < globalMin) {
198+
globalMin = minDist_[n];
199+
min1 = minAtom1_[n];
200+
min2 = minAtom2_[n];
201+
}
202+
++min1;
203+
//++min2;
204+
// By convention make atom 1 the lowest number
205+
//int lowest_num = std::min(min1, min2);
206+
//int highest_num = std::max(min1, min2);
207+
//atom1_->Add(frameNum, &lowest_num);
208+
//atom2_->Add(frameNum, &highest_num);
209+
atom1_->Add(frameNum, &min1);
210+
atom2_->Add(frameNum, faceStr[min2]);
211+
212+
return globalMin;
213+
}
214+
119215
double Action_MinImage::MinNonSelfDist2(Vec3 const& a1, Vec3 const& a2, Box const& boxIn) {
120216
// a1.Print("A1");
121217
// a2.Print("A2");
@@ -165,7 +261,10 @@ Action::RetType Action_MinImage::DoAction(int frameNum, ActionFrame& frm) {
165261
// pdbout_.OpenWrite("minimage.pdb");
166262
double Dist2;
167263

168-
if (calcUsingMask_) {
264+
if (toFace_) {
265+
Dist2 = minDistToCellFace(Mask1_, frm.Frm(), frameNum);
266+
Dist2 = sqrt( Dist2 );
267+
} else if (calcUsingMask_) {
169268
// Use center of mask1 and mask2
170269
Vec3 a1, a2;
171270
if (useMass_) {

src/Action_MinImage.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,16 @@ class Action_MinImage: public Action {
1414
Action::RetType DoAction(int, ActionFrame&);
1515
void Print() {}
1616

17+
double minDistToCellFace(AtomMask const&, Frame const&, int);
18+
1719
double MinNonSelfDist2(Vec3 const&, Vec3 const&, Box const&);
1820

1921
DataSet* dist_; ///< Will hold DataSet of calculated distances.
2022
DataSet* atom1_;
2123
DataSet* atom2_;
2224
bool useMass_; ///< If true, mass-weight distances.
2325
bool calcUsingMask_; ///< If true use center of masks
26+
bool toFace_;
2427
AtomMask Mask1_;
2528
AtomMask Mask2_;
2629
std::vector<double> minDist_;

0 commit comments

Comments
 (0)