diff --git a/src/main/java/de/unijena/cheminf/mortar/model/data/MoleculeDataModel.java b/src/main/java/de/unijena/cheminf/mortar/model/data/MoleculeDataModel.java index 32e07cd1..7194e123 100644 --- a/src/main/java/de/unijena/cheminf/mortar/model/data/MoleculeDataModel.java +++ b/src/main/java/de/unijena/cheminf/mortar/model/data/MoleculeDataModel.java @@ -278,7 +278,8 @@ public boolean hasMoleculeUndergoneSpecificFragmentation(String aKey) { public ImageView getStructure() { try { IAtomContainer tmpAtomContainer = this.getAtomContainer(); - return new ImageView(DepictionUtil.depictImageWithZoomAndFillToFit(tmpAtomContainer, 1, this.getStructureImageWidth(), this.getStructureImageHeight(), true)); + boolean tmpFillToFit = tmpAtomContainer.getAtomCount() > 6; + return new ImageView(DepictionUtil.depictImageWithZoomAndFillToFit(tmpAtomContainer, 1, this.getStructureImageWidth(), this.getStructureImageHeight(), tmpFillToFit)); } catch (CDKException aCDKException) { Logger.getLogger(MoleculeDataModel.class.getName()).log(Level.SEVERE, aCDKException.toString(), aCDKException); return new ImageView(DepictionUtil.depictErrorImage(aCDKException.getMessage(), 250, 250)); diff --git a/src/main/java/de/unijena/cheminf/mortar/model/fragmentation/FragmentationService.java b/src/main/java/de/unijena/cheminf/mortar/model/fragmentation/FragmentationService.java index 9acf735c..4f7d7d1e 100644 --- a/src/main/java/de/unijena/cheminf/mortar/model/fragmentation/FragmentationService.java +++ b/src/main/java/de/unijena/cheminf/mortar/model/fragmentation/FragmentationService.java @@ -29,6 +29,8 @@ import de.unijena.cheminf.mortar.message.Message; import de.unijena.cheminf.mortar.model.data.FragmentDataModel; import de.unijena.cheminf.mortar.model.data.MoleculeDataModel; +import de.unijena.cheminf.mortar.model.fragmentation.algorithm.AlkylStructureFragmenter; +import de.unijena.cheminf.mortar.model.fragmentation.algorithm.ConjugatedPiSystemFragmenter; import de.unijena.cheminf.mortar.model.fragmentation.algorithm.ErtlFunctionalGroupsFinderFragmenter; import de.unijena.cheminf.mortar.model.fragmentation.algorithm.IMoleculeFragmenter; import de.unijena.cheminf.mortar.model.fragmentation.algorithm.ScaffoldGeneratorFragmenter; @@ -151,6 +153,14 @@ public class FragmentationService { * String for the name of the current fragmentation algorithm. */ private String currentFragmentationName; + /** + * Alkyl Structure Fragmenter + */ + private IMoleculeFragmenter AlkylSF; + /** + * Conjugated Pi System Fragmenter + */ + private IMoleculeFragmenter ConjPiSysF; /** * String for the name of the current pipeline fragmentation. */ @@ -206,13 +216,17 @@ public class FragmentationService { */ public FragmentationService(SettingsContainer aSettingsContainer) { //Note: Every fragmenter class should only be added once to the array or there will be problems with setting persistence! - this.fragmenters = new IMoleculeFragmenter[3]; + this.fragmenters = new IMoleculeFragmenter[5]; this.ertlFGF = new ErtlFunctionalGroupsFinderFragmenter(); this.fragmenters[0] = this.ertlFGF; this.sugarRUF = new SugarRemovalUtilityFragmenter(); this.fragmenters[1] = this.sugarRUF; this.scaffoldGF = new ScaffoldGeneratorFragmenter(); this.fragmenters[2] = this.scaffoldGF; + this.AlkylSF = new AlkylStructureFragmenter(); + this.fragmenters[3] = this.AlkylSF; + this.ConjPiSysF = new ConjugatedPiSystemFragmenter(); + this.fragmenters[4] = this.ConjPiSysF; // Objects.requireNonNull(aSettingsContainer, "aSettingsContainer must not be null"); this.settingsContainer = aSettingsContainer; diff --git a/src/main/java/de/unijena/cheminf/mortar/model/fragmentation/algorithm/AlkylStructureFragmenter.java b/src/main/java/de/unijena/cheminf/mortar/model/fragmentation/algorithm/AlkylStructureFragmenter.java new file mode 100644 index 00000000..f0ad4748 --- /dev/null +++ b/src/main/java/de/unijena/cheminf/mortar/model/fragmentation/algorithm/AlkylStructureFragmenter.java @@ -0,0 +1,1229 @@ +/* + * MORTAR - MOlecule fRagmenTAtion fRamework + * Copyright (C) 2024 Felix Baensch, Jonas Schaub (felix.baensch@w-hs.de, jonas.schaub@uni-jena.de) + * + * Source code is available at + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package de.unijena.cheminf.mortar.model.fragmentation.algorithm; + +// +/* +TODO: (01|08|24): -implement spiro carbon detection + ring integrity setting + -overhaul test class (with new functionalities) + -large scale testing of fragmentation (regarding correct fragmentation and performance) + */ +// + +import de.unijena.cheminf.mortar.gui.util.GuiUtil; +import de.unijena.cheminf.mortar.message.Message; +import de.unijena.cheminf.mortar.model.io.Importer; +import de.unijena.cheminf.mortar.model.util.BasicDefinitions; +import de.unijena.cheminf.mortar.model.util.ChemUtil; +import de.unijena.cheminf.mortar.model.util.CollectionUtil; +import de.unijena.cheminf.mortar.model.util.IDisplayEnum; +import de.unijena.cheminf.mortar.model.util.SimpleIDisplayEnumConstantProperty; + +import javafx.beans.property.Property; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleIntegerProperty; + +import org.openscience.cdk.AtomContainer; +import org.openscience.cdk.AtomContainerSet; +import org.openscience.cdk.Bond; +import org.openscience.cdk.PseudoAtom; +import org.openscience.cdk.exception.CDKException; +import org.openscience.cdk.graph.ConnectivityChecker; +import org.openscience.cdk.graph.CycleFinder; +import org.openscience.cdk.graph.Cycles; +import org.openscience.cdk.graph.invariant.ConjugatedPiSystemsDetector; +import org.openscience.cdk.interfaces.IAtom; +import org.openscience.cdk.interfaces.IAtomContainer; +import org.openscience.cdk.interfaces.IAtomContainerSet; +import org.openscience.cdk.interfaces.IBond; +import org.openscience.cdk.interfaces.IElement; +import org.openscience.cdk.interfaces.IRingSet; +import org.openscience.cdk.ringsearch.RingSearch; +import org.openscience.cdk.tools.manipulator.AtomContainerManipulator; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Java class implementing an algorithm for detection and fragmentation of alkyl + * structures in MORTAR using the CDK. + * + * @author Maximilian Rottmann (maximilian.rottmann@studmail.w-hs.de) + * @version 1.0.0.0 + */ +public class AlkylStructureFragmenter implements IMoleculeFragmenter{ + // + // + /** + * Name of the fragmenter. + */ + public static final String ALGORITHM_NAME = "Alkyl Fragmenter"; + /** + * Default value for maximum length of carbon side chains. + */ + public static final int MAX_CHAIN_LENGTH_SETTING_DEFAULT = 0; + /** + * Default boolean value for determination of further side chain dissection. + */ + public static final boolean FRAGMENT_SIDE_CHAINS_SETTING_DEFAULT = false; + /** + * Default boolean value for determining whether alternative or standard single carbon handling should be used. + */ + public static final boolean ALTERNATIVE_SINGLE_CARBON_HANDLING_SETTING_DEFAULT = false; + /** + * Default boolean value for determining which single ring detection method should be used. + */ + public static final boolean ALTERNATIVE_SINGLE_RING_DETECTION_SETTING_DEFAULT = false; + /** + * Default boolean value for setting keeping rings intact. + */ + public static final boolean KEEP_RINGS_SETTING_DEFAULT = true; + // + /** + * Key for an internal index property, used in uniquely identifying atoms during fragmentation. + */ + public static final String INTERNAL_ASF_ATOM_INDEX_PROPERTY_KEY = "ASF.ATOM_INDEX"; + /** + * Key for an internal index property used in uniquely identifying bonds during fragmentation. + */ + public static final String INTERNAL_ASF_BOND_INDEX_PROPERTY_KEY = "ASF.BOND_INDEX"; + /** + * Key for an internal boolean property, used in identifying tertiary carbon atoms. + */ + public static final String INTERNAL_ASF_TERTIARY_CARBON_PROPERTY_KEY = "ASF.TERTIARY_CARBON"; + /** + * Key for an internal boolean property used in identifying quaternary carbon atoms. + */ + public static final String INTERNAL_ASF_QUATERNARY_CARBON_PROPERTY_KEY = "ASF.QUATERNARY_CARBON"; + /** + * Key for an internal integer property used in indexing detected fused rings. + */ + public static final String INTERNAL_ASF_FUSED_RING_INDEX_PROPERTY_KEY = "ASF.FUSED_RING_INDEX"; + /** + * Key for an internal integer property used in indexing detected isolated rings. + */ + public static final String INTERNAL_ASF_ISOLATED_RING_INDEX_PROPERTY_KEY = "ASF.ISOLATED_RING_INDEX"; + /** + * Key for an internal boolean property used in identifying double bonds and their atoms. + */ + public static final String INTERNAL_ASF_DOUBLE_BOND_MARKER_KEY = "ASF.DOUBLE_BOND_MARKER"; + /** + * Key for an internal boolean property used in identifying triple bonds and their atoms. + */ + public static final String INTERNAL_ASF_TRIPLE_BOND_MARKER_KEY = "ASF.TRIPLE_BOND_MARKER"; + /** + * Key for an internal boolean property used in identifying neighboring atoms of tertiary or quaternary carbon atoms + * during fragmentation. + */ + public static final String INTERNAL_ASF_NEIGHBOR_MARKER_KEY = "ASF.NEIGHBOR_MARKER"; + /** + * Key for an internal boolean property used in identifying ring structure atoms and bonds during fragmentation. + */ + public static final String INTERNAL_ASF_RING_MARKER_KEY = "ASF.RING_MARKER"; + /** + * Key for an internal boolean property used in identifying conjugated pi bond systems during fragmentation. + */ + public static final String INTERNAL_ASF_CONJ_PI_MARKER_KEY = "ASF.CONJ_PI_MARKER"; + /** + * Key for an internal array list property used in identifying spiro configuration carbons. + */ + public static final String INTERNAL_ASF_RING_ATOM_LIST_KEY = "ASF.RING_ATOM_LIST"; + // + // + // + // + /** + * A property that has a constant fragment hydrogen saturation setting. + */ + private final SimpleIDisplayEnumConstantProperty fragmentSaturationSetting; + /** + * A property that has a constant boolean value determining whether side chains should be fragmented. + */ + private final SimpleBooleanProperty fragmentSideChainsSetting; + /** + * A property that has a constant carbon side chain setting. + */ + private final SimpleIntegerProperty maxChainLengthSetting; + /** + * A property that has a constant boolean value determining which single carbon handling to use during fragmentation. + */ + private final SimpleBooleanProperty alternativeSingleCarbonHandlingSetting; + /** + * A property that has a constant boolean value determining which single ring detection method to use during fragmentation. + */ + private final SimpleBooleanProperty alternativeSingleRingDetectionSetting; + /** + * A property that has a constant boolean value defining if rings should be dissected further. + */ + private final SimpleBooleanProperty keepRingsSetting; + /** + * Map to store pairs of {@literal }. + */ + private final HashMap settingNameTooltipTextMap; + /** + * All settings of this fragmenter, encapsulated in JavaFX properties for binding to GUI. + */ + private final List> settings; + /** + * HashMap of all display names of corresponding settings. + */ + private final HashMap settingNameDisplayNameMap; + /** + * Logger of this class. + */ + private static final Logger logger = Logger.getLogger(AlkylStructureFragmenter.class.getName()); + + // + // + // + /** + * Constructor, all settings are initialised with their respective default values. + */ + public AlkylStructureFragmenter(){ + int tmpSettingsNameTooltipNumber = 6; + int tmpInitialCapacitySettingsNameTooltipHashMap = CollectionUtil.calculateInitialHashCollectionCapacity( + tmpSettingsNameTooltipNumber, + BasicDefinitions.DEFAULT_HASH_COLLECTION_LOAD_FACTOR); + this.settingNameTooltipTextMap = new HashMap<>(tmpInitialCapacitySettingsNameTooltipHashMap, BasicDefinitions.DEFAULT_HASH_COLLECTION_LOAD_FACTOR); + this.settingNameDisplayNameMap = new HashMap<>(tmpInitialCapacitySettingsNameTooltipHashMap, BasicDefinitions.DEFAULT_HASH_COLLECTION_LOAD_FACTOR); + this.fragmentSaturationSetting = new SimpleIDisplayEnumConstantProperty(this, "Fragment saturation setting", + IMoleculeFragmenter.FRAGMENT_SATURATION_OPTION_DEFAULT, IMoleculeFragmenter.FragmentSaturationOption.class) { + @Override + public void set(IDisplayEnum newValue) throws NullPointerException, IllegalArgumentException { + try { + //call to super.set() for parameter checks + super.set(newValue); + } catch (NullPointerException | IllegalArgumentException anException) { + AlkylStructureFragmenter.this.logger.log(Level.WARNING, anException.toString(), anException); + GuiUtil.guiExceptionAlert(Message.get("Fragmenter.IllegalSettingValue.Title"), + Message.get("Fragmenter.IllegalSettingValue.Header"), + anException.toString(), + anException); + //re-throws the exception to properly reset the binding + throw anException; + } + } + }; + this.settingNameTooltipTextMap.put(this.fragmentSaturationSetting.getName(), + Message.get("AlkylStructureFragmenter.fragmentSaturationSetting.tooltip")); + this.settingNameDisplayNameMap.put(this.fragmentSaturationSetting.getName(), + Message.get("AlkylStructureFragmenter.fragmentSaturationSetting.displayName")); + this.fragmentSideChainsSetting = new SimpleBooleanProperty(this, "Fragmentation of hydrocarbon side chains setting", + AlkylStructureFragmenter.FRAGMENT_SIDE_CHAINS_SETTING_DEFAULT); + this.settingNameTooltipTextMap.put(this.fragmentSideChainsSetting.getName(), + Message.get("AlkylStructureFragmenter.fragmentSideChainsSetting.tooltip")); + this.settingNameDisplayNameMap.put(this.fragmentSideChainsSetting.getName(), + Message.get("AlkylStructureFragmenter.fragmentSideChainsSetting.displayName")); + this.maxChainLengthSetting = new SimpleIntegerProperty(this, "Carbon side chains maximum length setting", + AlkylStructureFragmenter.MAX_CHAIN_LENGTH_SETTING_DEFAULT); + this.settingNameTooltipTextMap.put(this.maxChainLengthSetting.getName(), + Message.get("AlkylStructureFragmenter.maxChainLengthSetting.tooltip")); + this.settingNameDisplayNameMap.put(this.maxChainLengthSetting.getName(), + Message.get("AlkylStructureFragmenter.maxChainLengthSetting.displayName")); + this.alternativeSingleCarbonHandlingSetting = new SimpleBooleanProperty(this, "Single carbon handling setting", + AlkylStructureFragmenter.ALTERNATIVE_SINGLE_CARBON_HANDLING_SETTING_DEFAULT); + this.settingNameTooltipTextMap.put(this.alternativeSingleCarbonHandlingSetting.getName(), + Message.get("AlkylStructureFragmenter.alternativeSingleCarbonHandlingSetting.tooltip")); + this.settingNameDisplayNameMap.put(this.alternativeSingleCarbonHandlingSetting.getName(), + Message.get("AlkylStructureFragmenter.alternativeSingleCarbonHandlingSetting.displayName")); + this.alternativeSingleRingDetectionSetting = new SimpleBooleanProperty(this, "Single ring detection setting", + AlkylStructureFragmenter.ALTERNATIVE_SINGLE_RING_DETECTION_SETTING_DEFAULT); + this.settingNameTooltipTextMap.put(this.alternativeSingleRingDetectionSetting.getName(), + Message.get("AlkylStructureFragmenter.alternativeSingleRingDetectionSetting.tooltip")); + this.settingNameDisplayNameMap.put(this.alternativeSingleRingDetectionSetting.getName(), + Message.get("AlkylStructureFragmenter.alternativeSingleRingDetectionSetting.displayName")); + this.keepRingsSetting = new SimpleBooleanProperty(this, "Keep rings setting", + AlkylStructureFragmenter.KEEP_RINGS_SETTING_DEFAULT); + this.settingNameTooltipTextMap.put(this.keepRingsSetting.getName(), + Message.get("AlkylStructureFragmenter.keepRingsSetting.tooltip")); + this.settingNameDisplayNameMap.put(this.keepRingsSetting.getName(), + Message.get("AlkylStructureFragmenter.keepRingsSetting.displayName")); + this.settings = new ArrayList<>(6); + this.settings.add(this.fragmentSaturationSetting); + this.settings.add(this.fragmentSideChainsSetting); + this.settings.add(this.maxChainLengthSetting); + this.settings.add(this.alternativeSingleCarbonHandlingSetting); + this.settings.add(this.alternativeSingleRingDetectionSetting); + this.settings.add(this.keepRingsSetting); + } + // + // + // + @Override + public List> settingsProperties() { + return this.settings; + } + + @Override + public Map getSettingNameToTooltipTextMap() { + return settingNameTooltipTextMap; + } + + /** + * Returns a map containing language-specific names (values) for the settings with the given names (keys) to be used + * in the GUI. + * + * @return map with display names + */ + @Override + public Map getSettingNameToDisplayNameMap() { + return this.settingNameDisplayNameMap; + } + + @Override + public String getFragmentationAlgorithmName() { + return AlkylStructureFragmenter.ALGORITHM_NAME; + } + + /** + * Returns a language-specific name of the fragmenter to be used in the GUI. + * The given name must be unique among the available fragmentation algorithms! + * + * @return language-specific name for display in GUI + */ + @Override + public String getFragmentationAlgorithmDisplayName() { + return Message.get("AlkylStructureFragmenter.displayName"); + } + + @Override + public FragmentSaturationOption getFragmentSaturationSetting() { + return (IMoleculeFragmenter.FragmentSaturationOption) this.fragmentSaturationSetting.get(); + } + + /** + * Public get method for maximum chain length setting. + * + * @return integer value of maxChainLengthSetting + */ + public int getMaxChainLengthSetting() { + return this.maxChainLengthSetting.get(); + } + + /** + * Public get method for maximum chain length setting property. + * + * @return SimpleIntegerProperty maxChainLengthSetting + */ + public SimpleIntegerProperty getMaxChainLengthSettingProperty() { + return this.maxChainLengthSetting; + } + + /** + * Public get method for alternative single carbon handling setting property. + * + * @return SimpleBooleanProperty alternativeSingleCarbonHandlingSetting + */ + public SimpleBooleanProperty getAlternativeSingleCarbonHandlingSettingProperty() {return this.alternativeSingleCarbonHandlingSetting;} + + @Override + public SimpleIDisplayEnumConstantProperty fragmentSaturationSettingProperty() { + return this.fragmentSaturationSetting; + } + // + // + // + @Override + public void setFragmentSaturationSetting(FragmentSaturationOption anOption) throws NullPointerException { + Objects.requireNonNull(anOption, "Given saturation option is null."); + this.fragmentSaturationSetting.set(anOption); + } + /** + * Set method for setting defining whether side chains should be fragmented. + * + * @param aBoolean whether side chains are to be dissected + * @throws NullPointerException if given boolean is null + */ + public void setFragmentSideChainsSetting(boolean aBoolean) throws NullPointerException{ + Objects.requireNonNull(aBoolean, "Given boolean is null."); + this.fragmentSideChainsSetting.set(aBoolean); + } + /** + * Set method for setting defining maximum side chain length. + * + * @param aValue the given integer value for chain length + * @throws IllegalArgumentException if given int is smaller than 0 + */ + public void setMaxChainLengthSetting(int aValue) throws IllegalArgumentException{ + if (aValue < 0) { + throw new IllegalArgumentException("Given chain length cannot be negative."); + } + this.maxChainLengthSetting.set(aValue); + } + /** + * Set method for setting defining whether alternative single carbon handling should be used. + * + * @param aBoolean the given boolean value for switching handling + */ + public void setAlternativeSingleCarbonHandlingSetting(boolean aBoolean){ + Objects.requireNonNull(aBoolean, "Given boolean is null."); + this.alternativeSingleCarbonHandlingSetting.set(aBoolean); + } + /** + * Set method for setting defining whether alternative single ring detection should be used. + * + * @param aBoolean the given boolean value for switching between alternative and default detection + */ + public void setAlternativeSingleRingDetectionSetting(boolean aBoolean) { + Objects.requireNonNull(aBoolean, "Given boolean is null"); + this.alternativeSingleRingDetectionSetting.set(aBoolean); + } + /** + * Set method for setting defining if rings should be dissected or kept intact. + * + * @param aBoolean the given boolean value for switching between dissecting rings and keeping them intact + */ + public void setKeepRingsSetting(boolean aBoolean) { + Objects.requireNonNull(aBoolean, "Given boolean is null"); + this.keepRingsSetting.set(aBoolean); + } + // + // + // + @Override + public IMoleculeFragmenter copy() { + AlkylStructureFragmenter tmpCopy = new AlkylStructureFragmenter(); + tmpCopy.setFragmentSaturationSetting((IMoleculeFragmenter.FragmentSaturationOption) this.fragmentSaturationSetting.get()); + tmpCopy.setFragmentSideChainsSetting(this.fragmentSideChainsSetting.get()); + tmpCopy.setMaxChainLengthSetting(this.maxChainLengthSetting.get()); + tmpCopy.setAlternativeSingleCarbonHandlingSetting(this.alternativeSingleCarbonHandlingSetting.get()); + tmpCopy.setAlternativeSingleRingDetectionSetting(this.alternativeSingleRingDetectionSetting.get()); + tmpCopy.setKeepRingsSetting(this.keepRingsSetting.get()); + return tmpCopy; + } + + @Override + public void restoreDefaultSettings() { + this.fragmentSaturationSetting.set(IMoleculeFragmenter.FRAGMENT_SATURATION_OPTION_DEFAULT); + this.fragmentSideChainsSetting.set(AlkylStructureFragmenter.FRAGMENT_SIDE_CHAINS_SETTING_DEFAULT); + this.maxChainLengthSetting.set(AlkylStructureFragmenter.MAX_CHAIN_LENGTH_SETTING_DEFAULT); + this.alternativeSingleCarbonHandlingSetting.set(AlkylStructureFragmenter.ALTERNATIVE_SINGLE_CARBON_HANDLING_SETTING_DEFAULT); + this.alternativeSingleRingDetectionSetting.set(AlkylStructureFragmenter.ALTERNATIVE_SINGLE_RING_DETECTION_SETTING_DEFAULT); + this.keepRingsSetting.set(AlkylStructureFragmenter.KEEP_RINGS_SETTING_DEFAULT); + } + // + // + /** + * Returns true if the given molecule cannot be fragmented by the algorithm. + * If the molecule is null: true is returned and therefore the molecule is filtered out. + *

+ * Checks the given IAtomContainer aMolecule for non-carbon and non-hydrogen atoms and returns true if + * non-conforming atoms are found, otherwise false is returned and the molecule can be fragmented. + *

+ * + * @param aMolecule the molecule to check + * @return true or false, depending on atom check + */ + @Override + public boolean shouldBeFiltered(IAtomContainer aMolecule) { + if (Objects.isNull(aMolecule) || aMolecule.isEmpty()) { + return true; + } + try { + for (IAtom tmpAtom : aMolecule.atoms()) { + if (tmpAtom.getAtomicNumber() != IElement.H && tmpAtom.getAtomicNumber() != IElement.C) { + return true; + } + } + return false; + } catch (Exception anException) { + AlkylStructureFragmenter.this.logger.log(Level.WARNING, + anException + " Molecule ID: " + aMolecule.getID(), anException); + return true; + } + } + /** + * Method for determining if given molecule needs preprocessing. + * Always returns false, as no preprocessing is currently needed. + * + * @param aMolecule the molecule to check + * @return currently always false + * @throws NullPointerException if the given molecule is null + */ + @Override + public boolean shouldBePreprocessed(IAtomContainer aMolecule) throws NullPointerException { + Objects.requireNonNull(aMolecule, "Given molecule is null"); + return false; + } + @Override + public boolean canBeFragmented(IAtomContainer aMolecule) throws NullPointerException { + //throws NullpointerException if molecule is null + Objects.requireNonNull(aMolecule, "Given molecule is null."); + boolean tmpShouldBeFiltered = this.shouldBeFiltered(aMolecule); + boolean tmpShouldBePreprocessed = this.shouldBePreprocessed(aMolecule); + return !tmpShouldBeFiltered && !tmpShouldBePreprocessed; + } + /** + * Method for applying special preprocessing steps before fragmenting the given molecule. + * Currently, no preprocessing applied as none is needed. + * + * @param aMolecule the molecule to preprocess + * @return aMolecule, unchanged molecule as no preprocessing is currently needed + * @throws NullPointerException if the given molecule is null + */ + @Override + public IAtomContainer applyPreprocessing(IAtomContainer aMolecule) throws NullPointerException { + Objects.requireNonNull(aMolecule, "Given molecule is null"); + return aMolecule; + } + //
+ // + // + @Override + public List fragmentMolecule(IAtomContainer aMolecule) + throws NullPointerException, IllegalArgumentException, CloneNotSupportedException { + // + IAtomContainer tmpClone = aMolecule.clone(); + int tmpPreFragmentationAtomCount = 0; + for (IAtom tmpAtom: tmpClone.atoms()) { + if (tmpAtom.getAtomicNumber() != 0) { + tmpPreFragmentationAtomCount++; + } + } + System.out.println("PreFragAtomCount: " + tmpPreFragmentationAtomCount); + System.out.println("PreFragBondCount: " + tmpClone.getBondCount()); + try { + AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(tmpClone); + //add ChemUtils atom checks? + } catch (CDKException aCDKException) { + AlkylStructureFragmenter.this.logger.log(Level.WARNING, + aCDKException + " Molecule ID: " + aMolecule.getID(), aCDKException); + throw new IllegalArgumentException(); + } + // + // + // + Object[] tmpObject = new Object[2]; + tmpObject[0] = this.fillAtomArray(tmpClone); + tmpObject[1] = this.fillBondArray(tmpClone); + tmpObject = this.markNeighborAtomsAndBonds((IAtom[]) tmpObject[0], (IBond[]) tmpObject[1]); + tmpObject = this.markRings(tmpClone, (IAtom[]) tmpObject[0], (IBond[]) tmpObject[1]); + tmpObject = this.markConjugatedPiSystems(tmpClone, (IAtom[]) tmpObject[0], (IBond[]) tmpObject[1]); + tmpObject = this.markMultiBonds((IAtom[]) tmpObject[0], (IBond[]) tmpObject[1]); + // + // + // + try { + int tmpPostFragmentationAtomCount = 0; + int tmpPostFragmentationBondCount = 0; + IAtomContainerSet tmpFragmentSet = this.extractFragments((IAtom[]) tmpObject[0],(IBond[]) tmpObject[1]); + for (IAtomContainer tmpAtomContainer: tmpFragmentSet.atomContainers()) { + for (IAtom tmpAtom: tmpAtomContainer.atoms()) { + if (tmpAtom.getAtomicNumber() != 0) + tmpPostFragmentationAtomCount++; + } + tmpPostFragmentationBondCount += tmpAtomContainer.getBondCount(); + } + System.out.println("PostFragAtomCount: " + tmpPostFragmentationAtomCount); + System.out.println("PostFragBondCount: " + tmpPostFragmentationBondCount); + if (tmpPostFragmentationAtomCount != tmpPreFragmentationAtomCount) { + throw new Exception("Molecular formula is not the same between original molecule and received fragments!"); + } + if (this.fragmentSaturationSetting.get().equals(FragmentSaturationOption.HYDROGEN_SATURATION)) { + return this.saturateWithImplicitHydrogen(tmpFragmentSet); + } + ArrayList tmpFragmentList = new ArrayList<>(tmpFragmentSet.getAtomContainerCount()); + for (IAtomContainer tmpAtomContainer: tmpFragmentSet.atomContainers()) { + tmpFragmentList.add(tmpAtomContainer); + } + return tmpFragmentList; + } catch (Exception anException) { + AlkylStructureFragmenter.this.logger.log(Level.WARNING, + anException + "extraction or saturation failed at molecule: " + tmpClone.getID(), anException); + throw new IllegalArgumentException("Unexpected error occurred during fragmentation of molecule: " + + tmpClone.getID() + ", at fragment extraction: " + anException.toString()); + } + // + // + } + + + // + // + //
+ // + // + + /** + * Method to fill an IAtom array with the atoms of the input IAtomContainer + * and place fragmentation properties on them. + * + * IMPORTANT: + * All used properties have to be set in this step ONCE (independent of set values) in order for correct + * fragmenter function, especially tertiary and quaternary carbon detection. + * + * @param aClone IAtomContainer, best a clone, from which atoms are put into array + * @return IAtom array containing the atoms + */ + protected IAtom[] fillAtomArray(IAtomContainer aClone) { + IAtom[] tmpAtomArray = new IAtom[aClone.getAtomCount()]; + int tmpAlkylSFAtomIndex; + for (tmpAlkylSFAtomIndex = 0; tmpAlkylSFAtomIndex < aClone.getAtomCount(); tmpAlkylSFAtomIndex++) { + IAtom tmpAtom = aClone.getAtom(tmpAlkylSFAtomIndex); + ArrayList tmpRingAtomList = new ArrayList<>(); + if (tmpAtom != null) { + /* + set atom properties, IMPORTANT: this needs to be done in array filling step for correct function of + tertiary or quaternary carbon atoms and neighbors + */ + tmpAtom.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_ATOM_INDEX_PROPERTY_KEY, tmpAlkylSFAtomIndex); + tmpAtom.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_RING_MARKER_KEY, false); + tmpAtom.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_CONJ_PI_MARKER_KEY, false); + tmpAtom.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_TERTIARY_CARBON_PROPERTY_KEY, false); + tmpAtom.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_QUATERNARY_CARBON_PROPERTY_KEY, false); + tmpAtom.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_DOUBLE_BOND_MARKER_KEY, false); + tmpAtom.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_TRIPLE_BOND_MARKER_KEY, false); + tmpAtom.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_NEIGHBOR_MARKER_KEY, false); + tmpAtom.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_RING_ATOM_LIST_KEY, tmpRingAtomList); + tmpAtomArray[tmpAlkylSFAtomIndex] = tmpAtom; + } + } + return tmpAtomArray; + } + /** + * Method to fill an IBond array with the bonds of the input IAtomContainer + * and place fragmentation properties on them. + * + * @param aClone IAtomContainer, best a clone, from which bonds are put into array + * @return IBond array containing the bonds + */ + protected IBond[] fillBondArray(IAtomContainer aClone) { + IBond[] tmpBondArray = new IBond[aClone.getBondCount()]; + int tmpAlkylSFBondIndex; + for (tmpAlkylSFBondIndex = 0; tmpAlkylSFBondIndex < aClone.getBondCount(); tmpAlkylSFBondIndex++) { + IBond tmpBond = aClone.getBond(tmpAlkylSFBondIndex); + if (tmpBond != null) { + tmpBond.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_BOND_INDEX_PROPERTY_KEY, tmpAlkylSFBondIndex); + tmpBond.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_RING_MARKER_KEY, false); + tmpBond.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_CONJ_PI_MARKER_KEY, false); + tmpBond.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_DOUBLE_BOND_MARKER_KEY, false); + tmpBond.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_TRIPLE_BOND_MARKER_KEY, false); + tmpBond.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_NEIGHBOR_MARKER_KEY, false); + tmpBondArray[tmpAlkylSFBondIndex] = tmpBond; + } + } + return tmpBondArray; + } + + /** + * Protected method for detecting and marking tertiary or quaternary carbon atoms and their surrounding neighbor atoms and bonds. + * + * @param anAtomArray Given array with atoms of molecule to be fragmented + * @param aBondArray Given array with bonds of molecule to be fragmented + * @return Object[] containing manipulated atom and bond arrays for easier data transfer + */ + protected Object[] markNeighborAtomsAndBonds(IAtom[] anAtomArray, IBond[] aBondArray) { + Objects.requireNonNull(anAtomArray, "Given atom array is null."); + Objects.requireNonNull(aBondArray,"Given bond array is null"); + //set general atom and specific bond properties + for (int tmpAtomIndex = 0; tmpAtomIndex < anAtomArray.length; tmpAtomIndex++) { + IAtom tmpAtom = anAtomArray[tmpAtomIndex]; + System.out.println("markNeighbor Index: " + tmpAtomIndex); + if (tmpAtom != null) { + //marking of tertiary or quaternary carbons together with their neighbor atoms + if (tmpAtom.getBondCount() == 3 && tmpAtom.getMaxBondOrder() == IBond.Order.SINGLE) { + tmpAtom.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_TERTIARY_CARBON_PROPERTY_KEY, true); + //may not work as no guarantee for proper atom or bond property set -> may have already been iterated over + for (IBond tmpBond: tmpAtom.bonds()) { + int tmpBondIndex = tmpBond.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_BOND_INDEX_PROPERTY_KEY); + int tmpBondBeginIndex = tmpBond.getBegin().getProperty(AlkylStructureFragmenter.INTERNAL_ASF_ATOM_INDEX_PROPERTY_KEY); + int tmpBondEndIndex = tmpBond.getEnd().getProperty(AlkylStructureFragmenter.INTERNAL_ASF_ATOM_INDEX_PROPERTY_KEY); + //System.out.println("bondIndex: " + tmpBondIndex + ", beginIndex: " + tmpBondBeginIndex + ", endIndex: " + tmpBondEndIndex); + if (tmpBond.getBegin() == tmpAtom) { + anAtomArray[tmpBondEndIndex].setProperty(AlkylStructureFragmenter.INTERNAL_ASF_NEIGHBOR_MARKER_KEY, true); + aBondArray[tmpBondIndex].setProperty(AlkylStructureFragmenter.INTERNAL_ASF_NEIGHBOR_MARKER_KEY, true); + System.out.println("neighbor atom end set"); + } else if (tmpBond.getEnd() == tmpAtom) { + anAtomArray[tmpBondBeginIndex].setProperty(AlkylStructureFragmenter.INTERNAL_ASF_NEIGHBOR_MARKER_KEY, true); + aBondArray[tmpBondIndex].setProperty(AlkylStructureFragmenter.INTERNAL_ASF_NEIGHBOR_MARKER_KEY, true); + System.out.println("neighbor atom begin set"); + } + //System.out.println("atomStatus Begin: " + anAtomArray[tmpBondBeginIndex].getProperty(AlkylStructureFragmenter.INTERNAL_ASF_NEIGHBOR_MARKER_KEY) + ", atomBondCount: " + anAtomArray[tmpBondBeginIndex].getBondCount()); + //System.out.println("atomStatus End: " + anAtomArray[tmpBondEndIndex].getProperty(AlkylStructureFragmenter.INTERNAL_ASF_NEIGHBOR_MARKER_KEY) + ", atomBondCount: " + anAtomArray[tmpBondEndIndex].getBondCount()); + //System.out.println("bondStatus: " + aBondArray[tmpBondIndex].getProperty(AlkylStructureFragmenter.INTERNAL_ASF_NEIGHBOR_MARKER_KEY)); + } + } else if (tmpAtom.getBondCount() == 4 && tmpAtom.getMaxBondOrder() == IBond.Order.SINGLE) { + tmpAtom.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_QUATERNARY_CARBON_PROPERTY_KEY, true); + //may also not work + for (IBond tmpBond: tmpAtom.bonds()) { + int tmpBondIndex = tmpBond.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_BOND_INDEX_PROPERTY_KEY); + int tmpBondBeginIndex = tmpBond.getBegin().getProperty(AlkylStructureFragmenter.INTERNAL_ASF_ATOM_INDEX_PROPERTY_KEY); + int tmpBondEndIndex = tmpBond.getEnd().getProperty(AlkylStructureFragmenter.INTERNAL_ASF_ATOM_INDEX_PROPERTY_KEY); + if (tmpBond.getBegin() == tmpAtom) { + anAtomArray[tmpBondEndIndex].setProperty(AlkylStructureFragmenter.INTERNAL_ASF_NEIGHBOR_MARKER_KEY, true); + aBondArray[tmpBondIndex].setProperty(AlkylStructureFragmenter.INTERNAL_ASF_NEIGHBOR_MARKER_KEY, true); + } else if (tmpBond.getEnd() == tmpAtom) { + anAtomArray[tmpBondBeginIndex].setProperty(AlkylStructureFragmenter.INTERNAL_ASF_NEIGHBOR_MARKER_KEY, true); + aBondArray[tmpBondIndex].setProperty(AlkylStructureFragmenter.INTERNAL_ASF_NEIGHBOR_MARKER_KEY, true); + } + } + } + } + } + //set general bond properties + for (int tmpBondIndex = 0; tmpBondIndex < aBondArray.length; tmpBondIndex++) { + IBond tmpBond = aBondArray[tmpBondIndex]; + if (tmpBond != null) { + + boolean tmpIsBeginTertiary = tmpBond.getBegin().getProperty(AlkylStructureFragmenter.INTERNAL_ASF_TERTIARY_CARBON_PROPERTY_KEY); + boolean tmpIsEndTertiary = tmpBond.getBegin().getProperty(AlkylStructureFragmenter.INTERNAL_ASF_TERTIARY_CARBON_PROPERTY_KEY); + boolean tmpIsBeginQuaternary = tmpBond.getBegin().getProperty(AlkylStructureFragmenter.INTERNAL_ASF_QUATERNARY_CARBON_PROPERTY_KEY); + boolean tmpIsEndQuaternary = tmpBond.getBegin().getProperty(AlkylStructureFragmenter.INTERNAL_ASF_QUATERNARY_CARBON_PROPERTY_KEY); + if (tmpIsBeginTertiary || tmpIsEndTertiary || tmpIsBeginQuaternary || tmpIsEndQuaternary) { + tmpBond.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_NEIGHBOR_MARKER_KEY, true); + } + aBondArray[tmpBondIndex] = tmpBond; + } + } + Object[] tmpObject = new Object[2]; + tmpObject[0] = anAtomArray; + tmpObject[1] = aBondArray; + return tmpObject; + } + /** + * Protected method to mark all atoms and bonds of any rings in the given atomcontainer. + * + * @param anAtomContainer IAtomContainer to mark atoms and bonds in + * @param anAtomArray containing atoms to be marked + * @param aBondArray containing bonds to be marked + * @return Object containing both input arrays + */ + protected Object[] markRings(IAtomContainer anAtomContainer, IAtom[] anAtomArray, IBond[] aBondArray) throws IllegalArgumentException { + // + // + Objects.requireNonNull(anAtomArray); + Objects.requireNonNull(aBondArray); + RingSearch tmpRingSearch = new RingSearch(anAtomContainer); + try { + List tmpFusedList = tmpRingSearch.fusedRingFragments(); + if (!tmpFusedList.isEmpty()) { + //think of way to incorporate ring index + int[] tmpRingIndexArray = new int[tmpFusedList.size()]; + for (int tmpFusedCount = 0; tmpFusedCount < tmpFusedList.size(); tmpFusedCount++) { + for (IAtom tmpFusedAtom: tmpFusedList.get(tmpFusedCount).atoms()) { + int tmpAtomInteger = tmpFusedAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_ATOM_INDEX_PROPERTY_KEY); + IAtom tmpAtom = anAtomArray[tmpAtomInteger]; + tmpAtom.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_RING_MARKER_KEY, true); + //currently ring index gets overwritten during marking -> use tmpRingIndexArray instead where ring affection is stored (possibly multiple) + tmpAtom.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_FUSED_RING_INDEX_PROPERTY_KEY, tmpFusedCount); + } + for (IBond tmpFusedBond: tmpFusedList.get(tmpFusedCount).bonds()) { + int tmpBondInteger = tmpFusedBond.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_BOND_INDEX_PROPERTY_KEY); + IBond tmpBond = aBondArray[tmpBondInteger]; + tmpBond.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_RING_MARKER_KEY, true); + tmpBond.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_FUSED_RING_INDEX_PROPERTY_KEY, tmpFusedCount); + } + } + } + } catch (Exception anException) { + AlkylStructureFragmenter.this.logger.log(Level.WARNING, anException.toString() + " MoleculeID: " + anAtomContainer.getID(), anException); + throw new IllegalArgumentException("Unexpected error occurred during fragmentation of molecule: " + + anAtomContainer.getID() + ", at fused ringsearch: " + anException.toString()); + } + // + // + // + if (this.alternativeSingleRingDetectionSetting.get()) { + // + CycleFinder tmpMCBCycleFinder = Cycles.mcb(); + IRingSet tmpMCBCyclesSet; + try { + Cycles tmpMCBCycles = tmpMCBCycleFinder.find(anAtomContainer); + tmpMCBCyclesSet = tmpMCBCycles.toRingSet(); + int tmpSingleRingCount = 0; + for (IAtomContainer tmpContainer: tmpMCBCyclesSet.atomContainers()) { + for (IAtom tmpSingleRingAtom: tmpContainer.atoms()) { + int tmpAtomInteger = tmpSingleRingAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_ATOM_INDEX_PROPERTY_KEY); + IAtom tmpAtom = anAtomArray[tmpAtomInteger]; + tmpAtom.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_RING_MARKER_KEY, true); + tmpAtom.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_ISOLATED_RING_INDEX_PROPERTY_KEY, tmpSingleRingCount); + } + for (IBond tmpSingleRingBond: tmpContainer.bonds()) { + int tmpBondInteger = tmpSingleRingBond.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_BOND_INDEX_PROPERTY_KEY); + IBond tmpBond = aBondArray[tmpBondInteger]; + tmpBond.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_RING_MARKER_KEY, true); + tmpBond.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_ISOLATED_RING_INDEX_PROPERTY_KEY, tmpSingleRingCount); + } + tmpSingleRingCount++; + } + } catch (Exception anException) { + AlkylStructureFragmenter.this.logger.log(Level.WARNING, + anException + " MoleculeID: " + anAtomContainer.getID(), anException); + throw new IllegalArgumentException("Unexpected error occurred during fragmentation of molecule: " + + anAtomContainer.getID() + ", at cyclefinder : " + anException.toString()); + } + // + } else { + // + //for spiro detection: set array property for each atom belonging to x rings + //--> if more than one ring --> check for connected atoms to determine if spiro config + try { + List tmpIsolatedRingList = tmpRingSearch.isolatedRingFragments(); + System.out.println("isolated rings: " + tmpIsolatedRingList.size()); + if (!tmpIsolatedRingList.isEmpty()) { + for (int tmpIsolatedCount = 0; tmpIsolatedCount < tmpIsolatedRingList.size(); tmpIsolatedCount++) { + for (IAtom tmpIsolatedAtom: tmpIsolatedRingList.get(tmpIsolatedCount).atoms()) { + int tmpAtomInteger = tmpIsolatedAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_ATOM_INDEX_PROPERTY_KEY); + IAtom tmpAtom = anAtomArray[tmpAtomInteger]; + tmpAtom.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_RING_MARKER_KEY, true); + ArrayList tmpList = tmpAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_RING_ATOM_LIST_KEY); + tmpList.add(tmpIsolatedCount); + tmpAtom.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_RING_ATOM_LIST_KEY, tmpList); + tmpAtom.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_ISOLATED_RING_INDEX_PROPERTY_KEY, tmpIsolatedCount); + } + for (IBond tmpFusedBond: tmpIsolatedRingList.get(tmpIsolatedCount).bonds()) { + int tmpBondInteger = tmpFusedBond.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_BOND_INDEX_PROPERTY_KEY); + IBond tmpBond = aBondArray[tmpBondInteger]; + tmpBond.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_RING_MARKER_KEY, true); + tmpBond.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_ISOLATED_RING_INDEX_PROPERTY_KEY, tmpIsolatedCount); + } + } + } + } catch (Exception anException) { + AlkylStructureFragmenter.this.logger.log(Level.WARNING, + anException + " MoleculeID: " + anAtomContainer.getID(), anException); + throw new IllegalArgumentException("Unexpected error occurred during fragmentation of molecule: " + + anAtomContainer.getID() + ", at isolated RingSearch : " + anException.toString()); + } + // + } + //returns object containing atoms array and bonds array + Object[] tmpObject = new Object[2]; + tmpObject[0] = anAtomArray; + tmpObject[1] = aBondArray; + return tmpObject; + // + // + } + + /** + * Protected method to mark all atoms and bonds of any conjugated pi systems in the given atomcontainer. + * + * @param anAtomContainer IAtomContainer to mark atoms and bonds in + * @param anAtomArray Array containing the atoms of a given fragmentation molecule + * @param aBondArray Array containing the bonds of a given fragmentation molecule + * + * @return new {@link java.lang.Object} for easy array transfer, containing the atom and bond array + */ + //test performance if pi system detection should be relocated to standard ring detection + protected Object[] markConjugatedPiSystems(IAtomContainer anAtomContainer, IAtom[] anAtomArray, IBond[] aBondArray) throws IllegalArgumentException{ + // + Objects.requireNonNull(anAtomArray); + Objects.requireNonNull(aBondArray); + try { + IAtomContainerSet tmpConjugatedAtomContainerSet; + tmpConjugatedAtomContainerSet = ConjugatedPiSystemsDetector.detect(anAtomContainer); + //molecule mapping + //iterate over every atomcontainer from ConjPiSystemsDetector output + for (IAtomContainer tmpConjAtomContainer: tmpConjugatedAtomContainerSet.atomContainers()) { + for (IAtom tmpConjAtom: tmpConjAtomContainer.atoms()) { + int tmpAtomInteger = tmpConjAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_ATOM_INDEX_PROPERTY_KEY); + IAtom tmpAtom = anAtomArray[tmpAtomInteger]; + tmpAtom.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_CONJ_PI_MARKER_KEY, true); + } + for (IBond tmpConjBond: tmpConjAtomContainer.bonds()) { + int tmpBondInteger = tmpConjBond.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_BOND_INDEX_PROPERTY_KEY); + IBond tmpBond = aBondArray[tmpBondInteger]; + tmpBond.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_CONJ_PI_MARKER_KEY, true); + } + } + return new Object[] {anAtomArray, aBondArray}; + } catch (Exception anException) { + AlkylStructureFragmenter.this.logger.log(Level.WARNING, + anException + " MoleculeID: " + anAtomContainer.getID(), anException); + throw new IllegalArgumentException("Unexpected error occurred during fragmentation of molecule: " + + anAtomContainer.getID() + " at conjugated pi systems detector: " + anException.toString()); + } + + // + } + + /** + * Protected method to check given atomcontainer for disconnected structures. + * + * @param anAtomContainer IAtomContainer to check + * @return IAtomContainerSet containing partitioned structures as single IAtomContainer + */ + protected IAtomContainerSet separateDisconnectedStructures(IAtomContainer anAtomContainer) throws IllegalArgumentException{ + Objects.requireNonNull(anAtomContainer,"Given IAtomContainer is null."); + try { + IAtomContainerSet tmpFragmentSet = new AtomContainerSet(); + //logger used only for debug purpose + AlkylStructureFragmenter.this.logger.log(Level.INFO, System.currentTimeMillis() + " start sD, AC size: " + anAtomContainer.getAtomCount() + ", " + anAtomContainer.getBondCount()); + if (!anAtomContainer.isEmpty()) { + if (!ConnectivityChecker.isConnected(anAtomContainer)) { + IAtomContainerSet tmpContainerSet = ConnectivityChecker.partitionIntoMolecules(anAtomContainer); + for (IAtomContainer tmpContainer : tmpContainerSet.atomContainers()) { + tmpFragmentSet.addAtomContainer(tmpContainer); + } + } else { + tmpFragmentSet.addAtomContainer(anAtomContainer); + } + } + return tmpFragmentSet; + } catch (Exception anException) { + AlkylStructureFragmenter.this.logger.log(Level.WARNING, anException + " Connectivity Checking failed at molecule: " + anAtomContainer.getID(), anException); + //logger only used for debug purpose + AlkylStructureFragmenter.this.logger.log(Level.INFO, System.currentTimeMillis() + " start sD, AC size: " + anAtomContainer.getAtomCount() + ", " + anAtomContainer.getBondCount()); + throw new IllegalArgumentException("An Error occurred during Connectivity Checking: " + anException.toString() + + ": " + anAtomContainer.getProperty(Importer.MOLECULE_NAME_PROPERTY_KEY)); + } + } + + /** + * Protected method to saturate a given molecule with implicit hydrogens after fragmentation. + * + * @param anUnsaturatedACSet IAtomContainerSet whose atomcontainers are to be saturated + * @return List of processed atomcontainers, @null if given Set is empty + * @throws CDKException if CDKHydrogenAdder throws an exception + */ + protected List saturateWithImplicitHydrogen(IAtomContainerSet anUnsaturatedACSet) throws CDKException { + Objects.requireNonNull(anUnsaturatedACSet, "Given IAtomContainerSet is null."); + try { + List tmpSaturatedFragments = new ArrayList<>(anUnsaturatedACSet.getAtomContainerCount()); + if (!anUnsaturatedACSet.isEmpty() && !anUnsaturatedACSet.getAtomContainer(0).isEmpty()) { + for (IAtomContainer tmpAtomContainer: anUnsaturatedACSet.atomContainers()) { + if (tmpAtomContainer != null && !tmpAtomContainer.isEmpty()) { + ChemUtil.saturateWithHydrogen(tmpAtomContainer); + tmpSaturatedFragments.add(tmpAtomContainer); + } + } + } + return tmpSaturatedFragments; + } catch (CDKException anException) { + AlkylStructureFragmenter.this.logger.log(Level.WARNING, anException + + " Unable to add Implicit Hydrogen", anException); + throw new CDKException("Unexpected error occurred during implicit hydrogen adding at " + + "hydrogen saturation of molecule, " + anException.toString(), anException); + } + } + + /** + * Protected method to extract detected molecules via properties. + * + * @param anAtomArray Array containing the atoms of a given fragmentation molecule + * @param aBondArray Array containing the bonds of a given fragmentation molecule + * @return IAtomContainerSet with extracted molecules + * @throws CloneNotSupportedException if input cannot be cloned + */ + protected IAtomContainerSet extractFragments(IAtom[] anAtomArray, IBond[] aBondArray) throws CloneNotSupportedException, IllegalArgumentException { + // + Objects.requireNonNull(anAtomArray); + Objects.requireNonNull(aBondArray); + // + IAtomContainerSet tmpExtractionSet = new AtomContainerSet(); + IAtomContainer tmpRingFragmentationContainer = new AtomContainer(); + IAtomContainer tmpChainFragmentationContainer = new AtomContainer(); + IAtomContainer tmpIsolatedMultiBondsContainer = new AtomContainer(); + IAtomContainer tmpTertQuatCarbonContainer = new AtomContainer(); + IAtomContainer tmpSpiroCarbonContainer = new AtomContainer(); + IAtomContainer tmpSpiroResidueContainer = new AtomContainer(); + // + // + //superior performance compared to normal for iteration over Array length + for (IAtom tmpAtom : anAtomArray) { + //checks atom if not part of ring or conjugated pi system + if (!((boolean) tmpAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_RING_MARKER_KEY) + || (boolean) tmpAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_CONJ_PI_MARKER_KEY))) { + if (tmpAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_TERTIARY_CARBON_PROPERTY_KEY)) { + tmpRingFragmentationContainer.addAtom(tmpAtom); + System.out.println("tert atom added"); + if (this.alternativeSingleCarbonHandlingSetting.get()) { + for (int i = 0; i < 3; i++) { + PseudoAtom tmpPseudoAtom = new PseudoAtom(); + tmpTertQuatCarbonContainer.addAtom(tmpPseudoAtom); + tmpTertQuatCarbonContainer.addBond(new Bond(tmpAtom, tmpPseudoAtom)); + } + } + } else if (tmpAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_QUATERNARY_CARBON_PROPERTY_KEY)) { + tmpRingFragmentationContainer.addAtom(tmpAtom); + System.out.println("quart atom added"); + if (this.alternativeSingleCarbonHandlingSetting.get()) { + for (int i = 0; i < 4; i++) { + PseudoAtom tmpPseudoAtom = new PseudoAtom(); + tmpTertQuatCarbonContainer.addAtom(tmpPseudoAtom); + tmpTertQuatCarbonContainer.addBond(new Bond(tmpAtom, tmpPseudoAtom)); + } + } + } //extract atoms for double/triple bonds + else if (tmpAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_DOUBLE_BOND_MARKER_KEY)) { + tmpIsolatedMultiBondsContainer.addAtom(tmpAtom); + System.out.println("double bond atom added"); + } else if (tmpAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_TRIPLE_BOND_MARKER_KEY)) { + tmpIsolatedMultiBondsContainer.addAtom(tmpAtom); + System.out.println("triple bond atom added"); + } + //extract neighbor atoms of tertiary or quaternary carbon atoms + else if (tmpAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_NEIGHBOR_MARKER_KEY)) { + if (this.alternativeSingleCarbonHandlingSetting.get()) {continue;} + tmpRingFragmentationContainer.addAtom(tmpAtom); + System.out.println("neighbor atom added"); + } + //extract residue atoms as linear chain atoms + else { + tmpChainFragmentationContainer.addAtom(tmpAtom); + System.out.println("chain residue atom added"); + } + } else { + if (!this.keepRingsSetting.get()) { + ArrayList tmpList = tmpAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_RING_ATOM_LIST_KEY); + if (tmpList.size() > 1) { + tmpSpiroCarbonContainer.addAtom(tmpAtom); + } /* + else { + tmpSpiroResidueContainer.addAtom(tmpAtom); + } + */ + tmpRingFragmentationContainer.addAtom(tmpAtom); + } else { + tmpRingFragmentationContainer.addAtom(tmpAtom); + System.out.println("ring atom list: " + tmpAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_RING_ATOM_LIST_KEY)); + System.out.println("ring atom added"); + } + } + } + // + // + // + //superior performance compared to normal for iteration over Array length + for (IBond tmpBond : aBondArray) { + IAtom tmpBeginAtom = tmpBond.getBegin(); + IAtom tmpEndAtom = tmpBond.getEnd(); + if (!((boolean) tmpBond.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_RING_MARKER_KEY) + || (boolean) tmpBond.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_CONJ_PI_MARKER_KEY))) { + // + //booleans for bond begin and end atom properties used in fragmentation, self-explanatory + boolean tmpIsBeginRing = tmpBeginAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_RING_MARKER_KEY); + boolean tmpIsEndRing = tmpEndAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_RING_MARKER_KEY); + // + boolean tmpIsBeginConjPi = tmpBeginAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_CONJ_PI_MARKER_KEY); + boolean tmpIsEndConjPi = tmpEndAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_CONJ_PI_MARKER_KEY); + // + boolean tmpIsBeginTertiary = tmpBeginAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_TERTIARY_CARBON_PROPERTY_KEY); + boolean tmpIsEndTertiary = tmpEndAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_TERTIARY_CARBON_PROPERTY_KEY); + // + boolean tmpIsBeginQuaternary = tmpBeginAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_QUATERNARY_CARBON_PROPERTY_KEY); + boolean tmpIsEndQuaternary = tmpEndAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_QUATERNARY_CARBON_PROPERTY_KEY); + // + boolean tmpIsBeginDouble = tmpBeginAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_DOUBLE_BOND_MARKER_KEY); + boolean tmpIsEndDouble = tmpEndAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_DOUBLE_BOND_MARKER_KEY); + // + boolean tmpIsBeginTriple = tmpBeginAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_TRIPLE_BOND_MARKER_KEY); + boolean tmpIsEndTriple = tmpEndAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_TRIPLE_BOND_MARKER_KEY); + // + boolean tmpIsBeginNeighbor = tmpBeginAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_NEIGHBOR_MARKER_KEY); + boolean tmpIsEndNeighbor = tmpEndAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_NEIGHBOR_MARKER_KEY); + + + // + if (tmpBond.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_DOUBLE_BOND_MARKER_KEY)) { + tmpIsolatedMultiBondsContainer.addBond(tmpBond); + System.out.println("double bond added"); + } + else if (tmpBond.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_TRIPLE_BOND_MARKER_KEY)) { + tmpIsolatedMultiBondsContainer.addBond(tmpBond); + System.out.println("triple bond added"); + } + else if ((boolean) tmpBond.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_NEIGHBOR_MARKER_KEY) + && !((tmpIsBeginRing && tmpIsBeginTertiary) || (tmpIsEndRing && tmpIsEndTertiary) + || (tmpIsBeginRing && tmpIsBeginQuaternary) || (tmpIsEndRing && tmpIsEndQuaternary))) { + tmpRingFragmentationContainer.addBond(tmpBond); + System.out.println("neighbor bond added"); + } + if (!(tmpIsBeginRing && tmpIsEndRing && tmpIsBeginConjPi && tmpIsEndConjPi) + && !(tmpIsBeginDouble || tmpIsEndDouble || tmpIsBeginTriple || tmpIsEndTriple)) { + if (!(tmpIsBeginTertiary || tmpIsEndTertiary || tmpIsBeginQuaternary || tmpIsEndQuaternary || tmpIsBeginNeighbor || tmpIsEndNeighbor)) { + tmpChainFragmentationContainer.addBond(tmpBond); + System.out.println("chain residue bond added"); + } + } + } + else { + if (!this.keepRingsSetting.get()) { + //check if begin or end of bond is spiro configuration carbon + boolean tmpIsBeginSpiro = false; + boolean tmpIsEndSpiro = false; + ArrayList tmpBeginList = tmpBeginAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_RING_ATOM_LIST_KEY); + if (tmpBeginList.size() > 1) { + tmpIsBeginSpiro = true; + } + ArrayList tmpEndList = tmpEndAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_RING_ATOM_LIST_KEY); + if (tmpEndList.size() > 1) { + tmpIsEndSpiro = true; + } + if (!tmpIsBeginSpiro && !tmpIsEndSpiro) { + tmpRingFragmentationContainer.addBond(tmpBond); + } + } else { + tmpRingFragmentationContainer.addBond(tmpBond); + System.out.println("ring bond added"); + } + } + } + // + // + // + IAtomContainerSet tmpRingACSet = new AtomContainerSet(); + if (!tmpRingFragmentationContainer.isEmpty()) { + tmpRingACSet = this.separateDisconnectedStructures(tmpRingFragmentationContainer); + } + IAtomContainerSet tmpSingleACSet = new AtomContainerSet(); + if (!tmpTertQuatCarbonContainer.isEmpty()) { + tmpExtractionSet.add(this.separateDisconnectedStructures(tmpTertQuatCarbonContainer)); + } + if (!tmpRingACSet.isEmpty() && tmpRingACSet.getAtomContainerCount() > 0) { + tmpExtractionSet.add(tmpRingACSet); + } + if (!tmpSingleACSet.isEmpty() && tmpSingleACSet.getAtomContainerCount() > 0) { + tmpExtractionSet.add(tmpSingleACSet); + } + //remnants after ring, conj. system and tertiary/quaternary carbon extractions + //expected to be only linear carbohydrates + if (!tmpIsolatedMultiBondsContainer.isEmpty()) { + tmpExtractionSet.add(this.separateDisconnectedStructures(tmpIsolatedMultiBondsContainer)); + } + IAtomContainerSet tmpChainACSet = this.separateDisconnectedStructures(tmpChainFragmentationContainer); + //ACSet for dissected chains + IAtomContainerSet tmpDissectedChainACSet = new AtomContainerSet(); + int tmpMaxChainLengthInteger = this.getMaxChainLengthSetting(); + if (this.fragmentSideChainsSetting.get()) { + //check maxchainlength + switch (tmpMaxChainLengthInteger) { + default -> { + //restrictions > 1 + for (IAtomContainer tmpAtomContainer : tmpChainACSet.atomContainers()) { + tmpDissectedChainACSet.add(this.separateDisconnectedStructures(this.dissectLinearChain(tmpAtomContainer, + tmpMaxChainLengthInteger))); + } + } + case 0 -> { + //if int maxChainLength gives 0 throw IllegalArgumentException + //not happy with how this works, preferably a gui warning would be nice + AlkylStructureFragmenter.this.logger.log(Level.WARNING, "Illegal restriction argument", new IllegalArgumentException()); + } + case 1 -> { + //single methane molecules + IAtomContainer tmpDissectedAC = new AtomContainer(); + for (IAtomContainer tmpAtomContainer : tmpChainACSet.atomContainers()) { + tmpAtomContainer.removeAllBonds(); + tmpDissectedAC.add(tmpAtomContainer); + } + tmpDissectedChainACSet.add(this.separateDisconnectedStructures(tmpDissectedAC)); + } + } + } else { + //no restrictions applied + tmpDissectedChainACSet.add(tmpChainACSet); + } + if (!tmpDissectedChainACSet.isEmpty() && tmpDissectedChainACSet.getAtomContainerCount() > 0) { + tmpExtractionSet.add(tmpDissectedChainACSet); + } + return tmpExtractionSet; + } + + /** + * Protected method to dissect given AtomContainer (containing linear carbon chain) into separate molecules with given length and remnants if + * molecule is too small for given length. + * + * @param anAC AtomContainer to be dissected + * @param aLength Given maximum length of molecule + * @return AtomContainer with separate dissected molecules + */ + protected IAtomContainer dissectLinearChain(IAtomContainer anAC, int aLength) { + IAtomContainer tmpReturnAC = new AtomContainer(); + //starts at 1 for usability, see aLength: 1on1 translation of input to counter + int tmpCounter = 1; + Iterator tmpBondIterator = anAC.bonds().iterator(); + while (tmpBondIterator.hasNext()) { + IBond tmpBond = tmpBondIterator.next(); + if (tmpCounter == aLength) { + if (!tmpBondIterator.hasNext()) { + tmpReturnAC.addAtom(tmpBond.getEnd()); + } + tmpCounter = 1; + } else { + IAtom tmpBeginAtom = tmpBond.getBegin(); + IAtom tmpEndAtom = tmpBond.getEnd(); + tmpReturnAC.addAtom(tmpBeginAtom); + tmpReturnAC.addAtom(tmpEndAtom); + tmpReturnAC.addBond(tmpBond); + tmpCounter++; + } + } + return tmpReturnAC; + } + + /** + * Protected method to mark atoms and bonds with order of double or triple. + * + * @param anAtomArray Atom array providing the atoms of a molecule + * @param aBondArray Bond array providing the bonds of a molecule + * @return new Object containing both given arrays after manipulation of method for easier data transfer + */ + protected Object[] markMultiBonds(IAtom[] anAtomArray, IBond[] aBondArray) { + Objects.requireNonNull(anAtomArray); + Objects.requireNonNull(aBondArray); + for (IBond tmpArrayBond: aBondArray) { + if (tmpArrayBond.getOrder().numeric() == 2) { + tmpArrayBond.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_DOUBLE_BOND_MARKER_KEY, true); + int tmpBeginIndex = tmpArrayBond.getBegin().getProperty(AlkylStructureFragmenter.INTERNAL_ASF_ATOM_INDEX_PROPERTY_KEY); + int tmpEndIndex = tmpArrayBond.getEnd().getProperty(AlkylStructureFragmenter.INTERNAL_ASF_ATOM_INDEX_PROPERTY_KEY); + for (IAtom tmpAtom: anAtomArray) { + int tmpAtomIndex = tmpAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_ATOM_INDEX_PROPERTY_KEY); + if (tmpAtomIndex == tmpBeginIndex) { + tmpAtom.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_DOUBLE_BOND_MARKER_KEY, true); + } else if (tmpAtomIndex == tmpEndIndex) { + tmpAtom.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_DOUBLE_BOND_MARKER_KEY, true); + } + } + } else if (tmpArrayBond.getOrder().numeric() == 3) { + tmpArrayBond.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_TRIPLE_BOND_MARKER_KEY, true); + int tmpBeginIndex = tmpArrayBond.getBegin().getProperty(AlkylStructureFragmenter.INTERNAL_ASF_ATOM_INDEX_PROPERTY_KEY); + int tmpEndIndex = tmpArrayBond.getEnd().getProperty(AlkylStructureFragmenter.INTERNAL_ASF_ATOM_INDEX_PROPERTY_KEY); + for (IAtom tmpAtom: anAtomArray) { + int tmpAtomIndex = tmpAtom.getProperty(AlkylStructureFragmenter.INTERNAL_ASF_ATOM_INDEX_PROPERTY_KEY); + if ( tmpAtomIndex == tmpBeginIndex) { + tmpAtom.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_TRIPLE_BOND_MARKER_KEY, true); + } else if (tmpAtomIndex == tmpEndIndex) { + tmpAtom.setProperty(AlkylStructureFragmenter.INTERNAL_ASF_TRIPLE_BOND_MARKER_KEY, true); + } + } + } + } + Object[] tmpObject = new Object[2]; + tmpObject[0] = anAtomArray; + tmpObject[1] = aBondArray; + return tmpObject; + } + // + // +} diff --git a/src/main/java/de/unijena/cheminf/mortar/model/fragmentation/algorithm/ConjugatedPiSystemFragmenter.java b/src/main/java/de/unijena/cheminf/mortar/model/fragmentation/algorithm/ConjugatedPiSystemFragmenter.java new file mode 100644 index 00000000..e3a87b25 --- /dev/null +++ b/src/main/java/de/unijena/cheminf/mortar/model/fragmentation/algorithm/ConjugatedPiSystemFragmenter.java @@ -0,0 +1,376 @@ +/* + * MORTAR - MOlecule fRagmenTAtion fRamework + * Copyright (C) 2024 Felix Baensch, Jonas Schaub (felix.baensch@w-hs.de, jonas.schaub@uni-jena.de) + * + * Source code is available at + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package de.unijena.cheminf.mortar.model.fragmentation.algorithm; + +import de.unijena.cheminf.mortar.gui.util.GuiUtil; +import de.unijena.cheminf.mortar.message.Message; +import de.unijena.cheminf.mortar.model.io.Importer; +import de.unijena.cheminf.mortar.model.util.BasicDefinitions; +import de.unijena.cheminf.mortar.model.util.CollectionUtil; +import de.unijena.cheminf.mortar.model.util.IDisplayEnum; +import de.unijena.cheminf.mortar.model.util.SimpleIDisplayEnumConstantProperty; + +import javafx.beans.property.Property; + +import org.openscience.cdk.AtomContainer; +import org.openscience.cdk.AtomContainerSet; +import org.openscience.cdk.exception.CDKException; +import org.openscience.cdk.graph.ConnectivityChecker; +import org.openscience.cdk.graph.invariant.ConjugatedPiSystemsDetector; +import org.openscience.cdk.interfaces.IAtom; +import org.openscience.cdk.interfaces.IAtomContainer; +import org.openscience.cdk.interfaces.IAtomContainerSet; +import org.openscience.cdk.interfaces.IBond; +import org.openscience.cdk.tools.CDKHydrogenAdder; +import org.openscience.cdk.tools.manipulator.AtomContainerManipulator; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Java class implementing an algorithm in MORTAR for detection of conjugated pi systems using + * the CDK functionality {@link org.openscience.cdk.graph.invariant.ConjugatedPiSystemsDetector} + * to simply test and validate its purpose. + * + * @author Maximilian Rottmann + * @version 1.1.1.0 + */ +public class ConjugatedPiSystemFragmenter implements IMoleculeFragmenter{ + // + // + /** + * Name of the fragmenter, CPS stands for Conjugated Pi System. + */ + public static final String ALGORITHM_NAME = "CPS Fragmenter"; + /** + * Key for an internal index property, used in uniquely identifying atoms during fragmentation. + */ + public static final String INTERNAL_CPSF_ATOM_INDEX_PROPERTY_KEY = "CPSF.ATOM_INDEX"; + /** + * Key for an internal index property, used in uniquely identifying bonds during fragmentation. + */ + public static final String INTERNAL_CPSF_BOND_INDEX_PROPERTY_KEY = "CPSF.BOND_INDEX"; + // + // + // + /** + * A property that has a constant fragment hydrogen saturation setting. + */ + private final SimpleIDisplayEnumConstantProperty fragmentSaturationSetting; + /** + * All settings of this fragmenter, encapsulated in JavaFX properties for binding to GUI. + */ + private final List> settings; + /** + * Map to store pairs of {@literal }. + */ + + private final HashMap settingNameTooltipTextMap; + private final HashMap settingNameDisplayNameMap; + /** + * The logger responsible for this fragmenter. + */ + private static final Logger logger = Logger.getLogger(ConjugatedPiSystemFragmenter.class.getName()); + private IAtom[] atomArray; + private IBond[] bondArray; + // + // + // + /** + * Constructor of this fragmenter class. + */ + public ConjugatedPiSystemFragmenter(){ + int tmpNumberOfSettingsForTooltipMapSize= 1; + int tmpInitialCapacityForSettingNameTooltipTextMap = CollectionUtil.calculateInitialHashCollectionCapacity( + tmpNumberOfSettingsForTooltipMapSize, + BasicDefinitions.DEFAULT_HASH_COLLECTION_LOAD_FACTOR); + this.settingNameTooltipTextMap = new HashMap<>(tmpInitialCapacityForSettingNameTooltipTextMap, BasicDefinitions.DEFAULT_HASH_COLLECTION_LOAD_FACTOR); + this.settingNameDisplayNameMap = new HashMap<>(tmpInitialCapacityForSettingNameTooltipTextMap, BasicDefinitions.DEFAULT_HASH_COLLECTION_LOAD_FACTOR); + this.fragmentSaturationSetting = new SimpleIDisplayEnumConstantProperty(this, "Fragment saturation setting", + IMoleculeFragmenter.FRAGMENT_SATURATION_OPTION_DEFAULT, IMoleculeFragmenter.FragmentSaturationOption.class) { + @Override + public void set(IDisplayEnum newValue) throws NullPointerException, IllegalArgumentException { + try { + //call to super.set() for parameter checks + super.set(newValue); + } catch (NullPointerException | IllegalArgumentException anException) { + ConjugatedPiSystemFragmenter.this.logger.log(Level.WARNING, anException.toString(), anException); + GuiUtil.guiExceptionAlert("Illegal Argument", "Illegal Argument was set", anException.toString(), anException); + //re-throws the exception to properly reset the binding + throw anException; + } + } + }; + + this.settingNameTooltipTextMap.put(this.fragmentSaturationSetting.getName(), + Message.get("ConjugatedPiSystemFragmenter.fragmentSaturationSetting.tooltip")); + this.settingNameDisplayNameMap.put(this.fragmentSaturationSetting.getName(), + Message.get("ConjugatedPiSystemFragmenter.fragmentSaturationSetting.displayName")); + this.settings = new ArrayList<>(1); + this.settings.add(this.fragmentSaturationSetting); + } + // + // + // + @Override + public List> settingsProperties() { + return this.settings; + } + + @Override + public Map getSettingNameToTooltipTextMap() { + return this.settingNameTooltipTextMap; + } + + /** + * Returns a map containing language-specific names (values) for the settings with the given names (keys) to be used + * in the GUI. + * + * @return map with display names + */ + @Override + public Map getSettingNameToDisplayNameMap() { + return Map.of(); + } + + @Override + public String getFragmentationAlgorithmName() { + return ConjugatedPiSystemFragmenter.ALGORITHM_NAME; + } + + /** + * Returns a language-specific name of the fragmenter to be used in the GUI. + * The given name must be unique among the available fragmentation algorithms! + * + * @return language-specific name for display in GUI + */ + @Override + public String getFragmentationAlgorithmDisplayName() { + return Message.get("ConjugatedPiSystemFragmenter.displayName"); + } + + @Override + public FragmentSaturationOption getFragmentSaturationSetting() { + return (IMoleculeFragmenter.FragmentSaturationOption) this.fragmentSaturationSetting.get(); + } + + @Override + public SimpleIDisplayEnumConstantProperty fragmentSaturationSettingProperty() { + return this.fragmentSaturationSetting; + } + + /* + @Override + public FragmentSaturationOption getFragmentSaturationSettingConstant() { + return FragmentSaturationOption.valueOf(this.fragmentSaturationSetting.get()); + } + */ + // + // + // + /* + @Override + public void setFragmentSaturationSetting(String anOptionName) throws NullPointerException, IllegalArgumentException { + Objects.requireNonNull(anOptionName, "Given saturation option name is null."); + //throws IllegalArgumentException if the given name does not match a constant name in the enum + FragmentSaturationOption tmpConstant = FragmentSaturationOption.valueOf(anOptionName); + this.fragmentSaturationSetting.set(tmpConstant.name()); + } + */ + + @Override + public void setFragmentSaturationSetting(FragmentSaturationOption anOption) throws NullPointerException { + Objects.requireNonNull(anOption, "Given saturation option is null."); + this.fragmentSaturationSetting.set(anOption); + } + // + // + // + @Override + public IMoleculeFragmenter copy() { + ConjugatedPiSystemFragmenter tmpCopy = new ConjugatedPiSystemFragmenter(); + tmpCopy.setFragmentSaturationSetting((IMoleculeFragmenter.FragmentSaturationOption) this.fragmentSaturationSetting.get()); + return tmpCopy; + } + + @Override + public void restoreDefaultSettings() { + this.fragmentSaturationSetting.set(IMoleculeFragmenter.FRAGMENT_SATURATION_OPTION_DEFAULT); + + } + + @Override + public List fragmentMolecule(IAtomContainer aMolecule) throws NullPointerException, IllegalArgumentException, CloneNotSupportedException { + + // + IAtomContainer tmpClone = aMolecule.clone(); + this.clearCache(); + this.atomArray = new IAtom[tmpClone.getAtomCount()]; + this.bondArray = new IBond[tmpClone.getBondCount()]; + int tmpCPSFAtomIndex = 0; + int tmpCPSFBondIndex = 0; + for (IAtom tmpAtom: tmpClone.atoms()) { + if (tmpAtom != null) { + tmpAtom.setProperty(ConjugatedPiSystemFragmenter.INTERNAL_CPSF_ATOM_INDEX_PROPERTY_KEY, tmpCPSFAtomIndex); + this.atomArray[tmpCPSFAtomIndex] = tmpAtom; + tmpCPSFAtomIndex++; + } + } + for (IBond tmpBond: tmpClone.bonds()) { + if (tmpBond != null) { + tmpBond.setProperty(ConjugatedPiSystemFragmenter.INTERNAL_CPSF_BOND_INDEX_PROPERTY_KEY, tmpCPSFBondIndex); + this.bondArray[tmpCPSFBondIndex] = tmpBond; + tmpCPSFBondIndex++; + } + } + // + + // + IAtomContainer tmpFragments = new AtomContainer(); + try { + IAtomContainerSet tmpConjugatedAtomContainerSet; + tmpConjugatedAtomContainerSet = ConjugatedPiSystemsDetector.detect(tmpClone); + for (IAtomContainer tmpConjAtomContainer: tmpConjugatedAtomContainerSet.atomContainers()) { + for (IAtom tmpConjAtom: tmpConjAtomContainer.atoms()) { + int tmpAtomInteger = tmpConjAtom.getProperty(ConjugatedPiSystemFragmenter.INTERNAL_CPSF_ATOM_INDEX_PROPERTY_KEY); + tmpFragments.addAtom(this.atomArray[tmpAtomInteger]); + } + for (IBond tmpConjBond: tmpConjAtomContainer.bonds()) { + int tmpBondInteger = tmpConjBond.getProperty(ConjugatedPiSystemFragmenter.INTERNAL_CPSF_BOND_INDEX_PROPERTY_KEY); + tmpFragments.addBond(this.bondArray[tmpBondInteger]); + } + } + } catch (Exception anException) { + ConjugatedPiSystemFragmenter.this.logger.log(Level.WARNING, + anException + " MoleculeID: " + tmpClone.getID(), anException); + throw new IllegalArgumentException("Unexpected error occurred during fragmentation of molecule: " + + tmpClone.getID() + " at conjugated pi systems detector: " + anException.toString()); + } + // + + // + IAtomContainerSet tmpFragmentSet = new AtomContainerSet(); + try { + if (!tmpFragments.isEmpty()) { + if (!ConnectivityChecker.isConnected(tmpFragments)) { + IAtomContainerSet tmpContainerSet = ConnectivityChecker.partitionIntoMolecules(tmpFragments); + for (IAtomContainer tmpContainer: tmpContainerSet.atomContainers()) { + tmpFragmentSet.addAtomContainer(tmpContainer); + } + } else { + tmpFragmentSet.addAtomContainer(tmpFragments); + } + } + } catch (Exception anException) { + ConjugatedPiSystemFragmenter.this.logger.log(Level.WARNING, anException + " Connectivity Checking failed at molecule: " + tmpClone.getID(), anException); + throw new IllegalArgumentException("An Error occurred during Connectivity Checking: " + anException.toString() + + ": " + tmpClone.getProperty(Importer.MOLECULE_NAME_PROPERTY_KEY)); + } + // + + // + List tmpProcessedFragments = new ArrayList<>(tmpFragmentSet.getAtomContainerCount()); + try { + if (!tmpFragmentSet.isEmpty() && tmpFragmentSet != null) { + CDKHydrogenAdder tmpAdder = CDKHydrogenAdder.getInstance(tmpFragmentSet.getAtomContainer(0).getBuilder()); + for (IAtomContainer tmpAtomContainer: tmpFragmentSet.atomContainers()) { + AtomContainerManipulator.percieveAtomTypesAndConfigureAtoms(tmpAtomContainer); + if (this.fragmentSaturationSetting.get().equals(FragmentSaturationOption.HYDROGEN_SATURATION.name())) { + try { + tmpAdder.addImplicitHydrogens(tmpAtomContainer); + } catch (CDKException anException) { + ConjugatedPiSystemFragmenter.this.logger.log(Level.WARNING, anException + + " Unable to add Implicit Hydrogen at MoleculeID: " + tmpClone.getID()); + throw new CDKException("Unexpected error occurred during implicit hydrogen adding at " + + "hydrogen saturation of molecule: " + tmpClone.getID() + ", " + anException.toString(), anException); + } + } + tmpProcessedFragments.add(tmpAtomContainer); + } + } + } catch (Exception anException) { + ConjugatedPiSystemFragmenter.this.logger.log(Level.WARNING, anException + + "Error during hydrogen saturation at MoleculeID: " + tmpClone.getID()); + throw new IllegalArgumentException("Unexpected error occurred during fragmentation of molecule: " + + tmpClone.getID() + ", at hydrogen saturation: " + anException.toString(), anException); + } + // + + return tmpProcessedFragments; + } + + @Override + public boolean shouldBeFiltered(IAtomContainer aMolecule) { + return (Objects.isNull(aMolecule) || aMolecule.isEmpty()); + } + + /** + * Method for determining if given molecule needs preprocessing. + * Always returns false, as no preprocessing is currently needed. + * + * @param aMolecule the molecule to check + * @return currently always false + * @throws NullPointerException if the given molecule is null + */ + @Override + public boolean shouldBePreprocessed(IAtomContainer aMolecule) throws NullPointerException { + Objects.requireNonNull(aMolecule, "Given molecule is null."); + return false; + } + + @Override + public boolean canBeFragmented(IAtomContainer aMolecule) throws NullPointerException { + Objects.requireNonNull(aMolecule, "Given molecule is null"); + boolean tmpShouldBeFiltered = this.shouldBeFiltered(aMolecule); + boolean tmpShouldBePreprocessed = this.shouldBePreprocessed(aMolecule); + return !tmpShouldBeFiltered && !tmpShouldBePreprocessed; + } + + /** + * Method for applying special preprocessing steps before fragmenting the given molecule. + * Currently, no preprocessing applied as none is needed. + * + * @param aMolecule the molecule to preprocess + * @return aMolecule, unchanged molecule as no preprocessing is currently needed + * @throws NullPointerException if the molecule is null + */ + @Override + public IAtomContainer applyPreprocessing(IAtomContainer aMolecule) throws NullPointerException { + Objects.requireNonNull(aMolecule, "Given molecule is null."); + return aMolecule; + } + // + private void clearCache(){ + this.atomArray = null; + this.bondArray = null; + } + // +} diff --git a/src/main/resources/de/unijena/cheminf/mortar/message/Message_en_GB.properties b/src/main/resources/de/unijena/cheminf/mortar/message/Message_en_GB.properties index 8e26b32b..262e68ff 100644 --- a/src/main/resources/de/unijena/cheminf/mortar/message/Message_en_GB.properties +++ b/src/main/resources/de/unijena/cheminf/mortar/message/Message_en_GB.properties @@ -524,4 +524,22 @@ IMoleculeFragmenter.CycleFinderOption.relevant.tooltip = Relevant cycles of a gr IMoleculeFragmenter.CycleFinderOption.tripletShort.displayName = triplet short IMoleculeFragmenter.CycleFinderOption.tripletShort.tooltip = The shortest cycle through each triple of vertices; allows to generate the envelope rings of some molecules (e.g. naphthalene) without generating all cycles IMoleculeFragmenter.CycleFinderOption.vertexShort.displayName = vertex short -IMoleculeFragmenter.CycleFinderOption.vertexShort.tooltip = The shortest cycles through each vertex; linear independence is not checked; in practice similar to MCB \ No newline at end of file +IMoleculeFragmenter.CycleFinderOption.vertexShort.tooltip = The shortest cycles through each vertex; linear independence is not checked; in practice similar to MCB +##AlkylStructureFragmenter## +AlkylStructureFragmenter.displayName = Alkyl Structure Fragmenter +AlkylStructureFragmenter.fragmentSaturationSetting.tooltip = Defines how open valences resulting from bond breakages during fragmentation should be saturated +AlkylStructureFragmenter.fragmentSaturationSetting.displayName = Fragment saturation setting +AlkylStructureFragmenter.maxChainLengthSetting.tooltip = Defines maximum length of carbon chains: 0 is no restrictions +AlkylStructureFragmenter.maxChainLengthSetting.displayName = Maximum side chain length setting +AlkylStructureFragmenter.fragmentSideChainsSetting.tooltip = Defines whether carbon side chains should be fragmented +AlkylStructureFragmenter.fragmentSideChainsSetting.displayName = Fragment side chains setting +AlkylStructureFragmenter.alternativeSingleCarbonHandlingSetting.tooltip = Defines to use alternative single carbon handling (CDK pseudo atoms as placeholders) +AlkylStructureFragmenter.alternativeSingleCarbonHandlingSetting.displayName = Alternative single carbon handling setting +AlkylStructureFragmenter.alternativeSingleRingDetectionSetting.tooltip = Defines to use alternative single ring detection method (MCB algorithm) +AlkylStructureFragmenter.alternativeSingleRingDetectionSetting.displayName = Alternative single ring detection setting +AlkylStructureFragmenter.keepRingsSetting.tooltip = Defines whether rings should be dissected further +AlkylStructureFragmenter.keepRingsSetting.displayName = Keep rings setting +##ConjugatedPiSystemFragmenter## +ConjugatedPiSystemFragmenter.displayName = Conjugated Pi System Fragmenter +ConjugatedPiSystemFragmenter.fragmentSaturationSetting.tooltip = Defines how open valences resulting from bond breakages during fragmentation should be saturated +ConjugatedPiSystemFragmenter.fragmentSaturationSetting.displayName = Fragment saturation setting \ No newline at end of file diff --git a/src/test/java/de/unijena/cheminf/mortar/model/fragmentation/algorithm/AlkylStructureFragmenterTest.java b/src/test/java/de/unijena/cheminf/mortar/model/fragmentation/algorithm/AlkylStructureFragmenterTest.java new file mode 100644 index 00000000..e33891ae --- /dev/null +++ b/src/test/java/de/unijena/cheminf/mortar/model/fragmentation/algorithm/AlkylStructureFragmenterTest.java @@ -0,0 +1,431 @@ +/* + * MORTAR - MOlecule fRagmenTAtion fRamework + * Copyright (C) 2024 Felix Baensch, Jonas Schaub (felix.baensch@w-hs.de, jonas.schaub@uni-jena.de) + * + * Source code is available at + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package de.unijena.cheminf.mortar.model.fragmentation.algorithm; + +import de.unijena.cheminf.mortar.model.util.ChemUtil; + +import javafx.beans.property.Property; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.openscience.cdk.AtomContainerSet; +import org.openscience.cdk.exception.CDKException; +import org.openscience.cdk.exception.Intractable; +import org.openscience.cdk.graph.CycleFinder; +import org.openscience.cdk.graph.Cycles; +import org.openscience.cdk.interfaces.IAtom; +import org.openscience.cdk.interfaces.IAtomContainer; +import org.openscience.cdk.interfaces.IAtomContainerSet; +import org.openscience.cdk.interfaces.IBond; +import org.openscience.cdk.io.iterator.IteratingSDFReader; +import org.openscience.cdk.ringsearch.RingSearch; +import org.openscience.cdk.silent.SilentChemObjectBuilder; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +/** + * Class to test the correct working of + * {@link de.unijena.cheminf.mortar.model.fragmentation.algorithm.AlkylStructureFragmenter}. + * + * @author Maximilian Rottmann + * @version 1.0.0.0 + */ +public class AlkylStructureFragmenterTest extends AlkylStructureFragmenter{ + /** + * Private AtomContainerSet containing the test structures. + */ + private IAtomContainerSet testStructuresACSet; + /** + * Private AtomContainer List containing the resulting fragments after fragmentation. + */ + private List testResultFragmentsACList; + /** + * Private AtomContainerSet containing the resulting fragments after fragmentation. + */ + private IAtomContainerSet testResultACSet; + /** + * Private AtomContainerSet containing the expected fragments. + */ + private IAtomContainerSet testExpectedFragmentsACSet; + /** + * Private AtomContainerSet containing the expected structures as AtomContainers. + */ + private List testExpectedFragmentsACList; + /** + * Private AlkylStructureFragmenter used in this test, currently without special parameters. + */ + private final AlkylStructureFragmenter basicAlkylStructureFragmenter; + /** + * Private IAtom Array containing the atoms of a given structure, used in unit testing of internal + * AlkylStructureFragmenter methods. + */ + private IAtom[] testAtomArray; + /** + * Private IBond Array containing the bonds of a given structure, used in unit testing of internal + * AlkylStructureFragmenter methods. + */ + private IBond[] testBondArray; + /** + * Static Locale set to its default: "en" and "GB". + */ + static { + Locale.setDefault(Locale.of("en", "GB")); + } + + /** + * Constructor of AlkylStructureFragmenter test class, setting all necessary settings in order of correct functionality + * during testing. + */ + public AlkylStructureFragmenterTest() throws FileNotFoundException, URISyntaxException { + this.basicAlkylStructureFragmenter = new AlkylStructureFragmenter(); + this.testStructuresACSet = new AtomContainerSet(); + this.testStructuresACSet = this.readStructuresToACSet("de.unijena.cheminf.mortar.model.fragmentation.algorithm.ASF/ASF_Test_Structures.sdf"); + this.testExpectedFragmentsACSet = this.readStructuresToACSet("de.unijena.cheminf.mortar.model.fragmentation.algorithm.ASF/ASF_Expected_Fragments_Natural_Compound.sdf"); + this.testExpectedFragmentsACList = new ArrayList<>(this.testExpectedFragmentsACSet.getAtomContainerCount()); + //ToDo: read structures into arrays in each test method, not in general test! + //this.testAtomArray = this.basicAlkylStructureFragmenter.fillAtomArray(tmpAC); + this.testAtomArray = this.basicAlkylStructureFragmenter.fillAtomArray(this.testStructuresACSet.getAtomContainer(0)); + //this.testBondArray = this.basicAlkylStructureFragmenter.fillBondArray(tmpAC); + this.testBondArray = this.basicAlkylStructureFragmenter.fillBondArray(this.testStructuresACSet.getAtomContainer(0)); + } + + // + /** + * Tests correct instantiation and basic settings retrieval. + * + * @throws Exception if anything goes wrong + */ + @Test + public void basicSettingsTest() throws Exception { + List tmpCheckList = new ArrayList<>(6); + List tmpExpectList = new ArrayList<>(6); + tmpExpectList.add("Fragment saturation setting"); + tmpExpectList.add("Fragmentation of hydrocarbon side chains setting"); + tmpExpectList.add("Carbon side chains maximum length setting"); + tmpExpectList.add("Single carbon handling setting"); + tmpExpectList.add("Single ring detection setting"); + tmpExpectList.add("Keep rings setting"); + for (Property tmpSetting: this.basicAlkylStructureFragmenter.settingsProperties()) { + tmpCheckList.add(tmpSetting.getName()); + } + Assertions.assertLinesMatch(tmpExpectList, tmpCheckList); + } + /** + * Test method for AlkylStructureFragmenter.markRings(). + * + * @throws NoSuchMethodException if method reflection returns null + * @throws InvocationTargetException if target method cannot be invoked + * @throws IllegalAccessException if method cannot be accessed + */ + @Test + public void markRingsTest() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + IAtomContainer tmpRingsAC = this.testStructuresACSet.getAtomContainer(0); + this.testAtomArray = this.basicAlkylStructureFragmenter.fillAtomArray(tmpRingsAC); + this.testBondArray = this.basicAlkylStructureFragmenter.fillBondArray(tmpRingsAC); + + //ToDo: write test structure in fragmenter arrays; create array for comparison with expected markings + this.basicAlkylStructureFragmenter.markRings(tmpRingsAC, this.testAtomArray, this.testBondArray); + //ToDo: find way to compare structures without extracting tested substructures + } + /** + * Test method for AlkylStructureFragmenter.markConjugatedPiSystems(). + * + * @throws NoSuchMethodException if method reflection returns null + * @throws InvocationTargetException if target method cannot be invoked + * @throws IllegalAccessException if method cannot be accessed + */ + @Test + public void markConjugatedPiSystemsTest() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + //IAtomContainer tmpConjugatedAC = this.testStructuresHashSet.getFirst(); + //this.basicAlkylStructureFragmenter.markConjugatedPiSystems(); + /* + Method tmpMarkConjugated = this.basicAlkylStructureFragmenter.getClass().getDeclaredMethod("AlkylStructureFragmenter.markConjugatedPiSystems", IAtomContainer.class); + tmpMarkConjugated.setAccessible(true); + //problem: marking on local(ASF) private variables + tmpMarkConjugated.invoke(this.basicAlkylStructureFragmenter, tmpConjugatedAC); + */ + //ToDo: find way to compare structures without extracting tested substructures + } + /** + * Test method for AlkylStructureFragmenter.saturateWithImplicitHydrogen(). + * + * @throws NoSuchMethodException if method reflection returns null + * @throws InvocationTargetException if target method cannot be invoked + * @throws IllegalAccessException if method cannot be accessed + */ + @Test + public void saturateWithImplicitHydrogenTest() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + IAtomContainerSet tmpSaturateACSet = new AtomContainerSet(); + tmpSaturateACSet.addAtomContainer(this.testStructuresACSet.getAtomContainer(0)); + Method tmpSaturate = this.basicAlkylStructureFragmenter.getClass().getDeclaredMethod("saturateWithImplicitHydrogen", IAtomContainerSet.class); + tmpSaturate.setAccessible(true); + //problem? + List tmpACList = (List) tmpSaturate.invoke(this.basicAlkylStructureFragmenter, tmpSaturateACSet); + //ToDo: generate test structures with open valences to be saturated + } + /** + * Test method for AlkylStructureFragmenter.separateDisconnectedStructures(). + * + * @throws NoSuchMethodException if method reflection returns null + * @throws InvocationTargetException if target method cannot be invoked + * @throws IllegalAccessException if method cannot be accessed + */ + @Test + public void separateDisconnectedStructuresTest() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + IAtomContainer tmpDisconnectedAC = this.testStructuresACSet.getAtomContainer(0); + Method tmpSeparateDisconnectedAC = this.basicAlkylStructureFragmenter.getClass().getDeclaredMethod("separateDisconnectedStructures", IAtomContainer.class); + tmpSeparateDisconnectedAC.setAccessible(true); + IAtomContainerSet tmpDisconnectedACSet = (IAtomContainerSet) tmpSeparateDisconnectedAC.invoke(this.basicAlkylStructureFragmenter, tmpDisconnectedAC); + //ToDo: generate disconnected structures in one AtomContainer + } + /** + * Test method for AlkylStructureFragmenter.extractFragments(). + * + * @throws NoSuchMethodException if method reflection returns null + * @throws InvocationTargetException if target method cannot be invoked + * @throws IllegalAccessException if method cannot be accessed + */ + @Test + public void extractFragmentsTest() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + //problem: no way to take input AC as they are local(ASF) private variables + IAtomContainer tmpDisconnectedAC = this.testStructuresACSet.getAtomContainer(0); + IAtomContainerSet tmpExtractFragments; + try { + tmpExtractFragments = this.basicAlkylStructureFragmenter.extractFragments(this.testAtomArray, this.testBondArray); + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + //ToDo: generate structures with marked substructures (mark with code) + } + /** + * Test method for AlkylStructureFragmenter.dissectLinearChain(). + * + * @throws NoSuchMethodException if method reflection returns null + * @throws InvocationTargetException if target method cannot be invoked + * @throws IllegalAccessException if method cannot be accessed + */ + @Test + public void dissectLinearChainTest() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + //get linear test structure from ACList + IAtomContainer tmpDissectLinearChainAC = this.testStructuresACSet.getAtomContainer(0);//correct index needed + Method tmpDissectMethod = this.basicAlkylStructureFragmenter.getClass().getDeclaredMethod("dissectLinearChain", IAtomContainer.class, int.class); + tmpDissectMethod.setAccessible(true); + IAtomContainer tmpNoRestrictAC = (IAtomContainer) tmpDissectMethod.invoke(this.basicAlkylStructureFragmenter, tmpDissectLinearChainAC, 0); + //dissect test structure with different settings + //compare with expected fragments + //ToDo: generate/choose linear structure to test linear dissection + } + /** + * Method to test a default alkyl structure fragmentation on a concept molecule. + * + * @throws Exception if anything goes wrong + */ + @Test + public void defaultFragmentationTest() throws Exception { + this.basicAlkylStructureFragmenter.setFragmentSaturationSetting(IMoleculeFragmenter.FRAGMENT_SATURATION_OPTION_DEFAULT); + this.basicAlkylStructureFragmenter.setFragmentSideChainsSetting(AlkylStructureFragmenter.FRAGMENT_SIDE_CHAINS_SETTING_DEFAULT); + this.basicAlkylStructureFragmenter.setMaxChainLengthSetting(AlkylStructureFragmenter.MAX_CHAIN_LENGTH_SETTING_DEFAULT); + this.basicAlkylStructureFragmenter.setAlternativeSingleCarbonHandlingSetting(AlkylStructureFragmenter.ALTERNATIVE_SINGLE_CARBON_HANDLING_SETTING_DEFAULT); + this.basicAlkylStructureFragmenter.setAlternativeSingleRingDetectionSetting(AlkylStructureFragmenter.ALTERNATIVE_SINGLE_RING_DETECTION_SETTING_DEFAULT); + this.basicAlkylStructureFragmenter.setKeepRingsSetting(AlkylStructureFragmenter.KEEP_RINGS_SETTING_DEFAULT); + for (IAtomContainer tmpAtomContainer : + this.testStructuresACSet.atomContainers()) { + Assertions.assertFalse(this.basicAlkylStructureFragmenter.shouldBeFiltered(tmpAtomContainer)); + Assertions.assertFalse(this.basicAlkylStructureFragmenter.shouldBePreprocessed(tmpAtomContainer)); + Assertions.assertTrue(this.basicAlkylStructureFragmenter.canBeFragmented(tmpAtomContainer)); + } + List tmpResultList = this.basicAlkylStructureFragmenter.fragmentMolecule(this.testStructuresACSet.getAtomContainer(1)); + IAtomContainerSet tmpResultACSet = new AtomContainerSet(); + for (int i = 0; i < tmpResultList.size(); i++) { + tmpResultACSet.addAtomContainer(tmpResultList.get(i)); + } + List tmpResultSMILESList = this.generateSMILESFromACSet(tmpResultACSet); + IAtomContainerSet tmpExpectedACSet = this.readStructuresToACSet("de.unijena.cheminf.mortar.model.fragmentation.algorithm.ASF/ASF_Expected_Fragments_Test_Structure.sdf"); + List tmpExpectedSMILESList = this.generateSMILESFromACSet(tmpExpectedACSet); + //System.out.println("expected: " + tmpExpectedSMILESList); + //System.out.println("result: " + tmpResultSMILESList); + Assertions.assertTrue(this.compareListsIgnoringOrder(new ArrayList<>(tmpResultSMILESList), + new ArrayList<>(tmpExpectedSMILESList))); + } + // + + // + //ToDo: change current placeholder molecule to fitting natural compound + @Test + public void naturalCompoundFragmentationTest() throws Exception { + this.basicAlkylStructureFragmenter.setFragmentSaturationSetting(IMoleculeFragmenter.FRAGMENT_SATURATION_OPTION_DEFAULT); + this.basicAlkylStructureFragmenter.setFragmentSideChainsSetting(AlkylStructureFragmenter.FRAGMENT_SIDE_CHAINS_SETTING_DEFAULT); + this.basicAlkylStructureFragmenter.setMaxChainLengthSetting(AlkylStructureFragmenter.MAX_CHAIN_LENGTH_SETTING_DEFAULT); + this.basicAlkylStructureFragmenter.setAlternativeSingleCarbonHandlingSetting(AlkylStructureFragmenter.ALTERNATIVE_SINGLE_CARBON_HANDLING_SETTING_DEFAULT); + this.basicAlkylStructureFragmenter.setAlternativeSingleRingDetectionSetting(AlkylStructureFragmenter.ALTERNATIVE_SINGLE_RING_DETECTION_SETTING_DEFAULT); + this.basicAlkylStructureFragmenter.setKeepRingsSetting(AlkylStructureFragmenter.KEEP_RINGS_SETTING_DEFAULT); + for (IAtomContainer tmpAtomContainer : + this.testStructuresACSet.atomContainers()) { + Assertions.assertFalse(this.basicAlkylStructureFragmenter.shouldBeFiltered(tmpAtomContainer)); + Assertions.assertFalse(this.basicAlkylStructureFragmenter.shouldBePreprocessed(tmpAtomContainer)); + Assertions.assertTrue(this.basicAlkylStructureFragmenter.canBeFragmented(tmpAtomContainer)); + } + List tmpAtomContainerList = this.basicAlkylStructureFragmenter.fragmentMolecule(this.testStructuresACSet.getAtomContainer(0)); + IAtomContainerSet tmpResultACSet = new AtomContainerSet(); + for (int i = 0; i < tmpAtomContainerList.size(); i++) { + tmpResultACSet.addAtomContainer(tmpAtomContainerList.get(i)); + } + List tmpResultSMILESList = this.generateSMILESFromACSet(tmpResultACSet); + IAtomContainerSet tmpExpectedACSet = this.readStructuresToACSet("de.unijena.cheminf.mortar.model.fragmentation.algorithm.ASF/ASF_Expected_Fragments_Natural_Compound.sdf"); + List tmpExpectedSMILESList = this.generateSMILESFromACSet(tmpExpectedACSet); + //System.out.println("expected: " + tmpExpectedSMILESList); + //System.out.println("result: " + tmpResultSMILESList); + Assertions.assertTrue(this.compareListsIgnoringOrder(new ArrayList<>(tmpResultSMILESList), + new ArrayList<>(tmpExpectedSMILESList))); + } + // + + // + @Test + public void detectRingsWithMCBTest() { + CycleFinder tmpCycleFinder = Cycles.mcb(); + IAtomContainerSet tmpACSet; + try { + tmpACSet = this.readStructuresToACSet("de.unijena.cheminf.mortar.model.fragmentation.algorithm.ASF/ASF_Spiro_Test_Structure1.mol"); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + for (IAtomContainer tmpAC: tmpACSet.atomContainers()) { + try { + Cycles tmpMCBCycles = tmpCycleFinder.find(tmpAC); + System.out.println("MCB number of detected Cycles: " + tmpMCBCycles.numberOfCycles()); + System.out.println("MCB detected ring atomcontainer below:"); + System.out.println(tmpAC); + System.out.println("-----"); + } catch (Intractable e) { + throw new RuntimeException(e); + } + } + + } + @Test + public void detectRingsWithRingSearchTest() { + IAtomContainerSet tmpACSet; + try { + tmpACSet = this.readStructuresToACSet("de.unijena.cheminf.mortar.model.fragmentation.algorithm.ASF/ASF_Spiro_Test_Structure1.mol"); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + for (IAtomContainer tmpAC: tmpACSet.atomContainers()) { + RingSearch tmpRingSearch = new RingSearch(tmpAC); + List tmpACList = tmpRingSearch.isolatedRingFragments(); + int i = 0; + System.out.println("RingSearch isolated ring count from List " + i + ": " + tmpACList.size()); + System.out.println("RingSearch detected ring atomcontainer below:"); + System.out.println(tmpAC); + System.out.println("-----"); + i++; + } + } + // + + // + + /** + * Compares two provided lists on equality while ignoring the lists' orders. + * + * @param aList1 First given list to compare + * @param aList2 Second given list to compare + * @return boolean whether given lists are equal + */ + private boolean compareListsIgnoringOrder(ArrayList aList1, ArrayList aList2) { + if (aList1 == null || aList2 == null) { + return false; + } + if (aList1.size() != aList2.size()) { + return false; + } + for (Object o : aList1) { + aList2.remove(o); + } + return aList2.isEmpty(); + } + + /** + * Utility method generating SMILES notation strings for a given AtomContainerSet. + * + * @param anACSet given AtomContainerSet + * @return List containing the generated Strings + * @throws CDKException if SmilesGenerator is unable to generate String from structure + */ + private List generateSMILESFromACSet(IAtomContainerSet anACSet) throws CDKException { + List tmpSmilesList = new ArrayList<>(anACSet.getAtomContainerCount()); + for (IAtomContainer tmpAC : anACSet.atomContainers()) { + tmpSmilesList.add(ChemUtil.createUniqueSmiles(tmpAC)); + } + return tmpSmilesList; + } + + /** + * Private method to read a given structure file to a CDK atomcontainer set. + * + * @param aFileName Name of the file to read from + * @return IAtomContainerSet with the read structures as AtomContainers + * @throws FileNotFoundException if no file with the given name can be located + * @throws URISyntaxException if given name of file cannot be parsed as URI reference + */ + private IAtomContainerSet readStructuresToACSet(String aFileName) throws FileNotFoundException, URISyntaxException { + URL tmpURL = this.getClass().getResource("/" + aFileName); + File tmpResourceFile = Paths.get(tmpURL.toURI()).toFile(); + IteratingSDFReader tmpSDFReader = new IteratingSDFReader(new FileReader(tmpResourceFile), new SilentChemObjectBuilder()); + AtomContainerSet tmpACSet = new AtomContainerSet(); + String tmpIndexString = "ASFTest.AtomContainerIndex"; + int tmpIndex = 0; + while (tmpSDFReader.hasNext()) { + IAtomContainer tmpAtomContainer = tmpSDFReader.next(); + tmpAtomContainer.setProperty(tmpIndexString, tmpIndex); + try { + ChemUtil.saturateWithHydrogen(tmpAtomContainer); + } catch (CDKException aCDKException) { + throw new RuntimeException(aCDKException); + } + tmpACSet.addAtomContainer(tmpAtomContainer); + tmpIndex++; + } + return tmpACSet; + } + // + +} diff --git a/src/test/java/de/unijena/cheminf/mortar/model/fragmentation/algorithm/ConjugatedPiSystemFragmenterTest.java b/src/test/java/de/unijena/cheminf/mortar/model/fragmentation/algorithm/ConjugatedPiSystemFragmenterTest.java new file mode 100644 index 00000000..4abf5e9c --- /dev/null +++ b/src/test/java/de/unijena/cheminf/mortar/model/fragmentation/algorithm/ConjugatedPiSystemFragmenterTest.java @@ -0,0 +1,101 @@ +/* + * MORTAR - MOlecule fRagmenTAtion fRamework + * Copyright (C) 2024 Felix Baensch, Jonas Schaub (felix.baensch@w-hs.de, jonas.schaub@uni-jena.de) + * + * Source code is available at + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package de.unijena.cheminf.mortar.model.fragmentation.algorithm; + +import javafx.beans.property.Property; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.openscience.cdk.interfaces.IAtomContainer; +import org.openscience.cdk.io.MDLV3000Reader; +import org.openscience.cdk.silent.SilentChemObjectBuilder; +import org.openscience.cdk.smiles.SmiFlavor; +import org.openscience.cdk.smiles.SmilesGenerator; + +import java.io.FileReader; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +/** + * Class to test the correct working of + * {@link de.unijena.cheminf.mortar.model.fragmentation.algorithm.ConjugatedPiSystemFragmenter}. + * + * @author Maximilian Rottmann + * @version 1.1.1.0 + */ +public class ConjugatedPiSystemFragmenterTest { + + /** + * Constructor that sets the default locale to british english, which is needed for correct functioning of the + * fragmenter as the settings tooltips are imported from the message.properties file. + */ + public ConjugatedPiSystemFragmenterTest() { + Locale.setDefault(new Locale("en", "GB")); + } + + /** + * Tests correct instantiation and basic settings retrieval. + * + * @throws Exception if anything goes wrong + */ + @Test + public void basicTest() throws Exception { + ConjugatedPiSystemFragmenter tmpFragmenter = new ConjugatedPiSystemFragmenter(); + List tmpCheckList = new ArrayList<>(); + List tmpExpectList = new ArrayList<>(); + tmpExpectList.add("Fragment saturation setting"); + for (Property tmpSetting: tmpFragmenter.settingsProperties()) { + tmpCheckList.add(tmpSetting.getName()); + } + Assertions.assertLinesMatch(tmpExpectList, tmpCheckList); + } + + /** + * Method to test a default conjugated pi system fragmentation on the natural product CNP0421388 + * from the Coconut Database (@see ...). + * + * @throws Exception if anything goes wrong + */ + @Test + public void defaultFragmentationTest() throws Exception { + try (MDLV3000Reader tmpMDLReader = new MDLV3000Reader(new FileReader("src/test/resources/de.unijena.cheminf.mortar.model.fragmentation.algorithm.ASF/TestCPSFStructure.mol"))) { + IAtomContainer tmpOriginalMolecule = tmpMDLReader.read(SilentChemObjectBuilder.getInstance().newAtomContainer()); + ConjugatedPiSystemFragmenter tmpFragmenter = new ConjugatedPiSystemFragmenter(); + tmpFragmenter.setFragmentSaturationSetting(ConjugatedPiSystemFragmenter.FRAGMENT_SATURATION_OPTION_DEFAULT); + Assertions.assertFalse(tmpFragmenter.shouldBeFiltered(tmpOriginalMolecule)); + Assertions.assertFalse(tmpFragmenter.shouldBePreprocessed(tmpOriginalMolecule)); + Assertions.assertTrue(tmpFragmenter.canBeFragmented(tmpOriginalMolecule)); + List tmpFragmentList; + tmpFragmentList = tmpFragmenter.fragmentMolecule(tmpOriginalMolecule); + SmilesGenerator tmpGenerator = new SmilesGenerator(SmiFlavor.Canonical); + for (IAtomContainer tmpFragment : tmpFragmentList) { + System.out.println(tmpGenerator.create(tmpFragment) + " " + + tmpFragment.getProperty(IMoleculeFragmenter.FRAGMENT_CATEGORY_PROPERTY_KEY)); + } + } + } +} diff --git a/src/test/resources/de.unijena.cheminf.mortar.model.fragmentation.algorithm.ASF/ASF_Expected_Fragments_Natural_Compound.sdf b/src/test/resources/de.unijena.cheminf.mortar.model.fragmentation.algorithm.ASF/ASF_Expected_Fragments_Natural_Compound.sdf new file mode 100644 index 00000000..27919f5c --- /dev/null +++ b/src/test/resources/de.unijena.cheminf.mortar.model.fragmentation.algorithm.ASF/ASF_Expected_Fragments_Natural_Compound.sdf @@ -0,0 +1,101 @@ + + CDK 09052414073D + + 4 3 0 0 0 0 0 0 0 0999 V2000 + -2.5981 2.2500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -1.2990 1.5000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 2.2500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.2990 1.5000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 + 2 3 1 0 0 0 0 + 3 4 1 0 0 0 0 +M END +$$$$ + + CDK 09052414073D + + 1 0 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 +M END +$$$$ + + CDK 09052414073D + + 1 0 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 +M END +$$$$ + + CDK 09052414073D + + 32 36 0 0 0 0 0 0 0 0999 V2000 + 3.9102 5.5731 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 3.9107 7.0739 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.6116 7.8239 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.3121 7.0731 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.3116 5.5723 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.6107 4.8223 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0131 7.8231 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0111 9.3153 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -1.2867 10.0675 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -2.5825 9.3275 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -2.5855 7.8266 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -1.2877 7.0745 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -3.8859 7.0790 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -5.1778 7.8257 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -6.4794 7.0803 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -6.4892 5.5881 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -5.1923 4.8328 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -3.8907 5.5782 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -5.1978 3.3328 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -6.4996 2.5875 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -6.5071 1.0954 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -7.8076 0.3480 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -9.1007 1.0927 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -9.0982 2.5936 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -7.7976 3.3410 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -10.3958 3.3459 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -10.3926 4.8468 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -11.6902 5.5991 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -12.9911 4.8507 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -12.9944 3.3499 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -11.6967 2.5975 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -3.9016 2.5780 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 + 2 3 2 0 0 0 0 + 3 4 1 0 0 0 0 + 4 5 2 0 0 0 0 + 5 6 1 0 0 0 0 + 1 6 2 0 0 0 0 + 4 7 1 0 0 0 0 + 7 8 1 0 0 0 0 + 8 9 2 0 0 0 0 + 9 10 1 0 0 0 0 + 10 11 2 0 0 0 0 + 11 12 1 0 0 0 0 + 7 12 2 0 0 0 0 + 11 13 1 0 0 0 0 + 13 14 1 0 0 0 0 + 14 15 2 0 0 0 0 + 15 16 1 0 0 0 0 + 16 17 2 0 0 0 0 + 17 18 1 0 0 0 0 + 13 18 2 0 0 0 0 + 17 19 1 0 0 0 0 + 19 20 1 0 0 0 0 + 20 21 2 0 0 0 0 + 21 22 1 0 0 0 0 + 22 23 2 0 0 0 0 + 23 24 1 0 0 0 0 + 24 25 2 0 0 0 0 + 20 25 1 0 0 0 0 + 24 26 1 0 0 0 0 + 26 27 1 0 0 0 0 + 27 28 2 0 0 0 0 + 28 29 1 0 0 0 0 + 29 30 2 0 0 0 0 + 30 31 1 0 0 0 0 + 26 31 2 0 0 0 0 + 19 32 1 0 0 0 0 +M END +$$$$ diff --git a/src/test/resources/de.unijena.cheminf.mortar.model.fragmentation.algorithm.ASF/ASF_Expected_Fragments_Test_Structure.sdf b/src/test/resources/de.unijena.cheminf.mortar.model.fragmentation.algorithm.ASF/ASF_Expected_Fragments_Test_Structure.sdf new file mode 100644 index 00000000..8f1aa0bb --- /dev/null +++ b/src/test/resources/de.unijena.cheminf.mortar.model.fragmentation.algorithm.ASF/ASF_Expected_Fragments_Test_Structure.sdf @@ -0,0 +1,90 @@ + + CDK 09052412473D + + 15 16 0 0 0 0 0 0 0 0999 V2000 + -2.9500 -0.8900 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -2.9500 0.6100 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -1.6500 1.3600 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -0.3500 0.6100 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.9400 1.3600 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.2400 0.6100 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.2400 -0.8900 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.9400 -1.6400 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -0.3500 -0.8900 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -1.6500 -1.6400 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 3.5389 1.3602 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 4.8381 0.6104 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 6.1370 1.3606 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 7.4362 0.6108 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 6.1368 2.8606 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 + 2 3 2 0 0 0 0 + 3 4 1 0 0 0 0 + 4 5 2 0 0 0 0 + 5 6 1 0 0 0 0 + 6 7 2 0 0 0 0 + 7 8 1 0 0 0 0 + 8 9 2 0 0 0 0 + 4 9 1 0 0 0 0 + 9 10 1 0 0 0 0 + 1 10 2 0 0 0 0 + 6 11 1 0 0 0 0 + 11 12 2 0 0 0 0 + 12 13 1 4 0 0 0 + 13 14 1 0 0 0 0 + 13 15 1 0 0 0 0 +M END +$$$$ + + CDK 09052412473D + + 5 4 0 0 0 0 0 0 0 0999 V2000 + -1.2990 0.7500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 1.5000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.7500 0.2010 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.2990 2.2500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -0.7500 2.7990 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 + 2 3 1 0 0 0 0 + 2 4 1 0 0 0 0 + 2 5 1 0 0 0 0 +M END +$$$$ + + CDK 09052412473D + + 2 1 0 0 0 0 0 0 0 0999 V2000 + 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.5000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 2 0 0 0 0 +M END +$$$$ + + CDK 09052412473D + + 3 2 0 0 0 0 0 0 0 0999 V2000 + 0.6495 1.1250 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -0.6495 1.8750 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -1.9486 1.1250 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 + 2 3 1 0 0 0 0 +M END +$$$$ + + CDK 09052412473D + + 6 6 0 0 0 0 0 0 0 0999 V2000 + -1.3000 0.7500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.0000 1.5000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.3000 0.7500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.3000 -0.7500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -0.0000 -1.5000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -1.3000 -0.7500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 + 2 3 1 0 0 0 0 + 3 4 1 0 0 0 0 + 4 5 1 0 0 0 0 + 5 6 1 0 0 0 0 + 1 6 1 0 0 0 0 +M END +$$$$ diff --git a/src/test/resources/de.unijena.cheminf.mortar.model.fragmentation.algorithm.ASF/ASF_Spiro_Test_Structure1.mol b/src/test/resources/de.unijena.cheminf.mortar.model.fragmentation.algorithm.ASF/ASF_Spiro_Test_Structure1.mol new file mode 100644 index 00000000..da973c2f --- /dev/null +++ b/src/test/resources/de.unijena.cheminf.mortar.model.fragmentation.algorithm.ASF/ASF_Spiro_Test_Structure1.mol @@ -0,0 +1,28 @@ + + ACD/LABS07042411542D + + 11 12 0 0 0 0 0 0 0 0 1 V2000 + 16.5519 -6.5317 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 17.2169 -7.6836 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 15.2219 -6.5317 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 16.5519 -8.8354 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 14.5569 -7.6836 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 15.2219 -8.8354 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 14.0701 -8.8354 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 14.0701 -6.5317 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 12.7401 -8.8354 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 12.7401 -6.5317 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 12.0751 -7.6836 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 + 1 3 1 0 0 0 0 + 2 4 1 0 0 0 0 + 3 5 1 0 0 0 0 + 4 6 1 0 0 0 0 + 5 6 1 0 0 0 0 + 5 7 1 0 0 0 0 + 5 8 1 0 0 0 0 + 7 9 1 0 0 0 0 + 8 10 1 0 0 0 0 + 9 11 1 0 0 0 0 + 10 11 1 0 0 0 0 +M END diff --git a/src/test/resources/de.unijena.cheminf.mortar.model.fragmentation.algorithm.ASF/ASF_Test_Structures.sdf b/src/test/resources/de.unijena.cheminf.mortar.model.fragmentation.algorithm.ASF/ASF_Test_Structures.sdf new file mode 100644 index 00000000..7dc6528f --- /dev/null +++ b/src/test/resources/de.unijena.cheminf.mortar.model.fragmentation.algorithm.ASF/ASF_Test_Structures.sdf @@ -0,0 +1,159 @@ + + ACD/Labs07262417042D + + 38 42 0 0 0 0 0 0 0 0 1 V2000 + 11.1814 -12.6105 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 11.1814 -13.9405 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 10.0296 -11.9455 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 10.0296 -14.6055 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 8.8778 -12.6105 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 8.8778 -13.9405 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 12.3332 -11.9455 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 12.3332 -10.6155 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 13.4851 -12.6105 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 13.4851 -9.9505 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 14.6369 -11.9455 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 14.6369 -10.6155 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 15.7887 -12.6105 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 16.9405 -11.9455 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 15.7887 -13.9405 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 18.0923 -12.6105 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 16.9405 -14.6055 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 18.0923 -13.9405 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 16.9405 -15.9355 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 15.7887 -16.6005 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 14.6369 -15.9355 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 13.4851 -16.6005 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 12.3332 -15.9355 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 11.1814 -16.6005 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 18.0923 -16.6005 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 19.2441 -15.9355 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 18.0923 -17.9305 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 20.3959 -16.6005 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 19.2441 -18.5955 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 20.3959 -17.9305 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 19.2441 -14.6055 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 21.5478 -15.9355 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 21.5478 -14.6055 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 22.6996 -16.6005 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 22.6996 -13.9405 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 23.8514 -15.9355 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 23.8514 -14.6055 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 22.6996 -17.9305 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 1 0 0 0 0 + 1 3 2 0 0 0 0 + 2 4 2 0 0 0 0 + 3 5 1 0 0 0 0 + 4 6 1 0 0 0 0 + 5 6 2 0 0 0 0 + 7 8 1 0 0 0 0 + 7 9 2 0 0 0 0 + 8 10 2 0 0 0 0 + 9 11 1 0 0 0 0 + 10 12 1 0 0 0 0 + 11 12 2 0 0 0 0 + 1 7 1 0 0 0 0 + 13 14 1 0 0 0 0 + 13 15 2 0 0 0 0 + 14 16 2 0 0 0 0 + 15 17 1 0 0 0 0 + 16 18 1 0 0 0 0 + 17 18 2 0 0 0 0 + 11 13 1 0 0 0 0 + 17 19 1 0 0 0 0 + 19 20 1 0 0 0 0 + 20 21 1 0 0 0 0 + 21 22 1 0 0 0 0 + 22 23 1 0 0 0 0 + 23 24 1 0 0 0 0 + 25 26 1 0 0 0 0 + 25 27 2 0 0 0 0 + 26 28 2 0 0 0 0 + 27 29 1 0 0 0 0 + 28 30 1 0 0 0 0 + 29 30 2 0 0 0 0 + 19 25 1 0 0 0 0 + 26 31 1 0 0 0 0 + 32 33 1 0 0 0 0 + 32 34 2 0 0 0 0 + 33 35 2 0 0 0 0 + 34 36 1 0 0 0 0 + 35 37 1 0 0 0 0 + 36 37 2 0 0 0 0 + 28 32 1 0 0 0 0 + 34 38 1 0 0 0 0 +M END + + +$$$$ + + ACD/Labs07252417562D + + 31 33 0 0 0 0 0 0 0 0 1 V2000 + 14.1922 -6.6872 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 14.1922 -8.0172 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 13.0404 -6.0222 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 13.0404 -8.6822 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 11.8886 -6.6872 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 11.8886 -8.0172 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 15.3440 -6.0222 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 16.4959 -6.6872 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 15.3440 -8.6822 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 16.4959 -8.0172 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 17.6477 -6.0222 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 17.6477 -4.6922 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 18.7995 -6.6872 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 18.7995 -4.0272 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 19.9513 -6.0222 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 19.9513 -4.6922 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 10.7368 -6.0222 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 9.5850 -6.6872 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 9.5850 -8.0172 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 8.4332 -8.6822 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 10.7368 -8.6822 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 15.3440 -10.0122 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 16.4959 -10.6772 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 15.8309 -11.8290 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 17.1609 -9.5254 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 17.6477 -11.3422 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 18.7995 -10.6772 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 19.9513 -11.3422 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 21.1031 -10.6772 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 22.2549 -11.3422 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 23.4067 -10.6772 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 3 2 0 0 0 0 + 2 4 2 0 0 0 0 + 3 5 1 0 0 0 0 + 4 6 1 0 0 0 0 + 5 6 2 0 0 0 0 + 1 7 1 0 0 0 0 + 1 2 1 0 0 0 0 + 7 8 2 0 0 0 0 + 2 9 1 0 0 0 0 + 8 10 1 0 0 0 0 + 9 10 2 0 0 0 0 + 11 12 1 0 0 0 0 + 11 13 1 0 0 0 0 + 12 14 1 0 0 0 0 + 13 15 1 0 0 0 0 + 14 16 1 0 0 0 0 + 15 16 1 0 0 0 0 + 8 11 1 0 0 0 0 + 5 17 1 0 0 0 0 + 17 18 2 0 0 0 0 + 18 19 1 0 0 0 0 + 19 20 1 0 0 0 0 + 19 21 1 0 0 0 0 + 9 22 1 0 0 0 0 + 22 23 1 0 0 0 0 + 23 24 1 0 0 0 0 + 23 25 1 0 0 0 0 + 23 26 1 0 0 0 0 + 26 27 1 0 0 0 0 + 27 28 2 0 0 0 0 + 28 29 1 0 0 0 0 + 29 30 1 0 0 0 0 + 30 31 1 0 0 0 0 +M END + +$$$$ \ No newline at end of file diff --git a/src/test/resources/de.unijena.cheminf.mortar.model.fragmentation.algorithm.ASF/TestASFStructure1.mol b/src/test/resources/de.unijena.cheminf.mortar.model.fragmentation.algorithm.ASF/TestASFStructure1.mol new file mode 100644 index 00000000..c1e46f55 --- /dev/null +++ b/src/test/resources/de.unijena.cheminf.mortar.model.fragmentation.algorithm.ASF/TestASFStructure1.mol @@ -0,0 +1,57 @@ + + Mrv1903 08142306252D + + 0 0 0 0 0 999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 22 23 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 21.8132 -10.2977 0 0 +M V30 2 C 21.8132 -11.8377 0 0 +M V30 3 C 20.4795 -9.5277 0 0 +M V30 4 C 20.4795 -12.6077 0 0 +M V30 5 C 19.1458 -10.2977 0 0 +M V30 6 C 19.1458 -11.8377 0 0 +M V30 7 C 23.147 -9.5277 0 0 +M V30 8 C 24.4807 -10.2977 0 0 +M V30 9 C 23.147 -12.6077 0 0 +M V30 10 C 24.4807 -11.8377 0 0 +M V30 11 C 23.147 -7.9877 0 0 +M V30 12 C 21.8132 -7.2177 0 0 +M V30 13 C 21.8132 -5.6777 0 0 +M V30 14 C 23.147 -14.1477 0 0 +M V30 15 C 21.8132 -14.9178 0 0 +M V30 16 C 24.4807 -14.9178 0 0 +M V30 17 C 24.4807 -16.4578 0 0 +M V30 18 C 17.8122 -12.6077 0 0 +M V30 19 C 16.4785 -11.8377 0 0 +M V30 20 C 17.2485 -10.5041 0 0 +M V30 21 C 15.7085 -13.1714 0 0 +M V30 22 C 15.1448 -11.0677 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 2 1 3 +M V30 2 2 2 4 +M V30 3 1 3 5 +M V30 4 1 4 6 +M V30 5 2 5 6 +M V30 6 1 1 7 +M V30 7 1 1 2 +M V30 8 1 7 8 +M V30 9 1 2 9 +M V30 10 1 8 10 +M V30 11 1 9 10 +M V30 12 2 7 11 +M V30 13 1 11 12 +M V30 14 2 12 13 +M V30 15 1 9 14 +M V30 16 1 14 15 +M V30 17 1 14 16 +M V30 18 1 16 17 +M V30 19 1 6 18 +M V30 20 1 18 19 +M V30 21 1 19 20 +M V30 22 1 19 21 +M V30 23 1 19 22 +M V30 END BOND +M V30 END CTAB +M END diff --git a/src/test/resources/de.unijena.cheminf.mortar.model.fragmentation.algorithm.ASF/TestCPSFStructure.mol b/src/test/resources/de.unijena.cheminf.mortar.model.fragmentation.algorithm.ASF/TestCPSFStructure.mol new file mode 100644 index 00000000..9917e001 --- /dev/null +++ b/src/test/resources/de.unijena.cheminf.mortar.model.fragmentation.algorithm.ASF/TestCPSFStructure.mol @@ -0,0 +1,76 @@ + +Actelion Java MolfileCreator 2.0 + + 0 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 30 34 0 0 0 +M V30 BEGIN ATOM +M V30 1 C -4.4461 3.549 0 0 +M V30 2 O 1.299 0 0 0 +M V30 3 C 0 -0.75 0 0 +M V30 4 C -5.9461 3.549 0 0 +M V30 5 C -1.299 0 0 0 +M V30 6 O -1.299 1.5 0 0 +M V30 7 C -2.598 -0.75 0 0 +M V30 8 C -3.8971 0 0 0 +M V30 9 O -3.8971 1.5 0 0 +M V30 10 C -5.1961 2.25 0 0 +M V30 11 C -6.4952 1.5 0 0 +M V30 12 C -7.9951 -4.4999 0 0 +M V30 13 C -6.4952 0 0 0 +M V30 14 C -7.2451 -5.799 0 0 +M V30 15 C -5.1961 -0.75 0 0 +M V30 16 C -5.1961 -2.25 0 0 +M V30 17 O -6.4952 -3 0 0 +M V30 18 C -6.4952 -4.5 0 0 +M V30 19 C -5.1961 -5.25 0 0 +M V30 20 C 1.299 -4.5 0 0 +M V30 21 C -3.8971 -4.5 0 0 +M V30 22 C 2.598 -5.25 0 0 +M V30 23 C -3.8971 -3 0 0 +M V30 24 C -2.598 -2.25 0 0 +M V30 25 O -1.299 -3 0 0 +M V30 26 C 0 -2.25 0 0 +M V30 27 C 3.8971 -4.5 0 0 +M V30 28 C 1.299 -3 0 0 +M V30 29 C 2.598 -2.25 0 0 +M V30 30 C 3.8971 -3 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 10 1 +M V30 2 1 2 3 +M V30 3 1 10 4 +M V30 4 1 3 5 +M V30 5 2 5 6 +M V30 6 1 5 7 +M V30 7 1 7 8 +M V30 8 1 8 9 +M V30 9 1 9 10 +M V30 10 1 10 11 +M V30 11 1 18 12 +M V30 12 2 11 13 +M V30 13 1 18 14 +M V30 14 1 13 15 +M V30 15 2 8 15 +M V30 16 1 15 16 +M V30 17 1 16 17 +M V30 18 1 17 18 +M V30 19 1 18 19 +M V30 20 2 28 20 +M V30 21 2 19 21 +M V30 22 1 22 20 +M V30 23 1 21 23 +M V30 24 2 16 23 +M V30 25 1 23 24 +M V30 26 2 7 24 +M V30 27 1 24 25 +M V30 28 1 25 26 +M V30 29 1 3 26 +M V30 30 2 27 22 +M V30 31 1 26 28 +M V30 32 1 28 29 +M V30 33 1 30 27 +M V30 34 2 29 30 +M V30 END BOND +M V30 END CTAB +M END