forked from enviPath/enviPy
436 lines
12 KiB
JavaScript
436 lines
12 KiB
JavaScript
/****************************************************************************
|
|
* Copyright 2017 EPAM Systems
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
***************************************************************************/
|
|
|
|
var Vec2 = require('../../util/vec2');
|
|
|
|
var element = require('../element');
|
|
var AtomList = require('./atomlist');
|
|
|
|
function Atom(params) { // eslint-disable-line max-statements
|
|
var def = Atom.attrGetDefault;
|
|
console.assert(params || 'label' in params, 'label must be specified!');
|
|
|
|
this.label = params.label;
|
|
this.fragment = ('fragment' in params) ? params.fragment : -1;
|
|
this.pseudo = params.pseudo || checkPseudo(params.label);
|
|
|
|
ifDef(this, params, 'alias', def('alias'));
|
|
ifDef(this, params, 'isotope', def('isotope'));
|
|
ifDef(this, params, 'radical', def('radical'));
|
|
ifDef(this, params, 'charge', def('charge'));
|
|
ifDef(this, params, 'rglabel', def('rglabel')); // r-group index mask, i-th bit stands for i-th r-site
|
|
ifDef(this, params, 'attpnt', def('attpnt')); // attachment point
|
|
ifDef(this, params, 'explicitValence', def('explicitValence'));
|
|
|
|
this.valence = 0;
|
|
this.implicitH = 0; // implicitH is not an attribute
|
|
this.pp = params.pp ? new Vec2(params.pp) : new Vec2();
|
|
|
|
// sgs should only be set when an atom is added to an s-group by an appropriate method,
|
|
// or else a copied atom might think it belongs to a group, but the group be unaware of the atom
|
|
// TODO: make a consistency check on atom/s-group assignments
|
|
this.sgs = {};
|
|
|
|
// query
|
|
ifDef(this, params, 'ringBondCount', def('ringBondCount'));
|
|
ifDef(this, params, 'substitutionCount', def('substitutionCount'));
|
|
ifDef(this, params, 'unsaturatedAtom', def('unsaturatedAtom'));
|
|
ifDef(this, params, 'hCount', def('hCount'));
|
|
|
|
// reaction
|
|
ifDef(this, params, 'aam', def('aam'));
|
|
ifDef(this, params, 'invRet', def('invRet'));
|
|
ifDef(this, params, 'exactChangeFlag', def('exactChangeFlag'));
|
|
ifDef(this, params, 'rxnFragmentType', -1); // this isn't really an attribute
|
|
|
|
this.atomList = params.atomList ? new AtomList(params.atomList) : null;
|
|
this.neighbors = []; // set of half-bonds having this atom as their origin
|
|
this.badConn = false;
|
|
}
|
|
|
|
Atom.getAttrHash = function (atom) {
|
|
var attrs = {};
|
|
for (var attr in Atom.attrlist) {
|
|
if (typeof (atom[attr]) !== 'undefined')
|
|
attrs[attr] = atom[attr];
|
|
}
|
|
return attrs;
|
|
};
|
|
|
|
Atom.attrGetDefault = function (attr) {
|
|
if (attr in Atom.attrlist)
|
|
return Atom.attrlist[attr];
|
|
console.assert(false, 'Attribute unknown');
|
|
};
|
|
|
|
|
|
Atom.PATTERN =
|
|
{
|
|
RADICAL:
|
|
{
|
|
NONE: 0,
|
|
SINGLET: 1,
|
|
DOUPLET: 2,
|
|
TRIPLET: 3
|
|
}
|
|
};
|
|
|
|
Atom.attrlist = {
|
|
alias: null,
|
|
label: 'C',
|
|
pseudo: null,
|
|
isotope: 0,
|
|
radical: 0,
|
|
charge: 0,
|
|
explicitValence: -1,
|
|
ringBondCount: 0,
|
|
substitutionCount: 0,
|
|
unsaturatedAtom: 0,
|
|
hCount: 0,
|
|
atomList: null,
|
|
invRet: 0,
|
|
exactChangeFlag: 0,
|
|
rglabel: null,
|
|
attpnt: null,
|
|
aam: 0
|
|
};
|
|
|
|
function radicalElectrons(radical) {
|
|
radical -= 0;
|
|
if (radical === Atom.PATTERN.RADICAL.NONE)
|
|
return 0;
|
|
else if (radical === Atom.PATTERN.RADICAL.DOUPLET)
|
|
return 1;
|
|
else if (radical === Atom.PATTERN.RADICAL.SINGLET ||
|
|
radical === Atom.PATTERN.RADICAL.TRIPLET)
|
|
return 2;
|
|
console.assert(false, 'Unknown radical value');
|
|
}
|
|
|
|
Atom.prototype.clone = function (fidMap) {
|
|
var ret = new Atom(this);
|
|
if (fidMap && this.fragment in fidMap)
|
|
ret.fragment = fidMap[this.fragment];
|
|
return ret;
|
|
};
|
|
|
|
Atom.prototype.isQuery = function () {
|
|
return this.atomList != null || this.label === 'A' || this.attpnt || this.hCount;
|
|
};
|
|
|
|
Atom.prototype.pureHydrogen = function () {
|
|
return this.label === 'H' && this.isotope === 0;
|
|
};
|
|
|
|
Atom.prototype.isPlainCarbon = function () {
|
|
return this.label === 'C' && this.isotope === 0 && this.radical == 0 && this.charge == 0 &&
|
|
this.explicitValence < 0 && this.ringBondCount == 0 && this.substitutionCount == 0 &&
|
|
this.unsaturatedAtom == 0 && this.hCount == 0 && !this.atomList;
|
|
};
|
|
|
|
Atom.prototype.isPseudo = function () {
|
|
// TODO: handle reaxys generics separately
|
|
return !this.atomList && !this.rglabel && !element.map[this.label];
|
|
};
|
|
|
|
Atom.prototype.hasRxnProps = function () {
|
|
return !!(this.invRet || this.exactChangeFlag || this.attpnt != null || this.aam);
|
|
};
|
|
|
|
Atom.prototype.calcValence = function (conn) { // eslint-disable-line max-statements
|
|
var atom = this;
|
|
var charge = atom.charge;
|
|
var label = atom.label;
|
|
if (atom.isQuery()) {
|
|
this.implicitH = 0;
|
|
return true;
|
|
}
|
|
var elem = element.map[label];
|
|
if (elem === undefined) {
|
|
this.implicitH = 0;
|
|
return true;
|
|
}
|
|
|
|
var groupno = element[elem].group;
|
|
var rad = radicalElectrons(atom.radical);
|
|
|
|
var valence = conn;
|
|
var hyd = 0;
|
|
var absCharge = Math.abs(charge);
|
|
|
|
if (groupno === 1) {
|
|
if (label === 'H' ||
|
|
label === 'Li' || label === 'Na' || label === 'K' ||
|
|
label === 'Rb' || label === 'Cs' || label === 'Fr') {
|
|
valence = 1;
|
|
hyd = 1 - rad - conn - absCharge;
|
|
}
|
|
} else if (groupno === 2) {
|
|
if (conn + rad + absCharge === 2 || conn + rad + absCharge === 0)
|
|
valence = 2;
|
|
else
|
|
hyd = -1;
|
|
} else if (groupno === 3) {
|
|
if (label === 'B' || label === 'Al' || label === 'Ga' || label === 'In') {
|
|
if (charge === -1) {
|
|
valence = 4;
|
|
hyd = 4 - rad - conn;
|
|
} else {
|
|
valence = 3;
|
|
hyd = 3 - rad - conn - absCharge;
|
|
}
|
|
} else if (label === 'Tl') {
|
|
if (charge === -1) {
|
|
if (rad + conn <= 2) {
|
|
valence = 2;
|
|
hyd = 2 - rad - conn;
|
|
} else {
|
|
valence = 4;
|
|
hyd = 4 - rad - conn;
|
|
}
|
|
} else if (charge === -2) {
|
|
if (rad + conn <= 3) {
|
|
valence = 3;
|
|
hyd = 3 - rad - conn;
|
|
} else {
|
|
valence = 5;
|
|
hyd = 5 - rad - conn;
|
|
}
|
|
} else if (rad + conn + absCharge <= 1) {
|
|
valence = 1;
|
|
hyd = 1 - rad - conn - absCharge;
|
|
} else {
|
|
valence = 3;
|
|
hyd = 3 - rad - conn - absCharge;
|
|
}
|
|
}
|
|
} else if (groupno === 4) {
|
|
if (label === 'C' || label === 'Si' || label === 'Ge') {
|
|
valence = 4;
|
|
hyd = 4 - rad - conn - absCharge;
|
|
} else if (label === 'Sn' || label === 'Pb') {
|
|
if (conn + rad + absCharge <= 2) {
|
|
valence = 2;
|
|
hyd = 2 - rad - conn - absCharge;
|
|
} else {
|
|
valence = 4;
|
|
hyd = 4 - rad - conn - absCharge;
|
|
}
|
|
}
|
|
} else if (groupno === 5) {
|
|
if (label === 'N' || label === 'P') {
|
|
if (charge === 1) {
|
|
valence = 4;
|
|
hyd = 4 - rad - conn;
|
|
} else if (charge === 2) {
|
|
valence = 3;
|
|
hyd = 3 - rad - conn;
|
|
} else if (label === 'N' || rad + conn + absCharge <= 3) {
|
|
valence = 3;
|
|
hyd = 3 - rad - conn - absCharge;
|
|
} else { // ELEM_P && rad + conn + absCharge > 3
|
|
valence = 5;
|
|
hyd = 5 - rad - conn - absCharge;
|
|
}
|
|
} else if (label === 'Bi' || label === 'Sb' || label === 'As') {
|
|
if (charge === 1) {
|
|
if (rad + conn <= 2 && label !== 'As') {
|
|
valence = 2;
|
|
hyd = 2 - rad - conn;
|
|
} else {
|
|
valence = 4;
|
|
hyd = 4 - rad - conn;
|
|
}
|
|
} else if (charge === 2) {
|
|
valence = 3;
|
|
hyd = 3 - rad - conn;
|
|
} else if (rad + conn <= 3) {
|
|
valence = 3;
|
|
hyd = 3 - rad - conn - absCharge;
|
|
} else {
|
|
valence = 5;
|
|
hyd = 5 - rad - conn - absCharge;
|
|
}
|
|
}
|
|
} else if (groupno === 6) {
|
|
if (label === 'O') {
|
|
if (charge >= 1) {
|
|
valence = 3;
|
|
hyd = 3 - rad - conn;
|
|
} else {
|
|
valence = 2;
|
|
hyd = 2 - rad - conn - absCharge;
|
|
}
|
|
} else if (label === 'S' || label === 'Se' || label === 'Po') {
|
|
if (charge === 1) {
|
|
if (conn <= 3) {
|
|
valence = 3;
|
|
hyd = 3 - rad - conn;
|
|
} else {
|
|
valence = 5;
|
|
hyd = 5 - rad - conn;
|
|
}
|
|
} else if (conn + rad + absCharge <= 2) {
|
|
valence = 2;
|
|
hyd = 2 - rad - conn - absCharge;
|
|
} else if (conn + rad + absCharge <= 4) {
|
|
// See examples in PubChem
|
|
// [S] : CID 16684216
|
|
// [Se]: CID 5242252
|
|
// [Po]: no example, just following ISIS/Draw logic here
|
|
valence = 4;
|
|
hyd = 4 - rad - conn - absCharge;
|
|
} else {
|
|
// See examples in PubChem
|
|
// [S] : CID 46937044
|
|
// [Se]: CID 59786
|
|
// [Po]: no example, just following ISIS/Draw logic here
|
|
valence = 6;
|
|
hyd = 6 - rad - conn - absCharge;
|
|
}
|
|
} else if (label === 'Te') {
|
|
if (charge === -1) {
|
|
if (conn <= 2) {
|
|
valence = 2;
|
|
hyd = 2 - rad - conn - absCharge;
|
|
}
|
|
} else if (charge === 0 || charge === 2) {
|
|
if (conn <= 2) {
|
|
valence = 2;
|
|
hyd = 2 - rad - conn - absCharge;
|
|
} else if (conn <= 4) {
|
|
valence = 4;
|
|
hyd = 4 - rad - conn - absCharge;
|
|
} else if (charge === 0 && conn <= 6) {
|
|
valence = 6;
|
|
hyd = 6 - rad - conn - absCharge;
|
|
} else {
|
|
hyd = -1;
|
|
}
|
|
}
|
|
}
|
|
} else if (groupno === 7) {
|
|
if (label === 'F') {
|
|
valence = 1;
|
|
hyd = 1 - rad - conn - absCharge;
|
|
} else if (label === 'Cl' || label === 'Br' ||
|
|
label === 'I' || label === 'At') {
|
|
if (charge === 1) {
|
|
if (conn <= 2) {
|
|
valence = 2;
|
|
hyd = 2 - rad - conn;
|
|
} else if (conn === 3 || conn === 5 || conn >= 7) {
|
|
hyd = -1;
|
|
}
|
|
} else if (charge === 0) {
|
|
if (conn <= 1) {
|
|
valence = 1;
|
|
hyd = 1 - rad - conn;
|
|
// While the halogens can have valence 3, they can not have
|
|
// hydrogens in that case.
|
|
} else if (conn === 2 || conn === 4 || conn === 6) {
|
|
if (rad === 1) {
|
|
valence = conn;
|
|
hyd = 0;
|
|
} else {
|
|
hyd = -1; // will throw an error in the end
|
|
}
|
|
} else if (conn > 7) {
|
|
hyd = -1; // will throw an error in the end
|
|
}
|
|
}
|
|
}
|
|
} else if (groupno === 8) {
|
|
if (conn + rad + absCharge === 0)
|
|
valence = 1;
|
|
else
|
|
hyd = -1;
|
|
}
|
|
|
|
this.valence = valence;
|
|
this.implicitH = hyd;
|
|
if (this.implicitH < 0) {
|
|
this.valence = conn;
|
|
this.implicitH = 0;
|
|
this.badConn = true;
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
Atom.prototype.calcValenceMinusHyd = function (conn) { // eslint-disable-line max-statements
|
|
var atom = this;
|
|
var charge = atom.charge;
|
|
var label = atom.label;
|
|
var elem = element.map[label];
|
|
if (elem === null)
|
|
console.assert('Element ' + label + ' unknown');
|
|
if (elem < 0) { // query atom, skip
|
|
this.implicitH = 0;
|
|
return null;
|
|
}
|
|
|
|
var groupno = element[elem].group;
|
|
var rad = radicalElectrons(atom.radical);
|
|
|
|
if (groupno === 3) {
|
|
if (label === 'B' || label === 'Al' || label === 'Ga' || label === 'In') {
|
|
if (charge === -1) {
|
|
if (rad + conn <= 4)
|
|
return rad + conn;
|
|
}
|
|
}
|
|
} else if (groupno === 5) {
|
|
if (label === 'N' || label === 'P') {
|
|
if (charge === 1)
|
|
return rad + conn;
|
|
if (charge === 2)
|
|
return rad + conn;
|
|
} else if (label === 'Sb' || label === 'Bi' || label === 'As') {
|
|
if (charge === 1)
|
|
return rad + conn;
|
|
else if (charge === 2)
|
|
return rad + conn;
|
|
}
|
|
} else if (groupno === 6) {
|
|
if (label === 'O') {
|
|
if (charge >= 1)
|
|
return rad + conn;
|
|
} else if (label === 'S' || label === 'Se' || label === 'Po') {
|
|
if (charge === 1)
|
|
return rad + conn;
|
|
}
|
|
} else if (groupno === 7) {
|
|
if (label === 'Cl' || label === 'Br' ||
|
|
label === 'I' || label === 'At') {
|
|
if (charge === 1)
|
|
return rad + conn;
|
|
}
|
|
}
|
|
|
|
return rad + conn + Math.abs(charge);
|
|
};
|
|
|
|
function ifDef(dst, src, prop, def) {
|
|
dst[prop] = !(typeof src[prop] === 'undefined') ? src[prop] : def;
|
|
}
|
|
|
|
function checkPseudo(label) {
|
|
return !element.map[label] && label !== 'L' && label !== 'L#' && label !== 'R#' ? label : null;
|
|
}
|
|
|
|
module.exports = Atom;
|