forked from enviPath/enviPy
Current Dev State
This commit is contained in:
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;
|
||||
Reference in New Issue
Block a user