forked from enviPath/enviPy
Current Dev State
This commit is contained in:
1294
static/js/ketcher2/script/chem/element.js
Normal file
1294
static/js/ketcher2/script/chem/element.js
Normal file
File diff suppressed because it is too large
Load Diff
110
static/js/ketcher2/script/chem/generics.js
Normal file
110
static/js/ketcher2/script/chem/generics.js
Normal file
@ -0,0 +1,110 @@
|
||||
/****************************************************************************
|
||||
* 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 generics = {
|
||||
atom: {
|
||||
'any': {
|
||||
labels: ['A', 'AH']
|
||||
},
|
||||
'no-carbon': {
|
||||
labels: ['Q', 'QH']
|
||||
},
|
||||
'metal': {
|
||||
labels: ['M', 'MH']
|
||||
},
|
||||
'halogen': {
|
||||
labels: ['X', 'XH']
|
||||
}
|
||||
},
|
||||
group: {
|
||||
labels: ['G', 'GH', 'G*', 'GH*'],
|
||||
acyclic: {
|
||||
labels: ['ACY', 'ACH'],
|
||||
carbo: {
|
||||
labels: ['ABC', 'ABH'],
|
||||
alkynyl: {
|
||||
labels: ['AYL', 'AYH']
|
||||
},
|
||||
alkyl: {
|
||||
labels: ['ALK', 'ALH']
|
||||
},
|
||||
alkenyl: {
|
||||
labels: ['AEL', 'AEH']
|
||||
}
|
||||
},
|
||||
hetero: {
|
||||
labels: ['AHC', 'AHH'],
|
||||
alkoxy: {
|
||||
labels: ['AOX', 'AOH']
|
||||
}
|
||||
}
|
||||
},
|
||||
cyclic: {
|
||||
'labels': ['CYC', 'CYH'],
|
||||
'no-carbon': {
|
||||
labels: ['CXX', 'CXH']
|
||||
},
|
||||
'carbo': {
|
||||
labels: ['CBC', 'CBH'],
|
||||
aryl: {
|
||||
labels: ['ARY', 'ARH']
|
||||
},
|
||||
cycloalkyl: {
|
||||
labels: ['CAL', 'CAH']
|
||||
},
|
||||
cycloalkenyl: {
|
||||
labels: ['CEL', 'CEH']
|
||||
}
|
||||
},
|
||||
'hetero': {
|
||||
labels: ['CHC', 'CHH'],
|
||||
aryl: {
|
||||
labels: ['HAR', 'HAH']
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
special: {
|
||||
labels: ['H+', 'D', 'T', 'R', 'Pol']
|
||||
}
|
||||
};
|
||||
|
||||
function mapify(tree, path, res) {
|
||||
return Object.keys(tree).reduce(function (res, key) {
|
||||
if (key === 'labels') {
|
||||
return tree.labels.reduce(function (res, label) {
|
||||
res[label] = path || true;
|
||||
return res;
|
||||
}, res);
|
||||
}
|
||||
return mapify(tree[key],
|
||||
path ? path.concat(key) : [key], res);
|
||||
}, res || {});
|
||||
}
|
||||
|
||||
function traverse(tree, path) {
|
||||
return path.reduce(function (res, cur) {
|
||||
return (res && res[cur]) || null;
|
||||
}, tree);
|
||||
}
|
||||
|
||||
generics.map = mapify(generics);
|
||||
generics.map['*'] = generics.map['A']; // alias
|
||||
generics.get = function (path) {
|
||||
return mapify(traverse(path));
|
||||
};
|
||||
|
||||
module.exports = generics;
|
||||
269
static/js/ketcher2/script/chem/molfile/common.js
Normal file
269
static/js/ketcher2/script/chem/molfile/common.js
Normal file
@ -0,0 +1,269 @@
|
||||
/****************************************************************************
|
||||
* 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 Set = require('../../util/set');
|
||||
|
||||
var v2000 = require('./v2000');
|
||||
var v3000 = require('./v3000');
|
||||
|
||||
var Struct = require('./../struct/index');
|
||||
var utils = require('./utils');
|
||||
|
||||
var loadRGroupFragments = true; // TODO: set to load the fragments
|
||||
|
||||
/* Parse Mol */
|
||||
function parseMol(/* string */ ctabLines) /* Struct */ {
|
||||
/* reader */
|
||||
if (ctabLines[0].search('\\$MDL') == 0)
|
||||
return v2000.parseRg2000(ctabLines);
|
||||
var struct = parseCTab(ctabLines.slice(3));
|
||||
struct.name = ctabLines[0].trim();
|
||||
return struct;
|
||||
}
|
||||
|
||||
function parseCTab(/* string */ ctabLines) /* Struct */ {
|
||||
/* reader */
|
||||
var countsSplit = partitionLine(ctabLines[0], utils.fmtInfo.countsLinePartition);
|
||||
var version = countsSplit[11].trim();
|
||||
ctabLines = ctabLines.slice(1);
|
||||
if (version == 'V2000')
|
||||
return v2000.parseCTabV2000(ctabLines, countsSplit);
|
||||
else if (version == 'V3000')
|
||||
return v3000.parseCTabV3000(ctabLines, !loadRGroupFragments);
|
||||
else
|
||||
throw new Error('Molfile version unknown: ' + version); // eslint-disable-line no-else-return
|
||||
}
|
||||
|
||||
/* Parse Rxn */
|
||||
function parseRxn(/* string[] */ ctabLines) /* Struct */ {
|
||||
/* reader */
|
||||
var split = ctabLines[0].trim().split(' ');
|
||||
if (split.length > 1 && split[1] == 'V3000')
|
||||
return v3000.parseRxn3000(ctabLines);
|
||||
else
|
||||
return v2000.parseRxn2000(ctabLines); // eslint-disable-line no-else-return
|
||||
}
|
||||
|
||||
/* Prepare For Saving */
|
||||
var prepareForSaving = {
|
||||
MUL: Struct.SGroup.prepareMulForSaving,
|
||||
SRU: prepareSruForSaving,
|
||||
SUP: prepareSupForSaving,
|
||||
DAT: prepareDatForSaving,
|
||||
GEN: prepareGenForSaving
|
||||
};
|
||||
|
||||
function prepareSruForSaving(sgroup, mol) {
|
||||
var xBonds = [];
|
||||
mol.bonds.each(function (bid, bond) {
|
||||
var a1 = mol.atoms.get(bond.begin);
|
||||
var a2 = mol.atoms.get(bond.end);
|
||||
/* eslint-disable no-mixed-operators*/
|
||||
if (Set.contains(a1.sgs, sgroup.id) && !Set.contains(a2.sgs, sgroup.id) ||
|
||||
Set.contains(a2.sgs, sgroup.id) && !Set.contains(a1.sgs, sgroup.id))
|
||||
/* eslint-enable no-mixed-operators*/
|
||||
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' };
|
||||
sgroup.bonds = xBonds;
|
||||
}
|
||||
|
||||
function prepareSupForSaving(sgroup, mol) {
|
||||
// This code is also used for GroupSru and should be moved into a separate common method
|
||||
// It seems that such code should be used for any sgroup by this this should be checked
|
||||
var xBonds = [];
|
||||
mol.bonds.each(function (bid, bond) {
|
||||
var a1 = mol.atoms.get(bond.begin);
|
||||
var a2 = mol.atoms.get(bond.end);
|
||||
/* eslint-disable no-mixed-operators*/
|
||||
if (Set.contains(a1.sgs, sgroup.id) && !Set.contains(a2.sgs, sgroup.id) ||
|
||||
Set.contains(a2.sgs, sgroup.id) && !Set.contains(a1.sgs, sgroup.id))
|
||||
/* eslint-enable no-mixed-operators*/
|
||||
xBonds.push(bid);
|
||||
}, sgroup);
|
||||
sgroup.bonds = xBonds;
|
||||
}
|
||||
|
||||
function prepareGenForSaving(sgroup, mol) { // eslint-disable-line no-unused-vars
|
||||
}
|
||||
|
||||
function prepareDatForSaving(sgroup, mol) {
|
||||
sgroup.atoms = Struct.SGroup.getAtoms(mol, sgroup);
|
||||
}
|
||||
|
||||
/* Save To Molfile */
|
||||
var saveToMolfile = {
|
||||
MUL: saveMulToMolfile,
|
||||
SRU: saveSruToMolfile,
|
||||
SUP: saveSupToMolfile,
|
||||
DAT: saveDatToMolfile,
|
||||
GEN: saveGenToMolfile
|
||||
};
|
||||
|
||||
function saveMulToMolfile(sgroup, mol, sgMap, atomMap, bondMap) { // eslint-disable-line max-params
|
||||
var idstr = (sgMap[sgroup.id] + '').padStart(3);
|
||||
|
||||
var lines = [];
|
||||
lines = lines.concat(makeAtomBondLines('SAL', idstr, Object.keys(sgroup.atomSet), atomMap)); // TODO: check atomSet
|
||||
lines = lines.concat(makeAtomBondLines('SPA', idstr, Object.keys(sgroup.parentAtomSet), atomMap));
|
||||
lines = lines.concat(makeAtomBondLines('SBL', idstr, sgroup.bonds, bondMap));
|
||||
var smtLine = 'M SMT ' + idstr + ' ' + sgroup.data.mul;
|
||||
lines.push(smtLine);
|
||||
lines = lines.concat(bracketsToMolfile(mol, sgroup, idstr));
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function saveSruToMolfile(sgroup, mol, sgMap, atomMap, bondMap) { // eslint-disable-line max-params
|
||||
var idstr = (sgMap[sgroup.id] + '').padStart(3);
|
||||
|
||||
var lines = [];
|
||||
lines = lines.concat(makeAtomBondLines('SAL', idstr, sgroup.atoms, atomMap));
|
||||
lines = lines.concat(makeAtomBondLines('SBL', idstr, sgroup.bonds, bondMap));
|
||||
lines = lines.concat(bracketsToMolfile(mol, sgroup, idstr));
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function saveSupToMolfile(sgroup, mol, sgMap, atomMap, bondMap) { // eslint-disable-line max-params
|
||||
var idstr = (sgMap[sgroup.id] + '').padStart(3);
|
||||
|
||||
var lines = [];
|
||||
lines = lines.concat(makeAtomBondLines('SAL', idstr, sgroup.atoms, atomMap));
|
||||
lines = lines.concat(makeAtomBondLines('SBL', idstr, sgroup.bonds, bondMap));
|
||||
if (sgroup.data.name && sgroup.data.name != '')
|
||||
lines.push('M SMT ' + idstr + ' ' + sgroup.data.name);
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function saveDatToMolfile(sgroup, mol, sgMap, atomMap) {
|
||||
var idstr = (sgMap[sgroup.id] + '').padStart(3);
|
||||
|
||||
var data = sgroup.data;
|
||||
var pp = sgroup.pp;
|
||||
if (!data.absolute)
|
||||
pp = pp.sub(Struct.SGroup.getMassCentre(mol, sgroup.atoms));
|
||||
var lines = [];
|
||||
lines = lines.concat(makeAtomBondLines('SAL', idstr, sgroup.atoms, atomMap));
|
||||
var sdtLine = 'M SDT ' + idstr + ' ' +
|
||||
(data.fieldName || '').padEnd(30) +
|
||||
(data.fieldType || '').padStart(2) +
|
||||
(data.units || '').padEnd(20) +
|
||||
(data.query || '').padStart(2);
|
||||
|
||||
if (data.queryOp) // see gitlab #184
|
||||
sdtLine += data.queryOp.padEnd(80 - 65);
|
||||
|
||||
lines.push(sdtLine);
|
||||
var sddLine = 'M SDD ' + idstr +
|
||||
' ' + utils.paddedNum(pp.x, 10, 4) + utils.paddedNum(-pp.y, 10, 4) +
|
||||
' ' + // ' eee'
|
||||
(data.attached ? 'A' : 'D') + // f
|
||||
(data.absolute ? 'A' : 'R') + // g
|
||||
(data.showUnits ? 'U' : ' ') + // h
|
||||
' ' + // i
|
||||
(data.nCharnCharsToDisplay >= 0 ? utils.paddedNum(data.nCharnCharsToDisplay, 3) : 'ALL') + // jjj
|
||||
' 1 ' + // 'kkk ll '
|
||||
(data.tagChar || ' ') + // m
|
||||
' ' + utils.paddedNum(data.daspPos, 1) + // n
|
||||
' '; // oo
|
||||
lines.push(sddLine);
|
||||
var val = normalizeNewlines(data.fieldValue).replace(/\n*$/, '');
|
||||
var charsPerLine = 69;
|
||||
val.split('\n').forEach(function (chars) {
|
||||
while (chars.length > charsPerLine) {
|
||||
lines.push('M SCD ' + idstr + ' ' + chars.slice(0, charsPerLine));
|
||||
chars = chars.slice(charsPerLine);
|
||||
}
|
||||
lines.push('M SED ' + idstr + ' ' + chars);
|
||||
});
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function saveGenToMolfile(sgroup, mol, sgMap, atomMap, bondMap) { // eslint-disable-line max-params
|
||||
var idstr = (sgMap[sgroup.id] + '').padStart(3);
|
||||
|
||||
var lines = [];
|
||||
lines = lines.concat(makeAtomBondLines('SAL', idstr, sgroup.atoms, atomMap));
|
||||
lines = lines.concat(makeAtomBondLines('SBL', idstr, sgroup.bonds, bondMap));
|
||||
lines = lines.concat(bracketsToMolfile(mol, sgroup, idstr));
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function makeAtomBondLines(prefix, idstr, ids, map) {
|
||||
if (!ids)
|
||||
return [];
|
||||
var lines = [];
|
||||
for (var i = 0; i < Math.floor((ids.length + 14) / 15); ++i) {
|
||||
var rem = Math.min(ids.length - 15 * i, 15); // eslint-disable-line no-mixed-operators
|
||||
var salLine = 'M ' + prefix + ' ' + idstr + ' ' + utils.paddedNum(rem, 2);
|
||||
for (var j = 0; j < rem; ++j)
|
||||
salLine += ' ' + utils.paddedNum(map[ids[i * 15 + j]], 3); // eslint-disable-line no-mixed-operators
|
||||
lines.push(salLine);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
function bracketsToMolfile(mol, sg, idstr) { // eslint-disable-line max-statements
|
||||
var inBonds = [];
|
||||
var xBonds = [];
|
||||
var atomSet = Set.fromList(sg.atoms);
|
||||
Struct.SGroup.getCrossBonds(inBonds, xBonds, mol, atomSet);
|
||||
Struct.SGroup.bracketPos(sg, mol, xBonds);
|
||||
var bb = sg.bracketBox;
|
||||
var d = sg.bracketDir;
|
||||
var n = d.rotateSC(1, 0);
|
||||
var brackets = Struct.SGroup.getBracketParameters(mol, xBonds, atomSet, bb, d, n);
|
||||
var lines = [];
|
||||
for (var i = 0; i < brackets.length; ++i) {
|
||||
var bracket = brackets[i];
|
||||
var a0 = bracket.c.addScaled(bracket.n, -0.5 * bracket.h).yComplement();
|
||||
var a1 = bracket.c.addScaled(bracket.n, 0.5 * bracket.h).yComplement();
|
||||
var line = 'M SDI ' + idstr + utils.paddedNum(4, 3);
|
||||
var coord = [a0.x, a0.y, a1.x, a1.y];
|
||||
for (var j = 0; j < coord.length; ++j)
|
||||
line += utils.paddedNum(coord[j], 10, 4);
|
||||
lines.push(line);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
// According Unicode Consortium sould be
|
||||
// nlRe = /\r\n|[\n\v\f\r\x85\u2028\u2029]/g;
|
||||
// http://www.unicode.org/reports/tr18/#Line_Boundaries
|
||||
var nlRe = /\r\n|[\n\r]/g;
|
||||
function normalizeNewlines(str) {
|
||||
return str.replace(nlRe, '\n');
|
||||
}
|
||||
|
||||
function partitionLine(/* string*/ str, /* array of int*/ parts, /* bool*/ withspace) {
|
||||
/* reader */
|
||||
var res = [];
|
||||
for (var i = 0, shift = 0; i < parts.length; ++i) {
|
||||
res.push(str.slice(shift, shift + parts[i]));
|
||||
if (withspace)
|
||||
shift++;
|
||||
shift += parts[i];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
parseCTab: parseCTab,
|
||||
parseMol: parseMol,
|
||||
parseRxn: parseRxn,
|
||||
prepareForSaving: prepareForSaving,
|
||||
saveToMolfile: saveToMolfile
|
||||
};
|
||||
54
static/js/ketcher2/script/chem/molfile/index.js
Normal file
54
static/js/ketcher2/script/chem/molfile/index.js
Normal file
@ -0,0 +1,54 @@
|
||||
/****************************************************************************
|
||||
* 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 Molfile = require('./molfile');
|
||||
|
||||
// TODO: reconstruct molfile string instead parsing multiple times
|
||||
// merge to bottom
|
||||
function parseCTFile(str, options) {
|
||||
var molfile = new Molfile();
|
||||
var lines = str.split(/\r\n|[\n\r]/g);
|
||||
try {
|
||||
return molfile.parseCTFile(lines);
|
||||
} catch (ex) {
|
||||
if (options.badHeaderRecover) {
|
||||
try {
|
||||
// check whether there's an extra empty line on top
|
||||
// this often happens when molfile text is pasted into the dialog window
|
||||
return molfile.parseCTFile(lines.slice(1));
|
||||
} catch (ex1) { //
|
||||
}
|
||||
try {
|
||||
// check for a missing first line
|
||||
// this sometimes happens when pasting
|
||||
return molfile.parseCTFile([''].concat(lines));
|
||||
} catch (ex2) { //
|
||||
}
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
stringify: function (struct, options) {
|
||||
var opts = options || {};
|
||||
return new Molfile(opts.v3000).saveMolecule(struct, opts.ignoreErrors,
|
||||
opts.noRgroups, opts.preserveIndigoDesc);
|
||||
},
|
||||
parse: function (str, options) {
|
||||
return parseCTFile(str, options || {});
|
||||
}
|
||||
};
|
||||
488
static/js/ketcher2/script/chem/molfile/molfile.js
Normal file
488
static/js/ketcher2/script/chem/molfile/molfile.js
Normal file
@ -0,0 +1,488 @@
|
||||
/****************************************************************************
|
||||
* 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');
|
||||
|
||||
var common = require('./common');
|
||||
var utils = require('./utils');
|
||||
|
||||
function Molfile(v3000) {
|
||||
/* reader */
|
||||
/* saver */
|
||||
this.molecule = null;
|
||||
this.molfile = null;
|
||||
this.v3000 = v3000 || false;
|
||||
}
|
||||
|
||||
Molfile.prototype.parseCTFile = function (molfileLines) {
|
||||
var ret = null;
|
||||
if (molfileLines[0].search('\\$RXN') == 0)
|
||||
ret = common.parseRxn(molfileLines);
|
||||
else
|
||||
ret = common.parseMol(molfileLines);
|
||||
ret.initHalfBonds();
|
||||
ret.initNeighbors();
|
||||
ret.markFragments();
|
||||
return ret;
|
||||
};
|
||||
|
||||
Molfile.prototype.prepareSGroups = function (skipErrors, preserveIndigoDesc) {
|
||||
var mol = this.molecule;
|
||||
var toRemove = [];
|
||||
var errors = 0;
|
||||
|
||||
this.molecule.sGroupForest.getSGroupsBFS().reverse().forEach(function (id) {
|
||||
var sgroup = mol.sgroups.get(id);
|
||||
var errorIgnore = false;
|
||||
|
||||
try {
|
||||
common.prepareForSaving[sgroup.type](sgroup, mol);
|
||||
} catch (ex) {
|
||||
if (!skipErrors || typeof (ex.id) != 'number')
|
||||
throw ex;
|
||||
errorIgnore = true;
|
||||
}
|
||||
/* eslint-disable no-mixed-operators*/
|
||||
if (errorIgnore ||
|
||||
!preserveIndigoDesc && /^INDIGO_.+_DESC$/i.test(sgroup.data.fieldName)) {
|
||||
/* eslint-enable no-mixed-operators*/
|
||||
errors += errorIgnore;
|
||||
toRemove.push(sgroup.id);
|
||||
}
|
||||
}, this);
|
||||
if (errors)
|
||||
throw new Error('WARNING: ' + errors + ' invalid S-groups were detected. They will be omitted.');
|
||||
|
||||
for (var i = 0; i < toRemove.length; ++i)
|
||||
mol.sGroupDelete(toRemove[i]);
|
||||
return mol;
|
||||
};
|
||||
|
||||
Molfile.prototype.getCTab = function (molecule, rgroups) {
|
||||
/* saver */
|
||||
this.molecule = molecule.clone();
|
||||
this.molfile = '';
|
||||
this.writeCTab2000(rgroups);
|
||||
return this.molfile;
|
||||
};
|
||||
|
||||
Molfile.prototype.saveMolecule = function (molecule, skipSGroupErrors, norgroups, preserveIndigoDesc) { // eslint-disable-line max-statements
|
||||
/* saver */
|
||||
this.reaction = molecule.rxnArrows.count() > 0;
|
||||
if (molecule.rxnArrows.count() > 1)
|
||||
throw new Error('Reaction may not contain more than one arrow');
|
||||
this.molfile = '' + molecule.name;
|
||||
if (this.reaction) {
|
||||
if (molecule.rgroups.count() > 0)
|
||||
throw new Error('Unable to save the structure - reactions with r-groups are not supported at the moment');
|
||||
var components = molecule.getComponents();
|
||||
|
||||
var reactants = components.reactants;
|
||||
var products = components.products;
|
||||
var all = reactants.concat(products);
|
||||
this.molfile = '$RXN\n\n\n\n' +
|
||||
utils.paddedNum(reactants.length, 3) +
|
||||
utils.paddedNum(products.length, 3) +
|
||||
utils.paddedNum(0, 3) + '\n';
|
||||
for (var i = 0; i < all.length; ++i) {
|
||||
var saver = new Molfile(false);
|
||||
var submol = molecule.clone(all[i], null, true);
|
||||
var molfile = saver.saveMolecule(submol, false, true);
|
||||
this.molfile += '$MOL\n' + molfile;
|
||||
}
|
||||
return this.molfile;
|
||||
}
|
||||
|
||||
if (molecule.rgroups.count() > 0) {
|
||||
if (norgroups) {
|
||||
molecule = molecule.getScaffold();
|
||||
} else {
|
||||
var scaffold = new Molfile(false).getCTab(molecule.getScaffold(), molecule.rgroups);
|
||||
this.molfile = '$MDL REV 1\n$MOL\n$HDR\n\n\n\n$END HDR\n';
|
||||
this.molfile += '$CTAB\n' + scaffold + '$END CTAB\n';
|
||||
|
||||
molecule.rgroups.each(function (rgid, rg) {
|
||||
this.molfile += '$RGP\n';
|
||||
this.writePaddedNumber(rgid, 3);
|
||||
this.molfile += '\n';
|
||||
rg.frags.each(function (fnum, fid) {
|
||||
var group = new Molfile(false).getCTab(molecule.getFragment(fid));
|
||||
this.molfile += '$CTAB\n' + group + '$END CTAB\n';
|
||||
}, this);
|
||||
this.molfile += '$END RGP\n';
|
||||
}, this);
|
||||
this.molfile += '$END MOL\n';
|
||||
|
||||
return this.molfile;
|
||||
}
|
||||
}
|
||||
|
||||
this.molecule = molecule.clone();
|
||||
|
||||
this.prepareSGroups(skipSGroupErrors, preserveIndigoDesc);
|
||||
|
||||
this.writeHeader();
|
||||
|
||||
// TODO: saving to V3000
|
||||
this.writeCTab2000();
|
||||
|
||||
return this.molfile;
|
||||
};
|
||||
|
||||
Molfile.prototype.writeHeader = function () {
|
||||
/* saver */
|
||||
|
||||
var date = new Date();
|
||||
|
||||
this.writeCR(); // TODO: write structure name
|
||||
this.writeWhiteSpace(2);
|
||||
this.write('Ketcher');
|
||||
this.writeWhiteSpace();
|
||||
this.writeCR(((date.getMonth() + 1) + '').padStart(2) + (date.getDate() + '').padStart(2) + ((date.getFullYear() % 100) + '').padStart(2) +
|
||||
(date.getHours() + '').padStart(2) + (date.getMinutes() + '').padStart(2) + '2D 1 1.00000 0.00000 0');
|
||||
this.writeCR();
|
||||
};
|
||||
|
||||
Molfile.prototype.write = function (str) {
|
||||
/* saver */
|
||||
this.molfile += str;
|
||||
};
|
||||
|
||||
Molfile.prototype.writeCR = function (str) {
|
||||
/* saver */
|
||||
if (arguments.length == 0)
|
||||
str = '';
|
||||
|
||||
this.molfile += str + '\n';
|
||||
};
|
||||
|
||||
Molfile.prototype.writeWhiteSpace = function (length) {
|
||||
/* saver */
|
||||
|
||||
if (arguments.length == 0)
|
||||
length = 1;
|
||||
|
||||
this.write(' '.repeat(Math.max(length, 0)));
|
||||
};
|
||||
|
||||
Molfile.prototype.writePadded = function (str, width) {
|
||||
/* saver */
|
||||
this.write(str);
|
||||
this.writeWhiteSpace(width - str.length);
|
||||
};
|
||||
|
||||
Molfile.prototype.writePaddedNumber = function (number, width) {
|
||||
/* saver */
|
||||
|
||||
var str = (number - 0).toString();
|
||||
|
||||
this.writeWhiteSpace(width - str.length);
|
||||
this.write(str);
|
||||
};
|
||||
|
||||
Molfile.prototype.writePaddedFloat = function (number, width, precision) {
|
||||
/* saver */
|
||||
|
||||
this.write(utils.paddedNum(number, width, precision));
|
||||
};
|
||||
|
||||
Molfile.prototype.writeCTab2000Header = function () {
|
||||
/* saver */
|
||||
|
||||
this.writePaddedNumber(this.molecule.atoms.count(), 3);
|
||||
this.writePaddedNumber(this.molecule.bonds.count(), 3);
|
||||
|
||||
this.writePaddedNumber(0, 3);
|
||||
this.writeWhiteSpace(3);
|
||||
this.writePaddedNumber(this.molecule.isChiral ? 1 : 0, 3);
|
||||
this.writePaddedNumber(0, 3);
|
||||
this.writeWhiteSpace(12);
|
||||
this.writePaddedNumber(999, 3);
|
||||
this.writeCR(' V2000');
|
||||
};
|
||||
|
||||
Molfile.prototype.writeCTab2000 = function (rgroups) { // eslint-disable-line max-statements
|
||||
/* saver */
|
||||
this.writeCTab2000Header();
|
||||
|
||||
this.mapping = {};
|
||||
var i = 1;
|
||||
|
||||
/* eslint-disable camelcase*/
|
||||
var atomList_list = [];
|
||||
var atomProps_list = [];
|
||||
/* eslint-enable camel-case*/
|
||||
this.molecule.atoms.each(function (id, atom) { // eslint-disable-line max-statements
|
||||
this.writePaddedFloat(atom.pp.x, 10, 4);
|
||||
this.writePaddedFloat(-atom.pp.y, 10, 4);
|
||||
this.writePaddedFloat(atom.pp.z, 10, 4);
|
||||
this.writeWhiteSpace();
|
||||
|
||||
var label = atom.label;
|
||||
if (atom.atomList != null) {
|
||||
label = 'L';
|
||||
atomList_list.push(id);
|
||||
} else if (atom['pseudo']) {
|
||||
if (atom['pseudo'].length > 3) {
|
||||
label = 'A';
|
||||
atomProps_list.push({ id: id, value: "'" + atom['pseudo'] + "'" });
|
||||
}
|
||||
} else if (atom['alias']) {
|
||||
atomProps_list.push({ id: id, value: atom['alias'] });
|
||||
} else if (!element.map[label] && ['A', 'Q', 'X', '*', 'R#'].indexOf(label) == -1) { // search in generics?
|
||||
label = 'C';
|
||||
atomProps_list.push({ id: id, value: atom.label });
|
||||
}
|
||||
this.writePadded(label, 3);
|
||||
this.writePaddedNumber(0, 2);
|
||||
this.writePaddedNumber(0, 3);
|
||||
this.writePaddedNumber(0, 3);
|
||||
|
||||
if (typeof atom.hCount === "undefined")
|
||||
atom.hCount = 0;
|
||||
this.writePaddedNumber(atom.hCount, 3);
|
||||
|
||||
if (typeof atom.stereoCare === "undefined")
|
||||
atom.stereoCare = 0;
|
||||
this.writePaddedNumber(atom.stereoCare, 3);
|
||||
|
||||
this.writePaddedNumber(atom.explicitValence < 0 ? 0 : (atom.explicitValence == 0 ? 15 : atom.explicitValence), 3); // eslint-disable-line no-nested-ternary
|
||||
|
||||
this.writePaddedNumber(0, 3);
|
||||
this.writePaddedNumber(0, 3);
|
||||
this.writePaddedNumber(0, 3);
|
||||
|
||||
if (typeof atom.aam === "undefined")
|
||||
atom.aam = 0;
|
||||
this.writePaddedNumber(atom.aam, 3);
|
||||
|
||||
if (typeof atom.invRet === "undefined")
|
||||
atom.invRet = 0;
|
||||
this.writePaddedNumber(atom.invRet, 3);
|
||||
|
||||
if (typeof atom.exactChangeFlag === "undefined")
|
||||
atom.exactChangeFlag = 0;
|
||||
this.writePaddedNumber(atom.exactChangeFlag, 3);
|
||||
|
||||
this.writeCR();
|
||||
|
||||
this.mapping[id] = i;
|
||||
i++;
|
||||
}, this);
|
||||
|
||||
this.bondMapping = {};
|
||||
i = 1;
|
||||
this.molecule.bonds.each(function (id, bond) {
|
||||
this.bondMapping[id] = i++;
|
||||
this.writePaddedNumber(this.mapping[bond.begin], 3);
|
||||
this.writePaddedNumber(this.mapping[bond.end], 3);
|
||||
this.writePaddedNumber(bond.type, 3);
|
||||
|
||||
if (typeof bond.stereo === "undefined")
|
||||
bond.stereo = 0;
|
||||
this.writePaddedNumber(bond.stereo, 3);
|
||||
|
||||
this.writePadded(bond.xxx, 3);
|
||||
|
||||
if (typeof bond.topology === "undefined")
|
||||
bond.topology = 0;
|
||||
this.writePaddedNumber(bond.topology, 3);
|
||||
|
||||
if (typeof bond.reactingCenterStatus === "undefined")
|
||||
bond.reactingCenterStatus = 0;
|
||||
this.writePaddedNumber(bond.reactingCenterStatus, 3);
|
||||
|
||||
this.writeCR();
|
||||
}, this);
|
||||
|
||||
while (atomProps_list.length > 0) {
|
||||
this.write('A ');
|
||||
this.writePaddedNumber(atomProps_list[0].id + 1, 3);
|
||||
this.writeCR();
|
||||
this.writeCR(atomProps_list[0].value);
|
||||
atomProps_list.splice(0, 1);
|
||||
}
|
||||
|
||||
var chargeList = [];
|
||||
var isotopeList = [];
|
||||
var radicalList = [];
|
||||
var rglabelList = [];
|
||||
var rglogicList = [];
|
||||
var aplabelList = [];
|
||||
var rbcountList = [];
|
||||
var unsaturatedList = [];
|
||||
var substcountList = [];
|
||||
|
||||
this.molecule.atoms.each(function (id, atom) {
|
||||
if (atom.charge != 0)
|
||||
chargeList.push([id, atom.charge]);
|
||||
if (atom.isotope != 0)
|
||||
isotopeList.push([id, atom.isotope]);
|
||||
if (atom.radical != 0)
|
||||
radicalList.push([id, atom.radical]);
|
||||
if (atom.rglabel != null && atom.label == 'R#') { // TODO need to force rglabel=null when label is not 'R#'
|
||||
for (var rgi = 0; rgi < 32; rgi++)
|
||||
if (atom.rglabel & (1 << rgi)) rglabelList.push([id, rgi + 1]);
|
||||
}
|
||||
if (atom.attpnt != null)
|
||||
aplabelList.push([id, atom.attpnt]);
|
||||
if (atom.ringBondCount != 0)
|
||||
rbcountList.push([id, atom.ringBondCount]);
|
||||
if (atom.substitutionCount != 0)
|
||||
substcountList.push([id, atom.substitutionCount]);
|
||||
if (atom.unsaturatedAtom != 0)
|
||||
unsaturatedList.push([id, atom.unsaturatedAtom]);
|
||||
});
|
||||
|
||||
if (rgroups) {
|
||||
rgroups.each(function (rgid, rg) {
|
||||
if (rg.resth || rg.ifthen > 0 || rg.range.length > 0) {
|
||||
var line = ' 1 ' +
|
||||
utils.paddedNum(rgid, 3) + ' ' +
|
||||
utils.paddedNum(rg.ifthen, 3) + ' ' +
|
||||
utils.paddedNum(rg.resth ? 1 : 0, 3) + ' ' + rg.range;
|
||||
rglogicList.push(line);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function writeAtomPropList(propId, values) {
|
||||
while (values.length > 0) {
|
||||
var part = [];
|
||||
|
||||
while (values.length > 0 && part.length < 8) {
|
||||
part.push(values[0]);
|
||||
values.splice(0, 1);
|
||||
}
|
||||
|
||||
this.write(propId);
|
||||
this.writePaddedNumber(part.length, 3);
|
||||
|
||||
part.forEach(function (value) {
|
||||
this.writeWhiteSpace();
|
||||
this.writePaddedNumber(this.mapping[value[0]], 3);
|
||||
this.writeWhiteSpace();
|
||||
this.writePaddedNumber(value[1], 3);
|
||||
}, this);
|
||||
|
||||
this.writeCR();
|
||||
}
|
||||
}
|
||||
|
||||
writeAtomPropList.call(this, 'M CHG', chargeList);
|
||||
writeAtomPropList.call(this, 'M ISO', isotopeList);
|
||||
writeAtomPropList.call(this, 'M RAD', radicalList);
|
||||
writeAtomPropList.call(this, 'M RGP', rglabelList);
|
||||
for (var j = 0; j < rglogicList.length; ++j)
|
||||
this.write('M LOG' + rglogicList[j] + '\n');
|
||||
|
||||
writeAtomPropList.call(this, 'M APO', aplabelList);
|
||||
writeAtomPropList.call(this, 'M RBC', rbcountList);
|
||||
writeAtomPropList.call(this, 'M SUB', substcountList);
|
||||
writeAtomPropList.call(this, 'M UNS', unsaturatedList);
|
||||
|
||||
if (atomList_list.length > 0) {
|
||||
for (j = 0; j < atomList_list.length; ++j) {
|
||||
var aid = atomList_list[j];
|
||||
var atomList = this.molecule.atoms.get(aid).atomList;
|
||||
this.write('M ALS');
|
||||
this.writePaddedNumber(aid + 1, 4);
|
||||
this.writePaddedNumber(atomList.ids.length, 3);
|
||||
this.writeWhiteSpace();
|
||||
this.write(atomList.notList ? 'T' : 'F');
|
||||
|
||||
var labelList = atomList.labelList();
|
||||
for (var k = 0; k < labelList.length; ++k) {
|
||||
this.writeWhiteSpace();
|
||||
this.writePadded(labelList[k], 3);
|
||||
}
|
||||
this.writeCR();
|
||||
}
|
||||
}
|
||||
|
||||
var sgmap = {};
|
||||
var cnt = 1;
|
||||
var sgmapback = {};
|
||||
var sgorder = this.molecule.sGroupForest.getSGroupsBFS();
|
||||
sgorder.forEach(function (id) {
|
||||
sgmapback[cnt] = id;
|
||||
sgmap[id] = cnt++;
|
||||
}, this);
|
||||
for (var q = 1; q < cnt; ++q) { // each group on its own
|
||||
var id = sgmapback[q];
|
||||
var sgroup = this.molecule.sgroups.get(id);
|
||||
this.write('M STY');
|
||||
this.writePaddedNumber(1, 3);
|
||||
this.writeWhiteSpace(1);
|
||||
this.writePaddedNumber(q, 3);
|
||||
this.writeWhiteSpace(1);
|
||||
this.writePadded(sgroup.type, 3);
|
||||
this.writeCR();
|
||||
|
||||
// TODO: write subtype, M SST
|
||||
|
||||
this.write('M SLB');
|
||||
this.writePaddedNumber(1, 3);
|
||||
this.writeWhiteSpace(1);
|
||||
this.writePaddedNumber(q, 3);
|
||||
this.writeWhiteSpace(1);
|
||||
this.writePaddedNumber(q, 3);
|
||||
this.writeCR();
|
||||
|
||||
var parentid = this.molecule.sGroupForest.parent.get(id);
|
||||
if (parentid >= 0) {
|
||||
this.write('M SPL');
|
||||
this.writePaddedNumber(1, 3);
|
||||
this.writeWhiteSpace(1);
|
||||
this.writePaddedNumber(q, 3);
|
||||
this.writeWhiteSpace(1);
|
||||
this.writePaddedNumber(sgmap[parentid], 3);
|
||||
this.writeCR();
|
||||
}
|
||||
|
||||
// connectivity
|
||||
if (sgroup.type == 'SRU' && sgroup.data.connectivity) {
|
||||
var connectivity = '';
|
||||
connectivity += ' ';
|
||||
connectivity += q.toString().padStart(3);
|
||||
connectivity += ' ';
|
||||
connectivity += (sgroup.data.connectivity || '').padEnd(3);
|
||||
this.write('M SCN');
|
||||
this.writePaddedNumber(1, 3);
|
||||
this.write(connectivity.toUpperCase());
|
||||
this.writeCR();
|
||||
}
|
||||
|
||||
if (sgroup.type == 'SRU') {
|
||||
this.write('M SMT ');
|
||||
this.writePaddedNumber(q, 3);
|
||||
this.writeWhiteSpace();
|
||||
this.write(sgroup.data.subscript || 'n');
|
||||
this.writeCR();
|
||||
}
|
||||
|
||||
this.writeCR(common.saveToMolfile[sgroup.type](sgroup, this.molecule, sgmap, this.mapping, this.bondMapping));
|
||||
}
|
||||
|
||||
// TODO: write M APO
|
||||
// TODO: write M AAL
|
||||
// TODO: write M RGP
|
||||
// TODO: write M LOG
|
||||
|
||||
this.writeCR('M END');
|
||||
};
|
||||
|
||||
module.exports = Molfile;
|
||||
301
static/js/ketcher2/script/chem/molfile/parseSGroup.js
Normal file
301
static/js/ketcher2/script/chem/molfile/parseSGroup.js
Normal file
@ -0,0 +1,301 @@
|
||||
/****************************************************************************
|
||||
* 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 Set = require('../../util/set');
|
||||
var Vec2 = require('../../util/vec2');
|
||||
|
||||
var Struct = require('./../struct/index');
|
||||
var utils = require('./utils');
|
||||
|
||||
function readKeyValuePairs(str, /* bool */ valueString) {
|
||||
/* reader */
|
||||
var ret = {};
|
||||
var partition = utils.partitionLineFixed(str, 3, true);
|
||||
var count = utils.parseDecimalInt(partition[0]);
|
||||
for (var i = 0; i < count; ++i) {
|
||||
/* eslint-disable no-mixed-operators*/
|
||||
ret[utils.parseDecimalInt(partition[2 * i + 1]) - 1] =
|
||||
valueString ? partition[2 * i + 2].trim() :
|
||||
utils.parseDecimalInt(partition[2 * i + 2]);
|
||||
/* eslint-enable no-mixed-operators*/
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function readKeyMultiValuePairs(str, /* bool */ valueString) {
|
||||
/* reader */
|
||||
var ret = [];
|
||||
var partition = utils.partitionLineFixed(str, 3, true);
|
||||
var count = utils.parseDecimalInt(partition[0]);
|
||||
for (var i = 0; i < count; ++i) {
|
||||
ret.push([
|
||||
/* eslint-disable no-mixed-operators*/
|
||||
utils.parseDecimalInt(partition[2 * i + 1]) - 1,
|
||||
valueString ? partition[2 * i + 2].trim() : utils.parseDecimalInt(partition[2 * i + 2])
|
||||
/* eslint-enable no-mixed-operators*/
|
||||
]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function postLoadMul(sgroup, mol, atomMap) { // eslint-disable-line max-statements
|
||||
sgroup.data.mul = sgroup.data.subscript - 0;
|
||||
var atomReductionMap = {};
|
||||
|
||||
sgroup.atoms = Struct.SGroup.filterAtoms(sgroup.atoms, atomMap);
|
||||
sgroup.patoms = Struct.SGroup.filterAtoms(sgroup.patoms, atomMap);
|
||||
|
||||
// mark repetitions for removal
|
||||
for (var k = 1; k < sgroup.data.mul; ++k) {
|
||||
for (var m = 0; m < sgroup.patoms.length; ++m) {
|
||||
var raid = sgroup.atoms[k * sgroup.patoms.length + m]; // eslint-disable-line no-mixed-operators
|
||||
if (raid < 0)
|
||||
continue; // eslint-disable-line no-continue
|
||||
if (sgroup.patoms[m] < 0)
|
||||
throw new Error('parent atom missing');
|
||||
atomReductionMap[raid] = sgroup.patoms[m]; // "merge" atom in parent
|
||||
}
|
||||
}
|
||||
sgroup.patoms = Struct.SGroup.removeNegative(sgroup.patoms);
|
||||
|
||||
var patomsMap = identityMap(sgroup.patoms);
|
||||
|
||||
var bondsToRemove = [];
|
||||
mol.bonds.each(function (bid, bond) {
|
||||
var beginIn = bond.begin in atomReductionMap;
|
||||
var endIn = bond.end in atomReductionMap;
|
||||
// if both adjacent atoms of a bond are to be merged, remove it
|
||||
/* eslint-disable no-mixed-operators*/
|
||||
if (beginIn && endIn ||
|
||||
beginIn && bond.end in patomsMap ||
|
||||
endIn && bond.begin in patomsMap)
|
||||
bondsToRemove.push(bid);
|
||||
/* eslint-enable no-mixed-operators*/
|
||||
// if just one atom is merged, modify the bond accordingly
|
||||
else if (beginIn)
|
||||
bond.begin = atomReductionMap[bond.begin];
|
||||
else if (endIn)
|
||||
bond.end = atomReductionMap[bond.end];
|
||||
}, sgroup);
|
||||
|
||||
// apply removal lists
|
||||
for (var b = 0; b < bondsToRemove.length; ++b)
|
||||
mol.bonds.remove(bondsToRemove[b]);
|
||||
for (var a in atomReductionMap) {
|
||||
mol.atoms.remove(a);
|
||||
atomMap[a] = -1;
|
||||
}
|
||||
sgroup.atoms = sgroup.patoms;
|
||||
sgroup.patoms = null;
|
||||
}
|
||||
|
||||
function postLoadSru(sgroup) {
|
||||
sgroup.data.connectivity = (sgroup.data.connectivity || 'EU').trim().toLowerCase();
|
||||
}
|
||||
|
||||
function postLoadSup(sgroup) {
|
||||
sgroup.data.name = (sgroup.data.subscript || '').trim();
|
||||
sgroup.data.subscript = '';
|
||||
}
|
||||
|
||||
function postLoadGen(sgroup, mol, atomMap) { // eslint-disable-line no-unused-vars
|
||||
}
|
||||
|
||||
function postLoadDat(sgroup, mol) {
|
||||
if (!sgroup.data.absolute)
|
||||
sgroup.pp = sgroup.pp.add(Struct.SGroup.getMassCentre(mol, sgroup.atoms));
|
||||
}
|
||||
|
||||
function loadSGroup(mol, sg, atomMap) {
|
||||
var postLoadMap = {
|
||||
MUL: postLoadMul,
|
||||
SRU: postLoadSru,
|
||||
SUP: postLoadSup,
|
||||
DAT: postLoadDat,
|
||||
GEN: postLoadGen
|
||||
};
|
||||
|
||||
// add the group to the molecule
|
||||
sg.id = mol.sgroups.add(sg);
|
||||
|
||||
// apply type-specific post-processing
|
||||
postLoadMap[sg.type](sg, mol, atomMap);
|
||||
// mark atoms in the group as belonging to it
|
||||
for (var s = 0; s < sg.atoms.length; ++s) {
|
||||
if (mol.atoms.has(sg.atoms[s]))
|
||||
Set.add(mol.atoms.get(sg.atoms[s]).sgs, sg.id);
|
||||
}
|
||||
|
||||
if (sg.type == 'DAT')
|
||||
mol.sGroupForest.insert(sg.id, -1, []);
|
||||
else
|
||||
mol.sGroupForest.insert(sg.id);
|
||||
|
||||
return sg.id;
|
||||
}
|
||||
|
||||
function initSGroup(sGroups, propData) {
|
||||
/* reader */
|
||||
var kv = readKeyValuePairs(propData, true);
|
||||
for (var key in kv) {
|
||||
var type = kv[key];
|
||||
if (!(type in Struct.SGroup.TYPES))
|
||||
throw new Error('Unsupported S-group type');
|
||||
var sg = new Struct.SGroup(type);
|
||||
sg.number = key;
|
||||
sGroups[key] = sg;
|
||||
}
|
||||
}
|
||||
|
||||
function applySGroupProp(sGroups, propName, propData, numeric, core) { // eslint-disable-line max-params
|
||||
var kv = readKeyValuePairs(propData, !(numeric));
|
||||
for (var key in kv)
|
||||
// "core" properties are stored directly in an sgroup, not in sgroup.data
|
||||
(core ? sGroups[key] : sGroups[key].data)[propName] = kv[key];
|
||||
}
|
||||
|
||||
function applySGroupArrayProp(sGroups, propName, propData, shift) {
|
||||
/* reader */
|
||||
var sid = utils.parseDecimalInt(propData.slice(1, 4)) - 1;
|
||||
var num = utils.parseDecimalInt(propData.slice(4, 8));
|
||||
var part = toIntArray(utils.partitionLineFixed(propData.slice(8), 3, true));
|
||||
|
||||
if (part.length != num)
|
||||
throw new Error('File format invalid');
|
||||
if (shift) {
|
||||
part = part.map(function (v) {
|
||||
return v + shift;
|
||||
});
|
||||
}
|
||||
sGroups[sid][propName] = sGroups[sid][propName].concat(part);
|
||||
}
|
||||
|
||||
function applyDataSGroupName(sg, name) {
|
||||
/* reader */
|
||||
sg.data.fieldName = name;
|
||||
}
|
||||
|
||||
function applyDataSGroupQuery(sg, query) {
|
||||
/* reader */
|
||||
sg.data.query = query;
|
||||
}
|
||||
|
||||
function applyDataSGroupQueryOp(sg, queryOp) {
|
||||
/* reader */
|
||||
sg.data.queryOp = queryOp;
|
||||
}
|
||||
|
||||
function applyDataSGroupDesc(sGroups, propData) {
|
||||
/* reader */
|
||||
var split = utils.partitionLine(propData, [4, 31, 2, 20, 2, 3], false);
|
||||
var id = utils.parseDecimalInt(split[0]) - 1;
|
||||
var fieldName = split[1].trim();
|
||||
var fieldType = split[2].trim();
|
||||
var units = split[3].trim();
|
||||
var query = split[4].trim();
|
||||
var queryOp = split[5].trim();
|
||||
var sGroup = sGroups[id];
|
||||
sGroup.data.fieldType = fieldType;
|
||||
sGroup.data.fieldName = fieldName;
|
||||
sGroup.data.units = units;
|
||||
sGroup.data.query = query;
|
||||
sGroup.data.queryOp = queryOp;
|
||||
}
|
||||
|
||||
function applyDataSGroupInfo(sg, propData) { // eslint-disable-line max-statements
|
||||
/* reader */
|
||||
var split = utils.partitionLine(propData, [10/* x.x*/, 10/* y.y*/, 4/* eee*/, 1/* f*/, 1/* g*/, 1/* h*/, 3/* i */, 3/* jjj*/, 3/* kkk*/, 3/* ll*/, 2/* m*/, 3/* n*/, 2/* oo*/], false);
|
||||
|
||||
var x = parseFloat(split[0]);
|
||||
var y = parseFloat(split[1]);
|
||||
var attached = split[3].trim() == 'A';
|
||||
var absolute = split[4].trim() == 'A';
|
||||
var showUnits = split[5].trim() == 'U';
|
||||
var nCharsToDisplay = split[7].trim();
|
||||
nCharsToDisplay = nCharsToDisplay == 'ALL' ? -1 : utils.parseDecimalInt(nCharsToDisplay);
|
||||
var tagChar = split[10].trim();
|
||||
var daspPos = utils.parseDecimalInt(split[11].trim());
|
||||
|
||||
sg.pp = new Vec2(x, -y);
|
||||
sg.data.attached = attached;
|
||||
sg.data.absolute = absolute;
|
||||
sg.data.showUnits = showUnits;
|
||||
sg.data.nCharsToDisplay = nCharsToDisplay;
|
||||
sg.data.tagChar = tagChar;
|
||||
sg.data.daspPos = daspPos;
|
||||
}
|
||||
|
||||
function applyDataSGroupInfoLine(sGroups, propData) {
|
||||
/* reader */
|
||||
var id = utils.parseDecimalInt(propData.substr(0, 4)) - 1;
|
||||
var sg = sGroups[id];
|
||||
applyDataSGroupInfo(sg, propData.substr(5));
|
||||
}
|
||||
|
||||
function applyDataSGroupData(sg, data, finalize) {
|
||||
/* reader */
|
||||
sg.data.fieldValue = (sg.data.fieldValue || '') + data;
|
||||
if (finalize) {
|
||||
sg.data.fieldValue = trimRight(sg.data.fieldValue);
|
||||
if (sg.data.fieldValue.startsWith('"') && sg.data.fieldValue.endsWith('"'))
|
||||
sg.data.fieldValue = sg.data.fieldValue.substr(1, sg.data.fieldValue.length - 2);
|
||||
}
|
||||
}
|
||||
|
||||
function applyDataSGroupDataLine(sGroups, propData, finalize) {
|
||||
/* reader */
|
||||
var id = utils.parseDecimalInt(propData.substr(0, 5)) - 1;
|
||||
var data = propData.substr(5);
|
||||
var sg = sGroups[id];
|
||||
applyDataSGroupData(sg, data, finalize);
|
||||
}
|
||||
|
||||
// Utilities functions
|
||||
function toIntArray(strArray) {
|
||||
/* reader */
|
||||
var ret = [];
|
||||
for (var j = 0; j < strArray.length; ++j)
|
||||
ret[j] = utils.parseDecimalInt(strArray[j]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
function trimRight(str) {
|
||||
return str.replace(/\s+$/, '');
|
||||
}
|
||||
|
||||
function identityMap(array) {
|
||||
var map = {};
|
||||
for (var i = 0; i < array.length; ++i)
|
||||
map[array[i]] = array[i];
|
||||
return map;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
readKeyValuePairs: readKeyValuePairs,
|
||||
readKeyMultiValuePairs: readKeyMultiValuePairs,
|
||||
loadSGroup: loadSGroup,
|
||||
initSGroup: initSGroup,
|
||||
applySGroupProp: applySGroupProp,
|
||||
applySGroupArrayProp: applySGroupArrayProp,
|
||||
applyDataSGroupName: applyDataSGroupName,
|
||||
applyDataSGroupQuery: applyDataSGroupQuery,
|
||||
applyDataSGroupQueryOp: applyDataSGroupQueryOp,
|
||||
applyDataSGroupDesc: applyDataSGroupDesc,
|
||||
applyDataSGroupInfo: applyDataSGroupInfo,
|
||||
applyDataSGroupData: applyDataSGroupData,
|
||||
applyDataSGroupInfoLine: applyDataSGroupInfoLine,
|
||||
applyDataSGroupDataLine: applyDataSGroupDataLine
|
||||
};
|
||||
274
static/js/ketcher2/script/chem/molfile/utils.js
Normal file
274
static/js/ketcher2/script/chem/molfile/utils.js
Normal file
@ -0,0 +1,274 @@
|
||||
/****************************************************************************
|
||||
* 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 Struct = require('./../struct/index');
|
||||
|
||||
function paddedNum(number, width, precision) {
|
||||
var numStr = number.toFixed(precision || 0).replace(',', '.'); // Really need to replace?
|
||||
if (numStr.length > width)
|
||||
throw new Error('number does not fit');
|
||||
return numStr.padStart(width);
|
||||
}
|
||||
|
||||
function parseDecimalInt(str) {
|
||||
/* reader */
|
||||
var val = parseInt(str, 10);
|
||||
|
||||
return isNaN(val) ? 0 : val;
|
||||
}
|
||||
|
||||
function partitionLine(/* string*/ str, /* array of int*/ parts, /* bool*/ withspace) {
|
||||
/* reader */
|
||||
var res = [];
|
||||
for (var i = 0, shift = 0; i < parts.length; ++i) {
|
||||
res.push(str.slice(shift, shift + parts[i]));
|
||||
if (withspace)
|
||||
shift++;
|
||||
shift += parts[i];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function partitionLineFixed(/* string*/ str, /* int*/ itemLength, /* bool*/ withspace) {
|
||||
/* reader */
|
||||
var res = [];
|
||||
for (var shift = 0; shift < str.length; shift += itemLength) {
|
||||
res.push(str.slice(shift, shift + itemLength));
|
||||
if (withspace)
|
||||
shift++;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
var fmtInfo = {
|
||||
bondTypeMap: {
|
||||
1: Struct.Bond.PATTERN.TYPE.SINGLE,
|
||||
2: Struct.Bond.PATTERN.TYPE.DOUBLE,
|
||||
3: Struct.Bond.PATTERN.TYPE.TRIPLE,
|
||||
4: Struct.Bond.PATTERN.TYPE.AROMATIC,
|
||||
5: Struct.Bond.PATTERN.TYPE.SINGLE_OR_DOUBLE,
|
||||
6: Struct.Bond.PATTERN.TYPE.SINGLE_OR_AROMATIC,
|
||||
7: Struct.Bond.PATTERN.TYPE.DOUBLE_OR_AROMATIC,
|
||||
8: Struct.Bond.PATTERN.TYPE.ANY
|
||||
},
|
||||
bondStereoMap: {
|
||||
0: Struct.Bond.PATTERN.STEREO.NONE,
|
||||
1: Struct.Bond.PATTERN.STEREO.UP,
|
||||
4: Struct.Bond.PATTERN.STEREO.EITHER,
|
||||
6: Struct.Bond.PATTERN.STEREO.DOWN,
|
||||
3: Struct.Bond.PATTERN.STEREO.CIS_TRANS
|
||||
},
|
||||
v30bondStereoMap: {
|
||||
0: Struct.Bond.PATTERN.STEREO.NONE,
|
||||
1: Struct.Bond.PATTERN.STEREO.UP,
|
||||
2: Struct.Bond.PATTERN.STEREO.EITHER,
|
||||
3: Struct.Bond.PATTERN.STEREO.DOWN
|
||||
},
|
||||
bondTopologyMap: {
|
||||
0: Struct.Bond.PATTERN.TOPOLOGY.EITHER,
|
||||
1: Struct.Bond.PATTERN.TOPOLOGY.RING,
|
||||
2: Struct.Bond.PATTERN.TOPOLOGY.CHAIN
|
||||
},
|
||||
countsLinePartition: [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 6],
|
||||
atomLinePartition: [10, 10, 10, 1, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
|
||||
bondLinePartition: [3, 3, 3, 3, 3, 3, 3],
|
||||
atomListHeaderPartition: [3, 1, 1, 4, 1, 1],
|
||||
atomListHeaderLength: 11, // = atomListHeaderPartition.reduce(function(a,b) { return a + b; }, 0)
|
||||
atomListHeaderItemLength: 4,
|
||||
chargeMap: [0, +3, +2, +1, 0, -1, -2, -3],
|
||||
valenceMap: [undefined, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 0],
|
||||
implicitHydrogenMap: [undefined, 0, 1, 2, 3, 4],
|
||||
v30atomPropMap: {
|
||||
CHG: 'charge',
|
||||
RAD: 'radical',
|
||||
MASS: 'isotope',
|
||||
VAL: 'explicitValence',
|
||||
HCOUNT: 'hCount',
|
||||
INVRET: 'invRet',
|
||||
SUBST: 'substitutionCount',
|
||||
UNSAT: 'unsaturatedAtom',
|
||||
RBCNT: 'ringBondCount'
|
||||
},
|
||||
rxnItemsPartition: [3, 3, 3]
|
||||
};
|
||||
|
||||
var FRAGMENT = {
|
||||
NONE: 0,
|
||||
REACTANT: 1,
|
||||
PRODUCT: 2,
|
||||
AGENT: 3
|
||||
};
|
||||
|
||||
var SHOULD_REACTION_FRAGMENT_RELAYOUT = true;
|
||||
var SHOULD_RESCALE_MOLECULES = true;
|
||||
|
||||
function rxnMerge(mols, nReactants, nProducts) /* Struct */ { // eslint-disable-line max-statements
|
||||
/* reader */
|
||||
var ret = new Struct();
|
||||
var bbReact = [],
|
||||
bbAgent = [],
|
||||
bbProd = [];
|
||||
var molReact = [],
|
||||
molAgent = [],
|
||||
molProd = [];
|
||||
var j;
|
||||
var bondLengthData = { cnt: 0, totalLength: 0 };
|
||||
for (j = 0; j < mols.length; ++j) {
|
||||
var mol = mols[j];
|
||||
var bondLengthDataMol = mol.getBondLengthData();
|
||||
bondLengthData.cnt += bondLengthDataMol.cnt;
|
||||
bondLengthData.totalLength += bondLengthDataMol.totalLength;
|
||||
}
|
||||
if (SHOULD_RESCALE_MOLECULES) {
|
||||
var avgBondLength = 1 / (bondLengthData.cnt == 0 ? 1 : bondLengthData.totalLength / bondLengthData.cnt);
|
||||
for (j = 0; j < mols.length; ++j) {
|
||||
mol = mols[j];
|
||||
mol.scale(avgBondLength);
|
||||
}
|
||||
}
|
||||
|
||||
for (j = 0; j < mols.length; ++j) {
|
||||
mol = mols[j];
|
||||
var bb = mol.getCoordBoundingBoxObj();
|
||||
if (!bb)
|
||||
continue; // eslint-disable-line no-continue
|
||||
|
||||
var fragmentType = (j < nReactants ? FRAGMENT.REACTANT : // eslint-disable-line no-nested-ternary
|
||||
(j < nReactants + nProducts ? FRAGMENT.PRODUCT :
|
||||
FRAGMENT.AGENT));
|
||||
if (fragmentType == FRAGMENT.REACTANT) {
|
||||
bbReact.push(bb);
|
||||
molReact.push(mol);
|
||||
} else if (fragmentType == FRAGMENT.AGENT) {
|
||||
bbAgent.push(bb);
|
||||
molAgent.push(mol);
|
||||
} else if (fragmentType == FRAGMENT.PRODUCT) {
|
||||
bbProd.push(bb);
|
||||
molProd.push(mol);
|
||||
}
|
||||
|
||||
mol.atoms.each(function (aid, atom) {
|
||||
atom.rxnFragmentType = fragmentType;
|
||||
});
|
||||
}
|
||||
|
||||
function shiftMol(ret, mol, bb, xorig, over) { // eslint-disable-line max-params
|
||||
var d = new Vec2(xorig - bb.min.x, over ? 1 - bb.min.y : -(bb.min.y + bb.max.y) / 2);
|
||||
mol.atoms.each(function (aid, atom) {
|
||||
atom.pp.add_(d); // eslint-disable-line no-underscore-dangle
|
||||
});
|
||||
mol.sgroups.each(function (id, item) {
|
||||
if (item.pp)
|
||||
item.pp.add_(d); // eslint-disable-line no-underscore-dangle
|
||||
});
|
||||
bb.min.add_(d); // eslint-disable-line no-underscore-dangle
|
||||
bb.max.add_(d); // eslint-disable-line no-underscore-dangle
|
||||
mol.mergeInto(ret);
|
||||
return bb.max.x - bb.min.x;
|
||||
}
|
||||
|
||||
if (SHOULD_REACTION_FRAGMENT_RELAYOUT) {
|
||||
// reaction fragment layout
|
||||
var xorig = 0;
|
||||
for (j = 0; j < molReact.length; ++j)
|
||||
xorig += shiftMol(ret, molReact[j], bbReact[j], xorig, false) + 2.0;
|
||||
xorig += 2.0;
|
||||
for (j = 0; j < molAgent.length; ++j)
|
||||
xorig += shiftMol(ret, molAgent[j], bbAgent[j], xorig, true) + 2.0;
|
||||
xorig += 2.0;
|
||||
|
||||
for (j = 0; j < molProd.length; ++j)
|
||||
xorig += shiftMol(ret, molProd[j], bbProd[j], xorig, false) + 2.0;
|
||||
} else {
|
||||
for (j = 0; j < molReact.length; ++j)
|
||||
molReact[j].mergeInto(ret);
|
||||
for (j = 0; j < molAgent.length; ++j)
|
||||
molAgent[j].mergeInto(ret);
|
||||
for (j = 0; j < molProd.length; ++j)
|
||||
molProd[j].mergeInto(ret);
|
||||
}
|
||||
|
||||
var bb1;
|
||||
var bb2;
|
||||
var x;
|
||||
var y;
|
||||
var bbReactAll = null;
|
||||
var bbProdAll = null;
|
||||
for (j = 0; j < bbReact.length - 1; ++j) {
|
||||
bb1 = bbReact[j];
|
||||
bb2 = bbReact[j + 1];
|
||||
|
||||
x = (bb1.max.x + bb2.min.x) / 2;
|
||||
y = (bb1.max.y + bb1.min.y + bb2.max.y + bb2.min.y) / 4;
|
||||
|
||||
ret.rxnPluses.add(new Struct.RxnPlus({ pp: new Vec2(x, y) }));
|
||||
}
|
||||
for (j = 0; j < bbReact.length; ++j) {
|
||||
if (j == 0) {
|
||||
bbReactAll = {};
|
||||
bbReactAll.max = new Vec2(bbReact[j].max);
|
||||
bbReactAll.min = new Vec2(bbReact[j].min);
|
||||
} else {
|
||||
bbReactAll.max = Vec2.max(bbReactAll.max, bbReact[j].max);
|
||||
bbReactAll.min = Vec2.min(bbReactAll.min, bbReact[j].min);
|
||||
}
|
||||
}
|
||||
for (j = 0; j < bbProd.length - 1; ++j) {
|
||||
bb1 = bbProd[j];
|
||||
bb2 = bbProd[j + 1];
|
||||
|
||||
x = (bb1.max.x + bb2.min.x) / 2;
|
||||
y = (bb1.max.y + bb1.min.y + bb2.max.y + bb2.min.y) / 4;
|
||||
|
||||
ret.rxnPluses.add(new Struct.RxnPlus({ pp: new Vec2(x, y) }));
|
||||
}
|
||||
for (j = 0; j < bbProd.length; ++j) {
|
||||
if (j == 0) {
|
||||
bbProdAll = {};
|
||||
bbProdAll.max = new Vec2(bbProd[j].max);
|
||||
bbProdAll.min = new Vec2(bbProd[j].min);
|
||||
} else {
|
||||
bbProdAll.max = Vec2.max(bbProdAll.max, bbProd[j].max);
|
||||
bbProdAll.min = Vec2.min(bbProdAll.min, bbProd[j].min);
|
||||
}
|
||||
}
|
||||
bb1 = bbReactAll;
|
||||
bb2 = bbProdAll;
|
||||
if (!bb1 && !bb2) {
|
||||
ret.rxnArrows.add(new Struct.RxnArrow({ pp: new Vec2(0, 0) }));
|
||||
} else {
|
||||
var v1 = bb1 ? new Vec2(bb1.max.x, (bb1.max.y + bb1.min.y) / 2) : null;
|
||||
var v2 = bb2 ? new Vec2(bb2.min.x, (bb2.max.y + bb2.min.y) / 2) : null;
|
||||
var defaultOffset = 3;
|
||||
if (!v1)
|
||||
v1 = new Vec2(v2.x - defaultOffset, v2.y);
|
||||
if (!v2)
|
||||
v2 = new Vec2(v1.x + defaultOffset, v1.y);
|
||||
ret.rxnArrows.add(new Struct.RxnArrow({ pp: Vec2.lc2(v1, 0.5, v2, 0.5) }));
|
||||
}
|
||||
ret.isReaction = true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
fmtInfo: fmtInfo,
|
||||
paddedNum: paddedNum,
|
||||
parseDecimalInt: parseDecimalInt,
|
||||
partitionLine: partitionLine,
|
||||
partitionLineFixed: partitionLineFixed,
|
||||
rxnMerge: rxnMerge
|
||||
};
|
||||
426
static/js/ketcher2/script/chem/molfile/v2000.js
Normal file
426
static/js/ketcher2/script/chem/molfile/v2000.js
Normal file
@ -0,0 +1,426 @@
|
||||
/****************************************************************************
|
||||
* 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 Map = require('../../util/map');
|
||||
|
||||
var element = require('./../element');
|
||||
var Struct = require('./../struct/index');
|
||||
|
||||
var sGroup = require('./parseSGroup');
|
||||
var utils = require('./utils');
|
||||
|
||||
var loadRGroupFragments = true; // TODO: set to load the fragments
|
||||
|
||||
function parseAtomLine(atomLine) {
|
||||
/* reader */
|
||||
var atomSplit = utils.partitionLine(atomLine, utils.fmtInfo.atomLinePartition);
|
||||
var params =
|
||||
{
|
||||
// generic
|
||||
pp: new Vec2(parseFloat(atomSplit[0]), -parseFloat(atomSplit[1]), parseFloat(atomSplit[2])),
|
||||
label: atomSplit[4].trim(),
|
||||
explicitValence: utils.fmtInfo.valenceMap[utils.parseDecimalInt(atomSplit[10])],
|
||||
|
||||
// obsolete
|
||||
massDifference: utils.parseDecimalInt(atomSplit[5]),
|
||||
charge: utils.fmtInfo.chargeMap[utils.parseDecimalInt(atomSplit[6])],
|
||||
|
||||
// query
|
||||
hCount: utils.parseDecimalInt(utils.parseDecimalInt(atomSplit[8])),
|
||||
stereoCare: utils.parseDecimalInt(atomSplit[9]) != 0,
|
||||
|
||||
// reaction
|
||||
aam: utils.parseDecimalInt(atomSplit[14]),
|
||||
invRet: utils.parseDecimalInt(atomSplit[15]),
|
||||
|
||||
// reaction query
|
||||
exactChangeFlag: utils.parseDecimalInt(atomSplit[16]) != 0
|
||||
};
|
||||
return new Struct.Atom(params);
|
||||
}
|
||||
|
||||
function parseBondLine(bondLine) {
|
||||
/* reader */
|
||||
var bondSplit = utils.partitionLine(bondLine, utils.fmtInfo.bondLinePartition);
|
||||
var params =
|
||||
{
|
||||
begin: utils.parseDecimalInt(bondSplit[0]) - 1,
|
||||
end: utils.parseDecimalInt(bondSplit[1]) - 1,
|
||||
type: utils.fmtInfo.bondTypeMap[utils.parseDecimalInt(bondSplit[2])],
|
||||
stereo: utils.fmtInfo.bondStereoMap[utils.parseDecimalInt(bondSplit[3])],
|
||||
xxx: bondSplit[4],
|
||||
topology: utils.fmtInfo.bondTopologyMap[utils.parseDecimalInt(bondSplit[5])],
|
||||
reactingCenterStatus: utils.parseDecimalInt(bondSplit[6])
|
||||
};
|
||||
|
||||
return new Struct.Bond(params);
|
||||
}
|
||||
|
||||
function parseAtomListLine(/* string */atomListLine) {
|
||||
/* reader */
|
||||
var split = utils.partitionLine(atomListLine, utils.fmtInfo.atomListHeaderPartition);
|
||||
|
||||
var number = utils.parseDecimalInt(split[0]) - 1;
|
||||
var notList = (split[2].trim() == 'T');
|
||||
var count = utils.parseDecimalInt(split[4].trim());
|
||||
|
||||
var ids = atomListLine.slice(utils.fmtInfo.atomListHeaderLength);
|
||||
var list = [];
|
||||
var itemLength = utils.fmtInfo.atomListHeaderItemLength;
|
||||
for (var i = 0; i < count; ++i)
|
||||
list[i] = utils.parseDecimalInt(ids.slice(i * itemLength, ((i + 1) * itemLength) - 1));
|
||||
|
||||
return {
|
||||
aid: number,
|
||||
atomList: new Struct.AtomList({
|
||||
notList: notList,
|
||||
ids: list
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
function parsePropertyLines(ctab, ctabLines, shift, end, sGroups, rLogic) { // eslint-disable-line max-statements, max-params
|
||||
/* reader */
|
||||
var props = new Map();
|
||||
while (shift < end) {
|
||||
var line = ctabLines[shift];
|
||||
if (line.charAt(0) == 'A') {
|
||||
var propValue = ctabLines[++shift];
|
||||
var isPseudo = /'.+'/.test(propValue);
|
||||
if (isPseudo && !props.get('pseudo'))
|
||||
props.set('pseudo', new Map());
|
||||
if (!isPseudo && !props.get('alias'))
|
||||
props.set('alias', new Map());
|
||||
if (isPseudo) propValue = propValue.replace(/'/g, '');
|
||||
props.get(isPseudo ? 'pseudo' : 'alias').set(utils.parseDecimalInt(line.slice(3, 6)) - 1, propValue);
|
||||
} else if (line.charAt(0) == 'M') {
|
||||
var type = line.slice(3, 6);
|
||||
var propertyData = line.slice(6);
|
||||
if (type == 'END') {
|
||||
break;
|
||||
} else if (type == 'CHG') {
|
||||
if (!props.get('charge'))
|
||||
props.set('charge', new Map());
|
||||
props.get('charge').update(sGroup.readKeyValuePairs(propertyData));
|
||||
} else if (type == 'RAD') {
|
||||
if (!props.get('radical'))
|
||||
props.set('radical', new Map());
|
||||
props.get('radical').update(sGroup.readKeyValuePairs(propertyData));
|
||||
} else if (type == 'ISO') {
|
||||
if (!props.get('isotope'))
|
||||
props.set('isotope', new Map());
|
||||
props.get('isotope').update(sGroup.readKeyValuePairs(propertyData));
|
||||
} else if (type == 'RBC') {
|
||||
if (!props.get('ringBondCount'))
|
||||
props.set('ringBondCount', new Map());
|
||||
props.get('ringBondCount').update(sGroup.readKeyValuePairs(propertyData));
|
||||
} else if (type == 'SUB') {
|
||||
if (!props.get('substitutionCount'))
|
||||
props.set('substitutionCount', new Map());
|
||||
props.get('substitutionCount').update(sGroup.readKeyValuePairs(propertyData));
|
||||
} else if (type == 'UNS') {
|
||||
if (!props.get('unsaturatedAtom'))
|
||||
props.set('unsaturatedAtom', new Map());
|
||||
props.get('unsaturatedAtom').update(sGroup.readKeyValuePairs(propertyData));
|
||||
// else if (type == "LIN") // link atom
|
||||
} else if (type == 'RGP') { // rgroup atom
|
||||
if (!props.get('rglabel'))
|
||||
props.set('rglabel', new Map());
|
||||
var rglabels = props.get('rglabel');
|
||||
var a2rs = sGroup.readKeyMultiValuePairs(propertyData);
|
||||
for (var a2ri = 0; a2ri < a2rs.length; a2ri++) {
|
||||
var a2r = a2rs[a2ri];
|
||||
rglabels.set(a2r[0], (rglabels.get(a2r[0]) || 0) | (1 << (a2r[1] - 1)));
|
||||
}
|
||||
} else if (type == 'LOG') { // rgroup atom
|
||||
propertyData = propertyData.slice(4);
|
||||
var rgid = utils.parseDecimalInt(propertyData.slice(0, 3).trim());
|
||||
var iii = utils.parseDecimalInt(propertyData.slice(4, 7).trim());
|
||||
var hhh = utils.parseDecimalInt(propertyData.slice(8, 11).trim());
|
||||
var ooo = propertyData.slice(12).trim();
|
||||
var logic = {};
|
||||
if (iii > 0)
|
||||
logic.ifthen = iii;
|
||||
logic.resth = hhh == 1;
|
||||
logic.range = ooo;
|
||||
rLogic[rgid] = logic;
|
||||
} else if (type == 'APO') {
|
||||
if (!props.get('attpnt'))
|
||||
props.set('attpnt', new Map());
|
||||
props.get('attpnt').update(sGroup.readKeyValuePairs(propertyData));
|
||||
} else if (type == 'ALS') { // atom list
|
||||
if (!props.get('atomList'))
|
||||
props.set('atomList', new Map());
|
||||
var list = parsePropertyLineAtomList(
|
||||
utils.partitionLine(propertyData, [1, 3, 3, 1, 1, 1]),
|
||||
utils.partitionLineFixed(propertyData.slice(10), 4, false));
|
||||
props.get('atomList').update(
|
||||
list);
|
||||
if (!props.get('label'))
|
||||
props.set('label', new Map());
|
||||
for (var aid in list) props.get('label').set(aid, 'L#');
|
||||
} else if (type == 'STY') { // introduce s-group
|
||||
sGroup.initSGroup(sGroups, propertyData);
|
||||
} else if (type == 'SST') {
|
||||
sGroup.applySGroupProp(sGroups, 'subtype', propertyData);
|
||||
} else if (type == 'SLB') {
|
||||
sGroup.applySGroupProp(sGroups, 'label', propertyData, true);
|
||||
} else if (type == 'SPL') {
|
||||
sGroup.applySGroupProp(sGroups, 'parent', propertyData, true, true);
|
||||
} else if (type == 'SCN') {
|
||||
sGroup.applySGroupProp(sGroups, 'connectivity', propertyData);
|
||||
} else if (type == 'SAL') {
|
||||
sGroup.applySGroupArrayProp(sGroups, 'atoms', propertyData, -1);
|
||||
} else if (type == 'SBL') {
|
||||
sGroup.applySGroupArrayProp(sGroups, 'bonds', propertyData, -1);
|
||||
} else if (type == 'SPA') {
|
||||
sGroup.applySGroupArrayProp(sGroups, 'patoms', propertyData, -1);
|
||||
} else if (type == 'SMT') {
|
||||
var sid = utils.parseDecimalInt(propertyData.slice(0, 4)) - 1;
|
||||
sGroups[sid].data.subscript = propertyData.slice(4).trim();
|
||||
} else if (type == 'SDT') {
|
||||
sGroup.applyDataSGroupDesc(sGroups, propertyData);
|
||||
} else if (type == 'SDD') {
|
||||
sGroup.applyDataSGroupInfoLine(sGroups, propertyData);
|
||||
} else if (type == 'SCD') {
|
||||
sGroup.applyDataSGroupDataLine(sGroups, propertyData, false);
|
||||
} else if (type == 'SED') {
|
||||
sGroup.applyDataSGroupDataLine(sGroups, propertyData, true);
|
||||
}
|
||||
}
|
||||
++shift;
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
function applyAtomProp(atoms /* Pool */, values /* Map */, propId /* string */) {
|
||||
/* reader */
|
||||
values.each(function (aid, propVal) {
|
||||
atoms.get(aid)[propId] = propVal;
|
||||
});
|
||||
}
|
||||
|
||||
function parseCTabV2000(ctabLines, countsSplit) { // eslint-disable-line max-statements
|
||||
/* reader */
|
||||
var ctab = new Struct();
|
||||
var i;
|
||||
var atomCount = utils.parseDecimalInt(countsSplit[0]);
|
||||
var bondCount = utils.parseDecimalInt(countsSplit[1]);
|
||||
var atomListCount = utils.parseDecimalInt(countsSplit[2]);
|
||||
ctab.isChiral = utils.parseDecimalInt(countsSplit[4]) != 0;
|
||||
var stextLinesCount = utils.parseDecimalInt(countsSplit[5]);
|
||||
var propertyLinesCount = utils.parseDecimalInt(countsSplit[10]);
|
||||
|
||||
var shift = 0;
|
||||
var atomLines = ctabLines.slice(shift, shift + atomCount);
|
||||
shift += atomCount;
|
||||
var bondLines = ctabLines.slice(shift, shift + bondCount);
|
||||
shift += bondCount;
|
||||
var atomListLines = ctabLines.slice(shift, shift + atomListCount);
|
||||
shift += atomListCount + stextLinesCount;
|
||||
|
||||
var atoms = atomLines.map(parseAtomLine);
|
||||
for (i = 0; i < atoms.length; ++i)
|
||||
ctab.atoms.add(atoms[i]);
|
||||
var bonds = bondLines.map(parseBondLine);
|
||||
for (i = 0; i < bonds.length; ++i)
|
||||
ctab.bonds.add(bonds[i]);
|
||||
|
||||
var atomLists = atomListLines.map(parseAtomListLine);
|
||||
atomLists.forEach(function (pair) {
|
||||
ctab.atoms.get(pair.aid).atomList = pair.atomList;
|
||||
ctab.atoms.get(pair.aid).label = 'L#';
|
||||
});
|
||||
|
||||
var sGroups = {};
|
||||
var rLogic = {};
|
||||
var props = parsePropertyLines(ctab, ctabLines, shift,
|
||||
Math.min(ctabLines.length, shift + propertyLinesCount), sGroups, rLogic);
|
||||
props.each(function (propId, values) {
|
||||
applyAtomProp(ctab.atoms, values, propId);
|
||||
});
|
||||
|
||||
var atomMap = {};
|
||||
var sid;
|
||||
for (sid in sGroups) {
|
||||
var sg = sGroups[sid];
|
||||
if (sg.type === 'DAT' && sg.atoms.length === 0) {
|
||||
var parent = sGroups[sid].parent;
|
||||
if (parent >= 0) {
|
||||
var psg = sGroups[parent - 1];
|
||||
if (psg.type === 'GEN')
|
||||
sg.atoms = [].slice.call(psg.atoms);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (sid in sGroups)
|
||||
sGroup.loadSGroup(ctab, sGroups[sid], atomMap);
|
||||
var emptyGroups = [];
|
||||
for (sid in sGroups) { // TODO: why do we need that?
|
||||
Struct.SGroup.filter(ctab, sGroups[sid], atomMap);
|
||||
if (sGroups[sid].atoms.length == 0 && !sGroups[sid].allAtoms)
|
||||
emptyGroups.push(sid);
|
||||
}
|
||||
for (i = 0; i < emptyGroups.length; ++i) {
|
||||
ctab.sGroupForest.remove(emptyGroups[i]);
|
||||
ctab.sgroups.remove(emptyGroups[i]);
|
||||
}
|
||||
for (var rgid in rLogic)
|
||||
ctab.rgroups.set(rgid, new Struct.RGroup(rLogic[rgid]));
|
||||
return ctab;
|
||||
}
|
||||
|
||||
function parseRg2000(/* string[] */ ctabLines) /* Struct */ { // eslint-disable-line max-statements
|
||||
ctabLines = ctabLines.slice(7);
|
||||
if (ctabLines[0].trim() != '$CTAB')
|
||||
throw new Error('RGFile format invalid');
|
||||
var i = 1;
|
||||
while (ctabLines[i].charAt(0) != '$') i++;
|
||||
if (ctabLines[i].trim() != '$END CTAB')
|
||||
throw new Error('RGFile format invalid');
|
||||
var coreLines = ctabLines.slice(1, i);
|
||||
ctabLines = ctabLines.slice(i + 1);
|
||||
var fragmentLines = {};
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
if (ctabLines.length == 0)
|
||||
throw new Error('Unexpected end of file');
|
||||
var line = ctabLines[0].trim();
|
||||
if (line == '$END MOL') {
|
||||
ctabLines = ctabLines.slice(1);
|
||||
break;
|
||||
}
|
||||
if (line != '$RGP')
|
||||
throw new Error('RGFile format invalid');
|
||||
var rgid = ctabLines[1].trim() - 0;
|
||||
fragmentLines[rgid] = [];
|
||||
ctabLines = ctabLines.slice(2);
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
if (ctabLines.length == 0)
|
||||
throw new Error('Unexpected end of file');
|
||||
line = ctabLines[0].trim();
|
||||
if (line == '$END RGP') {
|
||||
ctabLines = ctabLines.slice(1);
|
||||
break;
|
||||
}
|
||||
if (line != '$CTAB')
|
||||
throw new Error('RGFile format invalid');
|
||||
i = 1;
|
||||
while (ctabLines[i].charAt(0) != '$') i++;
|
||||
if (ctabLines[i].trim() != '$END CTAB')
|
||||
throw new Error('RGFile format invalid');
|
||||
fragmentLines[rgid].push(ctabLines.slice(1, i));
|
||||
ctabLines = ctabLines.slice(i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
var core = parseCTab(coreLines);
|
||||
var frag = {};
|
||||
if (loadRGroupFragments) {
|
||||
for (var id in fragmentLines) {
|
||||
frag[id] = [];
|
||||
for (var j = 0; j < fragmentLines[id].length; ++j)
|
||||
frag[id].push(parseCTab(fragmentLines[id][j]));
|
||||
}
|
||||
}
|
||||
return rgMerge(core, frag);
|
||||
}
|
||||
|
||||
function parseRxn2000(/* string[] */ ctabLines) /* Struct */ { // eslint-disable-line max-statements
|
||||
/* reader */
|
||||
ctabLines = ctabLines.slice(4);
|
||||
var countsSplit = utils.partitionLine(ctabLines[0], utils.fmtInfo.rxnItemsPartition);
|
||||
var nReactants = countsSplit[0] - 0,
|
||||
nProducts = countsSplit[1] - 0,
|
||||
nAgents = countsSplit[2] - 0;
|
||||
ctabLines = ctabLines.slice(1); // consume counts line
|
||||
|
||||
var mols = [];
|
||||
while (ctabLines.length > 0 && ctabLines[0].substr(0, 4) == '$MOL') {
|
||||
ctabLines = ctabLines.slice(1);
|
||||
var n = 0;
|
||||
while (n < ctabLines.length && ctabLines[n].substr(0, 4) != '$MOL') n++;
|
||||
|
||||
var lines = ctabLines.slice(0, n);
|
||||
var struct;
|
||||
if (lines[0].search('\\$MDL') == 0) {
|
||||
struct = parseRg2000(lines);
|
||||
} else {
|
||||
struct = parseCTab(lines.slice(3));
|
||||
struct.name = lines[0].trim();
|
||||
}
|
||||
mols.push(struct);
|
||||
ctabLines = ctabLines.slice(n);
|
||||
}
|
||||
return utils.rxnMerge(mols, nReactants, nProducts, nAgents);
|
||||
}
|
||||
|
||||
function parseCTab(/* string */ ctabLines) /* Struct */ {
|
||||
/* reader */
|
||||
var countsSplit = utils.partitionLine(ctabLines[0], utils.fmtInfo.countsLinePartition);
|
||||
ctabLines = ctabLines.slice(1);
|
||||
return parseCTabV2000(ctabLines, countsSplit);
|
||||
}
|
||||
|
||||
function rgMerge(scaffold, rgroups) /* Struct */ {
|
||||
/* reader */
|
||||
var ret = new Struct();
|
||||
|
||||
scaffold.mergeInto(ret, null, null, false, true);
|
||||
for (var rgid in rgroups) {
|
||||
for (var j = 0; j < rgroups[rgid].length; ++j) {
|
||||
var ctab = rgroups[rgid][j];
|
||||
ctab.rgroups.set(rgid, new Struct.RGroup());
|
||||
var frag = {};
|
||||
var frid = ctab.frags.add(frag);
|
||||
ctab.rgroups.get(rgid).frags.add(frid);
|
||||
ctab.atoms.each(function (aid, atom) {
|
||||
atom.fragment = frid;
|
||||
});
|
||||
ctab.mergeInto(ret);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
function labelsListToIds(labels) {
|
||||
/* reader */
|
||||
var ids = [];
|
||||
for (var i = 0; i < labels.length; ++i)
|
||||
ids.push(element.map[labels[i].trim()]);
|
||||
return ids;
|
||||
}
|
||||
|
||||
function parsePropertyLineAtomList(hdr, lst) {
|
||||
/* reader */
|
||||
var aid = utils.parseDecimalInt(hdr[1]) - 1;
|
||||
var count = utils.parseDecimalInt(hdr[2]);
|
||||
var notList = hdr[4].trim() == 'T';
|
||||
var ids = labelsListToIds(lst.slice(0, count));
|
||||
var ret = {};
|
||||
ret[aid] = new Struct.AtomList({
|
||||
notList: notList,
|
||||
ids: ids
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
parseCTabV2000: parseCTabV2000,
|
||||
parseRg2000: parseRg2000,
|
||||
parseRxn2000: parseRxn2000
|
||||
};
|
||||
482
static/js/ketcher2/script/chem/molfile/v3000.js
Normal file
482
static/js/ketcher2/script/chem/molfile/v3000.js
Normal file
@ -0,0 +1,482 @@
|
||||
/****************************************************************************
|
||||
* 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 Struct = require('./../struct/index');
|
||||
|
||||
var sGroup = require('./parseSGroup');
|
||||
var utils = require('./utils');
|
||||
|
||||
function parseAtomLineV3000(line) { // eslint-disable-line max-statements
|
||||
/* reader */
|
||||
var split, subsplit, key, value, i;
|
||||
split = spaceparsplit(line);
|
||||
var params = {
|
||||
pp: new Vec2(parseFloat(split[2]), -parseFloat(split[3]), parseFloat(split[4])),
|
||||
aam: split[5].trim()
|
||||
};
|
||||
var label = split[1].trim();
|
||||
if (label.charAt(0) == '"' && label.charAt(label.length - 1) == '"')
|
||||
label = label.substr(1, label.length - 2); // strip qutation marks
|
||||
if (label.charAt(label.length - 1) == ']') { // assume atom list
|
||||
label = label.substr(0, label.length - 1); // remove ']'
|
||||
var atomListParams = {};
|
||||
atomListParams.notList = false;
|
||||
if (label.substr(0, 5) == 'NOT [') {
|
||||
atomListParams.notList = true;
|
||||
label = label.substr(5); // remove 'NOT ['
|
||||
} else if (label.charAt(0) != '[') {
|
||||
throw new Error('Error: atom list expected, found \'' + label + '\'');
|
||||
} else {
|
||||
label = label.substr(1); // remove '['
|
||||
}
|
||||
atomListParams.ids = labelsListToIds(label.split(','));
|
||||
params['atomList'] = new Struct.AtomList(atomListParams);
|
||||
params['label'] = 'L#';
|
||||
} else {
|
||||
params['label'] = label;
|
||||
}
|
||||
split.splice(0, 6);
|
||||
for (i = 0; i < split.length; ++i) {
|
||||
subsplit = splitonce(split[i], '=');
|
||||
key = subsplit[0];
|
||||
value = subsplit[1];
|
||||
if (key in utils.fmtInfo.v30atomPropMap) {
|
||||
var ival = utils.parseDecimalInt(value);
|
||||
if (key == 'VAL') {
|
||||
if (ival == 0)
|
||||
continue; // eslint-disable-line no-continue
|
||||
if (ival == -1)
|
||||
ival = 0;
|
||||
}
|
||||
params[utils.fmtInfo.v30atomPropMap[key]] = ival;
|
||||
} else if (key == 'RGROUPS') {
|
||||
value = value.trim().substr(1, value.length - 2);
|
||||
var rgrsplit = value.split(' ').slice(1);
|
||||
params.rglabel = 0;
|
||||
for (var j = 0; j < rgrsplit.length; ++j)
|
||||
params.rglabel |= 1 << (rgrsplit[j] - 1);
|
||||
} else if (key == 'ATTCHPT') {
|
||||
params.attpnt = value.trim() - 0;
|
||||
}
|
||||
}
|
||||
|
||||
return new Struct.Atom(params);
|
||||
}
|
||||
|
||||
function parseBondLineV3000(line) {
|
||||
/* reader */
|
||||
var split, subsplit, key, value, i;
|
||||
split = spaceparsplit(line);
|
||||
var params = {
|
||||
begin: utils.parseDecimalInt(split[2]) - 1,
|
||||
end: utils.parseDecimalInt(split[3]) - 1,
|
||||
type: utils.fmtInfo.bondTypeMap[utils.parseDecimalInt(split[1])]
|
||||
};
|
||||
split.splice(0, 4);
|
||||
for (i = 0; i < split.length; ++i) {
|
||||
subsplit = splitonce(split[i], '=');
|
||||
key = subsplit[0];
|
||||
value = subsplit[1];
|
||||
if (key == 'CFG') {
|
||||
params.stereo = utils.fmtInfo.v30bondStereoMap[utils.parseDecimalInt(value)];
|
||||
if (params.type == Struct.Bond.PATTERN.TYPE.DOUBLE && params.stereo == Struct.Bond.PATTERN.STEREO.EITHER)
|
||||
params.stereo = Struct.Bond.PATTERN.STEREO.CIS_TRANS;
|
||||
} else if (key == 'TOPO') {
|
||||
params.topology = utils.fmtInfo.bondTopologyMap[utils.parseDecimalInt(value)];
|
||||
} else if (key == 'RXCTR') {
|
||||
params.reactingCenterStatus = utils.parseDecimalInt(value);
|
||||
} else if (key == 'STBOX') {
|
||||
params.stereoCare = utils.parseDecimalInt(value);
|
||||
}
|
||||
}
|
||||
return new Struct.Bond(params);
|
||||
}
|
||||
|
||||
function v3000parseCollection(ctab, ctabLines, shift) {
|
||||
/* reader */
|
||||
shift++;
|
||||
while (ctabLines[shift].trim() != 'M V30 END COLLECTION')
|
||||
shift++;
|
||||
shift++;
|
||||
return shift;
|
||||
}
|
||||
|
||||
function v3000parseSGroup(ctab, ctabLines, sgroups, atomMap, shift) { // eslint-disable-line max-params, max-statements
|
||||
/* reader */
|
||||
var line = '';
|
||||
shift++;
|
||||
while (shift < ctabLines.length) {
|
||||
line = stripV30(ctabLines[shift++]).trim();
|
||||
if (line.trim() == 'END SGROUP')
|
||||
return shift;
|
||||
while (line.charAt(line.length - 1) == '-')
|
||||
line = (line.substr(0, line.length - 1) + stripV30(ctabLines[shift++])).trim();
|
||||
var split = splitSGroupDef(line);
|
||||
var type = split[1];
|
||||
var sg = new Struct.SGroup(type);
|
||||
sg.number = split[0] - 0;
|
||||
sg.type = type;
|
||||
sg.label = split[2] - 0;
|
||||
sgroups[sg.number] = sg;
|
||||
var props = {};
|
||||
for (var i = 3; i < split.length; ++i) {
|
||||
var subsplit = splitonce(split[i], '=');
|
||||
if (subsplit.length != 2)
|
||||
throw new Error('A record of form AAA=BBB or AAA=(...) expected, got \'' + split[i] + '\'');
|
||||
var name = subsplit[0];
|
||||
if (!(name in props))
|
||||
props[name] = [];
|
||||
props[name].push(subsplit[1]);
|
||||
}
|
||||
sg.atoms = parseBracedNumberList(props['ATOMS'][0], -1);
|
||||
if (props['PATOMS'])
|
||||
sg.patoms = parseBracedNumberList(props['PATOMS'][0], -1);
|
||||
sg.bonds = props['BONDS'] ? parseBracedNumberList(props['BONDS'][0], -1) : [];
|
||||
var brkxyzStrs = props['BRKXYZ'];
|
||||
sg.brkxyz = [];
|
||||
if (brkxyzStrs) {
|
||||
for (var j = 0; j < brkxyzStrs.length; ++j)
|
||||
sg.brkxyz.push(parseBracedNumberList(brkxyzStrs[j]));
|
||||
}
|
||||
if (props['MULT'])
|
||||
sg.data.subscript = props['MULT'][0] - 0;
|
||||
if (props['LABEL'])
|
||||
sg.data.subscript = props['LABEL'][0].trim();
|
||||
if (props['CONNECT'])
|
||||
sg.data.connectivity = props['CONNECT'][0].toLowerCase();
|
||||
if (props['FIELDDISP'])
|
||||
sGroup.applyDataSGroupInfo(sg, stripQuotes(props['FIELDDISP'][0]));
|
||||
if (props['FIELDDATA'])
|
||||
sGroup.applyDataSGroupData(sg, props['FIELDDATA'][0], true);
|
||||
if (props['FIELDNAME'])
|
||||
sGroup.applyDataSGroupName(sg, props['FIELDNAME'][0]);
|
||||
if (props['QUERYTYPE'])
|
||||
sGroup.applyDataSGroupQuery(sg, props['QUERYTYPE'][0]);
|
||||
if (props['QUERYOP'])
|
||||
sGroup.applyDataSGroupQueryOp(sg, props['QUERYOP'][0]);
|
||||
sGroup.loadSGroup(ctab, sg, atomMap);
|
||||
}
|
||||
throw new Error('S-group declaration incomplete.');
|
||||
}
|
||||
|
||||
function parseCTabV3000(ctabLines, norgroups) { // eslint-disable-line max-statements
|
||||
/* reader */
|
||||
var ctab = new Struct();
|
||||
|
||||
var shift = 0;
|
||||
if (ctabLines[shift++].trim() != 'M V30 BEGIN CTAB')
|
||||
throw Error('CTAB V3000 invalid');
|
||||
if (ctabLines[shift].slice(0, 13) != 'M V30 COUNTS')
|
||||
throw Error('CTAB V3000 invalid');
|
||||
var vals = ctabLines[shift].slice(14).split(' ');
|
||||
ctab.isChiral = (utils.parseDecimalInt(vals[4]) == 1);
|
||||
shift++;
|
||||
|
||||
if (ctabLines[shift].trim() == 'M V30 BEGIN ATOM') {
|
||||
shift++;
|
||||
var line;
|
||||
while (shift < ctabLines.length) {
|
||||
line = stripV30(ctabLines[shift++]).trim();
|
||||
if (line == 'END ATOM')
|
||||
break;
|
||||
while (line.charAt(line.length - 1) == '-')
|
||||
line = (line.substring(0, line.length - 1) + stripV30(ctabLines[shift++])).trim();
|
||||
ctab.atoms.add(parseAtomLineV3000(line));
|
||||
}
|
||||
|
||||
if (ctabLines[shift].trim() == 'M V30 BEGIN BOND') {
|
||||
shift++;
|
||||
while (shift < ctabLines.length) {
|
||||
line = stripV30(ctabLines[shift++]).trim();
|
||||
if (line == 'END BOND')
|
||||
break;
|
||||
while (line.charAt(line.length - 1) == '-')
|
||||
line = (line.substring(0, line.length - 1) + stripV30(ctabLines[shift++])).trim();
|
||||
ctab.bonds.add(parseBondLineV3000(line));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: let sections follow in arbitrary order
|
||||
var sgroups = {};
|
||||
var atomMap = {};
|
||||
|
||||
while (ctabLines[shift].trim() != 'M V30 END CTAB') {
|
||||
if (ctabLines[shift].trim() == 'M V30 BEGIN COLLECTION')
|
||||
// TODO: read collection information
|
||||
shift = v3000parseCollection(ctab, ctabLines, shift);
|
||||
else if (ctabLines[shift].trim() == 'M V30 BEGIN SGROUP')
|
||||
shift = v3000parseSGroup(ctab, ctabLines, sgroups, atomMap, shift);
|
||||
else
|
||||
throw Error('CTAB V3000 invalid');
|
||||
}
|
||||
}
|
||||
if (ctabLines[shift++].trim() != 'M V30 END CTAB')
|
||||
throw Error('CTAB V3000 invalid');
|
||||
|
||||
if (!norgroups)
|
||||
readRGroups3000(ctab, ctabLines.slice(shift));
|
||||
|
||||
return ctab;
|
||||
}
|
||||
|
||||
function readRGroups3000(ctab, /* string */ ctabLines) /* Struct */ { // eslint-disable-line max-statements
|
||||
/* reader */
|
||||
var rfrags = {};
|
||||
var rLogic = {};
|
||||
var shift = 0;
|
||||
while (shift < ctabLines.length && ctabLines[shift].search('M V30 BEGIN RGROUP') == 0) {
|
||||
var id = ctabLines[shift++].split(' ').pop();
|
||||
rfrags[id] = [];
|
||||
rLogic[id] = {};
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
var line = ctabLines[shift].trim();
|
||||
if (line.search('M V30 RLOGIC') == 0) {
|
||||
line = line.slice(13);
|
||||
var rlsplit = line.trim().split(/\s+/g);
|
||||
var iii = utils.parseDecimalInt(rlsplit[0]);
|
||||
var hhh = utils.parseDecimalInt(rlsplit[1]);
|
||||
var ooo = rlsplit.slice(2).join(' ');
|
||||
var logic = {};
|
||||
if (iii > 0)
|
||||
logic.ifthen = iii;
|
||||
logic.resth = hhh == 1;
|
||||
logic.range = ooo;
|
||||
rLogic[id] = logic;
|
||||
shift++;
|
||||
continue; // eslint-disable-line no-continue
|
||||
}
|
||||
if (line != 'M V30 BEGIN CTAB')
|
||||
throw Error('CTAB V3000 invalid');
|
||||
for (var i = 0; i < ctabLines.length; ++i) {
|
||||
if (ctabLines[shift + i].trim() == 'M V30 END CTAB')
|
||||
break;
|
||||
}
|
||||
var lines = ctabLines.slice(shift, shift + i + 1);
|
||||
var rfrag = parseCTabV3000(lines, true);
|
||||
rfrags[id].push(rfrag);
|
||||
shift = shift + i + 1;
|
||||
if (ctabLines[shift].trim() == 'M V30 END RGROUP') {
|
||||
shift++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var rgid in rfrags) {
|
||||
for (var j = 0; j < rfrags[rgid].length; ++j) {
|
||||
var rg = rfrags[rgid][j];
|
||||
rg.rgroups.set(rgid, new Struct.RGroup(rLogic[rgid]));
|
||||
var frid = rg.frags.add({});
|
||||
rg.rgroups.get(rgid).frags.add(frid);
|
||||
rg.atoms.each(function (aid, atom) {
|
||||
atom.fragment = frid;
|
||||
});
|
||||
rg.mergeInto(ctab);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseRxn3000(/* string[] */ ctabLines) /* Struct */ { // eslint-disable-line max-statements
|
||||
/* reader */
|
||||
ctabLines = ctabLines.slice(4);
|
||||
var countsSplit = ctabLines[0].split(/\s+/g).slice(3);
|
||||
var nReactants = countsSplit[0] - 0,
|
||||
nProducts = countsSplit[1] - 0,
|
||||
nAgents = countsSplit.length > 2 ? countsSplit[2] - 0 : 0;
|
||||
|
||||
function findCtabEnd(i) {
|
||||
for (var j = i; j < ctabLines.length; ++j) {
|
||||
if (ctabLines[j].trim() == 'M V30 END CTAB')
|
||||
return j;
|
||||
}
|
||||
console.error('CTab format invalid');
|
||||
}
|
||||
|
||||
function findRGroupEnd(i) {
|
||||
for (var j = i; j < ctabLines.length; ++j) {
|
||||
if (ctabLines[j].trim() == 'M V30 END RGROUP')
|
||||
return j;
|
||||
}
|
||||
console.error('CTab format invalid');
|
||||
}
|
||||
|
||||
var molLinesReactants = [];
|
||||
var molLinesProducts = [];
|
||||
var current = null;
|
||||
var rGroups = [];
|
||||
for (var i = 0; i < ctabLines.length; ++i) {
|
||||
var line = ctabLines[i].trim();
|
||||
var j;
|
||||
|
||||
if (line.startsWith('M V30 COUNTS')) {
|
||||
// do nothing
|
||||
} else if (line == 'M END') {
|
||||
break; // stop reading
|
||||
} else if (line == 'M V30 BEGIN PRODUCT') {
|
||||
console.assert(current == null, 'CTab format invalid');
|
||||
current = molLinesProducts;
|
||||
} else if (line == 'M V30 END PRODUCT') {
|
||||
console.assert(current === molLinesProducts, 'CTab format invalid');
|
||||
current = null;
|
||||
} else if (line == 'M V30 BEGIN REACTANT') {
|
||||
console.assert(current == null, 'CTab format invalid');
|
||||
current = molLinesReactants;
|
||||
} else if (line == 'M V30 END REACTANT') {
|
||||
console.assert(current === molLinesReactants, 'CTab format invalid');
|
||||
current = null;
|
||||
} else if (line.startsWith('M V30 BEGIN RGROUP')) {
|
||||
console.assert(current == null, 'CTab format invalid');
|
||||
j = findRGroupEnd(i);
|
||||
rGroups.push(ctabLines.slice(i, j + 1));
|
||||
i = j;
|
||||
} else if (line == 'M V30 BEGIN CTAB') {
|
||||
j = findCtabEnd(i);
|
||||
current.push(ctabLines.slice(i, j + 1));
|
||||
i = j;
|
||||
} else {
|
||||
throw new Error('line unrecognized: ' + line);
|
||||
}
|
||||
}
|
||||
var mols = [];
|
||||
var molLines = molLinesReactants.concat(molLinesProducts);
|
||||
for (j = 0; j < molLines.length; ++j) {
|
||||
var mol = parseCTabV3000(molLines[j], countsSplit);
|
||||
mols.push(mol);
|
||||
}
|
||||
var ctab = utils.rxnMerge(mols, nReactants, nProducts, nAgents);
|
||||
|
||||
readRGroups3000(ctab, function (array) {
|
||||
var res = [];
|
||||
for (var k = 0; k < array.length; ++k)
|
||||
res = res.concat(array[k]);
|
||||
return res;
|
||||
}(rGroups));
|
||||
|
||||
return ctab;
|
||||
}
|
||||
|
||||
// split a line by spaces outside parentheses
|
||||
function spaceparsplit(line) { // eslint-disable-line max-statements
|
||||
/* reader */
|
||||
var split = [];
|
||||
var pc = 0;
|
||||
var c;
|
||||
var i;
|
||||
var i0 = -1;
|
||||
var quoted = false;
|
||||
|
||||
for (i = 0; i < line.length; ++i) {
|
||||
c = line[i];
|
||||
if (c == '(')
|
||||
pc++;
|
||||
else if (c == ')')
|
||||
pc--;
|
||||
if (c == '"')
|
||||
quoted = !quoted;
|
||||
if (!quoted && line[i] == ' ' && pc == 0) {
|
||||
if (i > i0 + 1)
|
||||
split.push(line.slice(i0 + 1, i));
|
||||
i0 = i;
|
||||
}
|
||||
}
|
||||
if (i > i0 + 1)
|
||||
split.push(line.slice(i0 + 1, i));
|
||||
return split;
|
||||
}
|
||||
|
||||
// utils
|
||||
function stripQuotes(str) {
|
||||
if (str[0] === '"' && str[str.length - 1] === '"')
|
||||
return str.substr(1, str.length - 2);
|
||||
return str;
|
||||
}
|
||||
|
||||
function splitonce(line, delim) {
|
||||
/* reader */
|
||||
var p = line.indexOf(delim);
|
||||
return [line.slice(0, p), line.slice(p + 1)];
|
||||
}
|
||||
|
||||
function splitSGroupDef(line) { // eslint-disable-line max-statements
|
||||
/* reader */
|
||||
var split = [];
|
||||
var braceBalance = 0;
|
||||
var quoted = false;
|
||||
for (var i = 0; i < line.length; ++i) {
|
||||
var c = line.charAt(i);
|
||||
if (c == '"') {
|
||||
quoted = !quoted;
|
||||
} else if (!quoted) {
|
||||
if (c == '(') {
|
||||
braceBalance++;
|
||||
} else if (c == ')') {
|
||||
braceBalance--;
|
||||
} else if (c == ' ' && braceBalance == 0) {
|
||||
split.push(line.slice(0, i));
|
||||
line = line.slice(i + 1).trim();
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (braceBalance != 0)
|
||||
throw new Error('Brace balance broken. S-group properies invalid!');
|
||||
if (line.length > 0)
|
||||
split.push(line.trim());
|
||||
return split;
|
||||
}
|
||||
|
||||
function parseBracedNumberList(line, shift) {
|
||||
/* reader */
|
||||
if (!line)
|
||||
return null;
|
||||
var list = [];
|
||||
line = line.trim();
|
||||
line = line.substr(1, line.length - 2);
|
||||
var split = line.split(' ');
|
||||
shift = shift || 0;
|
||||
|
||||
for (var i = 1; i < split.length; ++i) {
|
||||
var value = parseInt(split[i]);
|
||||
if (!isNaN(value))
|
||||
list.push(value + shift);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
function stripV30(line) {
|
||||
/* reader */
|
||||
if (line.slice(0, 7) != 'M V30 ')
|
||||
throw new Error('Prefix invalid');
|
||||
return line.slice(7);
|
||||
}
|
||||
|
||||
function labelsListToIds(labels) {
|
||||
/* reader */
|
||||
var ids = [];
|
||||
for (var i = 0; i < labels.length; ++i)
|
||||
ids.push(element.map[labels[i].trim()]);
|
||||
return ids;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
parseCTabV3000: parseCTabV3000,
|
||||
readRGroups3000: readRGroups3000,
|
||||
parseRxn3000: parseRxn3000
|
||||
};
|
||||
64
static/js/ketcher2/script/chem/sdf.js
Normal file
64
static/js/ketcher2/script/chem/sdf.js
Normal file
@ -0,0 +1,64 @@
|
||||
/****************************************************************************
|
||||
* 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 molfile = require('./molfile');
|
||||
|
||||
function parse(str, options) {
|
||||
var regexp = /^[^]+?\$\$\$\$$/gm;
|
||||
var m, chunk;
|
||||
var result = [];
|
||||
while ((m = regexp.exec(str)) !== null) {
|
||||
chunk = m[0].replace(/\r/g, ''); // TODO: normalize newline?
|
||||
chunk = chunk.trim();
|
||||
var end = chunk.indexOf('M END');
|
||||
if (end !== -1) {
|
||||
var item = {};
|
||||
var propChunks = chunk.substr(end + 7).trim().split(/^$\n?/m);
|
||||
|
||||
item.struct = molfile.parse(chunk.substring(0, end + 6), options);
|
||||
item.props = propChunks.reduce(function (props, pc) {
|
||||
var m = pc.match(/^> [ \d]*<(\S+)>/);
|
||||
if (m) {
|
||||
var field = m[1];
|
||||
var value = pc.split('\n')[1].trim();
|
||||
props[field] = value;
|
||||
}
|
||||
return props;
|
||||
}, {});
|
||||
|
||||
result.push(item);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function stringify(items, options) {
|
||||
return items.reduce(function (res, item) {
|
||||
res += molfile.stringify(item.struct, options);
|
||||
|
||||
for (var prop in item.props) {
|
||||
res += "> <" + prop + ">\n";
|
||||
res += item.props[prop] + "\n\n";
|
||||
}
|
||||
|
||||
return res + '\$\$\$\$';
|
||||
}, '');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
stringify: stringify,
|
||||
parse: parse
|
||||
};
|
||||
212
static/js/ketcher2/script/chem/smiles/cis_trans.js
Normal file
212
static/js/ketcher2/script/chem/smiles/cis_trans.js
Normal file
@ -0,0 +1,212 @@
|
||||
/****************************************************************************
|
||||
* 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 Vec2 = require('../../util/vec2');
|
||||
|
||||
var Struct = require('../struct');
|
||||
|
||||
function CisTrans(mol, neighborsFunc, context) {
|
||||
this.molecule = mol;
|
||||
this.bonds = new Map();
|
||||
this.getNeighbors = neighborsFunc;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
CisTrans.PARITY = {
|
||||
NONE: 0,
|
||||
CIS: 1,
|
||||
TRANS: 2
|
||||
};
|
||||
|
||||
CisTrans.prototype.each = function (func, context) {
|
||||
this.bonds.each(func, context);
|
||||
};
|
||||
|
||||
CisTrans.prototype.getParity = function (idx) {
|
||||
return this.bonds.get(idx).parity;
|
||||
};
|
||||
|
||||
CisTrans.prototype.getSubstituents = function (idx) {
|
||||
return this.bonds.get(idx).substituents;
|
||||
};
|
||||
|
||||
CisTrans.prototype.sameside = function (beg, end, neiBeg, neiEnd) {
|
||||
var diff = Vec2.diff(beg, end);
|
||||
var norm = new Vec2(-diff.y, diff.x);
|
||||
|
||||
if (!norm.normalize())
|
||||
return 0;
|
||||
|
||||
var normBeg = Vec2.diff(neiBeg, beg);
|
||||
var normEnd = Vec2.diff(neiEnd, end);
|
||||
|
||||
if (!normBeg.normalize())
|
||||
return 0;
|
||||
if (!normEnd.normalize())
|
||||
return 0;
|
||||
|
||||
var prodBeg = Vec2.dot(normBeg, norm);
|
||||
var prodEnd = Vec2.dot(normEnd, norm);
|
||||
|
||||
if (Math.abs(prodBeg) < 0.001 || Math.abs(prodEnd) < 0.001)
|
||||
return 0;
|
||||
|
||||
return (prodBeg * prodEnd > 0) ? 1 : -1;
|
||||
};
|
||||
|
||||
CisTrans.prototype.samesides = function (iBeg, iEnd, iNeiBeg, iNeiEnd) {
|
||||
return this.sameside(this.molecule.atoms.get(iBeg).pp, this.molecule.atoms.get(iEnd).pp,
|
||||
this.molecule.atoms.get(iNeiBeg).pp, this.molecule.atoms.get(iNeiEnd).pp);
|
||||
};
|
||||
|
||||
CisTrans.prototype.sortSubstituents = function (substituents) { // eslint-disable-line max-statements
|
||||
var h0 = this.molecule.atoms.get(substituents[0]).pureHydrogen();
|
||||
var h1 = substituents[1] < 0 || this.molecule.atoms.get(substituents[1]).pureHydrogen();
|
||||
var h2 = this.molecule.atoms.get(substituents[2]).pureHydrogen();
|
||||
var h3 = substituents[3] < 0 || this.molecule.atoms.get(substituents[3]).pureHydrogen();
|
||||
|
||||
if (h0 && h1)
|
||||
return false;
|
||||
if (h2 && h3)
|
||||
return false;
|
||||
|
||||
if (h1) {
|
||||
substituents[1] = -1;
|
||||
} else if (h0) {
|
||||
substituents[0] = substituents[1];
|
||||
substituents[1] = -1;
|
||||
} else if (substituents[0] > substituents[1]) {
|
||||
swap(substituents, 0, 1);
|
||||
}
|
||||
|
||||
if (h3) {
|
||||
substituents[3] = -1;
|
||||
} else if (h2) {
|
||||
substituents[2] = substituents[3];
|
||||
substituents[3] = -1;
|
||||
} else if (substituents[2] > substituents[3]) {
|
||||
swap(substituents, 2, 3);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
CisTrans.prototype.isGeomStereoBond = function (bondIdx, substituents) { // eslint-disable-line max-statements
|
||||
// it must be [C,N,Si]=[C,N,Si] bond
|
||||
var bond = this.molecule.bonds.get(bondIdx);
|
||||
|
||||
if (bond.type != Struct.Bond.PATTERN.TYPE.DOUBLE)
|
||||
return false;
|
||||
|
||||
var label1 = this.molecule.atoms.get(bond.begin).label;
|
||||
var label2 = this.molecule.atoms.get(bond.end).label;
|
||||
|
||||
if (label1 != 'C' && label1 != 'N' && label1 != 'Si' && label1 != 'Ge')
|
||||
return false;
|
||||
if (label2 != 'C' && label2 != 'N' && label2 != 'Si' && label2 != 'Ge')
|
||||
return false;
|
||||
|
||||
// the atoms should have 1 or 2 single bonds
|
||||
// (apart from the double bond under consideration)
|
||||
var neiBegin = this.getNeighbors.call(this.context, bond.begin);
|
||||
var neiЕnd = this.getNeighbors.call(this.context, bond.end);
|
||||
|
||||
if (
|
||||
neiBegin.length < 2 || neiBegin.length > 3 ||
|
||||
neiЕnd.length < 2 || neiЕnd.length > 3
|
||||
)
|
||||
return false;
|
||||
|
||||
substituents[0] = -1;
|
||||
substituents[1] = -1;
|
||||
substituents[2] = -1;
|
||||
substituents[3] = -1;
|
||||
|
||||
var i;
|
||||
var nei;
|
||||
|
||||
for (i = 0; i < neiBegin.length; i++) {
|
||||
nei = neiBegin[i];
|
||||
|
||||
if (nei.bid == bondIdx)
|
||||
continue; // eslint-disable-line no-continue
|
||||
|
||||
if (this.molecule.bonds.get(nei.bid).type != Struct.Bond.PATTERN.TYPE.SINGLE)
|
||||
return false;
|
||||
|
||||
if (substituents[0] == -1)
|
||||
substituents[0] = nei.aid;
|
||||
else // (substituents[1] == -1)
|
||||
substituents[1] = nei.aid;
|
||||
}
|
||||
|
||||
for (i = 0; i < neiЕnd.length; i++) {
|
||||
nei = neiЕnd[i];
|
||||
|
||||
if (nei.bid == bondIdx)
|
||||
continue; // eslint-disable-line no-continue
|
||||
|
||||
if (this.molecule.bonds.get(nei.bid).type != Struct.Bond.PATTERN.TYPE.SINGLE)
|
||||
return false;
|
||||
|
||||
if (substituents[2] == -1)
|
||||
substituents[2] = nei.aid;
|
||||
else // (substituents[3] == -1)
|
||||
substituents[3] = nei.aid;
|
||||
}
|
||||
|
||||
if (substituents[1] != -1 && this.samesides(bond.begin, bond.end, substituents[0], substituents[1]) != -1)
|
||||
return false;
|
||||
if (substituents[3] != -1 && this.samesides(bond.begin, bond.end, substituents[2], substituents[3]) != -1)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
CisTrans.prototype.build = function (excludeBonds) {
|
||||
this.molecule.bonds.each(function (bid, bond) {
|
||||
var ct = this.bonds.set(bid,
|
||||
{
|
||||
parity: 0,
|
||||
substituents: []
|
||||
});
|
||||
|
||||
if (Array.isArray(excludeBonds) && excludeBonds[bid])
|
||||
return;
|
||||
|
||||
if (!this.isGeomStereoBond(bid, ct.substituents))
|
||||
return;
|
||||
|
||||
if (!this.sortSubstituents(ct.substituents))
|
||||
return;
|
||||
|
||||
var sign = this.samesides(bond.begin, bond.end, ct.substituents[0], ct.substituents[2]);
|
||||
|
||||
if (sign == 1)
|
||||
ct.parity = CisTrans.PARITY.CIS;
|
||||
else if (sign == -1)
|
||||
ct.parity = CisTrans.PARITY.TRANS;
|
||||
}, this);
|
||||
};
|
||||
|
||||
function swap(arr, i1, i2) {
|
||||
var tmp = arr[i1];
|
||||
arr[i1] = arr[i2];
|
||||
arr[i2] = tmp;
|
||||
}
|
||||
|
||||
module.exports = CisTrans;
|
||||
177
static/js/ketcher2/script/chem/smiles/dfs.js
Normal file
177
static/js/ketcher2/script/chem/smiles/dfs.js
Normal file
@ -0,0 +1,177 @@
|
||||
/****************************************************************************
|
||||
* 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 Set = require('../../util/set');
|
||||
|
||||
function Dfs(mol, atomData, components, nReactants) {
|
||||
this.molecule = mol;
|
||||
this.atom_data = atomData;
|
||||
this.components = components;
|
||||
this.nComponentsInReactants = -1;
|
||||
this.nReactants = nReactants;
|
||||
|
||||
this.vertices = new Array(this.molecule.atoms.count()); // Minimum size
|
||||
this.molecule.atoms.each(function (aid) {
|
||||
this.vertices[aid] = new Dfs.VertexDesc();
|
||||
}, this);
|
||||
|
||||
this.edges = new Array(this.molecule.bonds.count()); // Minimum size
|
||||
this.molecule.bonds.each(function (bid) {
|
||||
this.edges[bid] = new Dfs.EdgeDesc();
|
||||
}, this);
|
||||
|
||||
this.v_seq = [];
|
||||
}
|
||||
|
||||
Dfs.VertexDesc = function () {
|
||||
this.dfs_state = 0; // 0 -- not on stack
|
||||
// 1 -- on stack
|
||||
// 2 -- removed from stack
|
||||
this.parent_vertex = 0; // parent vertex in DFS tree
|
||||
this.parent_edge = 0; // edge to parent vertex
|
||||
this.branches = 0; // how many DFS branches go out from this vertex}
|
||||
};
|
||||
|
||||
Dfs.EdgeDesc = function () {
|
||||
this.opening_cycles = 0; // how many cycles are
|
||||
// (i) starting with this edge
|
||||
// and (ii) ending in this edge's first vertex
|
||||
this.closing_cycle = 0; // 1 if this edge closes a cycle
|
||||
};
|
||||
|
||||
Dfs.SeqElem = function (vIdx, parVertex, parEdge) {
|
||||
this.idx = vIdx; // index of vertex in _graph
|
||||
this.parent_vertex = parVertex; // parent vertex in DFS tree
|
||||
this.parent_edge = parEdge; // edge to parent vertex
|
||||
};
|
||||
|
||||
Dfs.prototype.walk = function () { // eslint-disable-line max-statements
|
||||
var vStack = [];
|
||||
var i, j;
|
||||
var cid = 0;
|
||||
var component = 0;
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
if (vStack.length < 1) {
|
||||
var selected = -1;
|
||||
|
||||
var findFunc = function (aid) { // eslint-disable-line func-style
|
||||
if (this.vertices[aid].dfs_state == 0) {
|
||||
selected = aid;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
while (cid < this.components.length && selected == -1) {
|
||||
selected = Set.find(this.components[cid], findFunc, this);
|
||||
if (selected === null) {
|
||||
selected = -1;
|
||||
cid++;
|
||||
}
|
||||
if (cid == this.nReactants)
|
||||
this.nComponentsInReactants = component;
|
||||
}
|
||||
if (selected < -1)
|
||||
this.molecule.atoms.find(findFunc, this);
|
||||
if (selected == -1)
|
||||
break;
|
||||
this.vertices[selected].parent_vertex = -1;
|
||||
this.vertices[selected].parent_edge = -1;
|
||||
vStack.push(selected);
|
||||
component++;
|
||||
}
|
||||
|
||||
var vIdx = vStack.pop();
|
||||
var parentVertex = this.vertices[vIdx].parent_vertex;
|
||||
|
||||
var seqElem = new Dfs.SeqElem(vIdx, parentVertex, this.vertices[vIdx].parent_edge);
|
||||
this.v_seq.push(seqElem);
|
||||
|
||||
this.vertices[vIdx].dfs_state = 2;
|
||||
|
||||
var atomD = this.atom_data[vIdx];
|
||||
|
||||
for (i = 0; i < atomD.neighbours.length; i++) {
|
||||
var neiIdx = atomD.neighbours[i].aid;
|
||||
var edgeIdx = atomD.neighbours[i].bid;
|
||||
|
||||
if (neiIdx == parentVertex)
|
||||
continue; // eslint-disable-line no-continue
|
||||
|
||||
if (this.vertices[neiIdx].dfs_state == 2) {
|
||||
this.edges[edgeIdx].closing_cycle = 1;
|
||||
|
||||
j = vIdx;
|
||||
|
||||
while (j != -1 && this.vertices[j].parent_vertex != neiIdx)
|
||||
j = this.vertices[j].parent_vertex;
|
||||
|
||||
if (j == -1)
|
||||
throw new Error('cycle unwind error');
|
||||
|
||||
this.edges[this.vertices[j].parent_edge].opening_cycles++;
|
||||
this.vertices[vIdx].branches++;
|
||||
|
||||
seqElem = new Dfs.SeqElem(neiIdx, vIdx, edgeIdx);
|
||||
this.v_seq.push(seqElem);
|
||||
} else {
|
||||
if (this.vertices[neiIdx].dfs_state == 1) {
|
||||
j = vStack.indexOf(neiIdx);
|
||||
|
||||
if (j == -1) // eslint-disable-line max-depth
|
||||
throw new Error('internal: removing vertex from stack');
|
||||
|
||||
vStack.splice(j, 1);
|
||||
|
||||
var parent = this.vertices[neiIdx].parent_vertex;
|
||||
|
||||
if (parent >= 0) // eslint-disable-line max-depth
|
||||
this.vertices[parent].branches--;
|
||||
}
|
||||
|
||||
this.vertices[vIdx].branches++;
|
||||
this.vertices[neiIdx].parent_vertex = vIdx;
|
||||
this.vertices[neiIdx].parent_edge = edgeIdx;
|
||||
this.vertices[neiIdx].dfs_state = 1;
|
||||
vStack.push(neiIdx);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Dfs.prototype.edgeClosingCycle = function (eIdx) {
|
||||
return this.edges[eIdx].closing_cycle != 0;
|
||||
};
|
||||
|
||||
Dfs.prototype.numBranches = function (vIdx) {
|
||||
return this.vertices[vIdx].branches;
|
||||
};
|
||||
|
||||
Dfs.prototype.numOpeningCycles = function (eIdx) {
|
||||
return this.edges[eIdx].opening_cycles;
|
||||
};
|
||||
|
||||
Dfs.prototype.toString = function () {
|
||||
var str = '';
|
||||
this.v_seq.each(function (seqElem) {
|
||||
str += seqElem.idx + ' -> ';
|
||||
});
|
||||
str += '*';
|
||||
return str;
|
||||
};
|
||||
|
||||
module.exports = Dfs;
|
||||
735
static/js/ketcher2/script/chem/smiles/index.js
Normal file
735
static/js/ketcher2/script/chem/smiles/index.js
Normal file
@ -0,0 +1,735 @@
|
||||
/****************************************************************************
|
||||
* 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 Set = require('../../util/set');
|
||||
|
||||
var Struct = require('../struct');
|
||||
var CisTrans = require('./cis_trans');
|
||||
var Dfs = require('./dfs');
|
||||
var Stereocenters = require('./stereocenters');
|
||||
|
||||
function Smiles() {
|
||||
this.smiles = '';
|
||||
this.writtenAtoms = [];
|
||||
this.writtenComponents = 0;
|
||||
|
||||
this.ignore_errors = false;
|
||||
}
|
||||
|
||||
Smiles._Atom = function (hСount) { // eslint-disable-line no-underscore-dangle
|
||||
this.neighbours = []; // Array of integer pairs {a, b}
|
||||
this.aromatic = false; // has aromatic bond
|
||||
this.lowercase = false; // aromatic and has to be written lowercase
|
||||
this.chirality = 0; // 0 means no chirality, 1 means CCW pyramid, 2 means CW pyramid
|
||||
this.branch_cnt = 0; // runs from 0 to (branches - 1)
|
||||
this.paren_written = false;
|
||||
this.h_count = hСount;
|
||||
this.parent = -1;
|
||||
};
|
||||
|
||||
// NB: only loops of length up to 6 are included here
|
||||
Smiles.prototype.isBondInRing = function (bid) {
|
||||
console.assert(this.inLoop, 'Init this.inLoop prior to calling this method');
|
||||
return this.inLoop[bid];
|
||||
};
|
||||
|
||||
Smiles.prototype.saveMolecule = function (molecule, ignoreErrors) { // eslint-disable-line max-statements
|
||||
var i, j, k;
|
||||
|
||||
if (!ignoreErrors) this.ignore_errors = ignoreErrors;
|
||||
|
||||
// [RB]: KETCHER-498 (Incorrect smile-string for multiple Sgroup)
|
||||
// TODO the fix is temporary, still need to implement error handling/reporting
|
||||
// BEGIN
|
||||
molecule = molecule.clone();
|
||||
molecule.initHalfBonds();
|
||||
molecule.initNeighbors();
|
||||
molecule.sortNeighbors();
|
||||
molecule.setImplicitHydrogen();
|
||||
molecule.sgroups.each(function (sgid, sg) {
|
||||
if (sg.type == 'MUL') {
|
||||
try {
|
||||
Struct.SGroup.prepareMulForSaving(sg, molecule);
|
||||
} catch (ex) {
|
||||
throw { message: 'Bad s-group (' + ex.message + ')' };
|
||||
}
|
||||
}
|
||||
// 'SMILES data format doesn\'t support s-groups'
|
||||
}, this);
|
||||
// END
|
||||
|
||||
this.atoms = new Array(molecule.atoms.count());
|
||||
|
||||
molecule.atoms.each(function (aid, atom) {
|
||||
this.atoms[aid] = new Smiles._Atom(atom.implicitH); // eslint-disable-line no-underscore-dangle
|
||||
}, this);
|
||||
|
||||
// From the SMILES specification:
|
||||
// Please note that only atoms on the following list
|
||||
// can be considered aromatic: C, N, O, P, S, As, Se, and * (wildcard).
|
||||
var allowedLowercase = ['B', 'C', 'N', 'O', 'P', 'S', 'Se', 'As'];
|
||||
|
||||
// Detect atoms that have aromatic bonds and count neighbours
|
||||
molecule.bonds.each(function (bid, bond) {
|
||||
if (bond.type == Struct.Bond.PATTERN.TYPE.AROMATIC) {
|
||||
this.atoms[bond.begin].aromatic = true;
|
||||
if (allowedLowercase.indexOf(molecule.atoms.get(bond.begin).label) != -1)
|
||||
this.atoms[bond.begin].lowercase = true;
|
||||
this.atoms[bond.end].aromatic = true;
|
||||
if (allowedLowercase.indexOf(molecule.atoms.get(bond.end).label) != -1)
|
||||
this.atoms[bond.end].lowercase = true;
|
||||
}
|
||||
this.atoms[bond.begin].neighbours.push({ aid: bond.end, bid: bid });
|
||||
this.atoms[bond.end].neighbours.push({ aid: bond.begin, bid: bid });
|
||||
}, this);
|
||||
|
||||
this.inLoop = (function () {
|
||||
molecule.prepareLoopStructure();
|
||||
var bondsInLoops = Set.empty();
|
||||
molecule.loops.each(function (lid, loop) {
|
||||
if (loop.hbs.length <= 6) {
|
||||
Set.mergeIn(bondsInLoops, Set.fromList(loop.hbs.map(function (hbid) {
|
||||
return molecule.halfBonds.get(hbid).bid;
|
||||
})));
|
||||
}
|
||||
});
|
||||
var inLoop = {};
|
||||
Set.each(bondsInLoops, function (bid) {
|
||||
inLoop[bid] = 1;
|
||||
}, this);
|
||||
return inLoop;
|
||||
})();
|
||||
|
||||
this.touchedCistransbonds = 0;
|
||||
this.markCisTrans(molecule);
|
||||
|
||||
var components = molecule.getComponents();
|
||||
var componentsAll = components.reactants.concat(components.products);
|
||||
|
||||
var walk = new Dfs(molecule, this.atoms, componentsAll, components.reactants.length);
|
||||
|
||||
walk.walk();
|
||||
this.atoms.forEach(function (atom) {
|
||||
atom.neighbours = [];
|
||||
}, this);
|
||||
|
||||
// fill up neighbor lists for the stereocenters calculation
|
||||
for (i = 0; i < walk.v_seq.length; i++) {
|
||||
var seqEl = walk.v_seq[i];
|
||||
var vIdx = seqEl.idx;
|
||||
var eIdx = seqEl.parent_edge;
|
||||
var vPrevIdx = seqEl.parent_vertex;
|
||||
|
||||
if (eIdx >= 0) {
|
||||
var atom = this.atoms[vIdx];
|
||||
|
||||
var openingCycles = walk.numOpeningCycles(eIdx);
|
||||
|
||||
for (j = 0; j < openingCycles; j++)
|
||||
this.atoms[vPrevIdx].neighbours.push({ aid: -1, bid: -1 });
|
||||
|
||||
if (walk.edgeClosingCycle(eIdx)) {
|
||||
for (k = 0; k < atom.neighbours.length; k++) {
|
||||
if (atom.neighbours[k].aid == -1) { // eslint-disable-line max-depth
|
||||
atom.neighbours[k].aid = vPrevIdx;
|
||||
atom.neighbours[k].bid = eIdx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (k == atom.neighbours.length)
|
||||
throw new Error('internal: can not put closing bond to its place');
|
||||
} else {
|
||||
atom.neighbours.push({ aid: vPrevIdx, bid: eIdx });
|
||||
atom.parent = vPrevIdx;
|
||||
}
|
||||
this.atoms[vPrevIdx].neighbours.push({ aid: vIdx, bid: eIdx });
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// detect chiral configurations
|
||||
var stereocenters = new Stereocenters(molecule, function (idx) {
|
||||
return this.atoms[idx].neighbours;
|
||||
}, this);
|
||||
stereocenters.buildFromBonds(this.ignore_errors);
|
||||
|
||||
stereocenters.each(function (atomIdx, sc) { // eslint-disable-line max-statements
|
||||
// if (sc.type < MoleculeStereocenters::ATOM_AND)
|
||||
// continue;
|
||||
|
||||
var implicitHIdx = -1;
|
||||
|
||||
if (sc.pyramid[3] == -1)
|
||||
implicitHIdx = 3;
|
||||
/*
|
||||
else for (j = 0; j < 4; j++)
|
||||
if (ignored_vertices[pyramid[j]])
|
||||
{
|
||||
implicit_h_idx = j;
|
||||
break;
|
||||
}
|
||||
*/
|
||||
|
||||
var pyramidMapping = [];
|
||||
var counter = 0;
|
||||
|
||||
var atom = this.atoms[atomIdx];
|
||||
|
||||
if (atom.parent != -1) {
|
||||
for (k = 0; k < 4; k++) {
|
||||
if (sc.pyramid[k] == atom.parent) {
|
||||
pyramidMapping[counter++] = k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (implicitHIdx != -1)
|
||||
pyramidMapping[counter++] = implicitHIdx;
|
||||
|
||||
for (j = 0; j != atom.neighbours.length; j++) {
|
||||
if (atom.neighbours[j].aid == atom.parent)
|
||||
continue; // eslint-disable-line no-continue
|
||||
|
||||
for (k = 0; k < 4; k++) {
|
||||
if (atom.neighbours[j].aid == sc.pyramid[k]) {
|
||||
if (counter >= 4)
|
||||
throw new Error('internal: pyramid overflow');
|
||||
pyramidMapping[counter++] = k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (counter == 4) {
|
||||
// move the 'from' atom to the end
|
||||
counter = pyramidMapping[0];
|
||||
pyramidMapping[0] = pyramidMapping[1];
|
||||
pyramidMapping[1] = pyramidMapping[2];
|
||||
pyramidMapping[2] = pyramidMapping[3];
|
||||
pyramidMapping[3] = counter;
|
||||
} else if (counter != 3) {
|
||||
throw new Error('cannot calculate chirality');
|
||||
}
|
||||
|
||||
if (Stereocenters.isPyramidMappingRigid(pyramidMapping))
|
||||
this.atoms[atomIdx].chirality = 1;
|
||||
else
|
||||
this.atoms[atomIdx].chirality = 2;
|
||||
}, this);
|
||||
} catch (ex) {
|
||||
alert('Warning: ' + ex.message);
|
||||
}
|
||||
|
||||
// write the SMILES itself
|
||||
|
||||
// cycle_numbers[i] == -1 means that the number is available
|
||||
// cycle_numbers[i] == n means that the number is used by vertex n
|
||||
var cycleNumbers = [];
|
||||
|
||||
cycleNumbers.push(0); // never used
|
||||
|
||||
var firstComponent = true;
|
||||
|
||||
for (i = 0; i < walk.v_seq.length; i++) {
|
||||
seqEl = walk.v_seq[i];
|
||||
vIdx = seqEl.idx;
|
||||
eIdx = seqEl.parent_edge;
|
||||
vPrevIdx = seqEl.parent_vertex;
|
||||
var writeAtom = true;
|
||||
|
||||
if (vPrevIdx >= 0) {
|
||||
if (walk.numBranches(vPrevIdx) > 1) {
|
||||
if (this.atoms[vPrevIdx].branch_cnt > 0 && this.atoms[vPrevIdx].paren_written)
|
||||
this.smiles += ')';
|
||||
}
|
||||
|
||||
openingCycles = walk.numOpeningCycles(eIdx);
|
||||
|
||||
for (j = 0; j < openingCycles; j++) {
|
||||
for (k = 1; k < cycleNumbers.length; k++) {
|
||||
if (cycleNumbers[k] == -1) // eslint-disable-line max-depth
|
||||
break;
|
||||
}
|
||||
if (k == cycleNumbers.length)
|
||||
cycleNumbers.push(vPrevIdx);
|
||||
else
|
||||
cycleNumbers[k] = vPrevIdx;
|
||||
|
||||
this.writeCycleNumber(k);
|
||||
}
|
||||
|
||||
if (vPrevIdx >= 0) {
|
||||
var branches = walk.numBranches(vPrevIdx);
|
||||
|
||||
if (branches > 1 && this.atoms[vPrevIdx].branch_cnt < branches - 1) {
|
||||
if (walk.edgeClosingCycle(eIdx)) { // eslint-disable-line max-depth
|
||||
this.atoms[vPrevIdx].paren_written = false;
|
||||
} else {
|
||||
this.smiles += '(';
|
||||
this.atoms[vPrevIdx].paren_written = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.atoms[vPrevIdx].branch_cnt++;
|
||||
|
||||
if (this.atoms[vPrevIdx].branch_cnt > branches)
|
||||
throw new Error('unexpected branch');
|
||||
}
|
||||
|
||||
var bond = molecule.bonds.get(eIdx);
|
||||
|
||||
var dir = 0;
|
||||
|
||||
if (bond.type == Struct.Bond.PATTERN.TYPE.SINGLE)
|
||||
dir = this.calcBondDirection(molecule, eIdx, vPrevIdx);
|
||||
|
||||
if ((dir == 1 && vIdx == bond.end) || (dir == 2 && vIdx == bond.begin))
|
||||
this.smiles += '/';
|
||||
else if ((dir == 2 && vIdx == bond.end) || (dir == 1 && vIdx == bond.begin))
|
||||
this.smiles += '\\';
|
||||
else if (bond.type == Struct.Bond.PATTERN.TYPE.ANY)
|
||||
this.smiles += '~';
|
||||
else if (bond.type == Struct.Bond.PATTERN.TYPE.DOUBLE)
|
||||
this.smiles += '=';
|
||||
else if (bond.type == Struct.Bond.PATTERN.TYPE.TRIPLE)
|
||||
this.smiles += '#';
|
||||
else if (bond.type == Struct.Bond.PATTERN.TYPE.AROMATIC &&
|
||||
(!this.atoms[bond.begin].lowercase || !this.atoms[bond.end].lowercase || !this.isBondInRing(eIdx)))
|
||||
this.smiles += ':'; // TODO: Check if this : is needed
|
||||
else if (bond.type == Struct.Bond.PATTERN.TYPE.SINGLE && this.atoms[bond.begin].aromatic && this.atoms[bond.end].aromatic)
|
||||
this.smiles += '-';
|
||||
|
||||
|
||||
if (walk.edgeClosingCycle(eIdx)) {
|
||||
for (j = 1; j < cycleNumbers.length; j++) {
|
||||
if (cycleNumbers[j] == vIdx)
|
||||
break;
|
||||
}
|
||||
|
||||
if (j == cycleNumbers.length)
|
||||
throw new Error('cycle number not found');
|
||||
|
||||
this.writeCycleNumber(j);
|
||||
|
||||
cycleNumbers[j] = -1;
|
||||
writeAtom = false;
|
||||
}
|
||||
} else {
|
||||
if (!firstComponent) {
|
||||
this.smiles += (this.writtenComponents === walk.nComponentsInReactants &&
|
||||
walk.nReactants !== 0) ? '>>' : '.'; // when walk.nReactants === 0 - not reaction
|
||||
}
|
||||
firstComponent = false;
|
||||
this.writtenComponents++;
|
||||
}
|
||||
if (writeAtom) {
|
||||
this.writeAtom(molecule, vIdx, this.atoms[vIdx].aromatic, this.atoms[vIdx].lowercase, this.atoms[vIdx].chirality);
|
||||
this.writtenAtoms.push(seqEl.idx);
|
||||
}
|
||||
}
|
||||
|
||||
this.comma = false;
|
||||
|
||||
// this._writeStereogroups(mol, atoms);
|
||||
this.writeRadicals(molecule);
|
||||
// this._writePseudoAtoms(mol);
|
||||
// this._writeHighlighting();
|
||||
|
||||
if (this.comma)
|
||||
this.smiles += '|';
|
||||
|
||||
return this.smiles;
|
||||
};
|
||||
|
||||
Smiles.prototype.writeCycleNumber = function (n) {
|
||||
if (n > 0 && n < 10)
|
||||
this.smiles += n;
|
||||
else if (n >= 10 && n < 100)
|
||||
this.smiles += '%' + n;
|
||||
else if (n >= 100 && n < 1000)
|
||||
this.smiles += '%%' + n;
|
||||
else
|
||||
throw new Error('bad cycle number: ' + n);
|
||||
};
|
||||
|
||||
Smiles.prototype.writeAtom = function (mol, idx, aromatic, lowercase, chirality) { // eslint-disable-line max-params, max-statements
|
||||
var atom = mol.atoms.get(idx);
|
||||
var needBrackets = false;
|
||||
var hydro = -1;
|
||||
var aam = 0;
|
||||
|
||||
/*
|
||||
if (mol.haveQueryAtoms())
|
||||
{
|
||||
query_atom = &mol.getQueryAtom(idx);
|
||||
|
||||
if (query_atom->type == QUERY_ATOM_RGROUP)
|
||||
{
|
||||
if (mol.getRGroups()->isRGroupAtom(idx))
|
||||
{
|
||||
const Array<int> &rg = mol.getRGroups()->getSiteRGroups(idx);
|
||||
|
||||
if (rg.length != 1)
|
||||
throw Error("rgroup count %d", rg.length);
|
||||
|
||||
_output.printf("[&%d]", rg[0] + 1);
|
||||
}
|
||||
else
|
||||
_output.printf("[&%d]", 1);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
if (atom.label == 'A') {
|
||||
this.smiles += '*';
|
||||
return;
|
||||
}
|
||||
|
||||
if (atom.label == 'R' || atom.label == 'R#') {
|
||||
this.smiles += '[*]';
|
||||
return;
|
||||
}
|
||||
|
||||
// KETCHER-598 (Ketcher does not save AAM into reaction SMILES)
|
||||
// BEGIN
|
||||
// if (this.atom_atom_mapping)
|
||||
// aam = atom_atom_mapping[idx];
|
||||
aam = atom.aam;
|
||||
// END
|
||||
|
||||
if (atom.label != 'C' && atom.label != 'P' &&
|
||||
atom.label != 'N' && atom.label != 'S' &&
|
||||
atom.label != 'O' && atom.label != 'Cl' &&
|
||||
atom.label != 'F' && atom.label != 'Br' &&
|
||||
atom.label != 'B' && atom.label != 'I')
|
||||
needBrackets = true;
|
||||
|
||||
if (atom.explicitValence >= 0 || atom.radical != 0 || chirality > 0 ||
|
||||
(aromatic && atom.label != 'C' && atom.label != 'O') ||
|
||||
(aromatic && atom.label == 'C' && this.atoms[idx].neighbours.length < 3 && this.atoms[idx].h_count == 0))
|
||||
hydro = this.atoms[idx].h_count;
|
||||
|
||||
var label = atom.label;
|
||||
if (atom.atomList && !atom.atomList.notList) {
|
||||
label = atom.atomList.label();
|
||||
needBrackets = false; // atom list label already has brackets
|
||||
} else if (atom.isPseudo() || (atom.atomList && atom.atomList.notList)) {
|
||||
label = '*';
|
||||
needBrackets = true;
|
||||
} else if (chirality || atom.charge != 0 || atom.isotope > 0 || hydro >= 0 || aam > 0) {
|
||||
needBrackets = true;
|
||||
}
|
||||
|
||||
if (needBrackets) {
|
||||
if (hydro == -1)
|
||||
hydro = this.atoms[idx].h_count;
|
||||
this.smiles += '[';
|
||||
}
|
||||
|
||||
if (atom.isotope > 0)
|
||||
this.smiles += atom.isotope;
|
||||
|
||||
if (lowercase)
|
||||
this.smiles += label.toLowerCase();
|
||||
else
|
||||
this.smiles += label;
|
||||
|
||||
if (chirality > 0) {
|
||||
if (chirality == 1)
|
||||
this.smiles += '@';
|
||||
else // chirality == 2
|
||||
this.smiles += '@@';
|
||||
|
||||
if (atom.implicitH > 1)
|
||||
throw new Error(atom.implicitH + ' implicit H near stereocenter');
|
||||
}
|
||||
|
||||
if (atom.label != 'H') {
|
||||
if (hydro > 1 || (hydro == 0 && !needBrackets))
|
||||
this.smiles += 'H' + hydro;
|
||||
else if (hydro == 1)
|
||||
this.smiles += 'H';
|
||||
}
|
||||
|
||||
if (atom.charge > 1)
|
||||
this.smiles += '+' + atom.charge;
|
||||
else if (atom.charge < -1)
|
||||
this.smiles += atom.charge;
|
||||
else if (atom.charge == 1)
|
||||
this.smiles += '+';
|
||||
else if (atom.charge == -1)
|
||||
this.smiles += '-';
|
||||
|
||||
if (aam > 0)
|
||||
this.smiles += ':' + aam;
|
||||
|
||||
if (needBrackets)
|
||||
this.smiles += ']';
|
||||
|
||||
/*
|
||||
if (mol.getRGroupFragment() != 0)
|
||||
{
|
||||
for (i = 0; i < 2; i++)
|
||||
{
|
||||
int j;
|
||||
|
||||
for (j = 0; mol.getRGroupFragment()->getAttachmentPoint(i, j) != -1; j++)
|
||||
if (idx == mol.getRGroupFragment()->getAttachmentPoint(i, j))
|
||||
{
|
||||
_output.printf("([*])");
|
||||
break;
|
||||
}
|
||||
|
||||
if (mol.getRGroupFragment()->getAttachmentPoint(i, j) != -1)
|
||||
break;
|
||||
}
|
||||
}
|
||||
*/
|
||||
};
|
||||
|
||||
Smiles.prototype.markCisTrans = function (mol) {
|
||||
this.cis_trans = new CisTrans(mol, function (idx) {
|
||||
return this.atoms[idx].neighbours;
|
||||
}, this);
|
||||
this.cis_trans.build();
|
||||
this.dbonds = new Array(mol.bonds.count());
|
||||
|
||||
mol.bonds.each(function (bid) {
|
||||
this.dbonds[bid] =
|
||||
{
|
||||
ctbond_beg: -1,
|
||||
ctbond_end: -1,
|
||||
saved: 0
|
||||
};
|
||||
}, this);
|
||||
|
||||
this.cis_trans.each(function (bid, ct) {
|
||||
var bond = mol.bonds.get(bid);
|
||||
|
||||
if (ct.parity != 0 && !this.isBondInRing(bid)) {
|
||||
var neiBeg = this.atoms[bond.begin].neighbours;
|
||||
var neiEnd = this.atoms[bond.end].neighbours;
|
||||
var aromFailBeg = true;
|
||||
var aromFailEnd = true;
|
||||
|
||||
neiBeg.forEach(function (nei) {
|
||||
if (nei.bid !== bid && mol.bonds.get(nei.bid).type === Struct.Bond.PATTERN.TYPE.SINGLE)
|
||||
aromFailBeg = false;
|
||||
}, this);
|
||||
|
||||
neiEnd.forEach(function (nei) {
|
||||
if (nei.bid !== bid && mol.bonds.get(nei.bid).type === Struct.Bond.PATTERN.TYPE.SINGLE)
|
||||
aromFailEnd = false;
|
||||
}, this);
|
||||
|
||||
if (aromFailBeg || aromFailEnd)
|
||||
return;
|
||||
|
||||
neiBeg.forEach(function (nei) {
|
||||
if (nei.bid === bid) return;
|
||||
if (mol.bonds.get(nei.bid).begin === bond.begin)
|
||||
this.dbonds[nei.bid].ctbond_beg = bid;
|
||||
else
|
||||
this.dbonds[nei.bid].ctbond_end = bid;
|
||||
}, this);
|
||||
|
||||
neiEnd.forEach(function (nei) {
|
||||
if (nei.bid === bid) return;
|
||||
if (mol.bonds.get(nei.bid).begin === bond.end)
|
||||
this.dbonds[nei.bid].ctbond_beg = bid;
|
||||
else
|
||||
this.dbonds[nei.bid].ctbond_end = bid;
|
||||
}, this);
|
||||
}
|
||||
}, this);
|
||||
};
|
||||
|
||||
Smiles.prototype.updateSideBonds = function (mol, bondIdx) { // eslint-disable-line max-statements
|
||||
var bond = mol.bonds.get(bondIdx);
|
||||
var subst = this.cis_trans.getSubstituents(bondIdx);
|
||||
var parity = this.cis_trans.getParity(bondIdx);
|
||||
|
||||
var sidebonds = [-1, -1, -1, -1];
|
||||
|
||||
sidebonds[0] = mol.findBondId(subst[0], bond.begin);
|
||||
if (subst[1] != -1)
|
||||
sidebonds[1] = mol.findBondId(subst[1], bond.begin);
|
||||
|
||||
sidebonds[2] = mol.findBondId(subst[2], bond.end);
|
||||
if (subst[3] != -1)
|
||||
sidebonds[3] = mol.findBondId(subst[3], bond.end);
|
||||
|
||||
var n1 = 0;
|
||||
var n2 = 0;
|
||||
var n3 = 0;
|
||||
var n4 = 0;
|
||||
|
||||
if (this.dbonds[sidebonds[0]].saved != 0) {
|
||||
if ((this.dbonds[sidebonds[0]].saved == 1 && mol.bonds.get(sidebonds[0]).begin == bond.begin) ||
|
||||
(this.dbonds[sidebonds[0]].saved == 2 && mol.bonds.get(sidebonds[0]).end == bond.begin))
|
||||
n1++;
|
||||
else
|
||||
n2++;
|
||||
}
|
||||
if (sidebonds[1] != -1 && this.dbonds[sidebonds[1]].saved != 0) {
|
||||
if ((this.dbonds[sidebonds[1]].saved == 2 && mol.bonds.get(sidebonds[1]).begin == bond.begin) ||
|
||||
(this.dbonds[sidebonds[1]].saved == 1 && mol.bonds.get(sidebonds[1]).end == bond.begin))
|
||||
n1++;
|
||||
else
|
||||
n2++;
|
||||
}
|
||||
if (this.dbonds[sidebonds[2]].saved != 0) {
|
||||
if ((this.dbonds[sidebonds[2]].saved == 1 && mol.bonds.get(sidebonds[2]).begin == bond.end) ||
|
||||
(this.dbonds[sidebonds[2]].saved == 2 && mol.bonds.get(sidebonds[2]).end == bond.end))
|
||||
n3++;
|
||||
else
|
||||
n4++;
|
||||
}
|
||||
if (sidebonds[3] != -1 && this.dbonds[sidebonds[3]].saved != 0) {
|
||||
if ((this.dbonds[sidebonds[3]].saved == 2 && mol.bonds.get(sidebonds[3]).begin == bond.end) ||
|
||||
(this.dbonds[sidebonds[3]].saved == 1 && mol.bonds.get(sidebonds[3]).end == bond.end))
|
||||
n3++;
|
||||
else
|
||||
n4++;
|
||||
}
|
||||
|
||||
if (parity == CisTrans.PARITY.CIS) {
|
||||
n1 += n3;
|
||||
n2 += n4;
|
||||
} else {
|
||||
n1 += n4;
|
||||
n2 += n3;
|
||||
}
|
||||
|
||||
if (n1 > 0 && n2 > 0)
|
||||
throw new Error('incompatible cis-trans configuration');
|
||||
|
||||
if (n1 == 0 && n2 == 0)
|
||||
return false;
|
||||
|
||||
if (n1 > 0) {
|
||||
this.dbonds[sidebonds[0]].saved =
|
||||
(mol.bonds.get(sidebonds[0]).begin == bond.begin) ? 1 : 2;
|
||||
if (sidebonds[1] != -1) {
|
||||
this.dbonds[sidebonds[1]].saved =
|
||||
(mol.bonds.get(sidebonds[1]).begin == bond.begin) ? 2 : 1;
|
||||
}
|
||||
|
||||
this.dbonds[sidebonds[2]].saved =
|
||||
((mol.bonds.get(sidebonds[2]).begin == bond.end) == (parity == CisTrans.PARITY.CIS)) ? 1 : 2;
|
||||
if (sidebonds[3] != -1) {
|
||||
this.dbonds[sidebonds[3]].saved =
|
||||
((mol.bonds.get(sidebonds[3]).begin == bond.end) == (parity == CisTrans.PARITY.CIS)) ? 2 : 1;
|
||||
}
|
||||
}
|
||||
if (n2 > 0) {
|
||||
this.dbonds[sidebonds[0]].saved =
|
||||
(mol.bonds.get(sidebonds[0]).begin == bond.begin) ? 2 : 1;
|
||||
if (sidebonds[1] != -1) {
|
||||
this.dbonds[sidebonds[1]].saved =
|
||||
(mol.bonds.get(sidebonds[1]).begin == bond.begin) ? 1 : 2;
|
||||
}
|
||||
|
||||
this.dbonds[sidebonds[2]].saved =
|
||||
((mol.bonds.get(sidebonds[2]).begin == bond.end) == (parity == CisTrans.PARITY.CIS)) ? 2 : 1;
|
||||
if (sidebonds[3] != -1) {
|
||||
this.dbonds[sidebonds[3]].saved =
|
||||
((mol.bonds.get(sidebonds[3]).begin == bond.end) == (parity == CisTrans.PARITY.CIS)) ? 1 : 2;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
Smiles.prototype.calcBondDirection = function (mol, idx, vprev) {
|
||||
var ntouched;
|
||||
|
||||
if (this.dbonds[idx].ctbond_beg == -1 && this.dbonds[idx].ctbond_end == -1)
|
||||
return 0;
|
||||
|
||||
if (mol.bonds.get(idx).type != Struct.Bond.PATTERN.TYPE.SINGLE)
|
||||
throw new Error('internal: directed bond type ' + mol.bonds.get(idx).type);
|
||||
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
ntouched = 0;
|
||||
this.cis_trans.each(function (bid, ct) {
|
||||
if (ct.parity != 0 && !this.isBondInRing(bid)) {
|
||||
if (this.updateSideBonds(mol, bid))
|
||||
ntouched++;
|
||||
}
|
||||
}, this);
|
||||
if (ntouched == this.touchedCistransbonds)
|
||||
break;
|
||||
this.touchedCistransbonds = ntouched;
|
||||
}
|
||||
|
||||
if (this.dbonds[idx].saved == 0) {
|
||||
if (vprev == mol.bonds.get(idx).begin)
|
||||
this.dbonds[idx].saved = 1;
|
||||
else
|
||||
this.dbonds[idx].saved = 2;
|
||||
}
|
||||
|
||||
return this.dbonds[idx].saved;
|
||||
};
|
||||
|
||||
Smiles.prototype.writeRadicals = function (mol) { // eslint-disable-line max-statements
|
||||
var marked = new Array(this.writtenAtoms.length);
|
||||
var i, j;
|
||||
|
||||
for (i = 0; i < this.writtenAtoms.length; i++) {
|
||||
if (marked[i])
|
||||
continue; // eslint-disable-line no-continue
|
||||
|
||||
var radical = mol.atoms.get(this.writtenAtoms[i]).radical;
|
||||
|
||||
if (radical == 0)
|
||||
continue; // eslint-disable-line no-continue
|
||||
|
||||
if (this.comma) {
|
||||
this.smiles += ',';
|
||||
} else {
|
||||
this.smiles += ' |';
|
||||
this.comma = true;
|
||||
}
|
||||
|
||||
if (radical == Struct.Atom.PATTERN.RADICAL.SINGLET)
|
||||
this.smiles += '^3:';
|
||||
else if (radical == Struct.Atom.PATTERN.RADICAL.DOUPLET)
|
||||
this.smiles += '^1:';
|
||||
else // RADICAL_TRIPLET
|
||||
this.smiles += '^4:';
|
||||
|
||||
this.smiles += i;
|
||||
|
||||
for (j = i + 1; j < this.writtenAtoms.length; j++) {
|
||||
if (mol.atoms.get(this.writtenAtoms[j]).radical == radical) {
|
||||
marked[j] = true;
|
||||
this.smiles += ',' + j;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
stringify: function (struct, options) {
|
||||
var opts = options || {};
|
||||
return new Smiles().saveMolecule(struct, opts.ignoreErrors);
|
||||
}
|
||||
};
|
||||
519
static/js/ketcher2/script/chem/smiles/stereocenters.js
Normal file
519
static/js/ketcher2/script/chem/smiles/stereocenters.js
Normal file
@ -0,0 +1,519 @@
|
||||
/****************************************************************************
|
||||
* 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');
|
||||
var Vec2 = require('../../util/vec2');
|
||||
|
||||
var Struct = require('../struct');
|
||||
|
||||
function Stereocenters(mol, neighborsFunc, context) {
|
||||
this.molecule = mol;
|
||||
this.atoms = new Map();
|
||||
this.getNeighbors = neighborsFunc;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
Stereocenters.prototype.each = function (func, context) {
|
||||
this.atoms.each(func, context);
|
||||
};
|
||||
|
||||
Stereocenters.prototype.buildFromBonds = function (/* const int *atom_types, const int *atom_groups, const int *bond_orientations, */ignoreErrors) {
|
||||
var atoms = this.molecule.atoms;
|
||||
var bonds = this.molecule.bonds;
|
||||
|
||||
// this is a set of atoms that are likely to belong to allene structures and
|
||||
// therefore should not be considered as potential stereocenters in the code below,
|
||||
// as allenes cannot be encoded in the SMILES notation
|
||||
var alleneMask = Set.empty();
|
||||
atoms.each(function (aid) {
|
||||
var neiList = this.getNeighbors.call(this.context, aid);
|
||||
if (neiList.length != 2)
|
||||
return false;
|
||||
var nei1 = neiList[0];
|
||||
var nei2 = neiList[1];
|
||||
// check atom labels
|
||||
if ([aid, nei1.aid, nei2.aid].findIndex(function (aid) {
|
||||
return ['C', 'Si'].indexOf(atoms.get(aid).label) < 0;
|
||||
}, this) >= 0)
|
||||
return false;
|
||||
|
||||
// check adjacent bond types
|
||||
if ([nei1.bid, nei2.bid].findIndex(function (bid) {
|
||||
return bonds.get(bid).type != Struct.Bond.PATTERN.TYPE.DOUBLE;
|
||||
}, this) >= 0)
|
||||
return false;
|
||||
|
||||
// get the other neighbors of the two adjacent atoms except for the central atom
|
||||
var nei1nei = this.getNeighbors.call(this.context, nei1.aid).filter(function (nei) {
|
||||
return nei.aid != aid;
|
||||
});
|
||||
var nei2nei = this.getNeighbors.call(this.context, nei2.aid).filter(function (nei) {
|
||||
return nei.aid != aid;
|
||||
});
|
||||
if (nei1nei.length < 1 || nei1nei.length > 2 || nei2nei.length < 1 || nei2nei.length > 2)
|
||||
return false;
|
||||
|
||||
if (nei1nei.concat(nei2nei).findIndex(function (nei) {
|
||||
return bonds.get(nei.bid).type != Struct.Bond.PATTERN.TYPE.SINGLE;
|
||||
}, this) >= 0)
|
||||
return false;
|
||||
|
||||
if (nei1nei.concat(nei2nei).findIndex(function (nei) {
|
||||
return bonds.get(nei.bid).stereo == Struct.Bond.PATTERN.STEREO.EITHER;
|
||||
}, this) >= 0)
|
||||
return false;
|
||||
Set.add(alleneMask, nei1.aid);
|
||||
Set.add(alleneMask, nei2.aid);
|
||||
}, this);
|
||||
|
||||
if (Set.size(alleneMask) > 0)
|
||||
alert('This structure may contain allenes, which cannot be represented in the SMILES notation. Relevant stereo-information will be discarded.');
|
||||
|
||||
atoms.each(function (aid) {
|
||||
if (Set.contains(alleneMask, aid))
|
||||
return;
|
||||
/*
|
||||
if (atom_types[atom_idx] == 0)
|
||||
continue;
|
||||
*/
|
||||
var neiList = this.getNeighbors.call(this.context, aid);
|
||||
var stereocenter = false;
|
||||
|
||||
neiList.find(function (nei) {
|
||||
var bond = this.molecule.bonds.get(nei.bid);
|
||||
|
||||
if (bond.type == Struct.Bond.PATTERN.TYPE.SINGLE && bond.begin == aid) {
|
||||
if (bond.stereo == Struct.Bond.PATTERN.STEREO.UP || bond.stereo == Struct.Bond.PATTERN.STEREO.DOWN) {
|
||||
stereocenter = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}, this);
|
||||
|
||||
if (!stereocenter)
|
||||
return;
|
||||
|
||||
if (ignoreErrors)
|
||||
// try
|
||||
// {
|
||||
this.buildOneCenter(aid/* , atom_groups[atom_idx], atom_types[atom_idx], bond_orientations*/);
|
||||
// }
|
||||
// catch (er)
|
||||
// {
|
||||
// }
|
||||
else
|
||||
this.buildOneCenter(aid/* , atom_groups[atom_idx], atom_types[atom_idx], bond_orientations*/);
|
||||
}, this);
|
||||
};
|
||||
|
||||
Stereocenters.allowed_stereocenters =
|
||||
[
|
||||
{ elem: 'C', charge: 0, degree: 3, n_double_bonds: 0, implicit_degree: 4 },
|
||||
{ elem: 'C', charge: 0, degree: 4, n_double_bonds: 0, implicit_degree: 4 },
|
||||
{ elem: 'Si', charge: 0, degree: 3, n_double_bonds: 0, implicit_degree: 4 },
|
||||
{ elem: 'Si', charge: 0, degree: 4, n_double_bonds: 0, implicit_degree: 4 },
|
||||
{ elem: 'N', charge: 1, degree: 3, n_double_bonds: 0, implicit_degree: 4 },
|
||||
{ elem: 'N', charge: 1, degree: 4, n_double_bonds: 0, implicit_degree: 4 },
|
||||
{ elem: 'N', charge: 0, degree: 3, n_double_bonds: 0, implicit_degree: 3 },
|
||||
{ elem: 'S', charge: 0, degree: 4, n_double_bonds: 2, implicit_degree: 4 },
|
||||
{ elem: 'S', charge: 1, degree: 3, n_double_bonds: 0, implicit_degree: 3 },
|
||||
{ elem: 'S', charge: 0, degree: 3, n_double_bonds: 1, implicit_degree: 3 },
|
||||
{ elem: 'P', charge: 0, degree: 3, n_double_bonds: 0, implicit_degree: 3 },
|
||||
{ elem: 'P', charge: 1, degree: 4, n_double_bonds: 0, implicit_degree: 4 },
|
||||
{ elem: 'P', charge: 0, degree: 4, n_double_bonds: 1, implicit_degree: 4 }
|
||||
];
|
||||
|
||||
|
||||
Stereocenters.prototype.buildOneCenter = function (atomIdx/* , int group, int type, const int *bond_orientations*/) { // eslint-disable-line max-statements
|
||||
var atom = this.molecule.atoms.get(atomIdx);
|
||||
|
||||
var neiList = this.getNeighbors.call(this.context, atomIdx);
|
||||
var degree = neiList.length;
|
||||
var implicitDegree = -1;
|
||||
|
||||
var stereocenter = {
|
||||
group: 0, // = group;
|
||||
type: 0, // = type;
|
||||
pyramid: []
|
||||
};
|
||||
|
||||
var edgeIds = [];
|
||||
|
||||
var lastAtomDir = 0;
|
||||
var nDoubleBonds = 0;
|
||||
|
||||
stereocenter.pyramid[0] = -1;
|
||||
stereocenter.pyramid[1] = -1;
|
||||
stereocenter.pyramid[2] = -1;
|
||||
stereocenter.pyramid[3] = -1;
|
||||
|
||||
var nPureHydrogens = 0;
|
||||
|
||||
if (degree > 4)
|
||||
throw new Error('stereocenter with %d bonds are not supported' + degree);
|
||||
|
||||
neiList.forEach(function (nei, neiIdx) {
|
||||
var neiAtom = this.molecule.atoms.get(nei.aid);
|
||||
var bond = this.molecule.bonds.get(nei.bid);
|
||||
edgeIds[neiIdx] = {
|
||||
edge_idx: nei.bid,
|
||||
nei_idx: nei.aid,
|
||||
rank: nei.aid,
|
||||
vec: Vec2.diff(neiAtom.pp, atom.pp).yComplement()
|
||||
};
|
||||
|
||||
if (neiAtom.pureHydrogen()) {
|
||||
nPureHydrogens++;
|
||||
edgeIds[neiIdx].rank = 10000;
|
||||
} else if (neiAtom.label === 'H') {
|
||||
edgeIds[neiIdx].rank = 5000;
|
||||
}
|
||||
|
||||
if (!edgeIds[neiIdx].vec.normalize())
|
||||
throw new Error('zero bond length');
|
||||
|
||||
if (bond.type === Struct.Bond.PATTERN.TYPE.TRIPLE)
|
||||
throw new Error('non-single bonds not allowed near stereocenter');
|
||||
else if (bond.type === Struct.Bond.PATTERN.TYPE.AROMATIC)
|
||||
throw new Error('aromatic bonds not allowed near stereocenter');
|
||||
else if (bond.type === Struct.Bond.PATTERN.TYPE.DOUBLE)
|
||||
nDoubleBonds++;
|
||||
}, this);
|
||||
|
||||
Stereocenters.allowed_stereocenters.find(function (as) {
|
||||
if (as.elem == atom.label && as.charge == atom.charge &&
|
||||
as.degree == degree && as.n_double_bonds == nDoubleBonds) {
|
||||
implicitDegree = as.implicit_degree;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, this);
|
||||
|
||||
if (implicitDegree === -1)
|
||||
throw new Error('unknown stereocenter configuration: ' + atom.label + ', charge ' + atom.charge + ', ' + degree + ' bonds (' + nDoubleBonds + ' double)');
|
||||
|
||||
if (degree === 4 && nPureHydrogens > 1)
|
||||
throw new Error(nPureHydrogens + ' hydrogens near stereocenter');
|
||||
|
||||
if (degree === 3 && implicitDegree == 4 && nPureHydrogens > 0)
|
||||
throw new Error('have hydrogen(s) besides implicit hydrogen near stereocenter');
|
||||
|
||||
/*
|
||||
if (stereocenter.type == ATOM_ANY)
|
||||
{
|
||||
_stereocenters.insert(atom_idx, stereocenter);
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
if (degree === 4) {
|
||||
// sort by neighbor atom index (ascending)
|
||||
if (edgeIds[0].rank > edgeIds[1].rank)
|
||||
swap(edgeIds, 0, 1);
|
||||
if (edgeIds[1].rank > edgeIds[2].rank)
|
||||
swap(edgeIds, 1, 2);
|
||||
if (edgeIds[2].rank > edgeIds[3].rank)
|
||||
swap(edgeIds, 2, 3);
|
||||
if (edgeIds[1].rank > edgeIds[2].rank)
|
||||
swap(edgeIds, 1, 2);
|
||||
if (edgeIds[0].rank > edgeIds[1].rank)
|
||||
swap(edgeIds, 0, 1);
|
||||
if (edgeIds[1].rank > edgeIds[2].rank)
|
||||
swap(edgeIds, 1, 2);
|
||||
|
||||
var main1 = -1;
|
||||
var main2 = -1;
|
||||
var side1 = -1;
|
||||
var side2 = -1;
|
||||
var mainDir = 0;
|
||||
|
||||
for (var neiIdx = 0; neiIdx < 4; neiIdx++) {
|
||||
var stereo = this.getBondStereo(atomIdx, edgeIds[neiIdx].edge_idx);
|
||||
|
||||
if (stereo == Struct.Bond.PATTERN.STEREO.UP || stereo == Struct.Bond.PATTERN.STEREO.DOWN) {
|
||||
main1 = neiIdx;
|
||||
mainDir = stereo;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (main1 == -1)
|
||||
throw new Error('none of 4 bonds going from stereocenter is stereobond');
|
||||
|
||||
var xyz1, xyz2;
|
||||
|
||||
// find main2 as opposite to main1
|
||||
if (main2 == -1) {
|
||||
xyz1 = Stereocenters.xyzzy(edgeIds[main1].vec, edgeIds[(main1 + 1) % 4].vec, edgeIds[(main1 + 2) % 4].vec);
|
||||
xyz2 = Stereocenters.xyzzy(edgeIds[main1].vec, edgeIds[(main1 + 1) % 4].vec, edgeIds[(main1 + 3) % 4].vec);
|
||||
|
||||
if (xyz1 + xyz2 == 3 || xyz1 + xyz2 == 12) {
|
||||
main2 = (main1 + 1) % 4;
|
||||
side1 = (main1 + 2) % 4;
|
||||
side2 = (main1 + 3) % 4;
|
||||
}
|
||||
}
|
||||
if (main2 == -1) {
|
||||
xyz1 = Stereocenters.xyzzy(edgeIds[main1].vec, edgeIds[(main1 + 2) % 4].vec, edgeIds[(main1 + 1) % 4].vec);
|
||||
xyz2 = Stereocenters.xyzzy(edgeIds[main1].vec, edgeIds[(main1 + 2) % 4].vec, edgeIds[(main1 + 3) % 4].vec);
|
||||
|
||||
if (xyz1 + xyz2 == 3 || xyz1 + xyz2 == 12) {
|
||||
main2 = (main1 + 2) % 4;
|
||||
side1 = (main1 + 1) % 4;
|
||||
side2 = (main1 + 3) % 4;
|
||||
}
|
||||
}
|
||||
if (main2 == -1) {
|
||||
xyz1 = Stereocenters.xyzzy(edgeIds[main1].vec, edgeIds[(main1 + 3) % 4].vec, edgeIds[(main1 + 1) % 4].vec);
|
||||
xyz2 = Stereocenters.xyzzy(edgeIds[main1].vec, edgeIds[(main1 + 3) % 4].vec, edgeIds[(main1 + 2) % 4].vec);
|
||||
|
||||
if (xyz1 + xyz2 == 3 || xyz1 + xyz2 == 12) {
|
||||
main2 = (main1 + 3) % 4;
|
||||
side1 = (main1 + 2) % 4;
|
||||
side2 = (main1 + 1) % 4;
|
||||
}
|
||||
}
|
||||
|
||||
if (main2 == -1)
|
||||
throw new Error('internal error: can not find opposite bond');
|
||||
|
||||
if (mainDir == Struct.Bond.PATTERN.STEREO.UP && this.getBondStereo(atomIdx, edgeIds[main2].edge_idx) == Struct.Bond.PATTERN.STEREO.DOWN)
|
||||
throw new Error('stereo types of the opposite bonds mismatch');
|
||||
if (mainDir == Struct.Bond.PATTERN.STEREO.DOWN && this.getBondStereo(atomIdx, edgeIds[main2].edge_idx) == Struct.Bond.PATTERN.STEREO.UP)
|
||||
throw new Error('stereo types of the opposite bonds mismatch');
|
||||
|
||||
if (mainDir == this.getBondStereo(atomIdx, edgeIds[side1].edge_idx))
|
||||
throw new Error('stereo types of non-opposite bonds match');
|
||||
if (mainDir == this.getBondStereo(atomIdx, edgeIds[side2].edge_idx))
|
||||
throw new Error('stereo types of non-opposite bonds match');
|
||||
|
||||
if (main1 == 3 || main2 == 3)
|
||||
lastAtomDir = mainDir;
|
||||
else
|
||||
lastAtomDir = (mainDir == Struct.Bond.PATTERN.STEREO.UP ? Struct.Bond.PATTERN.STEREO.DOWN : Struct.Bond.PATTERN.STEREO.UP);
|
||||
|
||||
sign = Stereocenters.sign(edgeIds[0].vec, edgeIds[1].vec, edgeIds[2].vec);
|
||||
|
||||
if ((lastAtomDir == Struct.Bond.PATTERN.STEREO.UP && sign > 0) ||
|
||||
(lastAtomDir == Struct.Bond.PATTERN.STEREO.DOWN && sign < 0)) {
|
||||
stereocenter.pyramid[0] = edgeIds[0].nei_idx;
|
||||
stereocenter.pyramid[1] = edgeIds[1].nei_idx;
|
||||
stereocenter.pyramid[2] = edgeIds[2].nei_idx;
|
||||
} else {
|
||||
stereocenter.pyramid[0] = edgeIds[0].nei_idx;
|
||||
stereocenter.pyramid[1] = edgeIds[2].nei_idx;
|
||||
stereocenter.pyramid[2] = edgeIds[1].nei_idx;
|
||||
}
|
||||
|
||||
stereocenter.pyramid[3] = edgeIds[3].nei_idx;
|
||||
} else if (degree === 3) {
|
||||
// sort by neighbor atom index (ascending)
|
||||
if (edgeIds[0].rank > edgeIds[1].rank)
|
||||
swap(edgeIds, 0, 1);
|
||||
if (edgeIds[1].rank > edgeIds[2].rank)
|
||||
swap(edgeIds, 1, 2);
|
||||
if (edgeIds[0].rank > edgeIds[1].rank)
|
||||
swap(edgeIds, 0, 1);
|
||||
|
||||
var stereo0 = this.getBondStereo(atomIdx, edgeIds[0].edge_idx);
|
||||
var stereo1 = this.getBondStereo(atomIdx, edgeIds[1].edge_idx);
|
||||
var stereo2 = this.getBondStereo(atomIdx, edgeIds[2].edge_idx);
|
||||
|
||||
var nUp = 0;
|
||||
var nDown = 0;
|
||||
|
||||
nUp += (stereo0 === Struct.Bond.PATTERN.STEREO.UP) ? 1 : 0;
|
||||
nUp += (stereo1 === Struct.Bond.PATTERN.STEREO.UP) ? 1 : 0;
|
||||
nUp += (stereo2 === Struct.Bond.PATTERN.STEREO.UP) ? 1 : 0;
|
||||
|
||||
nDown += (stereo0 === Struct.Bond.PATTERN.STEREO.DOWN) ? 1 : 0;
|
||||
nDown += (stereo1 === Struct.Bond.PATTERN.STEREO.DOWN) ? 1 : 0;
|
||||
nDown += (stereo2 === Struct.Bond.PATTERN.STEREO.DOWN) ? 1 : 0;
|
||||
|
||||
if (implicitDegree == 4) { // have implicit hydrogen
|
||||
if (nUp == 3)
|
||||
throw new Error('all 3 bonds up near stereoatom');
|
||||
if (nDown == 3)
|
||||
throw new Error('all 3 bonds down near stereoatom');
|
||||
|
||||
if (nUp == 0 && nDown == 0)
|
||||
throw new Error('no up/down bonds near stereoatom -- indefinite case');
|
||||
if (nUp == 1 && nDown == 1)
|
||||
throw new Error('one bond up, one bond down -- indefinite case');
|
||||
|
||||
mainDir = 0;
|
||||
|
||||
if (nUp == 2) {
|
||||
lastAtomDir = Struct.Bond.PATTERN.STEREO.DOWN;
|
||||
} else if (nDown == 2) {
|
||||
lastAtomDir = Struct.Bond.PATTERN.STEREO.UP;
|
||||
} else {
|
||||
main1 = -1;
|
||||
side1 = -1;
|
||||
side2 = -1;
|
||||
|
||||
for (neiIdx = 0; neiIdx < 3; neiIdx++) {
|
||||
dir = this.getBondStereo(atomIdx, edgeIds[neiIdx].edge_idx);
|
||||
|
||||
if (dir == Struct.Bond.PATTERN.STEREO.UP || dir == Struct.Bond.PATTERN.STEREO.DOWN) { // eslint-disable-line max-depth
|
||||
main1 = neiIdx;
|
||||
mainDir = dir;
|
||||
side1 = (neiIdx + 1) % 3;
|
||||
side2 = (neiIdx + 2) % 3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (main1 == -1)
|
||||
throw new Error('internal error: can not find up or down bond');
|
||||
|
||||
var xyz = Stereocenters.xyzzy(edgeIds[side1].vec, edgeIds[side2].vec, edgeIds[main1].vec);
|
||||
|
||||
if (xyz == 3 || xyz == 4)
|
||||
throw new Error('degenerate case for 3 bonds near stereoatom');
|
||||
|
||||
if (xyz == 1)
|
||||
lastAtomDir = mainDir;
|
||||
else
|
||||
lastAtomDir = (mainDir == Struct.Bond.PATTERN.STEREO.UP ? Struct.Bond.PATTERN.STEREO.DOWN : Struct.Bond.PATTERN.STEREO.UP);
|
||||
}
|
||||
|
||||
var sign = Stereocenters.sign(edgeIds[0].vec, edgeIds[1].vec, edgeIds[2].vec);
|
||||
|
||||
if ((lastAtomDir == Struct.Bond.PATTERN.STEREO.UP && sign > 0) ||
|
||||
(lastAtomDir == Struct.Bond.PATTERN.STEREO.DOWN && sign < 0)) {
|
||||
stereocenter.pyramid[0] = edgeIds[0].nei_idx;
|
||||
stereocenter.pyramid[1] = edgeIds[1].nei_idx;
|
||||
stereocenter.pyramid[2] = edgeIds[2].nei_idx;
|
||||
} else {
|
||||
stereocenter.pyramid[0] = edgeIds[0].nei_idx;
|
||||
stereocenter.pyramid[1] = edgeIds[2].nei_idx;
|
||||
stereocenter.pyramid[2] = edgeIds[1].nei_idx;
|
||||
}
|
||||
|
||||
stereocenter.pyramid[3] = -1;
|
||||
} else { // 3-connected P, N or S; no implicit hydrogens
|
||||
var dir;
|
||||
|
||||
if (nDown > 0 && nUp > 0)
|
||||
throw new Error('one bond up, one bond down -- indefinite case');
|
||||
else if (nDown == 0 && nUp == 0)
|
||||
throw new Error('no up-down bonds attached to stereocenter');
|
||||
else if (nUp > 0)
|
||||
dir = 1;
|
||||
else
|
||||
dir = -1;
|
||||
|
||||
if (Stereocenters.xyzzy(edgeIds[0].vec, edgeIds[1].vec, edgeIds[2].vec) === 1 ||
|
||||
Stereocenters.xyzzy(edgeIds[0].vec, edgeIds[2].vec, edgeIds[1].vec) === 1 ||
|
||||
Stereocenters.xyzzy(edgeIds[2].vec, edgeIds[1].vec, edgeIds[0].vec) === 1)
|
||||
// all bonds belong to the same half-plane
|
||||
dir = -dir;
|
||||
|
||||
sign = Stereocenters.sign(edgeIds[0].vec, edgeIds[1].vec, edgeIds[2].vec);
|
||||
|
||||
if (sign == dir) {
|
||||
stereocenter.pyramid[0] = edgeIds[0].nei_idx;
|
||||
stereocenter.pyramid[1] = edgeIds[2].nei_idx;
|
||||
stereocenter.pyramid[2] = edgeIds[1].nei_idx;
|
||||
} else {
|
||||
stereocenter.pyramid[0] = edgeIds[0].nei_idx;
|
||||
stereocenter.pyramid[1] = edgeIds[1].nei_idx;
|
||||
stereocenter.pyramid[2] = edgeIds[2].nei_idx;
|
||||
}
|
||||
stereocenter.pyramid[3] = -1;
|
||||
}
|
||||
}
|
||||
this.atoms.set(atomIdx, stereocenter);
|
||||
};
|
||||
|
||||
Stereocenters.prototype.getBondStereo = function (centerIdx, edgeIdx) {
|
||||
var bond = this.molecule.bonds.get(edgeIdx);
|
||||
|
||||
if (centerIdx != bond.begin) // TODO: check this
|
||||
return 0;
|
||||
|
||||
return bond.stereo;
|
||||
};
|
||||
|
||||
// 1 -- in the smaller angle, 2 -- in the bigger angle,
|
||||
// 4 -- in the 'positive' straight angle, 8 -- in the 'negative' straight angle
|
||||
Stereocenters.xyzzy = function (v1, v2, u) {
|
||||
var eps = 0.001;
|
||||
|
||||
var sine1 = Vec2.cross(v1, v2);
|
||||
var cosine1 = Vec2.dot(v1, v2);
|
||||
|
||||
var sine2 = Vec2.cross(v1, u);
|
||||
var cosine2 = Vec2.dot(v1, u);
|
||||
|
||||
if (Math.abs(sine1) < eps) {
|
||||
if (Math.abs(sine2) < eps)
|
||||
throw new Error('degenerate case -- bonds overlap');
|
||||
|
||||
return (sine2 > 0) ? 4 : 8;
|
||||
}
|
||||
|
||||
if (sine1 * sine2 < -eps * eps)
|
||||
return 2;
|
||||
|
||||
if (cosine2 < cosine1)
|
||||
return 2;
|
||||
|
||||
return 1;
|
||||
};
|
||||
|
||||
Stereocenters.sign = function (v1, v2, v3) {
|
||||
var res = (v1.x - v3.x) * (v2.y - v3.y) - (v1.y - v3.y) * (v2.x - v3.x); // eslint-disable-line no-mixed-operators
|
||||
var eps = 0.001;
|
||||
|
||||
if (res > eps)
|
||||
return 1;
|
||||
if (res < -eps)
|
||||
return -1;
|
||||
|
||||
throw new Error('degenerate triangle');
|
||||
};
|
||||
|
||||
Stereocenters.isPyramidMappingRigid = function (mapping) {
|
||||
var arr = mapping.slice();
|
||||
var rigid = true;
|
||||
|
||||
if (arr[0] > arr[1])
|
||||
swap(arr, 0, 1), rigid = !rigid;
|
||||
if (arr[1] > arr[2])
|
||||
swap(arr, 1, 2), rigid = !rigid;
|
||||
if (arr[2] > arr[3])
|
||||
swap(arr, 2, 3), rigid = !rigid;
|
||||
if (arr[1] > arr[2])
|
||||
swap(arr, 1, 2), rigid = !rigid;
|
||||
if (arr[0] > arr[1])
|
||||
swap(arr, 0, 1), rigid = !rigid;
|
||||
if (arr[1] > arr[2])
|
||||
swap(arr, 1, 2), rigid = !rigid;
|
||||
|
||||
return rigid;
|
||||
};
|
||||
|
||||
function swap(arr, i1, i2) {
|
||||
var tmp = arr[i1];
|
||||
arr[i1] = arr[i2];
|
||||
arr[i2] = tmp;
|
||||
}
|
||||
|
||||
module.exports = Stereocenters;
|
||||
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