chem.SmilesSaver.prototype.saveMolecule = function()

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;

};