/// <summary> /// Add double-bond stereo configuration to the Beam GraphBuilder. /// </summary> /// <param name="dbs">stereo element specifying double-bond configuration</param> /// <param name="gb">the current graph builder</param> /// <param name="indices">atom indices</param> private static void AddGeometricConfiguration(IDoubleBondStereochemistry dbs, SmiFlavors flavour, GraphBuilder gb, Dictionary <IAtom, int> indices) { var db = dbs.StereoBond; var bs = dbs.Bonds; // don't try to set a configuration on aromatic bonds if (SmiFlavorTool.IsSet(flavour, SmiFlavors.UseAromaticSymbols) && db.IsAromatic) { return; } var u = indices[db.Begin]; var v = indices[db.End]; // is bs[0] always connected to db.Atom(0)? var x = indices[bs[0].GetOther(db.Begin)]; var y = indices[bs[1].GetOther(db.End)]; if (dbs.Stereo == DoubleBondConformation.Together) { gb.Geometric(u, v).Together(x, y); } else { gb.Geometric(u, v).Opposite(x, y); } }
/// <summary> /// Convert an CDK <see cref="IAtom"/> to a Beam Atom. The symbol and implicit /// hydrogen count are not optional. If the symbol is not supported by the /// SMILES notation (e.g. 'R1') the element will automatically default to /// Unknown ('*'). /// </summary> /// <param name="a">cdk Atom instance</param> /// <returns>a Beam atom</returns> /// <exception cref="NullReferenceException">the atom had an undefined symbol or implicit hydrogen count</exception> static Beam.IAtom ToBeamAtom(IAtom a, SmiFlavors flavour) { var aromatic = SmiFlavorTool.IsSet(flavour, SmiFlavors.UseAromaticSymbols) && a.IsAromatic; var charge = a.FormalCharge; string symbol = CheckNotNull(a.Symbol, "An atom had an undefined symbol"); var element = Beam.Element.OfSymbol(symbol); if (element == null) { element = Beam.Element.Unknown; } var ab = aromatic ? AtomBuilder.Aromatic(element) : AtomBuilder.Aliphatic(element); // CDK leaves nulls on pseudo atoms - we need to check this special case var hCount = a.ImplicitHydrogenCount; if (element == Beam.Element.Unknown) { ab.NumOfHydrogens(hCount ?? 0); } else { ab.NumOfHydrogens(CheckNotNull(hCount, "One or more atoms had an undefined number of implicit hydrogens")); } if (charge.HasValue) { ab.Charge(charge.Value); } // use the mass number to specify isotope? if (SmiFlavorTool.IsSet(flavour, SmiFlavors.AtomicMass | SmiFlavors.AtomicMassStrict)) { var massNumber = a.MassNumber; if (massNumber != null) { ab.Isotope(massNumber.Value); } } var atomClass = a.GetProperty <int?>(CDKPropertyName.AtomAtomMapping); if (SmiFlavorTool.IsSet(flavour, SmiFlavors.AtomAtomMap) && atomClass != null) { ab.AtomClass(atomClass.Value); } return(ab.Build()); }
/// <summary> /// Given a molecule (possibly disconnected) compute the labels which /// would order the atoms by increasing canonical labelling. If the SMILES /// are isomeric (i.e. stereo and isotope specific) the InChI numbers are /// used. These numbers are loaded via reflection and the 'cdk-inchi' module /// should be present on the path. /// </summary> /// <param name="molecule">the molecule to</param> /// <returns>the permutation</returns> /// <seealso cref="Canon"/> private static int[] Labels(SmiFlavors flavour, IAtomContainer molecule) { // FIXME: use SmiOpt.InChiLabelling var labels = SmiFlavorTool.IsSet(flavour, SmiFlavors.Isomeric) ? InChINumbers(molecule) : Canon.Label(molecule, GraphUtil.ToAdjList(molecule), CreateComparator(molecule, flavour)); var cpy = new int[labels.Length]; for (int i = 0; i < labels.Length; i++) { cpy[i] = (int)labels[i] - 1; } return(cpy); }
/// <summary> /// Convert a CDK <see cref="IAtomContainer"/> to a Beam ChemicalGraph. The graph /// can when be written directly as to a SMILES or manipulated further (e.g /// canonical ordering/standard-form and other normalisations). /// </summary> /// <param name="ac">an atom container instance</param> /// <returns>the Beam ChemicalGraph for additional manipulation</returns> internal static Graph ToBeamGraph(IAtomContainer ac, SmiFlavors flavour) { int order = ac.Atoms.Count; var gb = GraphBuilder.Create(order); var indices = new Dictionary <IAtom, int>(order); foreach (var a in ac.Atoms) { indices[a] = indices.Count; gb.Add(ToBeamAtom(a, flavour)); } foreach (var b in ac.Bonds) { gb.Add(ToBeamEdge(b, flavour, indices)); } // configure stereo-chemistry by encoding the stereo-elements if (SmiFlavorTool.IsSet(flavour, SmiFlavors.Stereo)) { foreach (var se in ac.StereoElements) { if (SmiFlavorTool.IsSet(flavour, SmiFlavors.StereoTetrahedral) && se is ITetrahedralChirality) { AddTetrahedralConfiguration((ITetrahedralChirality)se, gb, indices); } else if (SmiFlavorTool.IsSet(flavour, SmiFlavors.StereoCisTrans) && se is IDoubleBondStereochemistry) { AddGeometricConfiguration((IDoubleBondStereochemistry)se, flavour, gb, indices); } else if (SmiFlavorTool.IsSet(flavour, SmiFlavors.StereoExTetrahedral) && se is ExtendedTetrahedral) { AddExtendedTetrahedralConfiguration((ExtendedTetrahedral)se, gb, indices); } else if (SmiFlavorTool.IsSet(flavour, SmiFlavors.StereoExCisTrans) && se is ExtendedCisTrans) { AddExtendedCisTransConfig((ExtendedCisTrans)se, gb, indices, ac); } } } return(gb.Build()); }
/// <summary> /// Convert a CDK <see cref="IBond"/> to the Beam edge label type. /// </summary> /// <param name="b">cdk bond</param> /// <returns>the edge label for the Beam edge</returns> /// <exception cref="NullReferenceException">the bond order was null and the bond was not-aromatic</exception> /// <exception cref="ArgumentException">the bond order could not be converted</exception> private static Bond ToBeamEdgeLabel(IBond b, SmiFlavors flavour) { if (SmiFlavorTool.IsSet(flavour, SmiFlavors.UseAromaticSymbols) && b.IsAromatic) { if (!b.Begin.IsAromatic || !b.End.IsAromatic) { throw new InvalidOperationException("Aromatic bond connects non-aromatic atomic atoms"); } return(Bond.Aromatic); } if (b.Order.IsUnset()) { throw new CDKException("A bond had undefined order, possible query bond?"); } var order = b.Order; switch (order) { case BondOrder.Single: return(Bond.Single); case BondOrder.Double: return(Bond.Double); case BondOrder.Triple: return(Bond.Triple); case BondOrder.Quadruple: return(Bond.Quadruple); default: throw new CDKException("Unsupported bond order: " + order); } }
internal static string Generate(CxSmilesState state, SmiFlavors opts, int[] components, int[] ordering) { if (!SmiFlavorTool.IsSet(opts, SmiFlavors.CxSmilesWithCoords)) { return(""); } var invorder = Inverse(ordering); var sb = new StringBuilder(); sb.Append(' '); sb.Append('|'); //int invComp(int a, int b) => invorder[a].CompareTo(invorder[b]); int comp(int a, int b) => ordering[a].CompareTo(ordering[b]); // Fragment Grouping if (SmiFlavorTool.IsSet(opts, SmiFlavors.CxFragmentGroup) && state.fragGroups != null && state.fragGroups.Any()) { int maxCompId = 0; foreach (int compId_ in components) { if (compId_ > maxCompId) { maxCompId = compId_; } } // get the output component order var compMap = new int[maxCompId + 1]; int compId = 1; foreach (int idx in invorder) { var component = components[idx]; if (compMap[component] == 0) { compMap[component] = compId++; } } // index vs number, we need to output index for (int i = 0; i < compMap.Length; i++) { compMap[i]--; } int compComp(int a, int b) => compMap[a].CompareTo(compMap[b]); var fragGroupCpy = new List <List <int> >(state.fragGroups); foreach (var idxs in fragGroupCpy) { idxs.Sort(compComp); } fragGroupCpy.Sort((a, b) => CxSmilesGenerator.Compare(compComp, a, b)); // C1=CC=CC=C1.C1=CC=CC=C1.[OH-].[Na+]>> |f:0.1,2.3,c:0,2,4,6,8,10| sb.Append('f'); sb.Append(':'); for (int i = 0; i < fragGroupCpy.Count; i++) { if (i > 0) { sb.Append(','); } AppendIntegers(compMap, '.', sb, fragGroupCpy[i]); } } // Atom Labels if (SmiFlavorTool.IsSet(opts, SmiFlavors.CxAtomLabel) && state.atomLabels != null && state.atomLabels.Any()) { if (sb.Length > 2) { sb.Append(','); } sb.Append('$'); int nonempty_cnt = 0; foreach (int idx in invorder) { if (!state.atomLabels.TryGetValue(idx, out string label)) { label = ""; } else { nonempty_cnt++; } sb.Append(Encode_alias(label)); // don't need to write anymore more ';' if (nonempty_cnt == state.atomLabels.Count) { break; } sb.Append(";"); } sb.Append('$'); } // Atom Values if (SmiFlavorTool.IsSet(opts, SmiFlavors.CxAtomValue) && state.atomValues != null && state.atomValues.Any()) { if (sb.Length > 2) { sb.Append(','); } sb.Append("$_AV:"); int nonempty_cnt = 0; foreach (int idx in invorder) { var label = state.atomValues[idx]; if (string.IsNullOrEmpty(label)) { label = ""; } else { nonempty_cnt++; } sb.Append(Encode_alias(label)); // don't need to write anymore more ';' if (nonempty_cnt == state.atomValues.Count) { break; } sb.Append(";"); } sb.Append('$'); } // 2D/3D Coordinates if (SmiFlavorTool.IsSet(opts, SmiFlavors.CxCoordinates) && state.atomCoords != null && state.atomCoords.Any()) { if (sb.Length > 2) { sb.Append(','); } sb.Append('('); for (int i = 0; i < ordering.Length; i++) { var xyz = state.atomCoords[invorder[i]]; if (i != 0) { sb.Append(';'); } if (xyz[0] != 0) { sb.Append(Strings.ToSimpleString(xyz[0], 2)); } sb.Append(','); if (xyz[1] != 0) { sb.Append(Strings.ToSimpleString(xyz[1], 2)); } sb.Append(','); if (xyz[2] != 0) { sb.Append(Strings.ToSimpleString(xyz[2], 2)); } } sb.Append(')'); } // Multi-center/Positional variation bonds if (SmiFlavorTool.IsSet(opts, SmiFlavors.CxMulticenter) && state.positionVar != null && state.positionVar.Any()) { if (sb.Length > 2) { sb.Append(','); } sb.Append('m'); sb.Append(':'); var multicenters = new List <KeyValuePair <int, IList <int> > >(state.positionVar); // consistent output order multicenters.Sort((a, b) => comp(a.Key, b.Key)); for (int i = 0; i < multicenters.Count; i++) { if (i != 0) { sb.Append(','); } var e = multicenters[i]; sb.Append(ordering[e.Key]); sb.Append(':'); var vals = new List <int>(e.Value); vals.Sort(comp); AppendIntegers(ordering, '.', sb, vals); } } // *CCO* |$_AP1;;;;_AP2$,Sg:n:1,2,3::ht| if (SmiFlavorTool.IsSet(opts, SmiFlavors.CxPolymer) && state.sgroups != null && state.sgroups.Any()) { var sgroups = new List <PolymerSgroup>(state.sgroups); foreach (PolymerSgroup psgroup in sgroups) { psgroup.atomset.Sort(comp); } sgroups.Sort((a, b) => { int cmp = 0; cmp = string.CompareOrdinal(a.type, b.type); if (cmp != 0) { return(cmp); } cmp = CxSmilesGenerator.Compare(comp, a.atomset, b.atomset); return(cmp); }); for (int i = 0; i < sgroups.Count; i++) { if (sb.Length > 2) { sb.Append(','); } sb.Append("Sg:"); var sgroup = sgroups[i]; sb.Append(sgroup.type); sb.Append(':'); AppendIntegers(ordering, ',', sb, sgroup.atomset); sb.Append(':'); if (sgroup.subscript != null) { sb.Append(sgroup.subscript); } sb.Append(':'); if (sgroup.supscript != null) { sb.Append(sgroup.supscript.ToLowerInvariant()); } } } // [C]1[CH][CH]CCC1 |^1:1,2,^3:0| if (SmiFlavorTool.IsSet(opts, SmiFlavors.CxRadical) && state.atomRads != null && state.atomRads.Any()) { var radinv = new SortedDictionary <CxSmilesState.Radical, List <int> >(); foreach (var e in state.atomRads) { if (!radinv.TryGetValue(e.Value, out List <int> idxs)) { radinv[e.Value] = idxs = new List <int>(); } idxs.Add(e.Key); } foreach (var e in radinv) { if (sb.Length > 2) { sb.Append(','); } sb.Append('^'); sb.Append((int)e.Key + 1); sb.Append(':'); e.Value.Sort(comp); AppendIntegers(ordering, ',', sb, e.Value); } } sb.Append('|'); if (sb.Length <= 3) { return(""); } else { return(sb.ToString()); } }
public int Compare(IAtom a, IAtom b) { var aBonds = mol.GetConnectedBonds(a).ToReadOnlyList(); var bBonds = mol.GetConnectedBonds(b).ToReadOnlyList(); var aH = Unbox(a.ImplicitHydrogenCount); var bH = Unbox(b.ImplicitHydrogenCount); int cmp; // 1) Connectivity, X=D+h if ((cmp = (aBonds.Count + aH).CompareTo(bBonds.Count + bH)) != 0) { return(cmp); } // 2) Degree, D if ((cmp = aBonds.Count.CompareTo(bBonds.Count)) != 0) { return(cmp); } // 3) Element, #<N> if ((cmp = Unbox(a.AtomicNumber).CompareTo(Unbox(b.AtomicNumber))) != 0) { return(cmp); } // 4a) charge sign int aQ = Unbox(a.FormalCharge); int bQ = Unbox(b.FormalCharge); if ((cmp = ((aQ >> 31) & 0x1).CompareTo((bQ >> 31) & 0x1)) != 0) { return(cmp); } // 4b) charge magnitude if ((cmp = Math.Abs(aQ).CompareTo(Math.Abs(bQ))) != 0) { return(cmp); } int aTotalH = aH; int bTotalH = bH; foreach (var bond in aBonds) { aTotalH += bond.GetOther(a).AtomicNumber == 1 ? 1 : 0; } foreach (var bond in bBonds) { bTotalH += bond.GetOther(b).AtomicNumber == 1 ? 1 : 0; } // 5) total H count if ((cmp = aTotalH.CompareTo(bTotalH)) != 0) { return(cmp); } // XXX: valence and ring membership should also be used to split // ties, but will change the current canonical labelling! // extra 1) atomic mass if (SmiFlavorTool.IsSet(flavor, SmiFlavors.Isomeric) && (cmp = Unbox(a.MassNumber).CompareTo(b.MassNumber)) != 0) { return(cmp); } // extra 2) atom map if (SmiFlavorTool.IsSet(flavor, SmiFlavors.AtomAtomMap) && (flavor & SmiFlavors.AtomAtomMapRenumber) != SmiFlavors.AtomAtomMapRenumber) { var aMapIdx = a.GetProperty <int>(CDKPropertyName.AtomAtomMapping); var bMapIdx = b.GetProperty <int>(CDKPropertyName.AtomAtomMapping); if ((cmp = Unbox(aMapIdx).CompareTo(Unbox(bMapIdx))) != 0) { return(cmp); } } return(0); }
// Creates a CxSmilesState from a molecule with atom labels, repeat units, multi-center bonds etc private static CxSmilesState GetCxSmilesState(SmiFlavors flavour, IAtomContainer mol) { CxSmilesState state = new CxSmilesState { atomCoords = new List <double[]>(), coordFlag = false }; // set the atom labels, values, and coordinates, // and build the atom->idx map required by other parts var atomidx = new Dictionary <IAtom, int>(); for (int idx = 0; idx < mol.Atoms.Count; idx++) { IAtom atom = mol.Atoms[idx]; if (atom is IPseudoAtom) { if (state.atomLabels == null) { state.atomLabels = new SortedDictionary <int, string>(); } IPseudoAtom pseudo = (IPseudoAtom)atom; if (pseudo.AttachPointNum > 0) { state.atomLabels[idx] = "_AP" + pseudo.AttachPointNum; } else { if (!"*".Equals(pseudo.Label, StringComparison.Ordinal)) { state.atomLabels[idx] = pseudo.Label; } } } object comment = atom.GetProperty <object>(CDKPropertyName.Comment); if (comment != null) { if (state.atomValues == null) { state.atomValues = new SortedDictionary <int, string>(); } state.atomValues[idx] = comment.ToString(); } atomidx[atom] = idx; var p2 = atom.Point2D; var p3 = atom.Point3D; if (SmiFlavorTool.IsSet(flavour, SmiFlavors.Cx2dCoordinates) && p2 != null) { state.atomCoords.Add(new double[] { p2.Value.X, p2.Value.Y, 0 }); state.coordFlag = true; } else if (SmiFlavorTool.IsSet(flavour, SmiFlavors.Cx3dCoordinates) && p3 != null) { state.atomCoords.Add(new double[] { p3.Value.X, p3.Value.Y, p3.Value.Z }); state.coordFlag = true; } else if (SmiFlavorTool.IsSet(flavour, SmiFlavors.CxCoordinates)) { state.atomCoords.Add(new double[3]); } } if (!state.coordFlag) { state.atomCoords = null; } // radicals if (mol.SingleElectrons.Count > 0) { state.atomRads = new SortedDictionary <int, CxSmilesState.Radical>(); foreach (ISingleElectron radical in mol.SingleElectrons) { // 0->1, 1->2, 2->3 if (!state.atomRads.TryGetValue(EnsureNotNull(atomidx[radical.Atom]), out CxSmilesState.Radical val)) { val = CxSmilesState.Radical.Monovalent; } else if (val == CxSmilesState.Radical.Monovalent) { val = CxSmilesState.Radical.Divalent; } else if (val == CxSmilesState.Radical.Divalent) { val = CxSmilesState.Radical.Trivalent; } else if (val == CxSmilesState.Radical.Trivalent) { throw new ArgumentException("Invalid radical state, can not be more than trivalent"); } state.atomRads[atomidx[radical.Atom]] = val; } } var sgroups = mol.GetCtabSgroups(); if (sgroups != null) { state.sgroups = new List <CxSmilesState.PolymerSgroup>(); state.positionVar = new SortedDictionary <int, IList <int> >(); foreach (Sgroup sgroup in sgroups) { switch (sgroup.Type) { // polymer SRU case SgroupType.CtabStructureRepeatUnit: case SgroupType.CtabMonomer: case SgroupType.CtabMer: case SgroupType.CtabCopolymer: case SgroupType.CtabCrossLink: case SgroupType.CtabModified: case SgroupType.CtabMixture: case SgroupType.CtabFormulation: case SgroupType.CtabAnyPolymer: case SgroupType.CtabGeneric: case SgroupType.CtabComponent: case SgroupType.CtabGraft: string supscript = (string)sgroup.GetValue(SgroupKey.CtabConnectivity); state.sgroups.Add(new CxSmilesState.PolymerSgroup(GetSgroupPolymerKey(sgroup), ToAtomIdxs(sgroup.Atoms, atomidx), sgroup.Subscript, supscript)); break; case SgroupType.ExtMulticenter: IAtom beg = null; List <IAtom> ends = new List <IAtom>(); ISet <IBond> bonds = sgroup.Bonds; if (bonds.Count != 1) { throw new ArgumentException("Multicenter Sgroup in inconsistent state!"); } IBond bond = bonds.First(); foreach (IAtom atom in sgroup.Atoms) { if (bond.Contains(atom)) { if (beg != null) { throw new ArgumentException("Multicenter Sgroup in inconsistent state!"); } beg = atom; } else { ends.Add(atom); } } state.positionVar[EnsureNotNull(atomidx[beg])] = ToAtomIdxs(ends, atomidx); break; case SgroupType.CtabAbbreviation: case SgroupType.CtabMultipleGroup: // display shortcuts are not output break; case SgroupType.CtabData: // can be generated but currently ignored break; default: throw new NotSupportedException("Unsupported Sgroup Polymer"); } } } return(state); }
/// <summary> /// Create a SMILES for a reaction of the flavour specified in the constructor and /// write the output order to the provided array. /// </summary> /// <param name="reaction">CDK reaction instance</param> /// <param name="ordering">order of output</param> /// <returns>reaction SMILES</returns> public string Create(IReaction reaction, int[] ordering) { var reactants = reaction.Reactants; var agents = reaction.Agents; var products = reaction.Products; var reactantPart = reaction.Builder.NewAtomContainer(); var agentPart = reaction.Builder.NewAtomContainer(); var productPart = reaction.Builder.NewAtomContainer(); var sgroups = new List <Sgroup>(); foreach (var reactant in reactants) { reactantPart.Add(reactant); SafeAddSgroups(sgroups, reactant); } foreach (var agent in agents) { agentPart.Add(agent); SafeAddSgroups(sgroups, agent); } foreach (var product in products) { productPart.Add(product); SafeAddSgroups(sgroups, product); } var reactantOrder = new int[reactantPart.Atoms.Count]; var agentOrder = new int[agentPart.Atoms.Count]; var productOrder = new int[productPart.Atoms.Count]; var expectedSize = reactantOrder.Length + agentOrder.Length + productOrder.Length; if (expectedSize != ordering.Length) { throw new CDKException($"Output ordering array does not have correct amount of space: {ordering.Length} expected: {expectedSize}"); } // we need to make sure we generate without the CXSMILES layers string smi = Create(reactantPart, flavour & ~SmiFlavors.CxSmilesWithCoords, reactantOrder) + ">" + Create(agentPart, flavour & ~SmiFlavors.CxSmilesWithCoords, agentOrder) + ">" + Create(productPart, flavour & ~SmiFlavors.CxSmilesWithCoords, productOrder); // copy ordering back to unified array and adjust values var agentBeg = reactantOrder.Length; var agentEnd = reactantOrder.Length + agentOrder.Length; var prodEnd = reactantOrder.Length + agentOrder.Length + productOrder.Length; System.Array.Copy(reactantOrder, 0, ordering, 0, agentBeg); System.Array.Copy(agentOrder, 0, ordering, agentBeg, agentEnd - agentBeg); System.Array.Copy(productOrder, 0, ordering, agentEnd, prodEnd - agentEnd); for (int i = agentBeg; i < agentEnd; i++) { ordering[i] += agentBeg; } for (int i = agentEnd; i < prodEnd; i++) { ordering[i] += agentEnd; } if (SmiFlavorTool.IsSet(flavour, SmiFlavors.CxSmilesWithCoords)) { var unified = reaction.Builder.NewAtomContainer(); unified.Add(reactantPart); unified.Add(agentPart); unified.Add(productPart); unified.SetCtabSgroups(sgroups); // base CXSMILES state information var cxstate = GetCxSmilesState(flavour, unified); int[] components = null; // extra state info on fragment grouping, specific to reactions if (SmiFlavorTool.IsSet(flavour, SmiFlavors.CxFragmentGroup)) { cxstate.fragGroups = new List <List <int> >(); // calculate the connected components components = new ConnectedComponents(GraphUtil.ToAdjList(unified)).GetComponents(); // AtomContainerSet is ordered so this is safe, it was actually a set we // would need some extra data structures var tmp = new HashSet <int>(); int beg = 0, end = 0; foreach (var mol in reactants) { end = end + mol.Atoms.Count; tmp.Clear(); for (int i = beg; i < end; i++) { tmp.Add(components[i]); } if (tmp.Count > 1) { cxstate.fragGroups.Add(new List <int>(tmp)); } beg = end; } foreach (var mol in agents) { end = end + mol.Atoms.Count; tmp.Clear(); for (int i = beg; i < end; i++) { tmp.Add(components[i]); } if (tmp.Count > 1) { cxstate.fragGroups.Add(new List <int>(tmp)); } beg = end; } foreach (var mol in products) { end = end + mol.Atoms.Count; tmp.Clear(); for (int i = beg; i < end; i++) { tmp.Add(components[i]); } if (tmp.Count > 1) { cxstate.fragGroups.Add(new List <int>(tmp)); } beg = end; } } smi += CxSmilesGenerator.Generate(cxstate, flavour, components, ordering); } return(smi); }
/// <summary> /// Creates a SMILES string of the flavour specified as a parameter /// and write the output order to the provided array. /// </summary> /// <remarks> /// The output order allows one to arrange auxiliary atom data in the /// order that a SMILES string will be read. A simple example is seen below /// where 2D coordinates are stored with a SMILES string. This method /// forms the basis of CXSMILES. /// </remarks> /// <example> /// <include file='IncludeExamples.xml' path='Comments/Codes[@id="NCDK.Smiles.SmilesGenerator_Example.cs+Create_IAtomContainer_int_int"]/*' /> /// </example> /// <param name="molecule">the molecule to write</param> /// <param name="order">array to store the output order of atoms</param> /// <returns>the SMILES string</returns> /// <exception cref="CDKException">a valid SMILES could not be created</exception> public static string Create(IAtomContainer molecule, SmiFlavors flavour, int[] order) { try { if (order.Length != molecule.Atoms.Count) { throw new ArgumentException("the array for storing output order should be the same length as the number of atoms"); } var g = CDKToBeam.ToBeamGraph(molecule, flavour); // apply the canonical labelling if (SmiFlavorTool.IsSet(flavour, SmiFlavors.Canonical)) { // determine the output order var labels = Labels(flavour, molecule); g = g.Permute(labels); if ((flavour & SmiFlavors.AtomAtomMapRenumber) == SmiFlavors.AtomAtomMapRenumber) { g = Functions.RenumberAtomMaps(g); } if (!SmiFlavorTool.IsSet(flavour, SmiFlavors.UseAromaticSymbols)) { g = g.Resonate(); } if (SmiFlavorTool.IsSet(flavour, SmiFlavors.StereoCisTrans)) { // FIXME: required to ensure canonical double bond labelling g.Sort(new Graph.VisitHighOrderFirst()); // canonical double-bond stereo, output be C/C=C/C or C\C=C\C // and we need to normalise to one g = Functions.NormaliseDirectionalLabels(g); // visit double bonds first, prefer C1=CC=C1 to C=1C=CC1 // visit hydrogens first g.Sort(new Graph.VisitHighOrderFirst()).Sort(new Graph.VisitHydrogenFirst()); } var smiles = g.ToSmiles(order); // the SMILES has been generated on a reordered molecule, transform // the ordering var canorder = new int[order.Length]; for (int i = 0; i < labels.Length; i++) { canorder[i] = order[labels[i]]; } System.Array.Copy(canorder, 0, order, 0, order.Length); if (SmiFlavorTool.IsSet(flavour, SmiFlavors.CxSmilesWithCoords)) { smiles += CxSmilesGenerator.Generate(GetCxSmilesState(flavour, molecule), flavour, null, order); } return(smiles); } else { string smiles = g.ToSmiles(order); if (SmiFlavorTool.IsSet(flavour, SmiFlavors.CxSmilesWithCoords)) { smiles += CxSmilesGenerator.Generate(GetCxSmilesState(flavour, molecule), flavour, null, order); } return(smiles); } } catch (IOException e) { throw new CDKException(e.Message); } }