in gsoc2022/seagrid-rich-client/molview/src/js/chem/chem/smiles.js [45:413]
chem.SmilesSaver.prototype.saveMolecule = function (molecule, ignore_errors)
{
var i, j, k;
if(!util.isUndefined(ignore_errors))
this.ignore_errors = ignore_errors;
//[RB]: KETCHER-498 (Incorrect smile-string for multiple Sgroup)
//TODO the fix is temporary, still need to implement error handling/reporting
//BEGIN
// if (molecule.sgroups.count() > 0 && !this.ignore_errors)
// throw new Error("SMILES doesn't support s-groups");
molecule = molecule.clone();
molecule.sgroups.each(function (sgid, sg)
{
if(sg.type == 'MUL')
{
try
{
sg.prepareForSaving(molecule);
}
catch(ex)
{
throw {
message: 'Bad s-group (' + ex.message + ')'
};
}
}
else if(!this.ignore_errors)
{
throw new Error("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 chem.SmilesSaver._Atom(atom.implicitH);
}, 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 allowed_lowercase = ['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 == chem.Struct.BOND.TYPE.AROMATIC)
{
this.atoms[bond.begin].aromatic = true;
if(allowed_lowercase.indexOf(molecule.atoms.get(bond.begin).label) != -1)
this.atoms[bond.begin].lowercase = true;
this.atoms[bond.end].aromatic = true;
if(allowed_lowercase.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 = util.Set.empty();
molecule.loops.each(function (lid, loop)
{
if(loop.hbs.length <= 6)
util.Set.mergeIn(bondsInLoops, util.Set.fromList(util.map(loop.hbs, function (hbid)
{
return molecule.halfBonds.get(hbid).bid;
}, this)));
}, this);
var inLoop = {};
util.Set.each(bondsInLoops, function (bid)
{
inLoop[bid] = 1;
}, this);
return inLoop;
})();
this._touched_cistransbonds = 0;
this._markCisTrans(molecule);
var components = chem.MolfileSaver.getComponents(molecule);
var componentsAll = components.reactants.concat(components.products);
var walk = new chem.Dfs(molecule, this.atoms, componentsAll, components.reactants.length);
walk.walk();
util.each(this.atoms, function (atom)
{
atom.neighbours = [];
}, this);
// fill up neighbor lists for the stereocenters calculation
for(i = 0; i < walk.v_seq.length; i++)
{
var seq_el = walk.v_seq[i];
var v_idx = seq_el.idx;
var e_idx = seq_el.parent_edge;
var v_prev_idx = seq_el.parent_vertex;
if(e_idx >= 0)
{
var atom = this.atoms[v_idx];
var opening_cycles = walk.numOpeningCycles(e_idx);
for(j = 0; j < opening_cycles; j++)
this.atoms[v_prev_idx].neighbours.push(
{
aid: -1,
bid: -1
});
if(walk.edgeClosingCycle(e_idx))
{
for(k = 0; k < atom.neighbours.length; k++)
{
if(atom.neighbours[k].aid == -1)
{
atom.neighbours[k].aid = v_prev_idx;
atom.neighbours[k].bid = e_idx;
break;
}
}
if(k == atom.neighbours.length)
throw new Error("Internal: can not put closing bond to its place");
}
else
{
atom.neighbours.push(
{
aid: v_prev_idx,
bid: e_idx
});
atom.parent = v_prev_idx;
}
this.atoms[v_prev_idx].neighbours.push(
{
aid: v_idx,
bid: e_idx
});
}
}
try
{
// detect chiral configurations
var stereocenters = new chem.Stereocenters(molecule, function (idx)
{
return this.atoms[idx].neighbours;
}, this);
stereocenters.buildFromBonds(this.ignore_errors);
stereocenters.each(function (atom_idx, sc)
{
//if (sc.type < MoleculeStereocenters::ATOM_AND)
// continue;
var implicit_h_idx = -1;
if(sc.pyramid[3] == -1)
implicit_h_idx = 3;
/*
else for (j = 0; j < 4; j++)
if (ignored_vertices[pyramid[j]])
{
implicit_h_idx = j;
break;
}
*/
var pyramid_mapping = new Array(4);
var counter = 0;
var atom = this.atoms[atom_idx];
if(atom.parent != -1)
for(k = 0; k < 4; k++)
if(sc.pyramid[k] == atom.parent)
{
pyramid_mapping[counter++] = k;
break;
}
if(implicit_h_idx != -1)
pyramid_mapping[counter++] = implicit_h_idx;
for(j = 0; j != atom.neighbours.length; j++)
{
if(atom.neighbours[j].aid == atom.parent)
continue;
for(k = 0; k < 4; k++)
if(atom.neighbours[j].aid == sc.pyramid[k])
{
if(counter >= 4)
throw new Error("Internal: pyramid overflow");
pyramid_mapping[counter++] = k;
break;
}
}
if(counter == 4)
{
// move the 'from' atom to the end
counter = pyramid_mapping[0];
pyramid_mapping[0] = pyramid_mapping[1];
pyramid_mapping[1] = pyramid_mapping[2];
pyramid_mapping[2] = pyramid_mapping[3];
pyramid_mapping[3] = counter;
}
else if(counter != 3)
throw new Error("Cannot calculate chirality");
if(chem.Stereocenters.isPyramidMappingRigid(pyramid_mapping))
this.atoms[atom_idx].chirality = 1;
else
this.atoms[atom_idx].chirality = 2;
}, this);
}
catch(ex)
{
throw new Error(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 cycle_numbers = new Array();
cycle_numbers.push(0); // never used
var first_component = true;
for(i = 0; i < walk.v_seq.length; i++)
{
seq_el = walk.v_seq[i];
v_idx = seq_el.idx;
e_idx = seq_el.parent_edge;
v_prev_idx = seq_el.parent_vertex;
var write_atom = true;
if(v_prev_idx >= 0)
{
if(walk.numBranches(v_prev_idx) > 1)
if(this.atoms[v_prev_idx].branch_cnt > 0 && this.atoms[v_prev_idx].paren_written)
this.smiles += ')';
opening_cycles = walk.numOpeningCycles(e_idx);
for(j = 0; j < opening_cycles; j++)
{
for(k = 1; k < cycle_numbers.length; k++)
if(cycle_numbers[k] == -1)
break;
if(k == cycle_numbers.length)
cycle_numbers.push(v_prev_idx);
else
cycle_numbers[k] = v_prev_idx;
this._writeCycleNumber(k);
}
if(v_prev_idx >= 0)
{
var branches = walk.numBranches(v_prev_idx);
if(branches > 1)
if(this.atoms[v_prev_idx].branch_cnt < branches - 1)
{
if(walk.edgeClosingCycle(e_idx))
this.atoms[v_prev_idx].paren_written = false;
else
{
this.smiles += '(';
this.atoms[v_prev_idx].paren_written = true;
}
}
this.atoms[v_prev_idx].branch_cnt++;
if(this.atoms[v_prev_idx].branch_cnt > branches)
throw new Error("Unexpected branch");
}
var bond = molecule.bonds.get(e_idx);
var bond_written = true;
var dir = 0;
if(bond.type == chem.Struct.BOND.TYPE.SINGLE)
dir = this._calcBondDirection(molecule, e_idx, v_prev_idx);
if((dir == 1 && v_idx == bond.end) || (dir == 2 && v_idx == bond.begin))
this.smiles += '/';
else if((dir == 2 && v_idx == bond.end) || (dir == 1 && v_idx == bond.begin))
this.smiles += '\\';
else if(bond.type == chem.Struct.BOND.TYPE.ANY)
this.smiles += '~';
else if(bond.type == chem.Struct.BOND.TYPE.DOUBLE)
this.smiles += '=';
else if(bond.type == chem.Struct.BOND.TYPE.TRIPLE)
this.smiles += '#';
else if(bond.type == chem.Struct.BOND.TYPE.AROMATIC &&
(!this.atoms[bond.begin].lowercase || !this.atoms[bond.end].lowercase || !this.isBondInRing(e_idx)))
this.smiles += ':'; // TODO: Check if this : is needed
else if(bond.type == chem.Struct.BOND.TYPE.SINGLE && this.atoms[bond.begin].aromatic && this.atoms[bond.end].aromatic)
this.smiles += '-';
else
bond_written = false;
if(walk.edgeClosingCycle(e_idx))
{
for(j = 1; j < cycle_numbers.length; j++)
if(cycle_numbers[j] == v_idx)
break;
if(j == cycle_numbers.length)
throw new Error("Cycle number not found");
this._writeCycleNumber(j);
cycle_numbers[j] = -1;
write_atom = false;
}
}
else
{
if(!first_component)
this.smiles += (this._written_components == walk.nComponentsInReactants) ? '>>' : '.';
first_component = false;
this._written_components++;
}
if(write_atom)
{
this._writeAtom(molecule, v_idx, this.atoms[v_idx].aromatic, this.atoms[v_idx].lowercase, this.atoms[v_idx].chirality);
this._written_atoms.push(seq_el.idx);
}
}
this.comma = false;
//this._writeStereogroups(mol, atoms);
this._writeRadicals(molecule);
//this._writePseudoAtoms(mol);
//this._writeHighlighting();
if(this.comma)
this.smiles += '|';
return this.smiles;
};