/// <summary> /// Convert a CDK <see cref="IBond"/> to a Beam edge. /// </summary> /// <param name="b">the CDK bond</param> /// <param name="indices">map of atom indices</param> /// <returns>a Beam edge</returns> /// <exception cref="ArgumentException">the bond did not have 2 atoms or an unsupported order</exception> /// <exception cref="NullReferenceException">the bond order was undefined</exception> internal static Edge ToBeamEdge(IBond b, SmiFlavors flavour, Dictionary <IAtom, int> indices) { CheckArgument(b.Atoms.Count == 2, "Invalid number of atoms on bond"); var u = indices[b.Begin]; var v = indices[b.End]; return(ToBeamEdgeLabel(b, flavour).CreateEdge(u, v)); }
static Beam.Graph Convert(IAtomContainer ac, bool perceiveAromaticity, SmiFlavors options) { AtomContainerManipulator.PercieveAtomTypesAndConfigureAtoms(ac); CDK.HydrogenAdder.AddImplicitHydrogens(ac); if (perceiveAromaticity) { Aromaticity.CDKLegacy.Apply(ac); } return(new CDKToBeam(options).ToBeamGraph(ac)); }
/// <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); } }
static Beam.Graph Convert(IAtomContainer ac, SmiFlavors options) { return(Convert(ac, false, options)); }
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()); } }
internal CDKToBeam(SmiFlavors flavour) { this.flavour = flavour; }
/// <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); } }
internal static bool IsSet(this SmiFlavors opts, SmiFlavors opt) { return((opts & opt) != 0); }
static IComparer <IAtom> CreateComparator(IAtomContainer mol, SmiFlavors flavor) { return(new Comparer(mol, flavor)); }
public Comparer(IAtomContainer mol, SmiFlavors flavor) { this.mol = mol; this.flavor = flavor; }
// 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> /// 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); } }
/// <summary> /// Create a SMILES generator with the specified <see cref="SmiFlavors"/>. /// </summary> /// <example> /// <include file='IncludeExamples.xml' path='Comments/Codes[@id="NCDK.Smiles.SmilesGenerator_Example.cs+ctor_SmiFlavor"]/*' /> /// </example> /// <param name="flavour">SMILES flavour flags <see cref="SmiFlavors"/></param> public SmilesGenerator(SmiFlavors flavour) { this.flavour = flavour; }