Skip to content

Commit 7e4081a

Browse files
drroeDaniel R. Roe
andauthored
Add 'bonddipole' mode to 'vector' action (#1154)
* Start vector bond dipole calc * Add missing printf var * Average origin * Enable bonddipole calc. Not yet working right * Add Pauling electronegativity values for atoms * Try to calculate bond dipoles using more chemical intuition * Warn about unhandled numbers of bonds * Use summation for bonds with multiple single substituents * Generalize * Try a hybrid method where the dipole vector is -SUM(R x Q) and origin is calculated from individual bonds, using electronegativity to orient them * Shift the origin so that the center of the dipole vector will end up at the calculated bond average origin * Hide some debug info * Add a test * Add warning * 6.29.18. Revision bump for vector bonddipole --------- Co-authored-by: Daniel R. Roe <daniel.roe@nih.gov>
1 parent 2d6e203 commit 7e4081a

9 files changed

Lines changed: 4302 additions & 12 deletions

File tree

src/Action_Vector.cpp

Lines changed: 210 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <algorithm> // sort
33
#include "Constants.h" // PI
44
#include "Action_Vector.h"
5+
#include "CharMask.h"
56
#include "CpptrajStdio.h"
67
#include "DistRoutines.h" // MinImagedVec, includes Matrix_3x3 for principal
78
#include "DataSet_Vector.h"
@@ -18,9 +19,17 @@ Action_Vector::Action_Vector() :
1819
useMass_(true),
1920
dipole_in_debye_(false),
2021
CurrentParm_(0),
21-
outfile_(0)
22+
outfile_(0),
23+
cmask_(0),
24+
debug_(0)
2225
{}
2326

27+
// DESTRUCTOR
28+
Action_Vector::~Action_Vector() {
29+
if (vcorr_!=0) delete[] vcorr_;
30+
if (cmask_ != 0) delete cmask_;
31+
}
32+
2433
// Action_Vector::Help()
2534
void Action_Vector::Help() const {
2635
mprintf("\t[<name>] <Type> [out <filename> [ptrajoutput]] [<mask1>] [<mask2>]\n"
@@ -46,23 +55,20 @@ void Action_Vector::Help() const {
4655
" If 'debye' is specified, report 'dipole' vector in Debye instead of e-*Ang.\n");
4756
}
4857

49-
// DESTRUCTOR
50-
Action_Vector::~Action_Vector() {
51-
if (vcorr_!=0) delete[] vcorr_;
52-
}
53-
5458
const char* Action_Vector::ModeString_[] = {
5559
"NO_OP", "Principal X", "Principal Y", "Principal Z",
5660
"Dipole", "Box", "Mask",
5761
"CorrPlane", "Center", "Unit cell X", "Unit cell Y", "Unit cell Z",
58-
"Box Center", "MinImage", "Momentum", "Velocity", "Force"
62+
"Box Center", "MinImage", "Momentum", "Velocity", "Force",
63+
"Bond Dipole"
5964
};
6065

6166
const bool Action_Vector::NeedsOrigin_[] = {
6267
false, true, true, true,
6368
true, false, true,
6469
true, false, true, true, true,
65-
false, true, false, false, false
70+
false, true, false, false, false,
71+
true
6672
};
6773

6874
static Action::RetType WarnDeprecated() {
@@ -82,6 +88,7 @@ static inline Action::RetType DeprecatedErr(const char* key) {
8288
// Action_Vector::Init()
8389
Action::RetType Action_Vector::Init(ArgList& actionArgs, ActionInit& init, int debugIn)
8490
{
91+
debug_ = debugIn;
8592
DataFile* df = 0;
8693
std::string filename = actionArgs.GetStringKey("out");
8794
if (actionArgs.hasKey("trajout")) return DeprecatedErr( "trajout" );
@@ -120,6 +127,8 @@ Action::RetType Action_Vector::Init(ArgList& actionArgs, ActionInit& init, int d
120127
mode_ = FORCE;
121128
else if (actionArgs.hasKey("dipole"))
122129
mode_ = DIPOLE;
130+
else if (actionArgs.hasKey("bonddipole"))
131+
mode_ = BONDDIPOLE;
123132
else if (actionArgs.hasKey("box"))
124133
mode_ = BOX;
125134
else if (actionArgs.hasKey("corrplane"))
@@ -176,6 +185,9 @@ Action::RetType Action_Vector::Init(ArgList& actionArgs, ActionInit& init, int d
176185
}
177186
if (mask2_.SetMaskString( maskexpr )) return Action::ERR;
178187
}
188+
// Allocate Char mask for bond dipole
189+
if (mode_ == BONDDIPOLE)
190+
cmask_ = new CharMask();
179191
// Set up vector dataset and IRED status
180192
MetaData md(actionArgs.GetStringNext(), MetaData::M_VECTOR);
181193
if (isIred) md.SetScalarType( MetaData::IREDVEC );
@@ -215,7 +227,9 @@ Action::RetType Action_Vector::Init(ArgList& actionArgs, ActionInit& init, int d
215227
mprintf("\n");
216228
if (gridSet_ != 0)
217229
mprintf("\tExtracting box vectors from grid set '%s'\n", gridSet_->legend());
218-
if (mode_ == DIPOLE) {
230+
if (mode_ == DIPOLE || mode_ == BONDDIPOLE) {
231+
if (mode_ == BONDDIPOLE) //TODO remove when no longer testing
232+
mprintf("Warning: 'bonddipole' mode is still experimental. Use with caution.\n");
219233
if (dipole_in_debye_)
220234
mprintf("\tDipole vector units will be Debye.\n");
221235
else
@@ -252,6 +266,10 @@ Action::RetType Action_Vector::Setup(ActionSetup& setup) {
252266
mprinterr("Error: First vector mask is empty.\n");
253267
return Action::ERR;
254268
}
269+
// Set up char mask if needed
270+
if (cmask_ != 0) {
271+
*cmask_ = CharMask( mask_.ConvertToCharMask(), mask_.Nselected() );
272+
}
255273
}
256274

257275
// Allocate space for CORRPLANE.
@@ -405,6 +423,187 @@ void Action_Vector::Mask(Frame const& currentFrame) {
405423
Vec_->AddVxyzo(VXYZ, CXYZ);
406424
}
407425

426+
/** Calculate dipole */
427+
bool Action_Vector::calcBondDipole(Vec3& vBond, Vec3& vDipole, Vec3& vOrigin, double q0, Vec3 const& XYZ0, Vec3 const& XYZ1)
428+
{
429+
bool hasCharge = true;
430+
if (q0 < 0.0) {
431+
vBond = XYZ0 - XYZ1;
432+
vDipole = vBond * (-q0);
433+
vOrigin = XYZ1;
434+
} else if (q0 > 0.0) {
435+
vBond = XYZ1 - XYZ0;
436+
vDipole = vBond * q0;
437+
vOrigin = XYZ0;
438+
} else
439+
hasCharge = false;
440+
return hasCharge;
441+
}
442+
443+
/// \return true if all atoms bonded to atomIn (ignoring ignoreIdx) have only the one bond back to atomIn
444+
static inline bool bondedAtomsHave1bond(Topology const& topIn, Atom const& atomIn, int ignoreIdx)
445+
{
446+
for (int ii=0; ii < atomIn.Nbonds(); ii++) {
447+
int bondedAtomIdx = atomIn.Bond(ii);
448+
if (bondedAtomIdx != ignoreIdx) {
449+
if (topIn[bondedAtomIdx].Nbonds() != 1) return false;
450+
}
451+
}
452+
return true;
453+
}
454+
455+
static inline double bondedAtomsCharge(Topology const& topIn, Atom const& atomIn, int ignoreIdx)
456+
{
457+
double sumQ = 0.0;
458+
for (int ii=0; ii < atomIn.Nbonds(); ii++) {
459+
int bondedAtomIdx = atomIn.Bond(ii);
460+
if (bondedAtomIdx != ignoreIdx) {
461+
if (topIn[bondedAtomIdx].Nbonds() == 1)
462+
sumQ += topIn[bondedAtomIdx].Charge();
463+
}
464+
}
465+
return sumQ;
466+
}
467+
468+
/** Calculate net dipole from individual bond dipoles. */
469+
void Action_Vector::BondDipole_individualBonds(Frame const& currentFrame) {
470+
Vec3 VXYZ(0.0, 0.0, 0.0); // Negative, head
471+
Vec3 OXYZ(0.0, 0.0, 0.0); // Positive, tail
472+
Topology const& currentTop = *CurrentParm_;
473+
474+
unsigned int Nbonds = 0;
475+
for (AtomMask::const_iterator iat = mask_.begin(); iat != mask_.end(); ++iat)
476+
{
477+
Atom const& atom0 = currentTop[*iat];
478+
Vec3 XYZ0( currentFrame.XYZ(*iat) );
479+
double q0 = atom0.Charge();
480+
for (Atom::bond_iterator bit = atom0.bondbegin(); bit != atom0.bondend(); ++bit)
481+
{
482+
if (*bit > *iat && cmask_->AtomInCharMask( *bit )) {
483+
// Both atoms in the bond are selected.
484+
Atom const& atom1 = currentTop[*bit];
485+
Vec3 XYZ1( currentFrame.XYZ(*bit) );
486+
double q1 = atom1.Charge();
487+
Vec3 vDipole(0.0, 0.0, 0.0);
488+
Vec3 vBond(0.0, 0.0, 0.0);
489+
Vec3 vOrigin(0.0, 0.0, 0.0);
490+
bool hasCharge = false;
491+
if (atom0.Element() != atom1.Element()) {
492+
if (atom0.Element()==Atom::HYDROGEN || atom0.Nbonds() == 1) {
493+
hasCharge = calcBondDipole( vBond, vDipole, vOrigin, q0, XYZ0, XYZ1 );
494+
} else if (atom1.Element()==Atom::HYDROGEN || atom1.Nbonds() == 1) {
495+
hasCharge = calcBondDipole( vBond, vDipole, vOrigin, q1, XYZ1, XYZ0 );
496+
} else if (atom0.Nbonds() > 1 && bondedAtomsHave1bond(currentTop, atom0, *bit)) {
497+
// atom0 is bonded to a single other atom, X-atom0-atom1. Sum up X and atom0 charge
498+
double q0X = q0 + bondedAtomsCharge(currentTop, atom0, *bit);
499+
if (debug_ > 1)
500+
mprintf("DEBUG: Total charge on %s is %f\n", currentTop.AtomMaskName(*iat).c_str(), q0X);
501+
hasCharge = calcBondDipole( vBond, vDipole, vOrigin, q0X, XYZ0, XYZ1 );
502+
} else if (atom1.Nbonds() > 1 && bondedAtomsHave1bond(currentTop, atom1, *iat)) {
503+
// atom1 is bonded to a single other atom, X-atom1-atom0. Sum up X and atom1 charge
504+
double q1X = q1 + bondedAtomsCharge(currentTop, atom1, *iat);
505+
if (debug_ > 1)
506+
mprintf("DEBUG: Total charge on %s is %f\n", currentTop.AtomMaskName(*bit).c_str(), q1X);
507+
hasCharge = calcBondDipole( vBond, vDipole, vOrigin, q1X, XYZ1, XYZ0 );
508+
} else {
509+
mprintf("Warning: Unhandled number of bonds: %s=%i, %s=%i\n",
510+
currentTop.AtomMaskName(*iat).c_str(), atom0.Nbonds(),
511+
currentTop.AtomMaskName(*bit).c_str(), atom1.Nbonds());
512+
}
513+
}
514+
515+
if (hasCharge) {
516+
// Subtract half the dipole from the bond center to get the dipole origin
517+
//Vec3 vBondCtr = vBond / 2.0;
518+
//Vec3 vDipoleHalf = vDipole / 2.0;
519+
//Vec3 vOrigin = vOrigin + vBondCtr - vDipoleHalf;
520+
// DEBUG
521+
if (debug_ > 1) {
522+
mprintf("DEBUG: Bond %s(%f) -- %s(%f) bxyz={%f %f %f} oxyz={%f %f %f} vxyz={%f %f %f} mag=%f\n",
523+
currentTop.AtomMaskName(*iat).c_str(), q0,
524+
currentTop.AtomMaskName(*bit).c_str(), q1,
525+
vBond[0], vBond[1], vBond[2],
526+
vOrigin[0], vOrigin[1], vOrigin[2],
527+
vDipole[0], vDipole[1], vDipole[2],
528+
sqrt(vDipole.Magnitude2()));
529+
}
530+
// Sum
531+
VXYZ += vDipole;
532+
OXYZ += vOrigin;
533+
Nbonds++;
534+
}
535+
}
536+
} // END loop over bonded atoms
537+
} // END loop over mask atoms
538+
if (dipole_in_debye_)
539+
VXYZ /= Constants::DEBYE_EA;
540+
OXYZ /= (double)Nbonds;
541+
Vec_->AddVxyzo( VXYZ, OXYZ );
542+
}
543+
544+
/** Calculate net dipole from atoms, origin from individual bond dipoles. */
545+
void Action_Vector::BondDipole_net_bondOrigin(Frame const& currentFrame) {
546+
Vec3 VXYZ(0.0, 0.0, 0.0); // Negative, head
547+
Vec3 OXYZ(0.0, 0.0, 0.0); // Positive, tail
548+
Topology const& currentTop = *CurrentParm_;
549+
550+
unsigned int Nbonds = 0;
551+
for (AtomMask::const_iterator iat = mask_.begin(); iat != mask_.end(); ++iat)
552+
{
553+
Atom const& atom0 = currentTop[*iat];
554+
Vec3 XYZ0( currentFrame.XYZ(*iat) );
555+
double q0 = atom0.Charge();
556+
double en0 = atom0.PaulingElectroNeg();
557+
// Note that negative charge will decrease the vector (towards the origin)
558+
// but by convention we want to point towards the negative charge, so at
559+
// the end the vector will have to be negated.
560+
VXYZ += ( XYZ0 * q0 );
561+
// Store bond origins according to electronegativity.
562+
for (Atom::bond_iterator bit = atom0.bondbegin(); bit != atom0.bondend(); ++bit)
563+
{
564+
if (*bit > *iat && cmask_->AtomInCharMask( *bit )) {
565+
// Both atoms in the bond are selected.
566+
Atom const& atom1 = currentTop[*bit];
567+
Vec3 XYZ1( currentFrame.XYZ(*bit) );
568+
double en1 = atom1.PaulingElectroNeg();
569+
Vec3 vOrigin(0.0, 0.0, 0.0);
570+
bool en_is_different = true;
571+
if (en0 > en1) {
572+
// atom0 is more negative
573+
//vBond = XYZ0 - XYZ1;
574+
vOrigin = XYZ1;
575+
} else if (en1 > en0) {
576+
// atom1 is more negative
577+
//vBond = XYZ1 - XYZ0;
578+
vOrigin = XYZ0;
579+
} else
580+
en_is_different = false;
581+
582+
if (en_is_different) {
583+
// DEBUG
584+
if (debug_ > 1) {
585+
double q1 = atom1.Charge(); // DEBUG
586+
mprintf("DEBUG: Bond %s(%f) -- %s(%f) oxyz={%f %f %f}\n",
587+
currentTop.AtomMaskName(*iat).c_str(), q0,
588+
currentTop.AtomMaskName(*bit).c_str(), q1,
589+
vOrigin[0], vOrigin[1], vOrigin[2]);
590+
}
591+
// Sum
592+
OXYZ += vOrigin;
593+
Nbonds++;
594+
}
595+
}
596+
} // END loop over bonded atoms
597+
} // END loop over mask atoms
598+
if (dipole_in_debye_)
599+
VXYZ /= Constants::DEBYE_EA;
600+
OXYZ /= (double)Nbonds;
601+
VXYZ.Neg();
602+
// Shift it so the center of VXYZ will end up at the origin.
603+
OXYZ -= (VXYZ / 2.0);
604+
Vec_->AddVxyzo( VXYZ, OXYZ );
605+
}
606+
408607
// Action_Vector::Dipole()
409608
void Action_Vector::Dipole(Frame const& currentFrame) {
410609
Vec3 VXYZ(0.0, 0.0, 0.0);
@@ -509,6 +708,8 @@ Action::RetType Action_Vector::DoAction(int frameNum, ActionFrame& frm) {
509708
case VELOCITY : Vec_->AddVxyz( CalcCenter(frm.Frm().vAddress(), mask_) ); break;
510709
case FORCE : Vec_->AddVxyz( CalcCenter(frm.Frm().fAddress(), mask_) ); break;
511710
case DIPOLE : Dipole(frm.Frm()); break;
711+
// case BONDDIPOLE : BondDipole_individualBonds(frm.Frm()); break;
712+
case BONDDIPOLE : BondDipole_net_bondOrigin(frm.Frm()); break;
512713
case PRINCIPAL_X :
513714
case PRINCIPAL_Y :
514715
case PRINCIPAL_Z : Principal(frm.Frm()); break;

src/Action_Vector.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "Action.h"
44
class DataSet_Vector;
55
class DataSet_3D;
6+
class CharMask;
67
class Action_Vector : public Action {
78
public:
89
Action_Vector();
@@ -14,7 +15,8 @@ class Action_Vector : public Action {
1415
NO_OP=0, PRINCIPAL_X, PRINCIPAL_Y, PRINCIPAL_Z,
1516
DIPOLE, BOX, MASK,
1617
CORRPLANE, CENTER, BOX_X, BOX_Y, BOX_Z,
17-
BOX_CTR, MINIMAGE, MOMENTUM, VELOCITY, FORCE
18+
BOX_CTR, MINIMAGE, MOMENTUM, VELOCITY, FORCE,
19+
BONDDIPOLE
1820
};
1921
static const char* ModeString_[];
2022
static const bool NeedsOrigin_[];
@@ -29,6 +31,9 @@ class Action_Vector : public Action {
2931
/// \return Center of mass or geometric center of atoms in given mask
3032
inline Vec3 GetVec(Frame const&, AtomMask const&) const;
3133
void Mask(Frame const&);
34+
static inline bool calcBondDipole(Vec3&, Vec3&, Vec3&, double, Vec3 const&, Vec3 const&);
35+
void BondDipole_individualBonds(Frame const&);
36+
void BondDipole_net_bondOrigin(Frame const&);
3237
void Dipole(Frame const&);
3338
void Principal(Frame const&);
3439
void CorrPlane(Frame const&);
@@ -49,5 +54,7 @@ class Action_Vector : public Action {
4954
AtomMask mask_;
5055
AtomMask mask2_;
5156
CpptrajFile* outfile_;
57+
CharMask* cmask_;
58+
int debug_;
5259
};
5360
#endif

src/Atom.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,32 @@ const double Atom::AtomicElementRadius_[NUMELEMENTS_] = { 1.0,
7575
0.000, 0.0 /* extra point/Drude has no radius */
7676
};
7777

78+
/** Pauling electronegativity values from:
79+
* A.L. Allred,
80+
* Electronegativity values from thermochemical data,
81+
* Journal of Inorganic and Nuclear Chemistry,
82+
* Volume 17, Issues 3–4, 1961, Pages 215-221,
83+
* https://doi.org/10.1016/0022-1902(61)80142-5.
84+
* https://www.sciencedirect.com/science/article/pii/0022190261801425
85+
* Atoms with no electronegativity value are assigned -1.0.
86+
*/
87+
const double Atom::PaulingElectroNeg_[NUMELEMENTS_] = { -1.0,
88+
2.20, 2.04, 2.55, 3.04, 3.44, 3.98,
89+
2.19, 2.58, 3.16, 2.96, 1.83, 1.00,
90+
2.66, 1.31, 1.90, 0.98, 0.82, 0.82,
91+
0.79, 1.65, 0.93, 1.61, -1.0, 2.18,
92+
1.93, 2.54, 2.20, 1.57, 0.89, 2.02,
93+
1.66, 1.88, 1.69, 0.70, 1.81, 2.01,
94+
-1.0, 1.30, 2.00, 1.78, 2.20, 3.00,
95+
1.55, 2.16, -1.0, 1.91, 1.60, 2.20,
96+
2.20, 2.28, 2.33, 2.00, 2.20, 2.28,
97+
1.90, -1.0, 0.90, 1.90, 1.36, 2.55,
98+
0.95, 1.96, 2.05, 1.54, 1.90, 2.10,
99+
1.50, 1.62, 1.63, 2.36, 2.60, 1.33,
100+
1.22, 1.27, 1.17,
101+
-1.0, -1.0 /* extra point/Drude has no EN value. TODO should that change? */
102+
};
103+
78104
// CONSTRUCTOR
79105
Atom::Atom() : charge_(0.0), polar_(0.0), mass_(1.0), gb_radius_(0.0),
80106
gb_screen_(0.0), aname_(""), atype_(""), atype_index_(0),

src/Atom.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ class Atom {
8181
inline int AtomicNumber() const { return AtomicElementNum_[element_]; }
8282
inline const char* ElementName() const { return AtomicElementName_[element_]; }
8383
inline double ElementRadius() const { return AtomicElementRadius_[element_]; }
84+
double PaulingElectroNeg() const { return PaulingElectroNeg_[element_]; }
8485
inline const NameType& Name() const { return aname_; }
8586
inline const NameType& Type() const { return atype_; }
8687
inline int TypeIndex() const { return atype_index_; }
@@ -108,6 +109,7 @@ class Atom {
108109
static CPPTRAJ_EXPORT const char* AtomicElementName_[];
109110
static CPPTRAJ_EXPORT const double AtomicElementMass_[];
110111
static CPPTRAJ_EXPORT const double AtomicElementRadius_[];
112+
static CPPTRAJ_EXPORT const double PaulingElectroNeg_[];
111113
double charge_; ///< Charge in e-
112114
double polar_; ///< Atomic polarizability in Ang^3
113115
double mass_; ///< mass in amu

src/Version.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
* Whenever a number that precedes <revision> is incremented, all subsequent
1313
* numbers should be reset to 0.
1414
*/
15-
#define CPPTRAJ_INTERNAL_VERSION "V6.29.17"
15+
#define CPPTRAJ_INTERNAL_VERSION "V6.29.18"
1616
/// PYTRAJ relies on this
1717
#define CPPTRAJ_VERSION_STRING CPPTRAJ_INTERNAL_VERSION
1818
#endif

0 commit comments

Comments
 (0)