001package org.biopax.paxtools.io.sif.level3;
002
003import org.biopax.paxtools.io.sif.BinaryInteractionType;
004import org.biopax.paxtools.model.BioPAXElement;
005import org.biopax.paxtools.model.Model;
006import org.biopax.paxtools.model.level3.*;
007import org.biopax.paxtools.model.level3.Process;
008import org.biopax.paxtools.util.ClassFilterSet;
009import org.biopax.paxtools.util.IllegalBioPAXArgumentException;
010
011import java.util.Arrays;
012import java.util.HashSet;
013import java.util.List;
014import java.util.Set;
015
016import static org.biopax.paxtools.io.sif.BinaryInteractionType.SEQUENTIAL_CATALYSIS;
017
018/**
019 * This class creates an interaction between two entities if they are catalyzing consecutive
020 * conversions. Conversions are considered consecutive if one of the RIGHT participants of one
021 * reaction is the LEFT of the other and if the directions of catalysis and control matches.
022 *
023 * @author Emek Demir
024 */
025public class ConsecutiveCatalysisRule extends InteractionRuleL3Adaptor
026{
027        /**
028         * Supported interaction types.
029         */
030    private static List<BinaryInteractionType> binaryInteractionTypes =
031                Arrays.asList(SEQUENTIAL_CATALYSIS);
032
033        /**
034         * Infers interactions using the given PhysicalEntity as seed.
035         * @param interactionSet to be populated
036         * @param pe PhysicalEntity that will be the seed of the inference
037         * @param model BioPAX model
038         */
039    public void inferInteractionsFromPE(InteractionSetL3 interactionSet, PhysicalEntity pe,
040                Model model)
041        {
042        for (Interaction inter : pe.getParticipantOf()) {
043            if (inter instanceof Catalysis) {
044                BioPAXElement bpe = interactionSet.getGroupMap().getEntityReferenceOrGroup(pe);
045                processCatalysis(interactionSet, (Catalysis) inter, bpe);
046            }
047        }
048    }
049
050        /**
051         * Continues the inference from the Catalysis of seed element.
052         * @param interactionSet inferred interactions
053         * @param aCatalysis the catalysis to follow
054         * @param bpe source of the interaction
055         */
056    private void processCatalysis(InteractionSetL3 interactionSet, Catalysis aCatalysis,
057                BioPAXElement bpe)
058        {
059        //We have to consider two direction statements
060        //Catalysis.direction and Conversion.spontaneous
061        //This method maps the former to the compatible latter
062        //null means reversible or unknown, both are treated in the same way.
063        ConversionDirectionType catalysisDirection = mapDirectionToConversion(aCatalysis.getCatalysisDirection());
064
065        //get the conversions and process them.
066        Set<Process> controlled = aCatalysis.getControlled();
067        for (Process process : controlled) {
068            assert process instanceof Conversion;
069
070            Conversion aConversion = (Conversion) process;
071
072            //let's find the direction that is compatible with catalysis direction
073            ConversionDirectionType dirA =
074                    findConsensusDirection(catalysisDirection, aConversion.getConversionDirection());
075
076            assert dirA != null;
077
078            //and let's get the interacting physical entities
079            createInteractions(aConversion, dirA, aCatalysis, interactionSet, bpe);
080        }
081    }
082
083    /**
084     * This method will find the compatible conversion and catalysis direction.
085     *
086     * @param direction1 type implied by catalysis
087     * @param direction2 type of the conversion
088     * @return compatible direction
089     */
090    private ConversionDirectionType findConsensusDirection(ConversionDirectionType direction1,
091                                                           ConversionDirectionType direction2)
092        {
093        ConversionDirectionType consensus;
094        boolean first = isReversible(direction1);
095        boolean second = isReversible(direction2);
096
097        //      If any one of them is not-spontaneous than consensus is the other direction.
098        //  If both of them are spontaneous, then consensus is null if they are spontenous
099        //  in opposite directions.
100        if (first) {
101            if (second) {
102                consensus = ConversionDirectionType.REVERSIBLE;
103            } else {
104                consensus = direction2;
105            }
106        } else {
107            if (second) {
108                consensus = direction1;
109            } else {
110                consensus = direction1.equals(direction2) ? direction1 : null;
111            }
112        }
113        return consensus;
114    }
115
116        /**
117         * Checks if the direction can be treated as reversible.
118         * @param direction1 direction to check
119         * @return true if direction is REVERSIBLE or null, false otherwise
120         */
121    private boolean isReversible(ConversionDirectionType direction1) {
122        return direction1 == null || direction1.equals(ConversionDirectionType.REVERSIBLE);
123    }
124
125        /**
126         * Continues inference with the current elements.
127         * @param centerConversion first Conversion
128         * @param dirA current direction fot the Conversion
129         * @param aCatalysis the traversed catalysis
130         * @param interactionSet inferred interactions
131         * @param bpe source of interaction
132         */
133    private void createInteractions(Conversion centerConversion, ConversionDirectionType dirA,
134                Catalysis aCatalysis, InteractionSetL3 interactionSet, BioPAXElement bpe)
135        {
136        //get the pes at the correct side of the conversion.
137        Set<PhysicalEntity> pes = getOutputPEs(dirA, centerConversion);
138
139        for (PhysicalEntity pe : pes) {
140            for (Interaction inter : pe.getParticipantOf()) {
141                if (inter instanceof Conversion) {
142                    Conversion nextConversion = (Conversion) inter;
143
144                    if (centerConversion == nextConversion) {
145                        continue;
146                    }
147
148                    Set<Control> controls = nextConversion.getControlledOf();
149
150                    for (Catalysis consequentCatalysis : new ClassFilterSet<Control, Catalysis>(
151                                                controls, Catalysis.class))
152                                        {
153                        ConversionDirectionType direction2 =
154                                mapDirectionToConversion(consequentCatalysis.getCatalysisDirection());
155                        ConversionDirectionType dirB =
156                                findConsensusDirection(nextConversion.getConversionDirection(), direction2);
157
158                        // Ensure intermediate molecule (pe) is input to the neighbor conversion
159                        if (commonSubstrateFollowsFlow(pe, nextConversion, dirB)) {
160                            for (Controller controller : consequentCatalysis.getController()) {
161                                if (controller instanceof PhysicalEntity) {
162                                    createAndAdd(bpe, interactionSet.getGroupMap().getEntityReferenceOrGroup(controller),
163                                            interactionSet, BinaryInteractionType.SEQUENTIAL_CATALYSIS,
164                                            aCatalysis, consequentCatalysis, pe);
165                                }
166                            }
167                        }
168                    }
169                }
170            }
171        }
172    }
173
174        /**
175         * Checks if the common substrate is an input to one and output of the other.
176         * @param pe common substrate
177         * @param nextConversion second conversion
178         * @param dirB direction for the second conversion
179         * @return true if the flow matches
180         */
181    private boolean commonSubstrateFollowsFlow(PhysicalEntity pe, Conversion nextConversion,
182                ConversionDirectionType dirB)
183        {
184        if (dirB == null) return true; //Assume reversible
185        switch (dirB) {
186            case REVERSIBLE:
187                return true;
188            case LEFT_TO_RIGHT:
189                return nextConversion.getLeft().contains(pe);
190            case RIGHT_TO_LEFT:
191                return nextConversion.getRight().contains(pe);
192            default:
193                throw new IllegalBioPAXArgumentException(); // Should never hit here
194        }
195    }
196
197
198
199    /**
200     * This method returns the PEPs that are on the correct side of the conversion
201     *
202     * @param direction   determining the side
203     * @param aConversion Conversion to get inputs or outputs
204     * @return inputs or outputs
205     */
206    private Set<PhysicalEntity> getOutputPEs(ConversionDirectionType direction, Conversion aConversion) {
207        switch (direction) {
208            case LEFT_TO_RIGHT:
209                return aConversion.getRight();
210            case RIGHT_TO_LEFT:
211                return aConversion.getLeft();
212            default:
213                return mergedSet(aConversion);
214        }
215    }
216
217        /**
218         * Gets all left and right participant of a conversion in a set.
219         * @param aConversion Conversion to get participants
220         * @return left and right participants of the conversion
221         */
222    private HashSet<PhysicalEntity> mergedSet(Conversion aConversion) {
223        HashSet<PhysicalEntity> hashSet = new HashSet<PhysicalEntity>();
224        hashSet.addAll(aConversion.getLeft());
225        hashSet.addAll(aConversion.getRight());
226        return hashSet;
227    }
228
229        /**
230         * Converts catalysis direction to the conversion direction.
231         * @param direction catalysis direction to convert
232         * @return conversion direction
233         */
234    private ConversionDirectionType mapDirectionToConversion(CatalysisDirectionType direction) {
235        if (direction != null) {
236            switch (direction) {
237                case LEFT_TO_RIGHT:
238                    return ConversionDirectionType.LEFT_TO_RIGHT;
239                case RIGHT_TO_LEFT:
240                    return ConversionDirectionType.RIGHT_TO_LEFT;
241            }
242        }
243        return null;
244    }
245
246        /**
247         * Gets supported interaction types.
248         * @return supported interaction types
249         */
250    public List<BinaryInteractionType> getRuleTypes() {
251        return binaryInteractionTypes;
252    }
253}