/**************************************************************************** * 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;