001package org.biopax.paxtools.io.sif.level2;
002
003import org.biopax.paxtools.io.sif.BinaryInteractionType;
004import org.biopax.paxtools.io.sif.InteractionSet;
005import org.biopax.paxtools.io.sif.SimpleInteraction;
006import org.biopax.paxtools.model.Model;
007import org.biopax.paxtools.model.level2.*;
008
009import java.util.*;
010
011import static org.biopax.paxtools.io.sif.BinaryInteractionType.METABOLIC_CATALYSIS;
012import static org.biopax.paxtools.io.sif.BinaryInteractionType.STATE_CHANGE;
013
014/**
015 * A controls a conversion which B is at left or right or both. -
016 * Controls.StateChange (B at both sides (one side may be as a member of a
017 * complex), or B is complex) - Controls.MetabolicChange (B at one side only)
018 * @author Ozgun Babur Date: Dec 29, 2007 Time: 1:27:55 AM
019 */
020public class ControlRule extends InteractionRuleL2Adaptor
021{
022        /**
023         * Supported interaction types.
024         */
025        private static List<BinaryInteractionType> binaryInteractionTypes =
026                        Arrays.asList(METABOLIC_CATALYSIS, STATE_CHANGE);
027
028        /**
029         * Option to mine METABOLIC_CHANGE type.
030         */
031        private boolean mineMetabolicChange;
032
033        /**
034         * Option to mine STATE_CHANGE type.
035         */
036        private boolean mineStateChange;
037
038        /**
039         * Initializes option.
040         * @param options options map
041         */
042        @Override public void initOptionsNotNull(Map options)
043        {
044                mineStateChange = !options.containsKey(STATE_CHANGE) ||
045                        options.get(STATE_CHANGE).equals(Boolean.TRUE);
046
047                mineMetabolicChange = !options.containsKey(METABOLIC_CATALYSIS) ||
048                        options.get(METABOLIC_CATALYSIS).equals(Boolean.TRUE);
049        }
050
051        /**
052         * When options map is null, then all rules are generated. Otherwise only rules
053         * that are contained in the options map as a key are generated.
054         * @param interactionSet set to fill in
055         * @param A first physical entity
056         * @param model biopax graph - may be null, has no use here
057         */
058        public void inferInteractionsFromPE(InteractionSet interactionSet, physicalEntity A, Model model)
059        {
060
061                // Iterate over all associated controls
062
063                for (control cont : A.getAllInteractions(control.class))
064                {
065                        // Iterate over all affected conversions of this control
066
067                        for (conversion conv : getAffectedConversions(cont, null))
068                        {
069                                Set<physicalEntity> presenceSet = collectEntities(conv.getLEFT(), null);
070                                collectEntities(conv.getRIGHT(), presenceSet);
071
072                                // Collect left and right simple physical entities of conversion in lists
073
074                                List<physicalEntity> left = collectSimpleEntities(conv.getLEFT());
075                                List<physicalEntity> right = collectSimpleEntities(conv.getRIGHT());
076
077                                // Detect physical entities which appear on both sides.
078
079                                List<physicalEntity> bothsided = new ArrayList<physicalEntity>();
080
081                                for (physicalEntity B : left)
082                                {
083                                        if (right.contains(B))
084                                        {
085                                                bothsided.add(B);
086                                        }
087                                }
088
089                                // Create simple interactions
090                                // Try creating a rule for each physical entity in presence list.
091
092                                for (physicalEntity B : presenceSet)
093                                {
094                                        // Consider only molecules that is changed by the conversion
095                                        if (!entityHasAChange(B, conv))
096                                        {
097                                                continue;
098                                        }
099
100                                        // Affecting a complex is accepted as type of state change.
101                                        // If it is simple, then we check if it is also on both sides, regarding the
102                                        // possibility that it may be nested in a complex.
103
104                                        if (B instanceof complex || bothsided.contains(B))
105                                        {
106                                                if (mineStateChange)
107                                                {
108                                                        SimpleInteraction sc = new SimpleInteraction(A, B, STATE_CHANGE);
109                                                        sc.addMediator(cont);
110                                                        sc.addMediator(conv);
111                                                        interactionSet.add(sc);
112                                                }
113                                        }
114
115                                        // Else it is a simple molecule appearing on one side of conversion. This means
116                                        // it is metabolic change.
117
118                                        else
119                                        {
120                                                if (mineMetabolicChange)
121                                                {
122                                                        SimpleInteraction mc = new SimpleInteraction(A, B, METABOLIC_CATALYSIS);
123                                                        mc.addMediator(cont);
124                                                        mc.addMediator(conv);
125                                                        interactionSet.add(mc);
126                                                }
127                                        }
128                                }
129                        }
130                }
131        }
132
133        /**
134         * Creates a list of conversions on which this control has an effect. If the
135         * control controls another control, then it is traversed recursively to find
136         * the affected conversions.
137         * @param cont control
138         * @param convList list of affected conversions
139         * @return list of affected conversions
140         */
141        private List<conversion> getAffectedConversions(control cont, List<conversion> convList)
142        {
143                if (convList == null)
144                {
145                        convList = new ArrayList<conversion>();
146                }
147
148                for (process prcss : cont.getCONTROLLED())
149                {
150                        if (prcss instanceof conversion)
151                        {
152                                convList.add((conversion) prcss);
153                        } else if (prcss instanceof control)
154                        {
155                                getAffectedConversions((control) prcss, convList);
156                        }
157                }
158
159                return convList;
160        }
161
162        /**
163         * Collects the associated physical entities of the given participant set.
164         * @param partics participants
165         * @param peSet physical entity set to collect in
166         * @return associated physical entities
167         */
168        private Set<physicalEntity> collectEntities(Set<physicalEntityParticipant> partics, Set<physicalEntity> peSet)
169        {
170                if (peSet == null)
171                {
172                        peSet = new HashSet<physicalEntity>();
173                }
174
175                for (physicalEntityParticipant partic : partics)
176                {
177                        peSet.add(partic.getPHYSICAL_ENTITY());
178                }
179
180                return peSet;
181        }
182
183        /**
184         * Collects the associated non-complex physical entities of the given
185         * participant set. This means we are not interested in complexes, but their
186         * members, at any nesting.
187         * @param partics participants
188         * @return associated physical entities
189         */
190        private List<physicalEntity> collectSimpleEntities(Set<physicalEntityParticipant> partics)
191        {
192                List<physicalEntity> peList = new ArrayList<physicalEntity>();
193
194                for (physicalEntityParticipant partic : partics)
195                {
196                        physicalEntity pe = partic.getPHYSICAL_ENTITY();
197
198                        if (pe instanceof complex)
199                        {
200                                collectSimpleMembersOfComplex(peList, (complex) pe);
201                        } else
202                        {
203                                peList.add(pe);
204                        }
205                }
206
207                return peList;
208        }
209
210        /**
211         * Recursive method for collecting simple members of the given complex in the
212         * given list.
213         * @param list where to collect
214         * @param comp complex to collect members
215         */
216        private void collectSimpleMembersOfComplex(List<physicalEntity> list, complex comp)
217        {
218                for (physicalEntityParticipant pep : comp.getCOMPONENTS())
219                {
220                        physicalEntity pe = pep.getPHYSICAL_ENTITY();
221
222                        if (pe instanceof complex)
223                        {
224                                collectSimpleMembersOfComplex(list, (complex) pe);
225                        } else
226                        {
227                                list.add(pe);
228                        }
229                }
230        }
231
232        /**
233         * Sometimes an entity is both an input and output to a conversion without any state change.
234         * Normally this phenomena should be modeled using controller property of conversion. In other
235         * cases this method detects entities that goes in and out without any change.
236         * @param entity entity to check
237         * @param conv conversion that the entity is participant
238         * @return true if entity has a change in conversion
239         */
240        private boolean entityHasAChange(physicalEntity entity, conversion conv)
241        {
242                Set<StateWrapper> leftonly = new HashSet<StateWrapper>();
243                Set<StateWrapper> rightonly = new HashSet<StateWrapper>();
244                Set<StateWrapper> both = new HashSet<StateWrapper>();
245
246                for (physicalEntityParticipant pep : conv.getLEFT())
247                {
248                        if (pep.getPHYSICAL_ENTITY() == entity)
249                        {
250                                leftonly.add(new StateWrapper(pep));
251                        }
252                }
253
254                for (physicalEntityParticipant pep : conv.getRIGHT())
255                {
256                        if (pep.getPHYSICAL_ENTITY() == entity)
257                        {
258                                StateWrapper sw = new StateWrapper(pep);
259                                if (leftonly.contains(sw))
260                                {
261                                        leftonly.remove(sw);
262                                        both.add(sw);
263                                } else if (!both.contains(sw))
264                                {
265                                        rightonly.add(sw);
266                                }
267                        }
268                }
269
270                return !leftonly.isEmpty() || !rightonly.isEmpty();
271        }
272
273        /**
274         * Gets supported interaction types.
275         * @return supported interaction types
276         */
277        public List<BinaryInteractionType> getRuleTypes()
278        {
279                return binaryInteractionTypes;
280        }
281
282        /**
283         * This wrapper is used for using state equality of participants in sets.
284         */
285        private class StateWrapper
286        {
287                /**
288                 * Wrapped participant.
289                 */
290                final physicalEntityParticipant pep;
291
292                /**
293                 * Constructor with the wrapped participant.
294                 * @param pep wrapped participant
295                 */
296                private StateWrapper(physicalEntityParticipant pep)
297                {
298                        this.pep = pep;
299                }
300
301                /**
302                 * Generates hash code for the state of the participant.
303                 * @return hash code
304                 */
305                public int hashCode()
306                {
307                        return pep.stateCode();
308                }
309
310                /**
311                 * Checks if two participants are in equivalent state.
312                 * @param obj other participant
313                 * @return true if they are in equivalent state
314                 */
315                public boolean equals(Object obj)
316                {
317                        if (obj instanceof StateWrapper)
318                        {
319                                StateWrapper sw = (StateWrapper) obj;
320
321                                return pep.isInEquivalentState(sw.pep);
322                        }
323
324                        return false;
325                }
326        }
327}