Smiles.prototype.saveMolecule = function()

in packages/ketcher-core/src/domain/serializers/smi/smiles.js [49:411]


Smiles.prototype.saveMolecule = function (struct, ignoreErrors) {
  // eslint-disable-line max-statements
  let i, j, k;

  if (!ignoreErrors) this.ignoreErrors = ignoreErrors;

  // [RB]: KETCHER-498 (Incorrect smile-string for multiple Sgroup)
  // TODO the fix is temporary, still need to implement error handling/reporting
  // BEGIN
  struct = struct.clone(
    undefined,
    undefined,
    !struct.hasRxnArrow(), // make it drop multiple reactions
    undefined,
    undefined,
    undefined,
  );
  struct.initHalfBonds();
  struct.initNeighbors();
  struct.sortNeighbors();
  struct.setImplicitHydrogen();
  struct.sgroups.forEach((sg) => {
    if (sg.type === 'MUL') {
      try {
        SGroup.prepareMulForSaving(sg, struct);
      } catch (error) {
        KetcherLogger.error('smiles.js::Smiles.prototype.saveMolecule', error);
        throw Error('Bad s-group (' + error.message + ')');
      }
    }
    // 'SMILES data format doesn\'t support s-groups'
  });
  // END

  this.atoms = new Array(struct.atoms.size);

  struct.atoms.forEach((atom, aid) => {
    this.atoms[aid] = new Smiles._Atom(atom.implicitH); // eslint-disable-line no-underscore-dangle
  });

  // 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).
  const allowedLowercase = ['B', 'C', 'N', 'O', 'P', 'S', 'Se', 'As'];

  // Detect atoms that have aromatic bonds and count neighbours
  struct.bonds.forEach((bond, bid) => {
    if (bond.type === Bond.PATTERN.TYPE.AROMATIC) {
      this.atoms[bond.begin].aromatic = true;
      if (allowedLowercase.indexOf(struct.atoms.get(bond.begin).label) !== -1) {
        this.atoms[bond.begin].lowercase = true;
      }
      this.atoms[bond.end].aromatic = true;
      if (allowedLowercase.indexOf(struct.atoms.get(bond.end).label) !== -1) {
        this.atoms[bond.end].lowercase = true;
      }
    }
    this.atoms[bond.begin].neighbours.push({ aid: bond.end, bid });
    this.atoms[bond.end].neighbours.push({ aid: bond.begin, bid });
  });

  this.inLoop = (function () {
    struct.prepareLoopStructure();
    let bondsInLoops = new Pile();
    struct.loops.forEach((loop) => {
      if (loop.hbs.length <= 6) {
        const hbids = loop.hbs.map((hbid) => struct.halfBonds.get(hbid).bid);
        bondsInLoops = bondsInLoops.union(new Pile(hbids));
      }
    });
    const inLoop = {};
    bondsInLoops.forEach((bid) => {
      inLoop[bid] = 1;
    });
    return inLoop;
  })();

  this.touchedCistransbonds = 0;
  this.markCisTrans(struct);

  const components = struct.getComponents();
  const componentsAll = components.reactants.concat(components.products);

  const walk = new Dfs(
    struct,
    this.atoms,
    componentsAll,
    components.reactants.length,
  );

  walk.walk();
  this.atoms.forEach((atom) => {
    atom.neighbours = [];
  });

  // 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) {
      const 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
    const stereocenters = new Stereocenters(
      struct,
      function (idx) {
        return this.atoms[idx].neighbours;
      },
      this,
    );
    stereocenters.buildFromBonds(this.ignoreErrors);

    stereocenters.each((sc, atomIdx) => {
      // eslint-disable-line max-statements
      // if (sc.type < MoleculeStereocenters::ATOM_AND)
      //    continue;

      let 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;
				}
				*/

      const pyramidMapping = [];
      let counter = 0;

      const 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;
    });
  } catch (e) {
    KetcherLogger.error('smiles.js::Smiles.prototype.saveMolecule', e);
    // TODO: add error handler call
  }

  // 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
  const cycleNumbers = [];

  cycleNumbers.push(0); // never used

  let 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;
    let 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) {
        const 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');
        }
      }

      const bond = struct.bonds.get(eIdx);

      let dir = 0;

      if (bond.type === Bond.PATTERN.TYPE.SINGLE) {
        dir = this.calcBondDirection(struct, 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 === Bond.PATTERN.TYPE.ANY) {
        this.smiles += '~';
      } else if (bond.type === Bond.PATTERN.TYPE.DOUBLE) {
        this.smiles += '=';
      } else if (bond.type === Bond.PATTERN.TYPE.TRIPLE) {
        this.smiles += '#';
      } else if (bond.type === Bond.PATTERN.TYPE.SINGLE_OR_AROMATIC) {
        this.smiles += '-,:';
      } else if (bond.type === Bond.PATTERN.TYPE.DOUBLE_OR_AROMATIC) {
        this.smiles += '=,:';
      } else if (bond.type === Bond.PATTERN.TYPE.SINGLE_OR_DOUBLE) {
        this.smiles += '-,=';
      } else if (
        bond.type === 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 === 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(
        struct,
        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(struct);
  // this._writePseudoAtoms(mol);
  // this._writeHighlighting();

  if (this.comma) this.smiles += '|';

  return this.smiles;
};