public void Multicenter() { var state = new CxSmilesState { positionVar = new SortedDictionary <int, IList <int> >() }; state.positionVar[0] = new[] { 4, 5, 6, 7 }; state.positionVar[2] = new[] { 4, 6, 5, 7 }; Assert.AreEqual(" |m:5:0.1.2.3,7:0.1.2.3|", CxSmilesGenerator.Generate(state, SmiFlavors.CxMulticenter, Array.Empty <int>(), new int[] { 7, 6, 5, 4, 3, 2, 1, 0 })); }
public void Sgroups() { CxSmilesState state = new CxSmilesState { sgroups = new List <CxSmilesState.PolymerSgroup>(1) { new CxSmilesState.PolymerSgroup("n", new[] { 2, 3 }, "n", "ht"), new CxSmilesState.PolymerSgroup("n", new[] { 5 }, "m", "ht") } }; Assert.AreEqual(" |Sg:n:2:m:ht,Sg:n:4,5:n:ht|", CxSmilesGenerator.Generate(state, SmiFlavors.CxPolymer, Array.Empty <int>(), new int[] { 7, 6, 5, 4, 3, 2, 1, 0 })); }
public void Radicals() { CxSmilesState state = new CxSmilesState { atomRads = new SortedDictionary <int, CxSmilesState.Radical> { [2] = CxSmilesState.Radical.Monovalent, [6] = CxSmilesState.Radical.Monovalent, [4] = CxSmilesState.Radical.Divalent } }; Assert.AreEqual(" |^1:1,5,^2:3|", CxSmilesGenerator.Generate(state, SmiFlavors.CxSmiles, Array.Empty <int>(), new int[] { 7, 6, 5, 4, 3, 2, 1, 0 })); }
public void Coords2d() { CxSmilesState state = new CxSmilesState { atomCoords = new List <double[]> { new double[] { 0, 1.5, 0 }, new double[] { 0, 3, 0 }, new double[] { 1.5, 1.5, 0 }, } }; Assert.AreEqual(" |(1.5,1.5,;,1.5,;,3,)|", CxSmilesGenerator.Generate(state, SmiFlavors.CxCoordinates, Array.Empty <int>(), new int[] { 1, 2, 0 })); }
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 void EmptyCXSMILES() { CxSmilesState state = new CxSmilesState(); Assert.AreEqual("", CxSmilesGenerator.Generate(state, SmiFlavors.CxSmiles, Array.Empty <int>(), Array.Empty <int>())); }
/// <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); } }