Current Dev State

This commit is contained in:
Tim Lorsbach
2025-06-23 20:13:54 +02:00
parent b4f9bb277d
commit ded50edaa2
22617 changed files with 4345095 additions and 174 deletions

View 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;

View 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;

View 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);
}
};

View 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;