forked from enviPath/enviPy
Current Dev State
This commit is contained in:
435
static/js/ketcher2/script/chem/struct/atom.js
Normal file
435
static/js/ketcher2/script/chem/struct/atom.js
Normal file
@ -0,0 +1,435 @@
|
||||
/****************************************************************************
|
||||
* 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;
|
||||
44
static/js/ketcher2/script/chem/struct/atomlist.js
Normal file
44
static/js/ketcher2/script/chem/struct/atomlist.js
Normal file
@ -0,0 +1,44 @@
|
||||
/****************************************************************************
|
||||
* 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 element = require('../element');
|
||||
|
||||
function AtomList(params) {
|
||||
console.assert(params && 'notList' in params && 'ids' in params, '\'notList\' and \'ids\' must be specified!');
|
||||
|
||||
this.notList = params.notList; /* boolean*/
|
||||
this.ids = params.ids; /* Array of integers*/
|
||||
}
|
||||
|
||||
AtomList.prototype.labelList = function () {
|
||||
var labels = [];
|
||||
for (var i = 0; i < this.ids.length; ++i)
|
||||
labels.push(element[this.ids[i]].label);
|
||||
return labels;
|
||||
};
|
||||
|
||||
AtomList.prototype.label = function () {
|
||||
var label = '[' + this.labelList().join(',') + ']';
|
||||
if (this.notList)
|
||||
label = '!' + label;
|
||||
return label;
|
||||
};
|
||||
|
||||
AtomList.prototype.equals = function (x) {
|
||||
return this.notList == x.notList && (this.ids || []).sort().toString() === (x.ids || []).sort().toString();
|
||||
};
|
||||
|
||||
module.exports = AtomList;
|
||||
145
static/js/ketcher2/script/chem/struct/bond.js
Normal file
145
static/js/ketcher2/script/chem/struct/bond.js
Normal file
@ -0,0 +1,145 @@
|
||||
/****************************************************************************
|
||||
* 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');
|
||||
|
||||
function Bond(params) { // eslint-disable-line max-statements
|
||||
console.assert(params && 'begin' in params && 'end' in params && 'type' in params,
|
||||
'\'begin\', \'end\' and \'type\' properties must be specified!');
|
||||
|
||||
this.begin = params.begin;
|
||||
this.end = params.end;
|
||||
this.type = params.type;
|
||||
this.xxx = params.xxx || '';
|
||||
this.stereo = Bond.PATTERN.STEREO.NONE;
|
||||
this.topology = Bond.PATTERN.TOPOLOGY.EITHER;
|
||||
this.reactingCenterStatus = 0;
|
||||
this.hb1 = null; // half-bonds
|
||||
this.hb2 = null;
|
||||
this.len = 0;
|
||||
this.sb = 0;
|
||||
this.sa = 0;
|
||||
this.angle = 0;
|
||||
|
||||
if (params.stereo)
|
||||
this.stereo = params.stereo;
|
||||
if (params.topology)
|
||||
this.topology = params.topology;
|
||||
if (params.reactingCenterStatus)
|
||||
this.reactingCenterStatus = params.reactingCenterStatus;
|
||||
|
||||
this.center = new Vec2();
|
||||
}
|
||||
|
||||
Bond.PATTERN =
|
||||
{
|
||||
TYPE:
|
||||
{
|
||||
SINGLE: 1,
|
||||
DOUBLE: 2,
|
||||
TRIPLE: 3,
|
||||
AROMATIC: 4,
|
||||
SINGLE_OR_DOUBLE: 5,
|
||||
SINGLE_OR_AROMATIC: 6,
|
||||
DOUBLE_OR_AROMATIC: 7,
|
||||
ANY: 8
|
||||
},
|
||||
|
||||
STEREO:
|
||||
{
|
||||
NONE: 0,
|
||||
UP: 1,
|
||||
EITHER: 4,
|
||||
DOWN: 6,
|
||||
CIS_TRANS: 3
|
||||
},
|
||||
|
||||
TOPOLOGY:
|
||||
{
|
||||
EITHER: 0,
|
||||
RING: 1,
|
||||
CHAIN: 2
|
||||
},
|
||||
|
||||
REACTING_CENTER:
|
||||
{
|
||||
NOT_CENTER: -1,
|
||||
UNMARKED: 0,
|
||||
CENTER: 1,
|
||||
UNCHANGED: 2,
|
||||
MADE_OR_BROKEN: 4,
|
||||
ORDER_CHANGED: 8,
|
||||
MADE_OR_BROKEN_AND_CHANGED: 12
|
||||
}
|
||||
};
|
||||
|
||||
Bond.attrlist = {
|
||||
type: Bond.PATTERN.TYPE.SINGLE,
|
||||
stereo: Bond.PATTERN.STEREO.NONE,
|
||||
topology: Bond.PATTERN.TOPOLOGY.EITHER,
|
||||
reactingCenterStatus: 0
|
||||
};
|
||||
|
||||
// TODO: not used
|
||||
Bond.getAttrHash = function (bond) {
|
||||
var attrs = {};
|
||||
for (var attr in Bond.attrlist) {
|
||||
if (typeof (bond[attr]) !== 'undefined')
|
||||
attrs[attr] = bond[attr];
|
||||
}
|
||||
return attrs;
|
||||
};
|
||||
|
||||
Bond.attrGetDefault = function (attr) {
|
||||
if (attr in Bond.attrlist)
|
||||
return Bond.attrlist[attr];
|
||||
console.error('Attribute unknown');
|
||||
};
|
||||
|
||||
Bond.prototype.hasRxnProps = function () {
|
||||
return !!this.reactingCenterStatus;
|
||||
};
|
||||
|
||||
Bond.prototype.getCenter = function (struct) {
|
||||
var p1 = struct.atoms.get(this.begin).pp;
|
||||
var p2 = struct.atoms.get(this.end).pp;
|
||||
return Vec2.lc2(p1, 0.5, p2, 0.5);
|
||||
};
|
||||
|
||||
Bond.prototype.getDir = function (struct) {
|
||||
var p1 = struct.atoms.get(this.begin).pp;
|
||||
var p2 = struct.atoms.get(this.end).pp;
|
||||
return p2.sub(p1).normalized();
|
||||
};
|
||||
|
||||
Bond.prototype.clone = function (aidMap) {
|
||||
var cp = new Bond(this);
|
||||
if (aidMap) {
|
||||
cp.begin = aidMap[cp.begin];
|
||||
cp.end = aidMap[cp.end];
|
||||
}
|
||||
return cp;
|
||||
};
|
||||
|
||||
Bond.prototype.findOtherEnd = function (i) {
|
||||
if (i == this.begin)
|
||||
return this.end;
|
||||
if (i == this.end)
|
||||
return this.begin;
|
||||
console.error('bond end not found');
|
||||
};
|
||||
|
||||
module.exports = Bond;
|
||||
975
static/js/ketcher2/script/chem/struct/index.js
Normal file
975
static/js/ketcher2/script/chem/struct/index.js
Normal file
@ -0,0 +1,975 @@
|
||||
/****************************************************************************
|
||||
* 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 Map = require('../../util/map');
|
||||
var Pool = require('../../util/pool');
|
||||
var Set = require('../../util/set');
|
||||
var Vec2 = require('../../util/vec2');
|
||||
|
||||
var element = require('../element');
|
||||
|
||||
var Atom = require('./atom');
|
||||
var AtomList = require('./atomlist');
|
||||
var Bond = require('./bond');
|
||||
var SGroup = require('./sgroup');
|
||||
var RGroup = require('./rgroup');
|
||||
var SGroupForest = require('./sgforest');
|
||||
|
||||
function Struct() {
|
||||
this.atoms = new Pool();
|
||||
this.bonds = new Pool();
|
||||
this.sgroups = new Pool();
|
||||
this.halfBonds = new Map();
|
||||
this.loops = new Pool();
|
||||
this.isChiral = false;
|
||||
this.isReaction = false;
|
||||
this.rxnArrows = new Pool();
|
||||
this.rxnPluses = new Pool();
|
||||
this.frags = new Pool();
|
||||
this.rgroups = new Map();
|
||||
this.name = '';
|
||||
this.sGroupForest = new SGroupForest(this);
|
||||
}
|
||||
|
||||
Struct.prototype.hasRxnProps = function () {
|
||||
return this.atoms.find(function (aid, atom) {
|
||||
return atom.hasRxnProps();
|
||||
}, this) >= 0 || this.bonds.find(function (bid, bond) {
|
||||
return bond.hasRxnProps();
|
||||
}, this) >= 0;
|
||||
};
|
||||
|
||||
Struct.prototype.hasRxnArrow = function () {
|
||||
return this.rxnArrows.count() > 0;
|
||||
};
|
||||
|
||||
// returns a list of id's of s-groups, which contain only atoms in the given list
|
||||
Struct.prototype.getSGroupsInAtomSet = function (atoms/* Array*/) {
|
||||
var sgCounts = {};
|
||||
|
||||
atoms.forEach(function (aid) {
|
||||
var sg = Set.list(this.atoms.get(aid).sgs);
|
||||
|
||||
sg.forEach(function (sid) {
|
||||
sgCounts[sid] = sgCounts[sid] ? (sgCounts[sid] + 1) : 1;
|
||||
}, this);
|
||||
}, this);
|
||||
|
||||
var sgroupList = [];
|
||||
for (var key in sgCounts) {
|
||||
var sid = parseInt(key, 10);
|
||||
var sgroup = this.sgroups.get(sid);
|
||||
var sgAtoms = SGroup.getAtoms(this, sgroup);
|
||||
if (sgCounts[key] === sgAtoms.length)
|
||||
sgroupList.push(sid);
|
||||
}
|
||||
return sgroupList;
|
||||
};
|
||||
|
||||
Struct.prototype.isBlank = function () {
|
||||
return this.atoms.count() === 0 &&
|
||||
this.rxnArrows.count() === 0 &&
|
||||
this.rxnPluses.count() === 0 && !this.isChiral;
|
||||
};
|
||||
|
||||
Struct.prototype.toLists = function () {
|
||||
var aidMap = {};
|
||||
var atomList = [];
|
||||
this.atoms.each(function (aid, atom) {
|
||||
aidMap[aid] = atomList.length;
|
||||
atomList.push(atom);
|
||||
});
|
||||
|
||||
var bondList = [];
|
||||
this.bonds.each(function (bid, bond) {
|
||||
var b = new Bond(bond);
|
||||
b.begin = aidMap[bond.begin];
|
||||
b.end = aidMap[bond.end];
|
||||
bondList.push(b);
|
||||
});
|
||||
|
||||
return {
|
||||
atoms: atomList,
|
||||
bonds: bondList
|
||||
};
|
||||
};
|
||||
|
||||
Struct.prototype.clone = function (atomSet, bondSet, dropRxnSymbols, aidMap) {
|
||||
var cp = new Struct();
|
||||
return this.mergeInto(cp, atomSet, bondSet, dropRxnSymbols, false, aidMap);
|
||||
};
|
||||
|
||||
Struct.prototype.getScaffold = function () {
|
||||
var atomSet = Set.empty();
|
||||
this.atoms.each(function (aid) {
|
||||
Set.add(atomSet, aid);
|
||||
}, this);
|
||||
this.rgroups.each(function (rgid, rg) {
|
||||
rg.frags.each(function (fnum, fid) {
|
||||
this.atoms.each(function (aid, atom) {
|
||||
if (atom.fragment === fid)
|
||||
Set.remove(atomSet, aid);
|
||||
}, this);
|
||||
}, this);
|
||||
}, this);
|
||||
return this.clone(atomSet);
|
||||
};
|
||||
|
||||
Struct.prototype.getFragmentIds = function (fid) {
|
||||
const atomSet = Set.empty();
|
||||
|
||||
this.atoms.each((aid, atom) => {
|
||||
if (atom.fragment === fid)
|
||||
Set.add(atomSet, aid);
|
||||
});
|
||||
|
||||
return atomSet;
|
||||
};
|
||||
|
||||
Struct.prototype.getFragment = function (fid) {
|
||||
return this.clone(this.getFragmentIds(fid));
|
||||
};
|
||||
|
||||
Struct.prototype.mergeInto = function (cp, atomSet, bondSet, dropRxnSymbols, keepAllRGroups, aidMap) { // eslint-disable-line max-params, max-statements
|
||||
atomSet = atomSet || Set.keySetInt(this.atoms);
|
||||
bondSet = bondSet || Set.keySetInt(this.bonds);
|
||||
bondSet = Set.filter(bondSet, function (bid) {
|
||||
var bond = this.bonds.get(bid);
|
||||
return Set.contains(atomSet, bond.begin) && Set.contains(atomSet, bond.end);
|
||||
}, this);
|
||||
|
||||
var fidMask = {};
|
||||
this.atoms.each(function (aid, atom) {
|
||||
if (Set.contains(atomSet, aid))
|
||||
fidMask[atom.fragment] = 1;
|
||||
});
|
||||
var fidMap = {};
|
||||
this.frags.each(function (fid, frag) {
|
||||
if (fidMask[fid])
|
||||
fidMap[fid] = cp.frags.add(Object.assign({}, frag));
|
||||
});
|
||||
|
||||
var rgroupsIds = [];
|
||||
this.rgroups.each(function (rgid, rgroup) {
|
||||
var keepGroup = keepAllRGroups;
|
||||
if (!keepGroup) {
|
||||
rgroup.frags.each(function (fnum, fid) {
|
||||
rgroupsIds.push(fid);
|
||||
if (fidMask[fid])
|
||||
keepGroup = true;
|
||||
});
|
||||
if (!keepGroup)
|
||||
return;
|
||||
}
|
||||
var rg = cp.rgroups.get(rgid);
|
||||
if (rg) {
|
||||
rgroup.frags.each(function (fnum, fid) {
|
||||
rgroupsIds.push(fid);
|
||||
if (fidMask[fid])
|
||||
rg.frags.add(fidMap[fid]);
|
||||
});
|
||||
} else {
|
||||
cp.rgroups.set(rgid, rgroup.clone(fidMap));
|
||||
}
|
||||
});
|
||||
|
||||
if (typeof aidMap === 'undefined' || aidMap === null)
|
||||
aidMap = {};
|
||||
// atoms in not RGroup
|
||||
this.atoms.each(function (aid, atom) {
|
||||
if (Set.contains(atomSet, aid) && rgroupsIds.indexOf(atom.fragment) === -1)
|
||||
aidMap[aid] = cp.atoms.add(atom.clone(fidMap));
|
||||
});
|
||||
// atoms in RGroup
|
||||
this.atoms.each(function (aid, atom) {
|
||||
if (Set.contains(atomSet, aid) && rgroupsIds.indexOf(atom.fragment) !== -1)
|
||||
aidMap[aid] = cp.atoms.add(atom.clone(fidMap));
|
||||
});
|
||||
|
||||
var bidMap = {};
|
||||
this.bonds.each(function (bid, bond) {
|
||||
if (Set.contains(bondSet, bid))
|
||||
bidMap[bid] = cp.bonds.add(bond.clone(aidMap));
|
||||
});
|
||||
|
||||
this.sgroups.each(function (sid, sg) {
|
||||
var i;
|
||||
for (i = 0; i < sg.atoms.length; ++i) {
|
||||
if (!Set.contains(atomSet, sg.atoms[i]))
|
||||
return;
|
||||
}
|
||||
sg = SGroup.clone(sg, aidMap, bidMap);
|
||||
var id = cp.sgroups.add(sg);
|
||||
sg.id = id;
|
||||
for (i = 0; i < sg.atoms.length; ++i)
|
||||
Set.add(cp.atoms.get(sg.atoms[i]).sgs, id);
|
||||
|
||||
if (sg.type === 'DAT')
|
||||
cp.sGroupForest.insert(sg.id, -1, []);
|
||||
else
|
||||
cp.sGroupForest.insert(sg.id);
|
||||
});
|
||||
cp.isChiral = this.isChiral;
|
||||
if (!dropRxnSymbols) {
|
||||
cp.isReaction = this.isReaction;
|
||||
this.rxnArrows.each(function (id, item) {
|
||||
cp.rxnArrows.add(item.clone());
|
||||
});
|
||||
this.rxnPluses.each(function (id, item) {
|
||||
cp.rxnPluses.add(item.clone());
|
||||
});
|
||||
}
|
||||
return cp;
|
||||
};
|
||||
|
||||
Struct.prototype.findBondId = function (begin, end) {
|
||||
var id = -1;
|
||||
|
||||
this.bonds.find(function (bid, bond) {
|
||||
if ((bond.begin === begin && bond.end === end) ||
|
||||
(bond.begin === end && bond.end === begin)) {
|
||||
id = bid;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, this);
|
||||
|
||||
return id;
|
||||
};
|
||||
|
||||
function HalfBond(/* num*/begin, /* num*/end, /* num*/bid) { // eslint-disable-line max-params, max-statements
|
||||
console.assert(arguments.length === 3, 'Invalid parameter number!');
|
||||
|
||||
this.begin = begin - 0;
|
||||
this.end = end - 0;
|
||||
this.bid = bid - 0;
|
||||
|
||||
// rendering properties
|
||||
this.dir = new Vec2(); // direction
|
||||
this.norm = new Vec2(); // left normal
|
||||
this.ang = 0; // angle to (1,0), used for sorting the bonds
|
||||
this.p = new Vec2(); // corrected origin position
|
||||
this.loop = -1; // left loop id if the half-bond is in a loop, otherwise -1
|
||||
this.contra = -1; // the half bond contrary to this one
|
||||
this.next = -1; // the half-bond next ot this one in CCW order
|
||||
this.leftSin = 0;
|
||||
this.leftCos = 0;
|
||||
this.leftNeighbor = 0;
|
||||
this.rightSin = 0;
|
||||
this.rightCos = 0;
|
||||
this.rightNeighbor = 0;
|
||||
}
|
||||
|
||||
Struct.prototype.initNeighbors = function () {
|
||||
this.atoms.each(function (aid, atom) {
|
||||
atom.neighbors = [];
|
||||
});
|
||||
this.bonds.each(function (bid, bond) {
|
||||
var a1 = this.atoms.get(bond.begin);
|
||||
var a2 = this.atoms.get(bond.end);
|
||||
a1.neighbors.push(bond.hb1);
|
||||
a2.neighbors.push(bond.hb2);
|
||||
}, this);
|
||||
};
|
||||
|
||||
Struct.prototype.bondInitHalfBonds = function (bid, /* opt*/ bond) {
|
||||
bond = bond || this.bonds.get(bid);
|
||||
bond.hb1 = 2 * bid;
|
||||
bond.hb2 = 2 * bid + 1; // eslint-disable-line no-mixed-operators
|
||||
this.halfBonds.set(bond.hb1, new HalfBond(bond.begin, bond.end, bid));
|
||||
this.halfBonds.set(bond.hb2, new HalfBond(bond.end, bond.begin, bid));
|
||||
var hb1 = this.halfBonds.get(bond.hb1);
|
||||
var hb2 = this.halfBonds.get(bond.hb2);
|
||||
hb1.contra = bond.hb2;
|
||||
hb2.contra = bond.hb1;
|
||||
};
|
||||
|
||||
Struct.prototype.halfBondUpdate = function (hbid) {
|
||||
var hb = this.halfBonds.get(hbid);
|
||||
var p1 = this.atoms.get(hb.begin).pp;
|
||||
var p2 = this.atoms.get(hb.end).pp;
|
||||
var d = Vec2.diff(p2, p1).normalized();
|
||||
hb.dir = Vec2.dist(p2, p1) > 1e-4 ? d : new Vec2(1, 0);
|
||||
hb.norm = hb.dir.turnLeft();
|
||||
hb.ang = hb.dir.oxAngle();
|
||||
if (hb.loop < 0)
|
||||
hb.loop = -1;
|
||||
};
|
||||
|
||||
Struct.prototype.initHalfBonds = function () {
|
||||
this.halfBonds.clear();
|
||||
this.bonds.each(this.bondInitHalfBonds, this);
|
||||
};
|
||||
|
||||
Struct.prototype.setHbNext = function (hbid, next) {
|
||||
this.halfBonds.get(this.halfBonds.get(hbid).contra).next = next;
|
||||
};
|
||||
|
||||
Struct.prototype.halfBondSetAngle = function (hbid, left) {
|
||||
var hb = this.halfBonds.get(hbid);
|
||||
var hbl = this.halfBonds.get(left);
|
||||
hbl.rightCos = hb.leftCos = Vec2.dot(hbl.dir, hb.dir);
|
||||
hbl.rightSin = hb.leftSin = Vec2.cross(hbl.dir, hb.dir);
|
||||
hb.leftNeighbor = left;
|
||||
hbl.rightNeighbor = hbid;
|
||||
};
|
||||
|
||||
Struct.prototype.atomAddNeighbor = function (hbid) {
|
||||
var hb = this.halfBonds.get(hbid);
|
||||
var atom = this.atoms.get(hb.begin);
|
||||
var i = 0;
|
||||
for (i = 0; i < atom.neighbors.length; ++i) {
|
||||
if (this.halfBonds.get(atom.neighbors[i]).ang > hb.ang)
|
||||
break;
|
||||
}
|
||||
atom.neighbors.splice(i, 0, hbid);
|
||||
var ir = atom.neighbors[(i + 1) % atom.neighbors.length];
|
||||
var il = atom.neighbors[(i + atom.neighbors.length - 1) %
|
||||
atom.neighbors.length];
|
||||
this.setHbNext(il, hbid);
|
||||
this.setHbNext(hbid, ir);
|
||||
this.halfBondSetAngle(hbid, il);
|
||||
this.halfBondSetAngle(ir, hbid);
|
||||
};
|
||||
|
||||
Struct.prototype.atomSortNeighbors = function (aid) {
|
||||
var atom = this.atoms.get(aid);
|
||||
var halfBonds = this.halfBonds;
|
||||
atom.neighbors = atom.neighbors.sort(function (nei, nei2) {
|
||||
return halfBonds.get(nei).ang - halfBonds.get(nei2).ang;
|
||||
});
|
||||
|
||||
var i;
|
||||
for (i = 0; i < atom.neighbors.length; ++i) {
|
||||
this.halfBonds.get(this.halfBonds.get(atom.neighbors[i]).contra).next =
|
||||
atom.neighbors[(i + 1) % atom.neighbors.length];
|
||||
}
|
||||
for (i = 0; i < atom.neighbors.length; ++i) {
|
||||
this.halfBondSetAngle(atom.neighbors[(i + 1) % atom.neighbors.length],
|
||||
atom.neighbors[i]);
|
||||
}
|
||||
};
|
||||
|
||||
Struct.prototype.sortNeighbors = function (list) {
|
||||
function f(aid) {
|
||||
this.atomSortNeighbors(aid);
|
||||
}
|
||||
if (!list)
|
||||
this.atoms.each(f, this);
|
||||
else
|
||||
list.forEach(f, this);
|
||||
};
|
||||
|
||||
Struct.prototype.atomUpdateHalfBonds = function (aid) {
|
||||
var nei = this.atoms.get(aid).neighbors;
|
||||
for (var i = 0; i < nei.length; ++i) {
|
||||
var hbid = nei[i];
|
||||
this.halfBondUpdate(hbid);
|
||||
this.halfBondUpdate(this.halfBonds.get(hbid).contra);
|
||||
}
|
||||
};
|
||||
|
||||
Struct.prototype.updateHalfBonds = function (list) {
|
||||
function f(aid) {
|
||||
this.atomUpdateHalfBonds(aid);
|
||||
}
|
||||
if (!list)
|
||||
this.atoms.each(f, this);
|
||||
else
|
||||
list.forEach(f, this);
|
||||
};
|
||||
|
||||
Struct.prototype.sGroupsRecalcCrossBonds = function () {
|
||||
this.sgroups.each(function (sgid, sg) {
|
||||
sg.xBonds = [];
|
||||
sg.neiAtoms = [];
|
||||
}, this);
|
||||
this.bonds.each(function (bid, bond) {
|
||||
var a1 = this.atoms.get(bond.begin);
|
||||
var a2 = this.atoms.get(bond.end);
|
||||
Set.each(a1.sgs, function (sgid) {
|
||||
if (!Set.contains(a2.sgs, sgid)) {
|
||||
var sg = this.sgroups.get(sgid);
|
||||
sg.xBonds.push(bid);
|
||||
arrayAddIfMissing(sg.neiAtoms, bond.end);
|
||||
}
|
||||
}, this);
|
||||
Set.each(a2.sgs, function (sgid) {
|
||||
if (!Set.contains(a1.sgs, sgid)) {
|
||||
var sg = this.sgroups.get(sgid);
|
||||
sg.xBonds.push(bid);
|
||||
arrayAddIfMissing(sg.neiAtoms, bond.begin);
|
||||
}
|
||||
}, this);
|
||||
}, this);
|
||||
};
|
||||
|
||||
Struct.prototype.sGroupDelete = function (sgid) {
|
||||
var sg = this.sgroups.get(sgid);
|
||||
for (var i = 0; i < sg.atoms.length; ++i)
|
||||
Set.remove(this.atoms.get(sg.atoms[i]).sgs, sgid);
|
||||
this.sGroupForest.remove(sgid);
|
||||
this.sgroups.remove(sgid);
|
||||
};
|
||||
|
||||
Struct.prototype.atomSetPos = function (id, pp) {
|
||||
var itemId = this['atoms'].get(id);
|
||||
itemId.pp = pp;
|
||||
};
|
||||
|
||||
Struct.prototype.rxnPlusSetPos = function (id, pp) {
|
||||
var itemId = this['rxnPluses'].get(id);
|
||||
itemId.pp = pp;
|
||||
};
|
||||
|
||||
Struct.prototype.rxnArrowSetPos = function (id, pp) {
|
||||
var itemId = this['rxnArrows'].get(id);
|
||||
itemId.pp = pp;
|
||||
};
|
||||
|
||||
Struct.prototype.getCoordBoundingBox = function (atomSet) {
|
||||
var bb = null;
|
||||
function extend(pp) {
|
||||
if (!bb) {
|
||||
bb = {
|
||||
min: pp,
|
||||
max: pp
|
||||
};
|
||||
} else {
|
||||
bb.min = Vec2.min(bb.min, pp);
|
||||
bb.max = Vec2.max(bb.max, pp);
|
||||
}
|
||||
}
|
||||
|
||||
var global = typeof (atomSet) === 'undefined';
|
||||
|
||||
this.atoms.each(function (aid, atom) {
|
||||
if (global || Set.contains(atomSet, aid))
|
||||
extend(atom.pp);
|
||||
});
|
||||
if (global) {
|
||||
this.rxnPluses.each(function (id, item) {
|
||||
extend(item.pp);
|
||||
});
|
||||
this.rxnArrows.each(function (id, item) {
|
||||
extend(item.pp);
|
||||
});
|
||||
}
|
||||
if (!bb && global) {
|
||||
bb = {
|
||||
min: new Vec2(0, 0),
|
||||
max: new Vec2(1, 1)
|
||||
};
|
||||
}
|
||||
return bb;
|
||||
};
|
||||
|
||||
Struct.prototype.getCoordBoundingBoxObj = function () {
|
||||
var bb = null;
|
||||
function extend(pp) {
|
||||
if (!bb) {
|
||||
bb = {
|
||||
min: new Vec2(pp),
|
||||
max: new Vec2(pp)
|
||||
};
|
||||
} else {
|
||||
bb.min = Vec2.min(bb.min, pp);
|
||||
bb.max = Vec2.max(bb.max, pp);
|
||||
}
|
||||
}
|
||||
|
||||
this.atoms.each(function (aid, atom) {
|
||||
extend(atom.pp);
|
||||
});
|
||||
return bb;
|
||||
};
|
||||
|
||||
Struct.prototype.getBondLengthData = function () {
|
||||
var totalLength = 0;
|
||||
var cnt = 0;
|
||||
this.bonds.each(function (bid, bond) {
|
||||
totalLength += Vec2.dist(
|
||||
this.atoms.get(bond.begin).pp,
|
||||
this.atoms.get(bond.end).pp);
|
||||
cnt++;
|
||||
}, this);
|
||||
return { cnt: cnt, totalLength: totalLength };
|
||||
};
|
||||
|
||||
Struct.prototype.getAvgBondLength = function () {
|
||||
var bld = this.getBondLengthData();
|
||||
return bld.cnt > 0 ? bld.totalLength / bld.cnt : -1;
|
||||
};
|
||||
|
||||
Struct.prototype.getAvgClosestAtomDistance = function () {
|
||||
var totalDist = 0;
|
||||
var minDist;
|
||||
var dist = 0;
|
||||
var keys = this.atoms.keys();
|
||||
var k;
|
||||
var j;
|
||||
for (k = 0; k < keys.length; ++k) {
|
||||
minDist = -1;
|
||||
for (j = 0; j < keys.length; ++j) {
|
||||
if (j == k)
|
||||
continue; // eslint-disable-line no-continue
|
||||
dist = Vec2.dist(this.atoms.get(keys[j]).pp, this.atoms.get(keys[k]).pp);
|
||||
if (minDist < 0 || minDist > dist)
|
||||
minDist = dist;
|
||||
}
|
||||
totalDist += minDist;
|
||||
}
|
||||
|
||||
return keys.length > 0 ? totalDist / keys.length : -1;
|
||||
};
|
||||
|
||||
Struct.prototype.checkBondExists = function (begin, end) {
|
||||
var bondExists = false;
|
||||
this.bonds.each(function (bid, bond) {
|
||||
if ((bond.begin == begin && bond.end == end) ||
|
||||
(bond.end == begin && bond.begin == end))
|
||||
bondExists = true;
|
||||
}, this);
|
||||
return bondExists;
|
||||
};
|
||||
|
||||
function Loop(/* Array of num*/hbs, /* Struct*/struct, /* bool*/convex) {
|
||||
this.hbs = hbs; // set of half-bonds involved
|
||||
this.dblBonds = 0; // number of double bonds in the loop
|
||||
this.aromatic = true;
|
||||
this.convex = convex || false;
|
||||
|
||||
hbs.forEach(function (hb) {
|
||||
var bond = struct.bonds.get(struct.halfBonds.get(hb).bid);
|
||||
if (bond.type != Bond.PATTERN.TYPE.AROMATIC)
|
||||
this.aromatic = false;
|
||||
if (bond.type == Bond.PATTERN.TYPE.DOUBLE)
|
||||
this.dblBonds++;
|
||||
}, this);
|
||||
}
|
||||
|
||||
Struct.prototype.findConnectedComponent = function (aid) {
|
||||
var map = {};
|
||||
var list = [aid];
|
||||
var ids = Set.empty();
|
||||
while (list.length > 0) {
|
||||
(function () {
|
||||
var aid = list.pop();
|
||||
map[aid] = 1;
|
||||
Set.add(ids, aid);
|
||||
var atom = this.atoms.get(aid);
|
||||
for (var i = 0; i < atom.neighbors.length; ++i) {
|
||||
var neiId = this.halfBonds.get(atom.neighbors[i]).end;
|
||||
if (!Set.contains(ids, neiId))
|
||||
list.push(neiId);
|
||||
}
|
||||
}).apply(this);
|
||||
}
|
||||
return ids;
|
||||
};
|
||||
|
||||
Struct.prototype.findConnectedComponents = function (discardExistingFragments) {
|
||||
// NB: this is a hack
|
||||
// TODO: need to maintain half-bond and neighbor structure permanently
|
||||
if (!this.halfBonds.count()) {
|
||||
this.initHalfBonds();
|
||||
this.initNeighbors();
|
||||
this.updateHalfBonds(this.atoms.keys());
|
||||
this.sortNeighbors(this.atoms.keys());
|
||||
}
|
||||
|
||||
var map = {};
|
||||
this.atoms.each(function (aid) {
|
||||
map[aid] = -1;
|
||||
}, this);
|
||||
var components = [];
|
||||
this.atoms.each(function (aid, atom) {
|
||||
if ((discardExistingFragments || atom.fragment < 0) && map[aid] < 0) {
|
||||
var component = this.findConnectedComponent(aid);
|
||||
components.push(component);
|
||||
Set.each(component, function (aid) {
|
||||
map[aid] = 1;
|
||||
}, this);
|
||||
}
|
||||
}, this);
|
||||
return components;
|
||||
};
|
||||
|
||||
Struct.prototype.markFragment = function (ids) {
|
||||
var frag = {};
|
||||
var fid = this.frags.add(frag);
|
||||
Set.each(ids, function (aid) {
|
||||
this.atoms.get(aid).fragment = fid;
|
||||
}, this);
|
||||
};
|
||||
|
||||
Struct.prototype.markFragmentByAtomId = function (aid) {
|
||||
this.markFragment(this.findConnectedComponent(aid));
|
||||
};
|
||||
|
||||
Struct.prototype.markFragments = function () {
|
||||
var components = this.findConnectedComponents();
|
||||
for (var i = 0; i < components.length; ++i)
|
||||
this.markFragment(components[i]);
|
||||
};
|
||||
|
||||
Struct.prototype.scale = function (scale) {
|
||||
if (scale != 1) {
|
||||
this.atoms.each(function (aid, atom) {
|
||||
atom.pp = atom.pp.scaled(scale);
|
||||
}, this);
|
||||
this.rxnPluses.each(function (id, item) {
|
||||
item.pp = item.pp.scaled(scale);
|
||||
}, this);
|
||||
this.rxnArrows.each(function (id, item) {
|
||||
item.pp = item.pp.scaled(scale);
|
||||
}, this);
|
||||
this.sgroups.each(function (id, item) {
|
||||
item.pp = item.pp ? item.pp.scaled(scale) : null;
|
||||
}, this);
|
||||
}
|
||||
};
|
||||
|
||||
Struct.prototype.rescale = function () {
|
||||
var avg = this.getAvgBondLength();
|
||||
if (avg < 0 && !this.isReaction) // TODO [MK] this doesn't work well for reactions as the distances between
|
||||
// the atoms in different components are generally larger than those between atoms of a single component
|
||||
// (KETCHER-341)
|
||||
avg = this.getAvgClosestAtomDistance();
|
||||
if (avg < 1e-3)
|
||||
avg = 1;
|
||||
var scale = 1 / avg;
|
||||
this.scale(scale);
|
||||
};
|
||||
|
||||
Struct.prototype.loopHasSelfIntersections = function (hbs) {
|
||||
for (var i = 0; i < hbs.length; ++i) {
|
||||
var hbi = this.halfBonds.get(hbs[i]);
|
||||
var ai = this.atoms.get(hbi.begin).pp;
|
||||
var bi = this.atoms.get(hbi.end).pp;
|
||||
var set = Set.fromList([hbi.begin, hbi.end]);
|
||||
for (var j = i + 2; j < hbs.length; ++j) {
|
||||
var hbj = this.halfBonds.get(hbs[j]);
|
||||
if (Set.contains(set, hbj.begin) || Set.contains(set, hbj.end))
|
||||
/* eslint-disable no-continue*/
|
||||
continue; // skip edges sharing an atom
|
||||
/* eslint-enable no-continue*/
|
||||
var aj = this.atoms.get(hbj.begin).pp;
|
||||
var bj = this.atoms.get(hbj.end).pp;
|
||||
if (Vec2.segmentIntersection(ai, bi, aj, bj))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// partition a cycle into simple cycles
|
||||
// TODO: [MK] rewrite the detection algorithm to only find simple ones right away?
|
||||
Struct.prototype.partitionLoop = function (loop) { // eslint-disable-line max-statements
|
||||
var subloops = [];
|
||||
var continueFlag = true;
|
||||
search: while (continueFlag) { // eslint-disable-line no-restricted-syntax
|
||||
var atomToHalfBond = {}; // map from every atom in the loop to the index of the first half-bond starting from that atom in the uniqHb array
|
||||
for (var l = 0; l < loop.length; ++l) {
|
||||
var hbid = loop[l];
|
||||
var aid1 = this.halfBonds.get(hbid).begin;
|
||||
var aid2 = this.halfBonds.get(hbid).end;
|
||||
if (aid2 in atomToHalfBond) { // subloop found
|
||||
var s = atomToHalfBond[aid2]; // where the subloop begins
|
||||
var subloop = loop.slice(s, l + 1);
|
||||
subloops.push(subloop);
|
||||
if (l < loop.length) // remove half-bonds corresponding to the subloop
|
||||
loop.splice(s, l - s + 1);
|
||||
continue search; // eslint-disable-line no-continue
|
||||
}
|
||||
atomToHalfBond[aid1] = l;
|
||||
}
|
||||
continueFlag = false; // we're done, no more subloops found
|
||||
subloops.push(loop);
|
||||
}
|
||||
return subloops;
|
||||
};
|
||||
|
||||
Struct.prototype.halfBondAngle = function (hbid1, hbid2) {
|
||||
var hba = this.halfBonds.get(hbid1);
|
||||
var hbb = this.halfBonds.get(hbid2);
|
||||
return Math.atan2(
|
||||
Vec2.cross(hba.dir, hbb.dir),
|
||||
Vec2.dot(hba.dir, hbb.dir));
|
||||
};
|
||||
|
||||
Struct.prototype.loopIsConvex = function (loop) {
|
||||
for (var k = 0; k < loop.length; ++k) {
|
||||
var angle = this.halfBondAngle(loop[k], loop[(k + 1) % loop.length]);
|
||||
if (angle > 0)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// check whether a loop is on the inner or outer side of the polygon
|
||||
// by measuring the total angle between bonds
|
||||
Struct.prototype.loopIsInner = function (loop) {
|
||||
var totalAngle = 2 * Math.PI;
|
||||
for (var k = 0; k < loop.length; ++k) {
|
||||
var hbida = loop[k];
|
||||
var hbidb = loop[(k + 1) % loop.length];
|
||||
var hbb = this.halfBonds.get(hbidb);
|
||||
var angle = this.halfBondAngle(hbida, hbidb);
|
||||
if (hbb.contra == loop[k]) // back and forth along the same edge
|
||||
totalAngle += Math.PI;
|
||||
else
|
||||
totalAngle += angle;
|
||||
}
|
||||
return Math.abs(totalAngle) < Math.PI;
|
||||
};
|
||||
|
||||
Struct.prototype.findLoops = function () {
|
||||
var newLoops = [];
|
||||
var bondsToMark = Set.empty();
|
||||
|
||||
/*
|
||||
Starting from each half-bond not known to be in a loop yet,
|
||||
follow the 'next' links until the initial half-bond is reached or
|
||||
the length of the sequence exceeds the number of half-bonds available.
|
||||
In a planar graph, as long as every bond is a part of some "loop" -
|
||||
either an outer or an inner one - every iteration either yields a loop
|
||||
or doesn't start at all. Thus this has linear complexity in the number
|
||||
of bonds for planar graphs.
|
||||
*/
|
||||
|
||||
var hbIdNext, c, loop, loopId;
|
||||
this.halfBonds.each(function (hbId, hb) {
|
||||
if (hb.loop !== -1)
|
||||
return;
|
||||
|
||||
for (hbIdNext = hbId, c = 0, loop = []; c <= this.halfBonds.count(); hbIdNext = this.halfBonds.get(hbIdNext).next, ++c) {
|
||||
if (!(c > 0 && hbIdNext === hbId)) {
|
||||
loop.push(hbIdNext);
|
||||
continue;
|
||||
}
|
||||
|
||||
// loop found
|
||||
var subloops = this.partitionLoop(loop);
|
||||
subloops.forEach(function (loop) {
|
||||
if (this.loopIsInner(loop) && !this.loopHasSelfIntersections(loop)) {
|
||||
/*
|
||||
loop is internal
|
||||
use lowest half-bond id in the loop as the loop id
|
||||
this ensures that the loop gets the same id if it is discarded and then recreated,
|
||||
which in turn is required to enable redrawing while dragging, as actions store item id's
|
||||
*/
|
||||
loopId = Math.min.apply(Math, loop);
|
||||
this.loops.set(loopId, new Loop(loop, this, this.loopIsConvex(loop)));
|
||||
} else {
|
||||
loopId = -2;
|
||||
}
|
||||
|
||||
loop.forEach(function (hbid) {
|
||||
this.halfBonds.get(hbid).loop = loopId;
|
||||
Set.add(bondsToMark, this.halfBonds.get(hbid).bid);
|
||||
}, this);
|
||||
|
||||
if (loopId >= 0)
|
||||
newLoops.push(loopId);
|
||||
}, this);
|
||||
break;
|
||||
}
|
||||
}, this);
|
||||
|
||||
return {
|
||||
newLoops: newLoops,
|
||||
bondsToMark: Set.list(bondsToMark)
|
||||
};
|
||||
};
|
||||
|
||||
// NB: this updates the structure without modifying the corresponding ReStruct.
|
||||
// To be applied to standalone structures only.
|
||||
Struct.prototype.prepareLoopStructure = function () {
|
||||
this.initHalfBonds();
|
||||
this.initNeighbors();
|
||||
this.updateHalfBonds(this.atoms.keys());
|
||||
this.sortNeighbors(this.atoms.keys());
|
||||
this.findLoops();
|
||||
};
|
||||
|
||||
Struct.prototype.atomAddToSGroup = function (sgid, aid) {
|
||||
// TODO: [MK] make sure the addition does not break the hierarchy?
|
||||
SGroup.addAtom(this.sgroups.get(sgid), aid);
|
||||
Set.add(this.atoms.get(aid).sgs, sgid);
|
||||
};
|
||||
|
||||
Struct.prototype.calcConn = function (aid) {
|
||||
var conn = 0;
|
||||
var atom = this.atoms.get(aid);
|
||||
var oddLoop = false;
|
||||
var hasAromatic = false;
|
||||
for (var i = 0; i < atom.neighbors.length; ++i) {
|
||||
var hb = this.halfBonds.get(atom.neighbors[i]);
|
||||
var bond = this.bonds.get(hb.bid);
|
||||
switch (bond.type) {
|
||||
case Bond.PATTERN.TYPE.SINGLE:
|
||||
conn += 1;
|
||||
break;
|
||||
case Bond.PATTERN.TYPE.DOUBLE:
|
||||
conn += 2;
|
||||
break;
|
||||
case Bond.PATTERN.TYPE.TRIPLE:
|
||||
conn += 3;
|
||||
break;
|
||||
case Bond.PATTERN.TYPE.AROMATIC:
|
||||
conn += 1;
|
||||
hasAromatic = true;
|
||||
this.loops.each(function (id, item) {
|
||||
if (item.hbs.indexOf(atom.neighbors[i]) != -1 && item.hbs.length % 2 == 1)
|
||||
oddLoop = true;
|
||||
}, this);
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (hasAromatic && !atom.hasImplicitH && !oddLoop)
|
||||
conn += 1;
|
||||
return conn;
|
||||
};
|
||||
|
||||
Struct.prototype.calcImplicitHydrogen = function (aid) {
|
||||
var conn = this.calcConn(aid);
|
||||
var atom = this.atoms.get(aid);
|
||||
atom.badConn = false;
|
||||
if (conn < 0 || atom.isQuery()) {
|
||||
atom.implicitH = 0;
|
||||
return;
|
||||
}
|
||||
if (atom.explicitValence >= 0) {
|
||||
var elem = element.map[atom.label];
|
||||
atom.implicitH = 0;
|
||||
if (elem != null) {
|
||||
atom.implicitH = atom.explicitValence - atom.calcValenceMinusHyd(conn);
|
||||
if (atom.implicitH < 0) {
|
||||
atom.implicitH = 0;
|
||||
atom.badConn = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
atom.calcValence(conn);
|
||||
}
|
||||
};
|
||||
|
||||
Struct.prototype.setImplicitHydrogen = function (list) {
|
||||
this.sgroups.each(function (id, item) {
|
||||
if (item.data.fieldName === "MRV_IMPLICIT_H")
|
||||
this.atoms.get(item.atoms[0]).hasImplicitH = true;
|
||||
}, this);
|
||||
function f(aid) {
|
||||
this.calcImplicitHydrogen(aid);
|
||||
}
|
||||
if (!list)
|
||||
this.atoms.each(f, this);
|
||||
else
|
||||
list.forEach(f, this);
|
||||
};
|
||||
|
||||
Struct.prototype.getComponents = function () { // eslint-disable-line max-statements
|
||||
/* saver */
|
||||
var ccs = this.findConnectedComponents(true);
|
||||
var barriers = [];
|
||||
var arrowPos = null;
|
||||
this.rxnArrows.each(function (id, item) { // there's just one arrow
|
||||
arrowPos = item.pp.x;
|
||||
});
|
||||
this.rxnPluses.each(function (id, item) {
|
||||
barriers.push(item.pp.x);
|
||||
});
|
||||
if (arrowPos != null)
|
||||
barriers.push(arrowPos);
|
||||
barriers.sort(function (a, b) {
|
||||
return a - b;
|
||||
});
|
||||
var components = [];
|
||||
|
||||
var i;
|
||||
for (i = 0; i < ccs.length; ++i) {
|
||||
var bb = this.getCoordBoundingBox(ccs[i]);
|
||||
var c = Vec2.lc2(bb.min, 0.5, bb.max, 0.5);
|
||||
var j = 0;
|
||||
while (c.x > barriers[j])
|
||||
++j;
|
||||
components[j] = components[j] || {};
|
||||
Set.mergeIn(components[j], ccs[i]);
|
||||
}
|
||||
var submolTexts = [];
|
||||
var reactants = [];
|
||||
var products = [];
|
||||
for (i = 0; i < components.length; ++i) {
|
||||
if (!components[i]) {
|
||||
submolTexts.push('');
|
||||
continue; // eslint-disable-line no-continue
|
||||
}
|
||||
bb = this.getCoordBoundingBox(components[i]);
|
||||
c = Vec2.lc2(bb.min, 0.5, bb.max, 0.5);
|
||||
if (c.x < arrowPos)
|
||||
reactants.push(components[i]);
|
||||
else
|
||||
products.push(components[i]);
|
||||
}
|
||||
|
||||
return {
|
||||
reactants: reactants,
|
||||
products: products
|
||||
};
|
||||
};
|
||||
|
||||
// Other struct objects
|
||||
|
||||
function RxnPlus(params) {
|
||||
params = params || {};
|
||||
this.pp = params.pp ? new Vec2(params.pp) : new Vec2();
|
||||
}
|
||||
|
||||
RxnPlus.prototype.clone = function () {
|
||||
return new RxnPlus(this);
|
||||
};
|
||||
|
||||
function RxnArrow(params) {
|
||||
params = params || {};
|
||||
this.pp = params.pp ? new Vec2(params.pp) : new Vec2();
|
||||
}
|
||||
|
||||
RxnArrow.prototype.clone = function () {
|
||||
return new RxnArrow(this);
|
||||
};
|
||||
|
||||
function arrayAddIfMissing(array, item) {
|
||||
for (var i = 0; i < array.length; ++i) {
|
||||
if (array[i] === item)
|
||||
return false;
|
||||
}
|
||||
array.push(item);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
module.exports = Object.assign(Struct, {
|
||||
Atom: Atom,
|
||||
AtomList: AtomList,
|
||||
Bond: Bond,
|
||||
SGroup: SGroup,
|
||||
RGroup: RGroup,
|
||||
RxnPlus: RxnPlus,
|
||||
RxnArrow: RxnArrow
|
||||
});
|
||||
51
static/js/ketcher2/script/chem/struct/rgroup.js
Normal file
51
static/js/ketcher2/script/chem/struct/rgroup.js
Normal file
@ -0,0 +1,51 @@
|
||||
/****************************************************************************
|
||||
* 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 Pool = require('../../util/pool');
|
||||
|
||||
function RGroup(logic) {
|
||||
logic = logic || {};
|
||||
this.frags = new Pool();
|
||||
this.resth = logic.resth || false;
|
||||
this.range = logic.range || '';
|
||||
this.ifthen = logic.ifthen || 0;
|
||||
}
|
||||
|
||||
RGroup.prototype.getAttrs = function () {
|
||||
return {
|
||||
resth: this.resth,
|
||||
range: this.range,
|
||||
ifthen: this.ifthen
|
||||
};
|
||||
};
|
||||
|
||||
RGroup.findRGroupByFragment = function (rgroups, frid) {
|
||||
var ret;
|
||||
rgroups.each(function (rgid, rgroup) {
|
||||
if (rgroup.frags.keyOf(frid)) ret = rgid;
|
||||
});
|
||||
return ret;
|
||||
};
|
||||
|
||||
RGroup.prototype.clone = function (fidMap) {
|
||||
var ret = new RGroup(this);
|
||||
this.frags.each(function (fnum, fid) {
|
||||
ret.frags.add(fidMap ? fidMap[fid] : fid);
|
||||
});
|
||||
return ret;
|
||||
};
|
||||
|
||||
module.exports = RGroup;
|
||||
164
static/js/ketcher2/script/chem/struct/sgforest.js
Normal file
164
static/js/ketcher2/script/chem/struct/sgforest.js
Normal file
@ -0,0 +1,164 @@
|
||||
/****************************************************************************
|
||||
* 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 Map = require('../../util/map');
|
||||
var Set = require('../../util/set');
|
||||
|
||||
function SGroupForest(molecule) {
|
||||
this.parent = new Map(); // child id -> parent id
|
||||
this.children = new Map(); // parent id -> list of child ids
|
||||
this.children.set(-1, []); // extra root node
|
||||
this.molecule = molecule;
|
||||
}
|
||||
|
||||
// returns an array or s-group ids in the order of breadth-first search
|
||||
SGroupForest.prototype.getSGroupsBFS = function () {
|
||||
var order = [];
|
||||
var id = -1;
|
||||
var queue = [].slice.call(this.children.get(-1));
|
||||
while (queue.length > 0) {
|
||||
id = queue.shift();
|
||||
queue = queue.concat(this.children.get(id));
|
||||
order.push(id);
|
||||
}
|
||||
return order;
|
||||
};
|
||||
|
||||
SGroupForest.prototype.getAtomSets = function () {
|
||||
return this.molecule.sgroups.map(function (sgid, sgroup) {
|
||||
return Set.fromList(sgroup.atoms);
|
||||
});
|
||||
};
|
||||
|
||||
SGroupForest.prototype.getAtomSetRelations = function (newId, atoms /* Set */) {
|
||||
// find the lowest superset in the hierarchy
|
||||
var isStrictSuperset = new Map();
|
||||
var isSubset = new Map();
|
||||
var atomSets = this.getAtomSets();
|
||||
atomSets.unset(newId);
|
||||
atomSets.each(function (id, atomSet) {
|
||||
isSubset.set(id, Set.subset(atoms, atomSet));
|
||||
isStrictSuperset.set(id, Set.subset(atomSet, atoms) && !Set.eq(atomSet, atoms));
|
||||
}, this);
|
||||
var parents = atomSets.findAll(function (id) {
|
||||
if (!isSubset.get(id))
|
||||
return false;
|
||||
if (this.children.get(id).findIndex(function (childId) {
|
||||
return isSubset.get(childId);
|
||||
}, this) >= 0)
|
||||
return false;
|
||||
return true;
|
||||
}, this);
|
||||
console.assert(parents.length <= 1, "We are here"); // there should be only one parent
|
||||
|
||||
var children = atomSets.findAll(function (id) {
|
||||
return isStrictSuperset.get(id) && !isStrictSuperset.get(this.parent.get(id));
|
||||
}, this);
|
||||
return {
|
||||
children: children,
|
||||
parent: parents.length === 0 ? -1 : parents[0]
|
||||
};
|
||||
};
|
||||
|
||||
SGroupForest.prototype.getPathToRoot = function (sgid) {
|
||||
var path = [];
|
||||
for (var id = sgid; id >= 0; id = this.parent.get(id)) {
|
||||
console.assert(path.indexOf(id) < 0, 'SGroupForest: loop detected');
|
||||
path.push(id);
|
||||
}
|
||||
return path;
|
||||
};
|
||||
|
||||
SGroupForest.prototype.validate = function () {
|
||||
var atomSets = this.getAtomSets();
|
||||
this.molecule.sgroups.each(function (id) {
|
||||
this.getPathToRoot(id); // this will throw an exception if there is a loop in the path to root
|
||||
}, this);
|
||||
|
||||
var valid = true;
|
||||
// 1) child group's atom set is a subset of the parent one's
|
||||
this.parent.each(function (id, parentId) {
|
||||
if (parentId >= 0 && !Set.subset(atomSets.get(id), atomSets.get(parentId)))
|
||||
valid = false;
|
||||
}, this);
|
||||
|
||||
// 2) siblings have disjoint atom sets
|
||||
this.children.each(function (parentId) {
|
||||
var list = this.children.get(parentId);
|
||||
for (var i = 0; i < list.length; ++i) {
|
||||
for (var j = i + 1; j < list.length; ++j) {
|
||||
var id1 = list[i];
|
||||
var id2 = list[j];
|
||||
var sg1 = this.molecule.sgroups.get(id1);
|
||||
var sg2 = this.molecule.sgroups.get(id2);
|
||||
if (!Set.disjoint(atomSets.get(id1), atomSets.get(id2)) && sg1.type != 'DAT' && sg2.type != 'DAT')
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
return valid;
|
||||
};
|
||||
|
||||
SGroupForest.prototype.insert = function (id, parent /* int, optional */, children /* [int], optional */) {
|
||||
console.assert(!this.parent.has(id), 'sgid already present in the forest');
|
||||
console.assert(!this.children.has(id), 'sgid already present in the forest');
|
||||
console.assert(this.validate(), 's-group forest invalid');
|
||||
|
||||
var atomSets = this.getAtomSets();
|
||||
var atoms = Set.fromList(this.molecule.sgroups.get(id).atoms);
|
||||
if (!parent || !children) { // if these are not provided, deduce automatically
|
||||
var guess = this.getAtomSetRelations(id, atoms, atomSets);
|
||||
parent = guess.parent;
|
||||
children = guess.children;
|
||||
}
|
||||
|
||||
// TODO: make children Map<int, Set> instead of Map<int, []>?
|
||||
children.forEach(function (childId) { // reset parent links
|
||||
var childs = this.children.get(this.parent.get(childId));
|
||||
var i = childs.indexOf(childId);
|
||||
console.assert(i >= 0 && childs.indexOf(childId, i + 1) < 0, 'Assertion failed'); // one element
|
||||
childs.splice(i, 1);
|
||||
this.parent.set(childId, id);
|
||||
}, this);
|
||||
this.children.set(id, children);
|
||||
this.parent.set(id, parent);
|
||||
this.children.get(parent).push(id);
|
||||
console.assert(this.validate(), 's-group forest invalid');
|
||||
return { parent: parent, children: children };
|
||||
};
|
||||
|
||||
SGroupForest.prototype.remove = function (id) {
|
||||
console.assert(this.parent.has(id), 'sgid is not in the forest');
|
||||
console.assert(this.children.has(id), 'sgid is not in the forest');
|
||||
console.assert(this.validate(), 's-group forest invalid');
|
||||
|
||||
var parentId = this.parent.get(id);
|
||||
this.children.get(id).forEach(function (childId) { // reset parent links
|
||||
this.parent.set(childId, parentId);
|
||||
this.children.get(parentId).push(childId);
|
||||
}, this);
|
||||
|
||||
var childs = this.children.get(parentId);
|
||||
var i = childs.indexOf(id);
|
||||
console.assert(i >= 0 && childs.indexOf(id, i + 1) < 0, 'Assertion failed'); // one element
|
||||
childs.splice(i, 1);
|
||||
|
||||
this.children.unset(id);
|
||||
this.parent.unset(id);
|
||||
console.assert(this.validate(), 's-group forest invalid');
|
||||
};
|
||||
|
||||
module.exports = SGroupForest;
|
||||
383
static/js/ketcher2/script/chem/struct/sgroup.js
Normal file
383
static/js/ketcher2/script/chem/struct/sgroup.js
Normal file
@ -0,0 +1,383 @@
|
||||
/****************************************************************************
|
||||
* 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 Box2Abs = require('../../util/box2abs');
|
||||
var Set = require('../../util/set');
|
||||
var Vec2 = require('../../util/vec2');
|
||||
|
||||
var Atom = require('./atom');
|
||||
var Bond = require('./bond');
|
||||
|
||||
function SGroup(type) { // eslint-disable-line max-statements
|
||||
console.assert(type && type in SGroup.TYPES, 'Invalid or unsupported s-group type');
|
||||
|
||||
this.type = type;
|
||||
this.id = -1;
|
||||
this.label = -1;
|
||||
this.bracketBox = null;
|
||||
this.bracketDir = new Vec2(1, 0);
|
||||
this.areas = [];
|
||||
|
||||
this.highlight = false;
|
||||
this.highlighting = null;
|
||||
this.selected = false;
|
||||
this.selectionPlate = null;
|
||||
|
||||
this.atoms = [];
|
||||
this.patoms = [];
|
||||
this.bonds = [];
|
||||
this.xBonds = [];
|
||||
this.neiAtoms = [];
|
||||
this.pp = null;
|
||||
this.data = {
|
||||
mul: 1, // multiplication count for MUL group
|
||||
connectivity: 'ht', // head-to-head, head-to-tail or either-unknown
|
||||
name: '',
|
||||
subscript: 'n',
|
||||
|
||||
// data s-group fields
|
||||
attached: false,
|
||||
absolute: true,
|
||||
showUnits: false,
|
||||
nCharsToDisplay: -1,
|
||||
tagChar: '',
|
||||
daspPos: 1,
|
||||
fieldType: 'F',
|
||||
fieldName: '',
|
||||
fieldValue: '',
|
||||
units: '',
|
||||
query: '',
|
||||
queryOp: ''
|
||||
};
|
||||
}
|
||||
|
||||
SGroup.TYPES = {
|
||||
MUL: 1,
|
||||
SRU: 2,
|
||||
SUP: 3,
|
||||
DAT: 4,
|
||||
GEN: 5
|
||||
};
|
||||
|
||||
// TODO: these methods should be overridden
|
||||
// and should only accept valid attributes for each S-group type.
|
||||
// The attributes should be accessed via these methods only and not directly through this.data.
|
||||
// stub
|
||||
SGroup.prototype.getAttr = function (attr) {
|
||||
return this.data[attr];
|
||||
};
|
||||
|
||||
// TODO: should be group-specific
|
||||
SGroup.prototype.getAttrs = function () {
|
||||
var attrs = {};
|
||||
for (var attr in this.data) {
|
||||
if (this.data.hasOwnProperty(attr))
|
||||
attrs[attr] = this.data[attr];
|
||||
}
|
||||
return attrs;
|
||||
};
|
||||
|
||||
// stub
|
||||
SGroup.prototype.setAttr = function (attr, value) {
|
||||
var oldValue = this.data[attr];
|
||||
this.data[attr] = value;
|
||||
return oldValue;
|
||||
};
|
||||
|
||||
// stub
|
||||
SGroup.prototype.checkAttr = function (attr, value) {
|
||||
return this.data[attr] == value;
|
||||
};
|
||||
|
||||
// SGroup.numberArrayToString = function (numbers, map) {
|
||||
// var str = util.stringPadded(numbers.length, 3);
|
||||
// for (var i = 0; i < numbers.length; ++i) {
|
||||
// str += ' ' + util.stringPadded(map[numbers[i]], 3);
|
||||
// }
|
||||
// return str;
|
||||
// };
|
||||
|
||||
SGroup.filterAtoms = function (atoms, map) {
|
||||
var newAtoms = [];
|
||||
for (var i = 0; i < atoms.length; ++i) {
|
||||
var aid = atoms[i];
|
||||
if (typeof (map[aid]) !== 'number')
|
||||
newAtoms.push(aid);
|
||||
else if (map[aid] >= 0)
|
||||
newAtoms.push(map[aid]);
|
||||
else
|
||||
newAtoms.push(-1);
|
||||
}
|
||||
return newAtoms;
|
||||
};
|
||||
|
||||
SGroup.removeNegative = function (atoms) {
|
||||
var newAtoms = [];
|
||||
for (var j = 0; j < atoms.length; ++j) {
|
||||
if (atoms[j] >= 0)
|
||||
newAtoms.push(atoms[j]);
|
||||
}
|
||||
return newAtoms;
|
||||
};
|
||||
|
||||
SGroup.filter = function (mol, sg, atomMap) {
|
||||
sg.atoms = SGroup.removeNegative(SGroup.filterAtoms(sg.atoms, atomMap));
|
||||
};
|
||||
|
||||
SGroup.clone = function (sgroup, aidMap) {
|
||||
var cp = new SGroup(sgroup.type);
|
||||
|
||||
for (var field in sgroup.data) // TODO: remove all non-primitive properties from 'data'
|
||||
cp.data[field] = sgroup.data[field];
|
||||
cp.atoms = sgroup.atoms.map(function (elem) {
|
||||
return aidMap[elem];
|
||||
});
|
||||
cp.pp = sgroup.pp;
|
||||
cp.bracketBox = sgroup.bracketBox;
|
||||
cp.patoms = null;
|
||||
cp.bonds = null;
|
||||
cp.allAtoms = sgroup.allAtoms;
|
||||
return cp;
|
||||
};
|
||||
|
||||
SGroup.addAtom = function (sgroup, aid) {
|
||||
sgroup.atoms.push(aid);
|
||||
};
|
||||
|
||||
SGroup.removeAtom = function (sgroup, aid) {
|
||||
for (var i = 0; i < sgroup.atoms.length; ++i) {
|
||||
if (sgroup.atoms[i] === aid) {
|
||||
sgroup.atoms.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
console.error('The atom is not found in the given s-group');
|
||||
};
|
||||
|
||||
SGroup.getCrossBonds = function (inBonds, xBonds, mol, parentAtomSet) {
|
||||
mol.bonds.each(function (bid, bond) {
|
||||
if (Set.contains(parentAtomSet, bond.begin) && Set.contains(parentAtomSet, bond.end)) {
|
||||
if (inBonds != null)
|
||||
inBonds.push(bid);
|
||||
} else if (Set.contains(parentAtomSet, bond.begin) || Set.contains(parentAtomSet, bond.end)) {
|
||||
if (xBonds != null)
|
||||
xBonds.push(bid);
|
||||
}
|
||||
}, this);
|
||||
};
|
||||
|
||||
SGroup.bracketPos = function (sg, mol, xbonds) { // eslint-disable-line max-statements
|
||||
var atoms = sg.atoms;
|
||||
if (!xbonds || xbonds.length !== 2) {
|
||||
sg.bracketDir = new Vec2(1, 0);
|
||||
} else {
|
||||
var p1 = mol.bonds.get(xbonds[0]).getCenter(mol);
|
||||
var p2 = mol.bonds.get(xbonds[1]).getCenter(mol);
|
||||
sg.bracketDir = Vec2.diff(p2, p1).normalized();
|
||||
}
|
||||
var d = sg.bracketDir;
|
||||
|
||||
var bb = null;
|
||||
var contentBoxes = [];
|
||||
atoms.forEach(function (aid) {
|
||||
var atom = mol.atoms.get(aid);
|
||||
var pos = new Vec2(atom.pp);
|
||||
var ext = new Vec2(0.05 * 3, 0.05 * 3);
|
||||
var bba = new Box2Abs(pos, pos).extend(ext, ext);
|
||||
contentBoxes.push(bba);
|
||||
}, this);
|
||||
contentBoxes.forEach(function (bba) {
|
||||
var bbb = null;
|
||||
[bba.p0.x, bba.p1.x].forEach(function (x) {
|
||||
[bba.p0.y, bba.p1.y].forEach(function (y) {
|
||||
var v = new Vec2(x, y);
|
||||
var p = new Vec2(Vec2.dot(v, d), Vec2.dot(v, d.rotateSC(1, 0)));
|
||||
bbb = (bbb === null) ? new Box2Abs(p, p) : bbb.include(p);
|
||||
}, this);
|
||||
}, this);
|
||||
bb = (bb === null) ? bbb : Box2Abs.union(bb, bbb);
|
||||
}, this);
|
||||
var vext = new Vec2(0.2, 0.4);
|
||||
if (bb !== null) bb = bb.extend(vext, vext);
|
||||
sg.bracketBox = bb;
|
||||
};
|
||||
|
||||
SGroup.getBracketParameters = function (mol, xbonds, atomSet, bb, d, n) { // eslint-disable-line max-params
|
||||
function BracketParams(c, d, w, h) {
|
||||
this.c = c;
|
||||
this.d = d;
|
||||
this.n = d.rotateSC(1, 0);
|
||||
this.w = w;
|
||||
this.h = h;
|
||||
}
|
||||
var brackets = [];
|
||||
if (xbonds.length < 2) {
|
||||
(function () {
|
||||
d = d || new Vec2(1, 0);
|
||||
n = n || d.rotateSC(1, 0);
|
||||
var bracketWidth = Math.min(0.25, bb.sz().x * 0.3);
|
||||
var cl = Vec2.lc2(d, bb.p0.x, n, 0.5 * (bb.p0.y + bb.p1.y));
|
||||
var cr = Vec2.lc2(d, bb.p1.x, n, 0.5 * (bb.p0.y + bb.p1.y));
|
||||
var bracketHeight = bb.sz().y;
|
||||
|
||||
brackets.push(new BracketParams(cl, d.negated(), bracketWidth, bracketHeight), new BracketParams(cr, d, bracketWidth, bracketHeight));
|
||||
})();
|
||||
} else if (xbonds.length === 2) {
|
||||
(function () { // eslint-disable-line max-statements
|
||||
var b1 = mol.bonds.get(xbonds[0]);
|
||||
var b2 = mol.bonds.get(xbonds[1]);
|
||||
var cl0 = b1.getCenter(mol);
|
||||
var cr0 = b2.getCenter(mol);
|
||||
var dr = Vec2.diff(cr0, cl0).normalized();
|
||||
var dl = dr.negated();
|
||||
|
||||
var bracketWidth = 0.25;
|
||||
var bracketHeight = 1.5;
|
||||
brackets.push(new BracketParams(cl0.addScaled(dl, 0), dl, bracketWidth, bracketHeight),
|
||||
new BracketParams(cr0.addScaled(dr, 0), dr, bracketWidth, bracketHeight));
|
||||
})();
|
||||
} else {
|
||||
(function () {
|
||||
for (var i = 0; i < xbonds.length; ++i) {
|
||||
var b = mol.bonds.get(xbonds[i]);
|
||||
var c = b.getCenter(mol);
|
||||
var d = Set.contains(atomSet, b.begin) ? b.getDir(mol) : b.getDir(mol).negated();
|
||||
brackets.push(new BracketParams(c, d, 0.2, 1.0));
|
||||
}
|
||||
})();
|
||||
}
|
||||
return brackets;
|
||||
};
|
||||
|
||||
SGroup.getObjBBox = function (atoms, mol) {
|
||||
console.assert(atoms.length != 0, 'Atom list is empty');
|
||||
|
||||
var a0 = mol.atoms.get(atoms[0]).pp;
|
||||
var bb = new Box2Abs(a0, a0);
|
||||
for (var i = 1; i < atoms.length; ++i) {
|
||||
var aid = atoms[i];
|
||||
var atom = mol.atoms.get(aid);
|
||||
var p = atom.pp;
|
||||
bb = bb.include(p);
|
||||
}
|
||||
return bb;
|
||||
};
|
||||
|
||||
SGroup.getAtoms = function (mol, sg) {
|
||||
/* shoud we use prototype? */
|
||||
if (!sg.allAtoms)
|
||||
return sg.atoms;
|
||||
var atoms = [];
|
||||
mol.atoms.each(function (aid) {
|
||||
atoms.push(aid);
|
||||
});
|
||||
return atoms;
|
||||
};
|
||||
|
||||
SGroup.getBonds = function (mol, sg) {
|
||||
var atoms = SGroup.getAtoms(mol, sg);
|
||||
var bonds = [];
|
||||
mol.bonds.each(function (bid, bond) {
|
||||
if (atoms.indexOf(bond.begin) >= 0 && atoms.indexOf(bond.end) >= 0) bonds.push(bid);
|
||||
});
|
||||
return bonds;
|
||||
};
|
||||
|
||||
SGroup.prepareMulForSaving = function (sgroup, mol) { // eslint-disable-line max-statements
|
||||
sgroup.atoms.sort((a, b) => a - b);
|
||||
sgroup.atomSet = Set.fromList(sgroup.atoms);
|
||||
sgroup.parentAtomSet = Set.clone(sgroup.atomSet);
|
||||
var inBonds = [];
|
||||
var xBonds = [];
|
||||
|
||||
mol.bonds.each(function (bid, bond) {
|
||||
if (Set.contains(sgroup.parentAtomSet, bond.begin) && Set.contains(sgroup.parentAtomSet, bond.end))
|
||||
inBonds.push(bid);
|
||||
else if (Set.contains(sgroup.parentAtomSet, bond.begin) || Set.contains(sgroup.parentAtomSet, bond.end))
|
||||
xBonds.push(bid);
|
||||
}, sgroup);
|
||||
if (xBonds.length !== 0 && xBonds.length !== 2) {
|
||||
throw {
|
||||
'id': sgroup.id,
|
||||
'error-type': 'cross-bond-number',
|
||||
'message': 'Unsupported cross-bonds number'
|
||||
};
|
||||
}
|
||||
|
||||
var xAtom1 = -1;
|
||||
var xAtom2 = -1;
|
||||
var crossBond = null;
|
||||
if (xBonds.length === 2) {
|
||||
var bond1 = mol.bonds.get(xBonds[0]);
|
||||
xAtom1 = Set.contains(sgroup.parentAtomSet, bond1.begin) ? bond1.begin : bond1.end;
|
||||
|
||||
var bond2 = mol.bonds.get(xBonds[1]);
|
||||
xAtom2 = Set.contains(sgroup.parentAtomSet, bond2.begin) ? bond2.begin : bond2.end;
|
||||
crossBond = bond2;
|
||||
}
|
||||
|
||||
var amap = null;
|
||||
var tailAtom = xAtom2;
|
||||
|
||||
var newAtoms = [];
|
||||
for (var j = 0; j < sgroup.data.mul - 1; j++) {
|
||||
amap = {};
|
||||
sgroup.atoms.forEach(function (aid) {
|
||||
var atom = mol.atoms.get(aid);
|
||||
var aid2 = mol.atoms.add(new Atom(atom));
|
||||
newAtoms.push(aid2);
|
||||
sgroup.atomSet[aid2] = 1;
|
||||
amap[aid] = aid2;
|
||||
});
|
||||
inBonds.forEach(function (bid) {
|
||||
var bond = mol.bonds.get(bid);
|
||||
var newBond = new Bond(bond);
|
||||
newBond.begin = amap[newBond.begin];
|
||||
newBond.end = amap[newBond.end];
|
||||
mol.bonds.add(newBond);
|
||||
});
|
||||
if (crossBond !== null) {
|
||||
var newCrossBond = new Bond(crossBond);
|
||||
newCrossBond.begin = tailAtom;
|
||||
newCrossBond.end = amap[xAtom1];
|
||||
mol.bonds.add(newCrossBond);
|
||||
tailAtom = amap[xAtom2];
|
||||
}
|
||||
}
|
||||
if (tailAtom >= 0) {
|
||||
var xBond2 = mol.bonds.get(xBonds[1]);
|
||||
if (xBond2.begin === xAtom2)
|
||||
xBond2.begin = tailAtom;
|
||||
else
|
||||
xBond2.end = tailAtom;
|
||||
}
|
||||
sgroup.bonds = xBonds;
|
||||
|
||||
newAtoms.forEach(function (aid) {
|
||||
mol.sGroupForest.getPathToRoot(sgroup.id).reverse().forEach(function (sgid) {
|
||||
mol.atomAddToSGroup(sgid, aid);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
SGroup.getMassCentre = function (mol, atoms) {
|
||||
var c = new Vec2(); // mass centre
|
||||
for (var i = 0; i < atoms.length; ++i)
|
||||
c = c.addScaled(mol.atoms.get(atoms[i]).pp, 1.0 / atoms.length);
|
||||
return c;
|
||||
};
|
||||
|
||||
module.exports = SGroup;
|
||||
Reference in New Issue
Block a user