$darkmode
DENOPTIM
MoleculeUtils.java
Go to the documentation of this file.
1/*
2 * DENOPTIM
3 * Copyright (C) 2019 Vishwesh Venkatraman <vishwesh.venkatraman@ntnu.no> and
4 * Marco Foscato <marco.foscato@uib.no>
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as published
8 * by the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20package denoptim.utils;
21
22import java.io.File;
23import java.util.ArrayList;
24import java.util.Arrays;
25import java.util.Collections;
26import java.util.HashMap;
27import java.util.HashSet;
28import java.util.List;
29import java.util.Map;
30import java.util.Set;
31import java.util.logging.Level;
32import java.util.logging.Logger;
33
34import javax.vecmath.Point2d;
35import javax.vecmath.Point3d;
36
37import org.openscience.cdk.Atom;
38import org.openscience.cdk.AtomContainer;
39import org.openscience.cdk.AtomRef;
40import org.openscience.cdk.CDKConstants;
41import org.openscience.cdk.Mapping;
42import org.openscience.cdk.PseudoAtom;
43import org.openscience.cdk.aromaticity.Kekulization;
44import org.openscience.cdk.config.IsotopeFactory;
45import org.openscience.cdk.config.Isotopes;
46import org.openscience.cdk.depict.Depiction;
47import org.openscience.cdk.depict.DepictionGenerator;
48import org.openscience.cdk.exception.CDKException;
49import org.openscience.cdk.geometry.GeometryUtil;
50import org.openscience.cdk.graph.ConnectivityChecker;
51import org.openscience.cdk.inchi.InChIGenerator;
52import org.openscience.cdk.inchi.InChIGeneratorFactory;
53import org.openscience.cdk.interfaces.IAtom;
54import org.openscience.cdk.interfaces.IAtomContainer;
55import org.openscience.cdk.interfaces.IAtomContainerSet;
56import org.openscience.cdk.interfaces.IAtomType.Hybridization;
57import org.openscience.cdk.interfaces.IBond;
58import org.openscience.cdk.interfaces.IChemObjectBuilder;
59import org.openscience.cdk.interfaces.IElement;
60import org.openscience.cdk.isomorphism.Mappings;
61import org.openscience.cdk.isomorphism.Pattern;
62import org.openscience.cdk.layout.StructureDiagramGenerator;
63import org.openscience.cdk.qsar.DescriptorValue;
64import org.openscience.cdk.qsar.IMolecularDescriptor;
65import org.openscience.cdk.qsar.descriptors.molecular.RotatableBondsCountDescriptor;
66import org.openscience.cdk.qsar.descriptors.molecular.WeightDescriptor;
67import org.openscience.cdk.qsar.result.DoubleResult;
68import org.openscience.cdk.qsar.result.IntegerResult;
69import org.openscience.cdk.silent.SilentChemObjectBuilder;
70import org.openscience.cdk.smiles.SmiFlavor;
71import org.openscience.cdk.smiles.SmilesGenerator;
72import org.openscience.cdk.tools.manipulator.AtomContainerManipulator;
73
74import denoptim.constants.DENOPTIMConstants;
75import denoptim.exception.DENOPTIMException;
76import denoptim.graph.APClass;
77import denoptim.graph.AttachmentPoint;
78import denoptim.graph.DGraph;
79import denoptim.graph.Edge.BondType;
80import denoptim.graph.Fragment;
81import denoptim.graph.Ring;
82import denoptim.graph.Vertex;
83import denoptim.graph.Vertex.BBType;
84import denoptim.graph.rings.RingClosingAttractor;
85import denoptim.io.DenoptimIO;
86import denoptim.logging.StaticLogger;
87import io.github.dan2097.jnainchi.InchiFlag;
88import io.github.dan2097.jnainchi.InchiOptions;
89import io.github.dan2097.jnainchi.InchiStatus;
90
91
97public class MoleculeUtils
98{
99 private static final StructureDiagramGenerator SDG =
100 new StructureDiagramGenerator();
101 private static final SmilesGenerator SMGEN = new SmilesGenerator(
102 SmiFlavor.Generic);
103 private static final IChemObjectBuilder builder =
104 SilentChemObjectBuilder.getInstance();
105
106//------------------------------------------------------------------------------
107
108 public static double getMolecularWeight(IAtomContainer mol)
109 throws DENOPTIMException
110 {
111 double ret_wd = 0;
112 try
113 {
114 WeightDescriptor wd = new WeightDescriptor();
115 Object[] pars = {"*"};
116 wd.setParameters(pars);
117 ret_wd = ((DoubleResult) wd.calculate(mol).getValue())
118 .doubleValue();
119 }
120 catch (Exception ex)
121 {
122 throw new DENOPTIMException(ex);
123 }
124 return ret_wd;
125 }
126
127//------------------------------------------------------------------------------
128
138 public static boolean isDummy(IAtom atm)
139 {
140 String el = getSymbolOrLabel(atm);
141 return DENOPTIMConstants.DUMMYATMSYMBOL.equals(el);
142 }
143
144//------------------------------------------------------------------------------
145
153 public static boolean isElement(IAtom atom)
154 {
155 String symbol = atom.getSymbol();
156 return isElement(symbol);
157 }
158
159//------------------------------------------------------------------------------
160
168 public static boolean isElement(String symbol)
169 {
170 boolean res = false;
171 IsotopeFactory ifact = null;
172 try {
173 ifact = Isotopes.getInstance();
174 if (ifact.isElement(symbol))
175 {
176 @SuppressWarnings("unused")
177 IElement el = ifact.getElement(symbol);
178 res = true;
179 }
180 } catch (Throwable t) {
181 throw new Error("ERROR! Unable to create Isotope.");
182 }
183 return res;
184 }
185
186//------------------------------------------------------------------------------
187
194 public static void removeRCA(IAtomContainer mol) {
195
196 // convert PseudoAtoms to H
197 for (IAtom a : mol.atoms())
198 {
199 boolean isRca = false;
200 Set<String> rcaElSymbols = RingClosingAttractor.RCATYPEMAP.keySet();
201 for (String rcaEl : rcaElSymbols)
202 {
203 if (MoleculeUtils.getSymbolOrLabel(a).equals(rcaEl))
204 {
205 isRca = true;
206 break;
207 }
208 }
209 if (isRca)
210 {
211 IAtom newAtm = new Atom("H",new Point3d(a.getPoint3d()));
212 newAtm.setProperties(a.getProperties());
213 AtomContainerManipulator.replaceAtomByAtom(mol,a,newAtm);
214 }
215 }
216 }
217
218//------------------------------------------------------------------------------
219
228 public static void removeUsedRCA(IAtomContainer mol, DGraph graph,
229 Logger logger) throws DENOPTIMException {
230
231 // add ring-closing bonds
232 ArrayList<Vertex> usedRcvs = graph.getUsedRCVertices();
233 Map<Vertex,ArrayList<Integer>> vIdToAtmId =
235 if (vIdToAtmId.size() == 0)
236 {
237 // No used RCV to remove.
238 return;
239 }
240 ArrayList<IAtom> atmsToRemove = new ArrayList<>();
241 ArrayList<Boolean> doneVertices =
242 new ArrayList<>(Collections.nCopies(usedRcvs.size(),false));
243
244 for (Vertex v : usedRcvs)
245 {
246 if (doneVertices.get(usedRcvs.indexOf(v)))
247 {
248 continue;
249 }
250 ArrayList<Ring> rings = graph.getRingsInvolvingVertex(v);
251 if (rings.size() != 1)
252 {
253 String s = "Unexpected inconsistency between used RCV list "
254 + v + " in {" + usedRcvs + "}"
255 + "and list of DENOPTIMRings "
256 + "{" + rings + "}. Check Code!";
257 throw new DENOPTIMException(s);
258 }
259 Vertex vH = rings.get(0).getHeadVertex();
260 Vertex vT = rings.get(0).getTailVertex();
261 IAtom aH = mol.getAtom(vIdToAtmId.get(vH).get(0));
262 IAtom aT = mol.getAtom(vIdToAtmId.get(vT).get(0));
263 if (mol.getConnectedAtomsList(aH).size() == 0
264 || mol.getConnectedAtomsList(aT).size() == 0)
265 {
266 // This can happen when building a graph with empty vertexes
267 continue;
268 }
269 int iSrcH = mol.indexOf(mol.getConnectedAtomsList(aH).get(0));
270 int iSrcT = mol.indexOf(mol.getConnectedAtomsList(aT).get(0));
271 atmsToRemove.add(aH);
272 atmsToRemove.add(aT);
273
274 BondType bndTyp = rings.get(0).getBondType();
275 if (bndTyp.hasCDKAnalogue())
276 {
277 mol.addBond(iSrcH, iSrcT, bndTyp.getCDKOrder());
278 } else {
279 logger.log(Level.WARNING, "WARNING! "
280 + "Attempt to add ring closing bond "
281 + "did not add any actual chemical bond because the "
282 + "bond type of the chord is '" + bndTyp +"'.");
283 }
284
285 doneVertices.set(usedRcvs.indexOf(vH),true);
286 doneVertices.set(usedRcvs.indexOf(vT),true);
287 }
288
289 // Adapt atom indexes in APs to the upcoming change of atom list
290 ArrayList<Integer> removedIds = new ArrayList<Integer>();
291 for (IAtom a : atmsToRemove)
292 {
293 removedIds.add(mol.indexOf(a));
294 }
295 Collections.sort(removedIds);
296 for (Vertex v : graph.getVertexList())
297 {
298 for (AttachmentPoint ap : v.getAttachmentPoints())
299 {
300 int apSrcId = ap.getAtomPositionNumberInMol();
301 int countOfAtmsBEforeSrc = 0;
302 for (Integer removingId : removedIds)
303 {
304 if (removingId < apSrcId)
305 {
306 countOfAtmsBEforeSrc++;
307 } else if (removingId > apSrcId)
308 {
309 break;
310 }
311 }
312 ap.setAtomPositionNumberInMol(apSrcId-countOfAtmsBEforeSrc);
313 }
314 }
315
316 // remove used RCAs
317 for (IAtom a : atmsToRemove)
318 {
319 mol.removeAtom(a);
320 }
321 }
322
323//------------------------------------------------------------------------------
324
334 public static String getSMILESForMolecule(IAtomContainer mol, Logger logger)
335 throws DENOPTIMException {
336 IAtomContainer fmol = builder.newAtomContainer();
337 try
338 {
339 fmol = mol.clone();
340 }
341 catch (CloneNotSupportedException e)
342 {
343 throw new DENOPTIMException(e);
344 }
345
346 // remove Dummy atoms
349 fmol = dan.removeDummyInHapto(fmol);
350 fmol = dan.removeDummy(fmol);
351
352 // convert PseudoAtoms to H
353 removeRCA(fmol);
354
355 // WARNING: assumptions on implicit H count and bond orders!
358
359 String smiles = "";
360 try
361 {
362 smiles = SMGEN.create(fmol);
363 }
364 catch (Throwable t)
365 {
366 t.printStackTrace();
367 String fileName = "failed_generation_of_SMILES.sdf";
368 logger.log(Level.WARNING, "WARNING: Skipping calculation of SMILES. "
369 + "See file '" + fileName + "'");
370 DenoptimIO.writeSDFFile(fileName,fmol,false);
371 smiles = "calculation_or_SMILES_crashed";
372 }
373 return smiles;
374 }
375
376//------------------------------------------------------------------------------
377
388 public static IAtomContainer generate2DCoordinates(IAtomContainer ac,
389 Logger logger) throws DENOPTIMException
390 {
391 IAtomContainer fmol = builder.newAtomContainer();
392 try
393 {
394 fmol = ac.clone();
395 }
396 catch (CloneNotSupportedException e)
397 {
398 throw new DENOPTIMException(e);
399 }
400
401 // remove Dummy atoms
404 fmol = dan.removeDummyInHapto(fmol);
405
406 // remove ring-closing attractors
407 removeRCA(fmol);
408
409 // Generate 2D structure diagram (for each connected component).
410 IAtomContainer ac2d = builder.newAtomContainer();
411 IAtomContainerSet som = ConnectivityChecker.partitionIntoMolecules(
412 fmol);
413
414 for (int n = 0; n < som.getAtomContainerCount(); n++)
415 {
416 synchronized (SDG)
417 {
418 IAtomContainer mol = som.getAtomContainer(n);
419 SDG.setMolecule(mol, true);
420 try
421 {
422 // Generate 2D coordinates for this molecule.
423 SDG.generateCoordinates();
424 mol = SDG.getMolecule();
425 }
426 catch (Exception e)
427 {
428 throw new DENOPTIMException(e);
429 }
430
431 ac2d.add(mol); // add 2D molecule.
432 }
433 }
434
435 return GeometryUtil.has2DCoordinates(ac2d) ? ac2d : null;
436 }
437
438//------------------------------------------------------------------------------
439
451 public static String getInChIKeyForMolecule(IAtomContainer mol,
452 Logger logger) throws DENOPTIMException {
453 InchiOptions options = new InchiOptions.InchiOptionsBuilder()
454 .withFlag(InchiFlag.AuxNone)
455 .withFlag(InchiFlag.RecMet)
456 .withFlag(InchiFlag.SUU)
457 .build();
458 return getInChIKeyForMolecule(mol, options, logger);
459 }
460
461//------------------------------------------------------------------------------
462
471 public static String getInChIKeyForMolecule(IAtomContainer mol,
472 InchiOptions options, Logger logger) throws DENOPTIMException {
473 IAtomContainer fmol = builder.newAtomContainer();
474 try
475 {
476 fmol = mol.clone();
477 }
478 catch (Throwable t)
479 {
480 throw new DENOPTIMException(t);
481 }
482
483 // remove Dummy atoms before generating the inchi
486 fmol = dan.removeDummyInHapto(fmol);
487
488 // remove PseudoAtoms
489 removeRCA(fmol);
490
491 String inchikey;
492 try
493 {
494 InChIGeneratorFactory factory = InChIGeneratorFactory.getInstance();
495 InChIGenerator gen = factory.getInChIGenerator(fmol, options);
496 InchiStatus ret = gen.getStatus();
497 if (ret == InchiStatus.WARNING)
498 {
499 //String error = gen.getMessage();
500 //InChI generated, but with warning message
501 //return new ObjectPair(null, error);
502 }
503 else if (ret != InchiStatus.SUCCESS)
504 {
505 return null;
506 }
507 inchikey = gen.getInchiKey();
508 }
509 catch (CDKException cdke)
510 {
511 throw new DENOPTIMException(cdke);
512 }
513 if (inchikey.length() > 0)
514 return inchikey;
515 else
516 return null;
517 }
518
519//------------------------------------------------------------------------------
520
528 public static int getNumberOfRotatableBonds(IAtomContainer mol)
529 throws DENOPTIMException {
530 int value;
531 try
532 {
533 IMolecularDescriptor descriptor =
534 new RotatableBondsCountDescriptor();
535 descriptor.setParameters(new Object[]{Boolean.FALSE,Boolean.FALSE});
536 DescriptorValue result = descriptor.calculate(mol);
537 value = ((IntegerResult)result.getValue()).intValue();
538 }
539 catch (CDKException cdke)
540 {
541 throw new DENOPTIMException(cdke);
542 }
543 return value;
544 }
545
546//------------------------------------------------------------------------------
547
554 public static int getHeavyAtomCount(IAtomContainer mol)
555 {
556 int n = 0;
557 for (IAtom atm : mol.atoms())
558 {
559 if (MoleculeUtils.isElement(atm)
560 && !MoleculeUtils.getSymbolOrLabel(atm).equals("H"))
561 n++;
562 }
563 return n;
564 }
565
566//------------------------------------------------------------------------------
567
575 public static int countAtomsOfElement(IAtomContainer mol, String symbol)
576 {
577 int n = 0;
578 for (IAtom atm : mol.atoms())
579 {
580 if (atm.getSymbol().equals(symbol))
581 n++;
582 }
583 return n;
584 }
585
586//------------------------------------------------------------------------------
587
599 public static Map<Vertex,ArrayList<Integer>> getVertexToAtomIdMap(
600 ArrayList<Vertex> vertLst,
601 IAtomContainer mol
602 ) {
603
604 ArrayList<Long> vertIDs = new ArrayList<>();
605 for (Vertex v : vertLst) {
606 vertIDs.add(v.getVertexId());
607 }
608
609 Map<Vertex,ArrayList<Integer>> map = new HashMap<>();
610 for (IAtom atm : mol.atoms())
611 {
612 long vID = Long.parseLong(atm.getProperty(
614 if (vertIDs.contains(vID))
615 {
616 Vertex v = vertLst.get(vertIDs.indexOf(vID));
617 int atmID = mol.indexOf(atm);
618 if (map.containsKey(v))
619 {
620 map.get(v).add(atmID);
621 }
622 else
623 {
624 ArrayList<Integer> atmLst = new ArrayList<>();
625 atmLst.add(atmID);
626 map.put(v, atmLst);
627 }
628 }
629 }
630 return map;
631 }
632
633//------------------------------------------------------------------------------
634
643 public static void moleculeToPNG(IAtomContainer mol, String filename,
644 Logger logger) throws DENOPTIMException
645 {
646 IAtomContainer iac = mol;
647
648 if (!GeometryUtil.has2DCoordinates(mol))
649 {
650 iac = generate2DCoordinates(mol, logger);
651 }
652
653 if (iac == null)
654 {
655 throw new DENOPTIMException("Failed to generate 2D coordinates.");
656 }
657
658 try
659 {
660 Depiction depiction = new DepictionGenerator().depict(iac);
661 depiction.writeTo(filename);
662 } catch (Exception e)
663 {
664 throw new DENOPTIMException("Failed to write image to "+filename);
665 }
666 }
667
668//------------------------------------------------------------------------------
669
677 public static Point3d getPoint3d(IAtom atm)
678 {
679 Point3d p = atm.getPoint3d();
680
681 if (p == null)
682 {
683 Point2d p2d = atm.getPoint2d();
684 if (p2d == null)
685 {
686 p = new Point3d(0.0, 0.0, 0.0);
687 } else {
688 p = new Point3d(p2d.x, p2d.y, 0.0);
689 }
690 }
691 return p;
692 }
693
694//------------------------------------------------------------------------------
695
696 //TODO: should we set only values that would otherwise be null?
697
702 public static void setZeroImplicitHydrogensToAllAtoms(IAtomContainer iac)
703 {
704 for (IAtom atm : iac.atoms()) {
705 atm.setImplicitHydrogenCount(0);
706 }
707 }
708
709//------------------------------------------------------------------------------
710
714 public static void explicitHydrogens(IAtomContainer mol)
715 {
716 AtomContainerManipulator.convertImplicitToExplicitHydrogens(mol);
717
718 }
719
720//------------------------------------------------------------------------------
721
729 public static void ensureNoUnsetBondOrdersSilent(IAtomContainer iac)
730 {
731 try {
733 } catch (CDKException e) {
734 StaticLogger.appLogger.log(Level.WARNING, "Kekulization failed. "
735 + "Bond orders will be unreliable as all unset bonds are"
736 + "now converted to single-order bonds.");
737 }
738 for (IBond bnd : iac.bonds())
739 {
740 if (bnd.getOrder().equals(IBond.Order.UNSET))
741 {
742 bnd.setOrder(IBond.Order.SINGLE);
743 }
744 }
745 }
746
747//------------------------------------------------------------------------------
748
757 public static void ensureNoUnsetBondOrders(IAtomContainer iac) throws CDKException
758 {
759 Kekulization.kekulize(iac);
760
761 for (IBond bnd : iac.bonds())
762 {
763 if (bnd.getOrder().equals(IBond.Order.UNSET))
764 {
765 bnd.setOrder(IBond.Order.SINGLE);
766 }
767 }
768 }
769
770//------------------------------------------------------------------------------
771
779 public static String missmatchingAromaticity(IAtomContainer mol)
780 {
781 String cause = "";
782 for (IAtom atm : mol.atoms())
783 {
784 //Check carbons with or without aromatic flags
785 if (atm.getSymbol().equals("C") && atm.getFormalCharge() == 0
786 && mol.getConnectedBondsCount(atm) == 3)
787 {
788 if (atm.getFlag(CDKConstants.ISAROMATIC))
789 {
790 int n = numOfBondsWithBO(atm, mol, IBond.Order.DOUBLE);
791 if (n == 0)
792 {
793 cause = "Aromatic atom " + getAtomRef(atm,mol)
794 + " has 3 connected atoms but no double bonds";
795 return cause;
796 }
797 } else {
798 for (IAtom nbr : mol.getConnectedAtomsList(atm))
799 {
800 if (nbr.getSymbol().equals("C"))
801 {
802 if (nbr.getFormalCharge() == 0)
803 {
804 if (mol.getConnectedBondsCount(nbr) == 3)
805 {
806 int nNbr = numOfBondsWithBO(nbr, mol,
807 IBond.Order.SINGLE);
808 int nAtm = numOfBondsWithBO(atm, mol,
809 IBond.Order.SINGLE);
810 if ((nNbr == 3) && (nAtm == 3))
811 {
812 cause = "Connected atoms "
813 + getAtomRef(atm, mol) + " "
814 + getAtomRef(nbr, mol)
815 + " have 3 connected atoms "
816 + "but no double bond. They are "
817 + "likely to be aromatic but no "
818 + "aromaticity has been reported.";
819 return cause;
820 }
821 }
822 }
823 }
824 }
825 }
826 }
827 }
828 return cause;
829 }
830
831//------------------------------------------------------------------------------
832
841 public static int numOfBondsWithBO(IAtom atm, IAtomContainer mol,
842 IBond.Order order)
843 {
844 int n = 0;
845 for (IBond bnd : mol.getConnectedBondsList(atm))
846 {
847 if (bnd.getOrder() == order)
848 n++;
849 }
850 return n;
851 }
852
853//------------------------------------------------------------------------------
854
864 public static IAtomContainer makeSameAs(IAtomContainer mol) throws DENOPTIMException
865 {
866 IChemObjectBuilder builder = SilentChemObjectBuilder.getInstance();
867 IAtomContainer iac = builder.newAtomContainer();
868
869 for (IAtom oAtm : mol.atoms())
870 {
871 IAtom nAtm = MoleculeUtils.makeSameAtomAs(oAtm,true,true);
872 iac.addAtom(nAtm);
873 }
874
875 for (IBond oBnd : mol.bonds())
876 {
877 if (oBnd.getAtomCount() != 2)
878 {
879 throw new DENOPTIMException("Unable to deal with bonds "
880 + "involving more than two atoms.");
881 }
882 int ia = mol.indexOf(oBnd.getAtom(0));
883 int ib = mol.indexOf(oBnd.getAtom(1));
884 iac.addBond(ia,ib,oBnd.getOrder());
885 }
886 return iac;
887 }
888
889//------------------------------------------------------------------------------
890
903 public static IAtom makeSameAtomAs(IAtom oAtm)
904 {
905 return makeSameAtomAs(oAtm, false, false);
906 }
907
908//------------------------------------------------------------------------------
909
923 public static IAtom makeSameAtomAs(IAtom oAtm, boolean ignoreValence,
924 boolean ignoreImplicitH)
925 {
926 IAtom nAtm = null;
927 String s = getSymbolOrLabel(oAtm);
928 if (MoleculeUtils.isElement(oAtm))
929 {
930 nAtm = new Atom(s);
931 } else {
932 nAtm = new PseudoAtom(s);
933 }
934 if (oAtm.getPoint3d() != null)
935 {
936 Point3d p3d = oAtm.getPoint3d();
937 nAtm.setPoint3d(new Point3d(p3d.x, p3d.y, p3d.z));
938 } else if (oAtm.getPoint2d() != null)
939 {
940 Point2d p2d = oAtm.getPoint2d();
941 nAtm.setPoint3d(new Point3d(p2d.x, p2d.y, 0.00001));
942 }
943 if (oAtm.getFormalCharge() != null)
944 nAtm.setFormalCharge(oAtm.getFormalCharge());
945 if (oAtm.getBondOrderSum() != null)
946 nAtm.setBondOrderSum(oAtm.getBondOrderSum());
947 if (oAtm.getCharge() != null)
948 nAtm.setCharge(oAtm.getCharge());
949 if (oAtm.getValency() != null && !ignoreValence)
950 nAtm.setValency(oAtm.getValency());
951 if (oAtm.getExactMass() != null)
952 nAtm.setExactMass(oAtm.getExactMass());
953 if (oAtm.getMassNumber() != null)
954 nAtm.setMassNumber(oAtm.getMassNumber());
955 if (oAtm.getFormalNeighbourCount() != null)
956 nAtm.setFormalNeighbourCount(oAtm.getFormalNeighbourCount());
957 if (oAtm.getFractionalPoint3d() != null)
958 nAtm.setFractionalPoint3d(new Point3d(oAtm.getFractionalPoint3d()));
959 if (oAtm.getHybridization() != null)
960 nAtm.setHybridization(Hybridization.valueOf(
961 oAtm.getHybridization().toString()));
962 if (oAtm.getImplicitHydrogenCount() != null && !ignoreImplicitH)
963 nAtm.setImplicitHydrogenCount(oAtm.getImplicitHydrogenCount());
964 if (oAtm.getMaxBondOrder() != null)
965 nAtm.setMaxBondOrder(oAtm.getMaxBondOrder());
966 if (oAtm.getNaturalAbundance() != null)
967 nAtm.setNaturalAbundance(oAtm.getNaturalAbundance());
968
969 return nAtm;
970 }
971
972//------------------------------------------------------------------------------
973
983 public static String getSymbolOrLabel(IAtom atm)
984 {
985 String s = "none";
986 if (MoleculeUtils.isElement(atm))
987 {
988 s = atm.getSymbol();
989 } else {
990 //TODO: one day will account for the possibility of having any
991 // other implementation of IAtom
992 IAtom a = null;
993 if (atm instanceof AtomRef)
994 {
995 a = ((AtomRef) atm).deref();
996 if (a instanceof PseudoAtom)
997 {
998 s = ((PseudoAtom) a).getLabel();
999 } else {
1000 // WARNING: we fall back to standard behavior, but there
1001 // could still be cases where this is not good...
1002 s = a.getSymbol();
1003 }
1004 } else if (atm instanceof PseudoAtom)
1005 {
1006 s = ((PseudoAtom) atm).getLabel();
1007 }
1008 }
1009 return s;
1010 }
1011
1012//------------------------------------------------------------------------------
1013
1018 public static String getAtomRef(IAtom atm, IAtomContainer mol)
1019 {
1020 return atm.getSymbol() + (mol.indexOf(atm) +1);
1021 }
1022
1023//------------------------------------------------------------------------------
1024
1030 public static Point3d calculateCentroid(IAtomContainer mol)
1031 {
1032 Point3d c = new Point3d(0,0,0);
1033 for (IAtom atm : mol.atoms())
1034 {
1035 c.add(getPoint3d(atm));
1036 }
1037 c.scale(1.0 / ((double) mol.getAtomCount()));
1038 return c;
1039 }
1040
1041//------------------------------------------------------------------------------
1042
1064 public static IAtomContainer extractIACForSubgraph(IAtomContainer wholeIAC,
1065 DGraph subGraph, DGraph wholeGraph, Logger logger,
1066 Randomizer randomizer) throws DENOPTIMException
1067 {
1068 IAtomContainer iac = makeSameAs(wholeIAC);
1069
1070 Set<Long> wantedVIDs = new HashSet<Long>();
1071 Map<Long,Vertex> wantedVertexesMap = new HashMap<>();
1072 for (Vertex v : subGraph.getVertexList())
1073 {
1074 Object o = v.getProperty(DENOPTIMConstants.STOREDVID);
1075 if (o == null)
1076 {
1077 throw new DENOPTIMException("Property '"
1078 + DENOPTIMConstants.STOREDVID + "' not defined in "
1079 + "vertex " + v + ", but is needed to extract "
1080 + "substructure.");
1081 }
1082 wantedVIDs.add(((Long) o).longValue());
1083 wantedVertexesMap.put(((Long) o).longValue(), v);
1084 }
1085
1086 // Identify the destiny of each atom: keep, remove, or make AP from it.
1087 IAtomContainer toRemove = new AtomContainer();
1088 Map<IAtom,IAtom> toAP = new HashMap<IAtom,IAtom>();
1089 Map<IAtom,AttachmentPoint> mapAtmToAPInG =
1090 new HashMap<IAtom,AttachmentPoint>();
1091 Map<IAtom,APClass> apcMap = new HashMap<IAtom,APClass>();
1092 for (int i=0; i<wholeIAC.getAtomCount(); i++)
1093 {
1094 IAtom cpAtm = iac.getAtom(i);
1095 IAtom oriAtm = wholeIAC.getAtom(i);
1096 Object o = oriAtm.getProperty(DENOPTIMConstants.ATMPROPVERTEXID);
1097 if (o == null)
1098 {
1099 throw new DENOPTIMException("Property '"
1101 + "' not defined in atom "
1102 + oriAtm.getSymbol() + wholeIAC.indexOf(oriAtm)
1103 + ", but is needed to extract substructure.");
1104 }
1105 long vid = ((Long) o).longValue();
1106 if (wantedVIDs.contains(vid))
1107 {
1108 continue; //keep this atom cpAtm
1109 }
1110 // Now, decide if the current atom should become an AP
1111 boolean willBecomeAP = false;
1112 for (IAtom nbr : wholeIAC.getConnectedAtomsList(oriAtm))
1113 {
1114 Object oNbr = nbr.getProperty(DENOPTIMConstants.ATMPROPVERTEXID);
1115 if (oNbr == null)
1116 {
1117 throw new DENOPTIMException("Property '"
1119 + "' not defined in atom "
1120 + nbr.getSymbol() + wholeIAC.indexOf(nbr)
1121 + ", but is needed to extract substructure.");
1122 }
1123 long nbrVid = ((Long) oNbr).longValue();
1124 if (wantedVIDs.contains(nbrVid))
1125 {
1126 // cpAtm is connected to an atom to keep and will therefore
1127 // become an AP. Note that the connection may go through
1128 // a chord in the graph, i.e., a pairs of RCVs!
1129 toAP.put(cpAtm,iac.getAtom(wholeIAC.indexOf(nbr)));
1130 AttachmentPoint apInWholeGraph =
1131 wholeGraph.getAPOnLeftVertexID(nbrVid,vid);
1132 if (apInWholeGraph == null)
1133 {
1134 String debugFile = "failedAPIdentificationIACSubGraph"
1135 + wholeGraph.getGraphId() + ".sdf";
1136 DenoptimIO.writeGraphToSDF(new File(debugFile),
1137 wholeGraph, willBecomeAP, logger, randomizer);
1138 throw new DENOPTIMException("Unmexpected null AP from "
1139 + nbrVid + " " + vid +" on " + wholeGraph
1140 + " See " + debugFile);
1141 }
1142 AttachmentPoint apInSubGraph =
1143 wantedVertexesMap.get(nbrVid).getAP(apInWholeGraph.getIndexInOwner());
1144 mapAtmToAPInG.put(cpAtm, apInSubGraph);
1145 APClass apc = apInWholeGraph.getAPClass();
1146 apcMap.put(cpAtm, apc);
1147 willBecomeAP = true;
1148 break;
1149 }
1150 }
1151 if (!willBecomeAP)
1152 toRemove.addAtom(cpAtm);
1153 }
1154
1155 iac.remove(toRemove);
1156
1157 // NB: the molecular representation in frag is NOT iac! It's a clone of it
1158 Fragment frag = new Fragment(iac,BBType.FRAGMENT);
1159
1160 List<IAtom> atmosToRemove = new ArrayList<>();
1161 List<IBond> bondsToRemove = new ArrayList<>();
1162 for (IAtom trgAtmInIAC : toAP.keySet())
1163 {
1164 IAtom trgAtm = frag.getAtom(iac.indexOf(trgAtmInIAC));
1165 IAtom srcAtmInISC = toAP.get(trgAtmInIAC);
1166 IAtom srcAtm = frag.getAtom(iac.indexOf(srcAtmInISC));
1167
1168 AttachmentPoint apInG = mapAtmToAPInG.get(trgAtmInIAC);
1169
1170 // Make Attachment point
1171 Point3d srcP3d = MoleculeUtils.getPoint3d(srcAtm);
1172 Point3d trgP3d = MoleculeUtils.getPoint3d(trgAtm);
1173 double currentLength = srcP3d.distance(trgP3d);
1174 //TODO-V3+? change hard-coded value with property of AP, when such
1175 // property will be available, i. e., one refactoring of AP and
1176 // atom coordinates is done.
1177 double idealLength = 1.53;
1178 /*
1179 double idealLength = apInG.getProperty(
1180 DENOPTIMConstants.APORIGINALLENGTH);
1181 */
1182 Point3d vector = new Point3d();
1183 vector.x = srcP3d.x + (trgP3d.x - srcP3d.x)*(idealLength/currentLength);
1184 vector.y = srcP3d.y + (trgP3d.y - srcP3d.y)*(idealLength/currentLength);
1185 vector.z = srcP3d.z + (trgP3d.z - srcP3d.z)*(idealLength/currentLength);
1186
1187 AttachmentPoint createdAP = frag.addAPOnAtom(srcAtm,
1188 apcMap.get(trgAtmInIAC), vector);
1189 createdAP.setProperty(DENOPTIMConstants.LINKAPS, apInG);
1190
1191 for (IBond bnd : frag.bonds())
1192 {
1193 if (bnd.contains(trgAtm))
1194 {
1195 bondsToRemove.add(bnd);
1196 }
1197 }
1198 atmosToRemove.add(trgAtm);
1199 }
1200
1201 // Remove atom that has become an AP and associated bonds
1202 for (IBond bnd : bondsToRemove)
1203 {
1204 frag.removeBond(bnd);
1205 }
1206 for (IAtom a : atmosToRemove)
1207 {
1208 frag.removeAtom(a);
1209 }
1210
1211 frag.updateAPs();
1212
1213 return frag.getIAtomContainer();
1214 }
1215
1216//------------------------------------------------------------------------------
1217
1223 public static int getDimensions(IAtomContainer mol)
1224 {
1225 final int is2D = 2;
1226 final int is3D = 3;
1227 final int not2or3D = -1;
1228
1229 int numOf2D = 0;
1230 int numOf3D = 0;
1231
1232 for (IAtom atm : mol.atoms())
1233 {
1234 Point2d p2d = new Point2d();
1235 Point3d p3d = new Point3d();
1236 p2d = atm.getPoint2d();
1237 boolean have2D = true;
1238 if (p2d == null)
1239 {
1240 have2D = false;
1241 p3d = atm.getPoint3d();
1242 if (p3d == null)
1243 {
1244 return not2or3D;
1245 }
1246 }
1247 ArrayList<Double> pointer = new ArrayList<Double>();
1248 try {
1249 if (have2D)
1250 {
1251 pointer.add(p2d.x);
1252 pointer.add(p2d.y);
1253 numOf2D++;
1254 } else {
1255 pointer.add(p3d.x);
1256 pointer.add(p3d.y);
1257 pointer.add(p3d.z);
1258 numOf3D++;
1259 }
1260 } catch (Throwable t) {
1261 return not2or3D;
1262 }
1263 }
1264
1265 if (numOf2D == mol.getAtomCount())
1266 return is2D;
1267 else if (numOf3D == mol.getAtomCount())
1268 return is3D;
1269 else
1270 return not2or3D;
1271 }
1272
1273//------------------------------------------------------------------------------
1274
1283 public static double[] calculateBondAngleAgreement(IAtomContainer templateMol,
1284 IAtomContainer mol, Map<IAtom, IAtom> templateToMolMap)
1285 {
1286 double totalAngleDifference = 0.0;
1287 int angleCount = 0;
1288 double totalChiralityPenalty = 0.0;
1289 int chiralityCount = 0;
1290 boolean hasAngles = false;
1291
1292 for (IAtom templateAtom : templateMol.atoms())
1293 {
1294 if (templateAtom.getBondCount() > 1)
1295 {
1296 hasAngles = true;
1297 }
1298 }
1299
1300 // For each atom in the template, calculate bond angles and chirality
1301 for (IAtom templateAtom : templateMol.atoms())
1302 {
1303 IAtom molAtom = templateToMolMap.get(templateAtom);
1304 if (molAtom == null)
1305 continue;
1306
1307 Point3d templatePos = MoleculeUtils.getPoint3d(templateAtom);
1308 Point3d molPos = MoleculeUtils.getPoint3d(molAtom);
1309
1310 if (templatePos == null || molPos == null)
1311 continue;
1312
1313 // Get neighbors of this atom in the template
1314 List<IAtom> templateNeighbors = templateMol.getConnectedAtomsList(templateAtom);
1315 if (templateNeighbors.size() < 2)
1316 continue; // Need at least 2 neighbors to form an angle
1317
1318 // Get corresponding neighbors in the matched molecule
1319 List<IAtom> molNeighbors = new ArrayList<>();
1320 for (IAtom templateNeighbor : templateNeighbors)
1321 {
1322 IAtom molNeighbor = templateToMolMap.get(templateNeighbor);
1323 if (molNeighbor != null)
1324 {
1325 molNeighbors.add(molNeighbor);
1326 }
1327 }
1328
1329 if (molNeighbors.size() < 2)
1330 continue;
1331
1332 // Calculate all bond angles in the template
1333 for (int i = 0; i < templateNeighbors.size(); i++)
1334 {
1335 for (int j = i + 1; j < templateNeighbors.size(); j++)
1336 {
1337 IAtom neighbor1 = templateNeighbors.get(i);
1338 IAtom neighbor2 = templateNeighbors.get(j);
1339
1340 IAtom molNeighbor1 = templateToMolMap.get(neighbor1);
1341 IAtom molNeighbor2 = templateToMolMap.get(neighbor2);
1342
1343 if (molNeighbor1 == null || molNeighbor2 == null)
1344 continue;
1345
1346 Point3d pos1 = MoleculeUtils.getPoint3d(neighbor1);
1347 Point3d pos2 = MoleculeUtils.getPoint3d(neighbor2);
1348 Point3d molPos1 = MoleculeUtils.getPoint3d(molNeighbor1);
1349 Point3d molPos2 = MoleculeUtils.getPoint3d(molNeighbor2);
1350
1351 if (pos1 == null || pos2 == null || molPos1 == null || molPos2 == null)
1352 continue;
1353
1354 // Calculate angle in template: angle at templateAtom between neighbor1 and neighbor2
1355 double[] templateA = {pos1.x, pos1.y, pos1.z};
1356 double[] templateB = {templatePos.x, templatePos.y, templatePos.z};
1357 double[] templateC = {pos2.x, pos2.y, pos2.z};
1358 double templateAngle = MathUtils.getAngle(templateA, templateB, templateC);
1359
1360 // Calculate corresponding angle in matched molecule
1361 double[] molA = {molPos1.x, molPos1.y, molPos1.z};
1362 double[] molB = {molPos.x, molPos.y, molPos.z};
1363 double[] molC = {molPos2.x, molPos2.y, molPos2.z};
1364 double molAngle = MathUtils.getAngle(molA, molB, molC);
1365
1366 // Add absolute difference to total
1367 totalAngleDifference += Math.abs(templateAngle - molAngle);
1368 angleCount++;
1369 }
1370 }
1371
1372 // Calculate chirality-sensitive term using scalar triple product
1373 // Need at least 3 neighbors to define chirality
1374 if (templateNeighbors.size() >= 3 && molNeighbors.size() >= 3)
1375 {
1376 // Use first three neighbors to compute scalar triple product
1377 IAtom n1 = templateNeighbors.get(0);
1378 IAtom n2 = templateNeighbors.get(1);
1379 IAtom n3 = templateNeighbors.get(2);
1380
1381 IAtom molN1 = templateToMolMap.get(n1);
1382 IAtom molN2 = templateToMolMap.get(n2);
1383 IAtom molN3 = templateToMolMap.get(n3);
1384
1385 if (molN1 != null && molN2 != null && molN3 != null)
1386 {
1387 Point3d p1 = MoleculeUtils.getPoint3d(n1);
1388 Point3d p2 = MoleculeUtils.getPoint3d(n2);
1389 Point3d p3 = MoleculeUtils.getPoint3d(n3);
1390 Point3d molP1 = MoleculeUtils.getPoint3d(molN1);
1391 Point3d molP2 = MoleculeUtils.getPoint3d(molN2);
1392 Point3d molP3 = MoleculeUtils.getPoint3d(molN3);
1393
1394 if (p1 != null && p2 != null && p3 != null &&
1395 molP1 != null && molP2 != null && molP3 != null)
1396 {
1397 // Compute vectors from central atom to neighbors
1398 double[] v1Template = {p1.x - templatePos.x, p1.y - templatePos.y, p1.z - templatePos.z};
1399 double[] v2Template = {p2.x - templatePos.x, p2.y - templatePos.y, p2.z - templatePos.z};
1400 double[] v3Template = {p3.x - templatePos.x, p3.y - templatePos.y, p3.z - templatePos.z};
1401
1402 double[] v1Mol = {molP1.x - molPos.x, molP1.y - molPos.y, molP1.z - molPos.z};
1403 double[] v2Mol = {molP2.x - molPos.x, molP2.y - molPos.y, molP2.z - molPos.z};
1404 double[] v3Mol = {molP3.x - molPos.x, molP3.y - molPos.y, molP3.z - molPos.z};
1405
1406 // Compute scalar triple product: v1 · (v2 × v3)
1407 double[] crossTemplate = MathUtils.computeCrossProduct(v2Template, v3Template);
1408 double[] crossMol = MathUtils.computeCrossProduct(v2Mol, v3Mol);
1409
1410 double stpTemplate = v1Template[0] * crossTemplate[0] +
1411 v1Template[1] * crossTemplate[1] +
1412 v1Template[2] * crossTemplate[2];
1413 double stpMol = v1Mol[0] * crossMol[0] +
1414 v1Mol[1] * crossMol[1] +
1415 v1Mol[2] * crossMol[2];
1416
1417 // Penalty based on sign difference and magnitude difference
1418 // If signs differ, add large penalty; if same sign, add magnitude difference
1419 double chiralityPenalty = 0.0;
1420 if (Math.signum(stpTemplate) != Math.signum(stpMol))
1421 {
1422 // Opposite chirality - large penalty
1423 chiralityPenalty = 1000.0 + Math.abs(stpTemplate - stpMol);
1424 }
1425 else
1426 {
1427 // Same chirality - small penalty based on magnitude difference
1428 chiralityPenalty = Math.abs(stpTemplate - stpMol);
1429 }
1430
1431 totalChiralityPenalty += chiralityPenalty;
1432 chiralityCount++;
1433 }
1434 }
1435 }
1436 }
1437
1438 // If no valid comparisons could be made (incomplete mapping), return null if angles expected
1439 if (angleCount == 0 && chiralityCount == 0)
1440 {
1441 if (hasAngles)
1442 {
1443 return null; // Indicates failure
1444 }
1445 else
1446 {
1447 // No angles expected, return zero values
1448 return new double[]{0.0, 0.0, 0.0, 0.0};
1449 }
1450 }
1451
1452 // Return array: [totalAngleDifference, angleCount, totalChiralityPenalty, chiralityCount]
1453 return new double[]{totalAngleDifference, angleCount, totalChiralityPenalty, chiralityCount};
1454 }
1455
1456//------------------------------------------------------------------------------
1457
1463 public static double normalizeBondAngleScore(double[] rawData)
1464 {
1465 if (rawData == null)
1466 {
1467 return Double.MAX_VALUE;
1468 }
1469
1470 double totalAngleDifference = rawData[0];
1471 double angleCount = rawData[1];
1472 double totalChiralityPenalty = rawData[2];
1473 double chiralityCount = rawData[3];
1474
1475 // If no valid comparisons could be made
1476 if (angleCount == 0 && chiralityCount == 0)
1477 {
1478 return 0.0;
1479 }
1480
1481 // Combine angle difference and chirality penalty (normalized)
1482 double angleScore = angleCount > 0 ? totalAngleDifference / angleCount : 0.0;
1483 double chiralityScore = chiralityCount > 0 ? totalChiralityPenalty / chiralityCount : 0.0;
1484
1485 // Return combined score (angle differences + chirality penalty)
1486 // Chirality penalty is weighted more heavily to ensure correct enantiomeric mapping
1487 return angleScore + chiralityScore;
1488 }
1489
1490//------------------------------------------------------------------------------
1491
1500 public static Map<IAtom,IAtom> findBestAtomMapping(
1501 IAtomContainer substructure, IAtomContainer mol, Logger logger)
1502 {
1503 List<Map<IAtom,IAtom>> uniqueAtomMappings = findUniqueAtomMappings(substructure, mol, logger);
1504 double bestScore = Double.MAX_VALUE;
1505 Map<IAtom,IAtom> bestTemplateToMolMap = new HashMap<>();
1506 for (Map<IAtom,IAtom> templateToMolMap : uniqueAtomMappings)
1507 {
1508 double[] rawData = calculateBondAngleAgreement(substructure, mol, templateToMolMap);
1509 double score = normalizeBondAngleScore(rawData);
1510 if (score < bestScore)
1511 {
1512 bestScore = score;
1513 bestTemplateToMolMap = templateToMolMap;
1514 }
1515 }
1516 return bestTemplateToMolMap;
1517 }
1518
1519//------------------------------------------------------------------------------
1520
1529 public static List<Map<IAtom,IAtom>> findUniqueAtomMappings(
1530 IAtomContainer substructure, IAtomContainer mol, Logger logger)
1531 {
1532 try
1533 {
1534 // Ensure molecules are properly configured for isomorphism matching
1539
1540 // Create a pattern from the template molecule
1541 Pattern pattern = Pattern.findSubstructure(substructure);
1542
1543 // Check if template is a substructure of mol
1544 if (!pattern.matches(mol))
1545 {
1546 // Template is not a substructure, try to find MCS using SMSD if available
1547 // For now, return null - could be enhanced with SMSD library
1548 return new ArrayList<>();
1549 }
1550
1551 // Get all matching substructures
1552 Mappings mappings = pattern.matchAll(mol);
1553 if (!mappings.iterator().hasNext())
1554 {
1555 return new ArrayList<>();
1556 }
1557
1558 // Find unique mappings based on which target atoms are mapped to
1559 // Use a sorted list of target atom indices as the key to identify unique mappings
1560 // This is more reliable than using Set<IAtom> as a HashMap key
1561 Map<String,Map<IAtom,IAtom>> uniqueAtomMappingsByKey = new HashMap<>();
1562 Map<String,Double> bestScores = new HashMap<>();
1563 // Track "bad" atom assignments: when we find a better mapping, we identify
1564 // which specific template->target atom assignments were in the worse mapping
1565 // and skip future mappings that have those same problematic assignments
1566 Map<String,Set<String>> badAssignmentsByKey = new HashMap<>();
1567 for (int[] mapping : mappings)
1568 {
1569 try
1570 {
1571 // Build atom mapping for this mapping
1572 Map<IAtom, IAtom> templateToMolMap = new HashMap<>();
1573 List<Integer> targetIndices = new ArrayList<>();
1574 for (int i = 0; i < mapping.length; i++)
1575 {
1576 IAtom templateAtom = substructure.getAtom(i);
1577 IAtom targetAtom = mol.getAtom(mapping[i]);
1578 templateToMolMap.put(templateAtom, targetAtom);
1579 targetIndices.add(mapping[i]);
1580 }
1581 // Create a unique key from sorted target atom indices
1582 Collections.sort(targetIndices);
1583 String key = targetIndices.toString();
1584
1585 // Check if this mapping has any "bad" assignments we've identified
1586 // for this target atom set
1587 if (badAssignmentsByKey.containsKey(key))
1588 {
1589 Set<String> badAssignments = badAssignmentsByKey.get(key);
1590 boolean hasBadAssignment = false;
1591 for (Map.Entry<IAtom, IAtom> entry : templateToMolMap.entrySet())
1592 {
1593 // Create a signature for this template->target assignment
1594 String assignment = entry.getKey().getIndex() + "->" + entry.getValue().getIndex();
1595 if (badAssignments.contains(assignment))
1596 {
1597 hasBadAssignment = true;
1598 break;
1599 }
1600 }
1601 if (hasBadAssignment)
1602 {
1603 // Skip this mapping - it has a known bad assignment
1604 continue;
1605 }
1606 }
1607
1608 // Calculate bond angle agreement score
1609 double[] rawData = calculateBondAngleAgreement(substructure, mol, templateToMolMap);
1610 double score = normalizeBondAngleScore(rawData);
1611
1612 // Keep the mapping with the best score for each unique set of target atoms
1613 if (!bestScores.containsKey(key) || score < bestScores.get(key))
1614 {
1615 // If we're replacing a previous mapping, identify the bad assignments
1616 if (bestScores.containsKey(key) && uniqueAtomMappingsByKey.containsKey(key))
1617 {
1618 Map<IAtom, IAtom> previousMapping = uniqueAtomMappingsByKey.get(key);
1619 Set<String> badAssignments = badAssignmentsByKey.computeIfAbsent(key, k -> new HashSet<>());
1620
1621 // Find assignments that were in the previous (worse) mapping but not in the new (better) one
1622 for (Map.Entry<IAtom, IAtom> entry : previousMapping.entrySet())
1623 {
1624 IAtom templateAtom = entry.getKey();
1625 IAtom oldTargetAtom = entry.getValue();
1626 IAtom newTargetAtom = templateToMolMap.get(templateAtom);
1627
1628 // If this template atom maps to a different target atom in the better mapping,
1629 // the old assignment was a "mistake"
1630 if (!oldTargetAtom.equals(newTargetAtom))
1631 {
1632 String badAssignment = templateAtom.getIndex() + "->" + oldTargetAtom.getIndex();
1633 badAssignments.add(badAssignment);
1634 }
1635 }
1636 }
1637
1638 bestScores.put(key, score);
1639 uniqueAtomMappingsByKey.put(key, templateToMolMap);
1640 }
1641 }
1642 catch (Exception e)
1643 {
1644 // Log but continue processing other mappings
1645 logger.log(Level.FINE, "Error processing mapping: " + e.getMessage());
1646 }
1647 }
1648
1649 return new ArrayList<>(uniqueAtomMappingsByKey.values());
1650 }
1651 catch (Exception e)
1652 {
1653 logger.log(Level.WARNING, "Error finding MCS: " + e.getMessage());
1654 return new ArrayList<>();
1655 }
1656 }
1657
1658//------------------------------------------------------------------------------
1659
1669 public static List<IAtom> findShortestPath(IAtomContainer mol, IAtom start,
1670 IAtom end, Map<IAtom, Long> atomToVertexId)
1671 {
1672 if (start.equals(end))
1673 {
1674 return new ArrayList<>();
1675 }
1676
1677 // BFS to find shortest path
1678 Map<IAtom, IAtom> parent = new HashMap<>();
1679 Set<IAtom> visited = new HashSet<>();
1680 List<IAtom> queue = new ArrayList<>();
1681
1682 queue.add(start);
1683 visited.add(start);
1684 parent.put(start, null);
1685
1686 while (!queue.isEmpty())
1687 {
1688 IAtom current = queue.remove(0);
1689
1690 if (current.equals(end))
1691 {
1692 // Reconstruct path
1693 List<IAtom> path = new ArrayList<>();
1694 IAtom node = end;
1695 while (node != null)
1696 {
1697 path.add(0, node);
1698 node = parent.get(node);
1699 }
1700 return path;
1701 }
1702
1703 List<IAtom> neighbors = mol.getConnectedAtomsList(current);
1704 for (IAtom neighbor : neighbors)
1705 {
1706 if (!visited.contains(neighbor))
1707 {
1708 // Only traverse if same vertex ID or if we're at a boundary
1709 Long currentVid = atomToVertexId.get(current);
1710 Long neighborVid = atomToVertexId.get(neighbor);
1711
1712 if (currentVid != null && neighborVid != null)
1713 {
1714 // Allow traversal if same vertex ID, or if moving to/from boundary
1715 boolean canTraverse = currentVid.equals(neighborVid);
1716 if (!canTraverse)
1717 {
1718 // Check if this is a boundary transition (both are boundary atoms)
1719 // This is already handled by the boundary detection, so allow it
1720 canTraverse = true;
1721 }
1722
1723 if (canTraverse)
1724 {
1725 visited.add(neighbor);
1726 parent.put(neighbor, current);
1727 queue.add(neighbor);
1728 }
1729 }
1730 }
1731 }
1732 }
1733
1734 return null; // No path found
1735 }
1736}
1737
1738//------------------------------------------------------------------------------
General set of constants used in DENOPTIM.
static final String ATMPROPVERTEXID
String tag of Atom property used to store the unique ID of the Vertex corresponding to the molecular ...
static final String DUMMYATMSYMBOL
Symbol of dummy atom.
static final Object STOREDVID
Key of the property remembering vertex IDs.
static final Object LINKAPS
Key of property used to records references of APs.
An attachment point (AP) is a possibility to attach a Vertex onto the vertex holding the AP (i....
APClass getAPClass()
Returns the Attachment Point class.
void setProperty(Object key, Object property)
Container for the list of vertices and the edges that connect them.
Definition: DGraph.java:102
Class representing a continuously connected portion of chemical object holding attachment points.
Definition: Fragment.java:61
IBond removeBond(int position)
Definition: Fragment.java:878
AttachmentPoint addAPOnAtom(IAtom srcAtm, APClass apc, Point3d vector)
Add an attachment point to the specifies atom.
Definition: Fragment.java:424
IAtom getAtom(int number)
Definition: Fragment.java:843
void removeAtom(IAtom atom)
Definition: Fragment.java:899
IAtomContainer getIAtomContainer()
Definition: Fragment.java:788
void updateAPs()
Changes the properties of each APs as to reflect the current atom list.
Definition: Fragment.java:511
Iterable< IBond > bonds()
Definition: Fragment.java:829
A vertex is a data structure that has an identity and holds a list of AttachmentPoints.
Definition: Vertex.java:61
The RingClosingAttractor represent the available valence/connection that allows to close a ring.
static final Map< String, String > RCATYPEMAP
Recognized types of RingClosingAttractor and compatible types.
Utility methods for input/output.
static void writeSDFFile(String fileName, IAtomContainer mol)
Writes IAtomContainer to SDF file.
static void writeGraphToSDF(File file, DGraph graph, boolean append, boolean make3D, Logger logger, Randomizer randomizer)
Writes the graph to SDF file.
Logger class for DENOPTIM.
static final Logger appLogger
Toll to add/remove dummy atoms from linearities or multi-hapto sites.
IAtomContainer removeDummy(IAtomContainer mol)
Removes all dummy atoms and the bonds connecting them to other atoms.
IAtomContainer removeDummyInHapto(IAtomContainer mol)
Some useful math operations.
Definition: MathUtils.java:39
static double getAngle(double[] A, double[] B, double[] C)
Calculates angle (in degrees) between vectors BA and BC.
Definition: MathUtils.java:324
static double[] computeCrossProduct(double[] v0, double[] v1)
Compute the cross product of two vectors.
Definition: MathUtils.java:162
Utilities for molecule conversion.
static double[] calculateBondAngleAgreement(IAtomContainer templateMol, IAtomContainer mol, Map< IAtom, IAtom > templateToMolMap)
Calculates bond angle agreement and returns raw data.
static int numOfBondsWithBO(IAtom atm, IAtomContainer mol, IBond.Order order)
Returns the number of bonds, with a certain bond order, surrounding the given atom.
static final StructureDiagramGenerator SDG
static String getInChIKeyForMolecule(IAtomContainer mol, Logger logger)
Generates the InChI key for the given atom container.
static void removeUsedRCA(IAtomContainer mol, DGraph graph, Logger logger)
Replace used RCAs (i.e., those involved in Rings) while adding the ring closing bonds.
static double normalizeBondAngleScore(double[] rawData)
Normalizes the raw bond angle agreement data to a single score.
static void setZeroImplicitHydrogensToAllAtoms(IAtomContainer iac)
Sets zero implicit hydrogen count to all atoms.
static IAtomContainer makeSameAs(IAtomContainer mol)
Constructs a copy of an atom container, i.e., a molecule that reflects the one given in the input arg...
static IAtom makeSameAtomAs(IAtom oAtm, boolean ignoreValence, boolean ignoreImplicitH)
Method that constructs an atom that reflect the same atom given as parameter in terms of element symb...
static int getNumberOfRotatableBonds(IAtomContainer mol)
Count number of rotatable bonds.
static int countAtomsOfElement(IAtomContainer mol, String symbol)
Count atoms with the given elemental symbol.
static int getDimensions(IAtomContainer mol)
Determines the dimensionality of the given chemical object.
static String getSMILESForMolecule(IAtomContainer mol, Logger logger)
Returns the SMILES representation of the molecule.
static IAtomContainer generate2DCoordinates(IAtomContainer ac, Logger logger)
Generates 2D coordinates for the molecule.
static void removeRCA(IAtomContainer mol)
Replace any PseudoAtoms representing ring closing attractors with H.
static double getMolecularWeight(IAtomContainer mol)
static String missmatchingAromaticity(IAtomContainer mol)
Looks for carbon atoms that are flagged as aromatic, but do not have any double bond and are,...
static IAtom makeSameAtomAs(IAtom oAtm)
Method that constructs an atom that reflect the same atom given as parameter in terms of element symb...
static Map< IAtom, IAtom > findBestAtomMapping(IAtomContainer substructure, IAtomContainer mol, Logger logger)
Finds the maximum common substructure (MCS) between two molecules.
static final IChemObjectBuilder builder
static String getSymbolOrLabel(IAtom atm)
Gets either the elemental symbol (for standard atoms) of the label (for pseudo-atoms).
static void ensureNoUnsetBondOrdersSilent(IAtomContainer iac)
Sets bond order = single to all otherwise unset bonds.
static boolean isDummy(IAtom atm)
Checks if the given atom is a dummy atom based on the elemental symbol and the string used for dummy ...
static List< Map< IAtom, IAtom > > findUniqueAtomMappings(IAtomContainer substructure, IAtomContainer mol, Logger logger)
Finds the maximum common substructure (MCS) between two molecules.
static String getAtomRef(IAtom atm, IAtomContainer mol)
static final SmilesGenerator SMGEN
static void ensureNoUnsetBondOrders(IAtomContainer iac)
Sets bond order = single to all otherwise unset bonds.
static Map< Vertex, ArrayList< Integer > > getVertexToAtomIdMap(ArrayList< Vertex > vertLst, IAtomContainer mol)
Method to generate the map making in relation DENOPTIMVertex ID and atom index in the IAtomContainer ...
static void explicitHydrogens(IAtomContainer mol)
Converts all the implicit hydrogens to explicit.
static void moleculeToPNG(IAtomContainer mol, String filename, Logger logger)
Generate a PNG image from molecule mol
static Point3d calculateCentroid(IAtomContainer mol)
Calculated the centroid of the given molecule.
static List< IAtom > findShortestPath(IAtomContainer mol, IAtom start, IAtom end, Map< IAtom, Long > atomToVertexId)
Finds the shortest path between two atoms in a molecule using BFS.
static Point3d getPoint3d(IAtom atm)
Return the 3D coordinates, if present.
static boolean isElement(String symbol)
Check element symbol corresponds to real element of Periodic Table.
static IAtomContainer extractIACForSubgraph(IAtomContainer wholeIAC, DGraph subGraph, DGraph wholeGraph, Logger logger, Randomizer randomizer)
Selects only the atoms that originate from a subgraph of a whole graph that originated the whole mole...
static boolean isElement(IAtom atom)
Check element symbol corresponds to real element of Periodic Table.
static int getHeavyAtomCount(IAtomContainer mol)
The heavy atom count.
static String getInChIKeyForMolecule(IAtomContainer mol, InchiOptions options, Logger logger)
Generates the INCHI key for the molecule.
Tool to generate random numbers and random decisions.
Definition: Randomizer.java:35
Possible chemical bond types an edge can represent.
Definition: Edge.java:303
boolean hasCDKAnalogue()
Checks if it is possible to convert this edge type into a CDK bond.
Definition: Edge.java:328
The type of building block.
Definition: Vertex.java:86