# rule_based_pattern.py import xmlrpclib # Connect to server: specify the location and port server = xmlrpclib.ServerProxy("http://localhost:9000") cy = server.Cytoscape # Test if connection works: should print 'It works!' # should go to Cytoscape and then activate the RPC plugin first print server.Cytoscape.test() curNetID = cy.getCurrentNetworkIndex() def wrapperTriangles(KGlist, withSynecho, metabolism): """ wrapper function that will call the components to perform the ranking genes """ KGdict_all = dict() allNodes = cy.getNodes() for keygene in KGlist: if keygene in allNodes: oldNodePattern = checkTriangleSets(keygene) newNodePattern = checkKeygene(KGlist, oldNodePattern, 1) edgePattern = checkEdgeType(newNodePattern) newEdgePattern = checkEdgePattern(edgePattern) candidateGene = candidateType_1(keygene, KGlist, newEdgePattern) KGdict_all.setdefault(keygene, candidateGene) else: print "***keygene", keygene, "not in the integrated network***" presentRanking(KGdict_all, withSynecho, metabolism, KGlist) presentCandidate(KGdict_all, withSynecho) return KGdict_all def checkTriangleSets(node): """ 1. look for the sets of three genes connecting in a triangular patterns (straight line or branching is not what we consider here) The seed of the search is the input key genes. """ triangleSet = set() duplicateSet = set() firstLayer = cy.getNodeNeighbors(node) for first in firstLayer: secondLayer = cy.getNodeNeighbors(first) for second in secondLayer: # avoid to select the same nodes if node!=first and node!=second and first!=second: if node in cy.getNodeNeighbors(second) and (node, first, second) not in duplicateSet: triangleSet.add((node, first, second)) # mark all the combinations to avoid repeating the search duplicateSet.add((node, first, second)) duplicateSet.add((node, second, first)) return triangleSet def checkKeygene(KGlist, patternSet, KGno): """ 2. check if the patterns contain the amount of key genes as defined - In this case, one key gene is used as a seed gene, so at least one key gene is required for the interesting patterns. - Also the pattern of all key genes are discarded. """ newPatternSet = set() for item in patternSet: # check the pattern has keygene at least equal to the setting # but the pattern does not contain only keygene if len(set(item) & set(KGlist)) >= KGno and len(set(item) & set(KGlist)) != 3: newPatternSet.add(item) return newPatternSet def checkEdgeType(patternSet): """ 3. check the types of interactions among three genes. (CytoscapeRPC can return pairs of source node and target node in both direction.) Allowed patterns are the mixtures of these edge types: EVEX synecho EVEX mpm-synecho Y2H CoEx """ edgeDict = dict() duplicateSet = set() for node in patternSet: # get edge of first node - second node for edge_1 in cy.getAdjacentEdges(curNetID, node[0]): if node[1] in edge_1: # get edge of second node - third node for edge_2 in cy.getAdjacentEdges(curNetID, node[1]): if edge_1!=edge_2 and node[2] in edge_2: # get edge of first node - third node for edge_3 in cy.getAdjacentEdges(curNetID, node[2]): if edge_1!=edge_2!=edge_3 and node[0] in edge_3: edgeDict.setdefault(node, set()).add(edge_1) edgeDict.setdefault(node, set()).add(edge_2) edgeDict.setdefault(node, set()).add(edge_3) return edgeDict def checkEdgePattern(edgePattern): """ 4. The interest is in the patterns are the mixed type (more than 1 type of information source) discard any triangular patterns which is not """ newEdgePattern = dict() for triPattern, edgeList in edgePattern.iteritems(): patternSet = set() for edge in edgeList: edgeType = edge.split("(")[1].split(")")[0] if edgeType in ["pos_interaction", "neg_interaction"]: patternSet.add("CoEx") elif edgeType=="Y2H": patternSet.add("Y2H") else: try: # distinct the types of EVEX to "EVEX synecho" and "EVEX non-synecho" cy.getEdgeAttributes(edge)['E-Taxonomy_Distance_from_Synechocystis'] if "0:Synechocystis sp. PCC 6803" not in cy.getEdgeAttributes(edge)['E-Taxonomy_Distance_from_Synechocystis']: patternSet.add("EVEX non-synecho") elif "0:Synechocystis sp. PCC 6803" in cy.getEdgeAttributes(edge)['E-Taxonomy_Distance_from_Synechocystis']: patternSet.add("EVEX synecho") except: patternSet.add("EVEX") # only mix edge patterns are considered here # allow pattern with only EVEX # EVEX, synecho and non-synecho are not the same type if len(patternSet) > 1: newEdgePattern.setdefault(triPattern, edgeList) return newEdgePattern def candidateType_1(keygene, KGlist, edgePattern): """ Main function to detect and rank the EVEX-mixed patterns 5.1 This type of candidate is aimed to catch the mixed between "EVEX" and experimental links; "CoEx" and "Y2H" 5.1.1 normalize edges -- call external function 5.1.2 normalize nodes -- call external function 5.1.3 normalize nodes id -- call external function 5.1.4 check the organism of EVEX link whether it was studied in "Synechocystis" or related ones & then assigned the sub-type of EVEX to be either "EVEX synecho" or "EVEX non-synecho" - studied in Synechocystis makes the ranking lower since the information is know - studied in related organism make the ranking higher since the information is new """ rankingDict = dict() patternDict = dict() nodeSetDict = dict() noCandidate = dict() for triPattern, edgeList in edgePattern.iteritems(): noCandidate.setdefault(triPattern, set()) criteriaDict = dict() nodeDict = dict() for edge in edgeList: edgeType = normalizeEdge(edge.split("(")[1].split(")")[0], edge) node_1 = normalizeNode(edge.split(" (")[0], triPattern, KGlist, keygene) node_2 = normalizeNode(edge.split(") ")[1], triPattern, KGlist, keygene) nodeDict.setdefault(node_1, edge.split(" (")[0]) nodeDict.setdefault(node_2, edge.split(") ")[1]) key = normalizeEdgeID(node_1, node_2, edgeType) criteriaDict.setdefault(key, set()).add(edge) # check if the numbers of reassigned edge is equal to the original numbers, if not, the patterns are reported so the genes and edge can be checked for missing attributes if len(edgeList) != sum(len(v) for v in criteriaDict.itervalues()): print triPattern, "--> Unable to decide because there is no organism attributes for EVEX link" else: result = patternRanking(criteriaDict, triPattern, KGlist, nodeDict) rankingDict.setdefault(triPattern, result[0]) patternDict.setdefault(triPattern, result[1]) nodeSetDict.setdefault(triPattern, result[2]) return rankingDict, patternDict, nodeSetDict def normalizeEdge(edgeType, edge): """ 5.1.1 normalize edge - EVEX-mixed, edge is normalized into EVEX and CoEx (experimental links include both CoEx and Y2H) - CoEx-Y2H, edge is not normalized since both Y2H and CoEx must be in the patterns """ edgeSource = "" if edgeType in ["Indirect_regulation", "Regulation", "Binding"]: try: # distinct the types of EVEX to "EVEX synecho" and "EVEX non-synecho" cy.getEdgeAttributes(edge)['E-Taxonomy_Distance_from_Synechocystis'] if "0:Synechocystis sp. PCC 6803" not in cy.getEdgeAttributes(edge)['E-Taxonomy_Distance_from_Synechocystis']: edgeSource = "EVEX non-synecho" elif "0:Synechocystis sp. PCC 6803" in cy.getEdgeAttributes(edge)['E-Taxonomy_Distance_from_Synechocystis']: edgeSource = "EVEX synecho" except: # This is aimed catch the EVEX link which contains no attributes if edgeType == "EVEX": edgeSource = "EVEX" print "No organism attributes for this edge", edge elif edgeType in ["pos_interaction", "neg_interaction"]: edgeSource = "CoEx" elif edgeType in ["Y2H"]: edgeSource = "Y2H" else: print edgeType, "*****different edge type than EVEX, CoEx and Y2H" return edgeSource def normalizeNode(nodeX, triPattern, KGlist, keygene): """ 5.1.2 normalize nodes to classify types of information hold by the patterns (it is a part of normalization of the pattern information) - KG key genes : KG1, KG2 -- key genes must be defined - CG candidate genes : CG1 CG2 -- candidate genes are genes that is not in the key genes list """ if nodeX == triPattern[0]: nodeX = "KG1" elif nodeX != keygene and nodeX in KGlist: nodeX = "KG2" elif nodeX != keygene and nodeX not in KGlist: if nodeX == triPattern[1]: nodeX = "CG1" elif nodeX == triPattern[2]: if len(set(KGlist) & set(triPattern)) > 1: nodeX = "CG1" else: nodeX = "CG2" return nodeX def normalizeEdgeID(node_1, node_2, edgeSource): """ 5.1.3 normalize the edge id to arrange the combination of normalized node and normalized edge to be always following this orders [KG1, KG2, CG1, CG2] Normalized edge id is in general a combination of normalized nodes and normalized edge, e.g. node_1 (interaction) node_2 """ key = "" if (node_2 in ["KG1", "KG2"] and node_1 in ["CG1", "CG2"]) or (node_2 == "CG1" and node_1 == "CG2") or (node_2 == "KG1" and node_1 == "KG2"): key = node_2 + " " + edgeSource + " " + node_1 elif (node_1 in ["KG1", "KG2"] and node_2 in ["CG1", "CG2"]) or (node_1 == "CG1" and node_2 == "CG2") or (node_1 == "KG1" and node_2 == "KG2"): key = node_1 + " " + edgeSource + " " + node_2 else: print node_1, node_2, edgeSource, "*****check the edge and node normalization!" return key def patternRanking(criteriaDict, triPattern, KGlist, nodeDict): """ 5.1.5 At this step, we get the set of edges for each candidate """ tempDict = dict() for k, v in criteriaDict.iteritems(): x = k.split(" ") for element in x: # there is always 1 CG tempDict.setdefault("CG1", set()).add(k) # 1 KG, there will be 2 CG if len(set(triPattern) & set(KGlist)) == 1: tempDict.setdefault("CG2", set()).add(k) result = rankingProcess(nodeDict, tempDict, criteriaDict, KGlist) return result def invertDict(oldDict): """ """ newDict = dict() for k, v in oldDict.iteritems(): newDict[v] = k return newDict def assignDirect(item): """ criteria of grading the edge information by giving a the best grade (information) and d the worst grade The grading concerns the novelty of information the edge brings to the pattern the most important factor a: non-synecho edge -- most valuable (novel) information b: CoEx c: Y2H d: synecho edge -- the least valuable (novel) information """ ranking = "" if "EVEX" in item: if "non-synecho" in item: ranking = "a" else: ranking = "d" elif "CoEx" in item: ranking = "b" elif "Y2H" in item: ranking = "c" return ranking def assignIndirect(item): """ criteria of grading the edge information by giving a the best grade (information) and d the worst grade The grading concerns the novelty of information the edge brings to the pattern the most important factor This ranking is different from direct link since we values the EVEX edges more if it is studied in the Synechocystis. a: Synecho edge -- most valuable (novel) information b: non-Synecho edge c: CoEx d: Y2H -- the least valuable (novel) information """ ranking = "" if "EVEX" in item: if "non-synecho" in item: ranking = "b" else: ranking = "a" elif "CoEx" in item: ranking = "c" elif "Y2H" in item: ranking = "d" return ranking def rankingProcess(nodeDict, tempDict, criteriaDict, KGlist): """ 5.1.6 ranking the candidate gene based on its linking information regarding the key gene The original (old) ranking is composed of 8 positions and now reduced to (new) one with 6 positions. old ranking 0. direct link to keygene 1. indirect link to keygene via intermediate node (CG - middle man - KG) 2. indirect link from midle man to keygene (middle man - KG) 3. link to another KG 4. link to the second middle man 5. additional link on (triangular) edges 0, 1, 2 """ moreKGdict = {1: "x", 2: "a"} rankingDict = dict() for candidate, pattern in tempDict.iteritems(): ranking = rankCandidateEdges(pattern, candidate) ranking[3] = checkMoreKG(nodeDict, candidate, KGlist) quadruple = checkIndirectNode(nodeDict, candidate, KGlist) if quadruple != []: # additional gene ranking[4] = "a" # if triangular pattern contains more than 1 KG, it gets higher ranking ranking.insert(5, moreKGdict[len(set(KGlist) & set(nodeDict.values()))]) rankingDict.setdefault(candidate, rankingList2String(ranking)) return rankingDict, quadruple, nodeDict def rankCandidateEdges(pattern, candidate): """ The criteria of grading the edge information following the function -- assignDirect 5.1.6.1 direct link to key gene : The grade pos 1 : assign the grade for KG1/2 - CG1/2 5.1.6.2 begining of indirect link from candidate genes to the middle man before middle man linking to the keygene pos 2 : assign the grade for CG1/2 - CG1/2 If the edge is not studied in Synechocystis and there are multiple types of interactions support, the edge will be graded with the best grade among the edges. If there is even a single edge studied in Synechocystis, the edge will be graded as the worst, d. """ oldranking = [[], [], [], "x", "x", "x"] for item in pattern: # direct link: KG1-CG, KG2-CG if "KG1" in item and candidate in item: oldranking[0].append(assignDirect(item)) # indirect link - connect to the keygene elif "KG2" in item and candidate in item: oldranking[1].append(assignDirect(item)) elif "KG1" in item and candidate not in item and "KG2" not in item: oldranking[1].append(assignIndirect(item)) elif "KG1" in item and "KG2" in item: oldranking[2].append(assignIndirect(item)) elif "KG1" not in item and candidate in item: oldranking[2].append(assignIndirect(item)) oldranking = multipleEdges(oldranking) return reRanking(oldranking) def multipleEdges(oldranking): """ """ i = 0 # only consider the position 0, 1, 2 while i < 3: # if there are more than 2 types of edges # position 1 -- EVEX are graded as a or d if len(oldranking[i]) > 1 and len(set(oldranking[i])) > 1 and i == 0 and oldranking[i].sort() != ['a', 'd']: oldranking[5] = "a" # position 2, 3 -- EVEX are graded as a or b elif len(oldranking[i]) > 1 and len(set(oldranking[i])) > 1 and i != 0 and oldranking[i].sort() != ['a', 'b']: oldranking[5] = "a" i += 1 return oldranking def reRanking(oldRanking): """ """ newRanking = ["x", "x", "x", "x", "x", "x"] i = 0 while i < len(oldRanking): if i < 3: oldRanking[i].sort() try: if i == 0: if "d" in oldRanking[i]: newRanking[i] = "d" else: newRanking[i] = oldRanking[i][0] else: newRanking[i] = oldRanking[i][0] except: newRanking[i] = newRanking[i] else: newRanking[i] = oldRanking[i] i += 1 return newRanking def checkMoreKG(nodeDict, candidate, KGlist): """ 5.1.6.4 check if candidate gene is linked to another key gene or not. The ranking is a bit different from standard ranking since we grade the candidate with higher score if the number of additional linked key genes is higher The highest grade on this category is 3 additional keygene links meaning that and candidate genes with 3 or more additional key gene links obtain the same highest grade, ``a'' """ grade = ["", "x", "c", "b", "a"] ranking = "x" currentCG = nodeDict[candidate] addKG = list(set(set(cy.getNodeNeighbors(currentCG)) & set(KGlist))) try: ranking = grade[len(addKG)] except: print addKG ranking = "a" return ranking def checkIndirectNode(nodeDict, candidate, KGlist): """ """ extraNodePattern = [] currentCG = nodeDict[candidate] # look for the neighbor of the CG for item in cy.getAdjacentEdges(curNetID, currentCG): for splitNode in item.split(" "): if (splitNode != currentCG) and ("(" not in splitNode) and (splitNode not in nodeDict.values()): for edgy in cy.getAdjacentEdges(curNetID, splitNode): for splitEdge in edgy.split(" "): if (splitEdge in nodeDict.values()) and (splitEdge != currentCG) and ("(" not in splitNode): extraNodePattern = nodeDict.values() extraNodePattern.append(splitNode) return extraNodePattern def rankingList2String(ranking): """ """ rankingString = "" for item in ranking: rankingString = rankingString + item return rankingString def presentRanking(result, withSynecho, metabolism, KGlist): """ The results are presented per each metabolism by 1. print the highest rank 2. print the (candidate) gene in the pattern 3. print the (triangular) pattern 4. print the (quadruple) pattern, if found 5. select all the nodes in both triangular and quadruple patterns in cytoscape """ print "\nPattern Ranking for {}".format(metabolism) print "List of keygenes:{}\n".format(KGlist) groupRank = dict() patternPool = dict() uniquePattern = set() uniquePatternCount = 0 for KG, v in result.iteritems(): for pattern_1, candidateRank in v[0].iteritems(): for candidate, rank in candidateRank.iteritems(): uniquePatternCount += 1 # uniquePattern.add(pattern_1) CG = v[2][pattern_1][candidate] # organisedPattern = set([KG, CG]) | (set(pattern_1) - set([KG, CG])) patternListFormat = list(pattern_1) groupRank.setdefault(rank, set()).add((CG, pattern_1)) for pattern_2, quadruple in v[1].iteritems(): patternPool.setdefault(pattern_2, set()).add(tuple(quadruple)) print "\nPatterns count = {}".format(uniquePatternCount) # print "Unique patterns count (some patterns contain 2 CGs) = {}\n".format(len(uniquePattern)) if withSynecho == True: sortedList = sorted(groupRank.keys()) else: newList = sorted(groupRank.keys()) sortedList = [] for item in newList: if item[0] != "d": sortedList.append(item) allNodes = set() for rank in sortedList: print rank for pattern in groupRank[rank]: poolList = list(patternPool[pattern[1]])[0] if list(pattern[1]).index(pattern[0]) == 1: sortList = list(pattern[1]) # reorder the pattern so that # position 1 -- key gene # position 2 -- candidate gene (the rank refers to) # position 3 -- indirect node to support the pattern if len(poolList) == 4: print pattern[0], "\t", sortList[0], sortList[1], sortList[2], "\t", poolList[0], poolList[1], poolList[2], poolList[3] else: print pattern[0], "\t", sortList[0], sortList[1], sortList[2] else: sortList = [list(pattern[1])[0], list(pattern[1])[2], list(pattern[1])[1]] if len(poolList) == 4: print pattern[0], "\t", sortList[0], sortList[1], sortList[2], "\t", poolList[0], poolList[1], poolList[2], poolList[3] else: print pattern[0], "\t", sortList[0], sortList[1], sortList[2] print "" unionNode = unionNodeSet(groupRank, set()) # unionNode = unionNodeSet(patternPool, unionNode) selectTriangular(unionNode) presentUniquePattern(sortedList, groupRank) def presentUniquePattern(sortedRank, groupRank): """ """ uPatternSet = set() presentDict = dict() for sRank in sortedRank: for rank, candidatePattern in groupRank.iteritems(): if rank == sRank: for pattern in candidatePattern: print pattern x = list(pattern[1]) if tuple([x[0], x[1], x[2]]) not in uPatternSet and tuple([x[0], x[2], x[1]]) not in uPatternSet and tuple([x[1], x[2], x[0]]) not in uPatternSet and tuple([x[1], x[0], x[2]]) not in uPatternSet and tuple([x[2], x[1], x[0]]) not in uPatternSet and tuple([x[2], x[0], x[1]]) not in uPatternSet: uPatternSet.add(pattern[1]) presentDict.setdefault(sRank, dict()).setdefault(pattern[1], set()).add(pattern[0]) print "\nUnique patterns count = {}\n".format(len(uPatternSet)) for xRank in sortedRank: for nRank, patterns in presentDict.iteritems(): if xRank == nRank: for pattern, candidates in patterns.iteritems(): x = list(candidates) x.sort() print nRank, pattern[0], x[0], list(set(pattern) - set([pattern[0], x[0]]))[0] def presentCandidate(result, withSynecho): """ """ # this part groups the candidate with different ranking together groupRank = dict() poolRank = dict() for KG, resultList in result.iteritems(): for tripattern, candidateRank in resultList[0].iteritems(): for candidate, rank in candidateRank.iteritems(): CG = resultList[2][tripattern][candidate] # include "synecho" as a direct rank if withSynecho == True: groupRank.setdefault(CG, set()).add((rank, tripattern)) # do not include "synecho" direct rank else: if rank[0] != "d": groupRank.setdefault(CG, set()).add((rank, tripattern)) print "\n\nCandidate Ranking -- Candidate gene counts = {}\n".format(len(groupRank.keys())) for candidate, rankPatternSet in groupRank.iteritems(): rankSort = [] patternCount = 0 KGset = set() for rankPattern in rankPatternSet: rankSort.append(rankPattern[0]) patternCount += 1 KGset.add(rankPattern[1][0]) rankSort.sort() poolRank.setdefault(rankSort[0], []).append((candidate, patternCount, KGset)) sortRank = poolRank.keys() sortRank.sort() for sRank in sortRank: for item in poolRank[sRank]: print sRank, item[0], item[1], "pattern(s)", str(list(item[2])).replace("[", "").replace("]", "").replace("'", "") print "----------------------------------------" def unionNodeSet(nestedSet, unionNode): """ """ for item_1 in nestedSet.values(): for item_2 in item_1: if len(item_2) == 2: for item_3 in item_2[1]: unionNode.add(item_3) else: for item_3 in item_2: unionNode.add(item_3) return unionNode def selectTriangular(triPattern): """ insert the list of nodes you want to select on the cytoscape """ for item in triPattern: cy.selectNode(item) def selectTriangle(triPattern): """ insert the list of nodes you want to select on the cytoscape """ for item in triPattern.split(" "): cy.selectNode(item) ########################################################################## # updated on 24 Oct 2013 # How to run script # 1. load all the above functions # 2. run the command below one at a time # for example, put these two command lines into python compile as shown, # >>> NADPH = ["slr1239", "slr1434", "slr1843", "slr1289", "slr1643", "ssl0020"] # >>> There are three types of commands that will give different results # The first type : only EVEX-mixed meaning that you want to rank all the patterns that include EVEX edges # >>> NADPHresult = wrapperTriangles(NADPH, True, "EVEX-mixed") # The second type : only EVEX-mixed meaning that you want to rank all the patterns that exclude EVEX edges # >>> NADPHresult = wrapperTriangles(NADPH, True, "CoEx-Y2H") # The third type : only EVEX-mixed meaning that you want to rank all the patterns # >>> NADPHresult = wrapperTriangles(NADPH, True, "all-result") # "True" argument in the command is used when you want to include "Synechocystis" direct link. # If you do not want "Synechocystis" direct link in pattern, change "True" argument to "False" (Note. Keep the writing with capital "F" since it is in Python command. # original command line: # >>> NADPHresult = wrapperTriangles(NADPH, True, "all-result") # modified command line # >>> NADPHresult = wrapperTriangles(NADPH, False, "all-result") # 3. the result of ranking shown on the python compiler windows # 4. go to network and generate a new network based selected nodes, all nodes will be present in the network if they have interesting pattern # 5. deselect nodes from the previous command before running a new command for another metabolism # 6. if you want to remove the effect of some keygenes, remove it from the metabolism keygene list # for example, the below command line is the list of keygenes in NADPH metabolism # >>> NADPH = ["slr1239", "slr1434", "slr1843", "slr1289", "slr1643", "ssl0020"] ########################################################################## # note of modification (22 Oct 2013) # 1. modify the algorithm to keep high grade (a,b,c,d) for double edges. # eg. There are two edges linking between KG1 and CG1, the better grade is kept for the final score # not implemented but would be a good idea to grade with multiple edges support as well. # 2. adjust the script so now each gene in the pattern gets its own ranking. If there are 2 candidate genes in the same patterns, the ranking will be assigned to each of them individually. This way, we can see which genes is more likely to be candidate. # 3. checkMoreKG is aimed to detect if the candidate gene (not the whole pattern) is linked to another keygene or not. # This script is modified a bit to support the distinction of multiple key genes. ########################################################################## # note of modification (26 Oct 2013) # 1. remove all other types of calling function. Now all the EVEX-mixed and CoEx-Y2H results are called and reported together. # 2. Improve the report function by listing the candidate first and then the triangular pattern and finally the quadruple pttern, in the triangular pattern, candidate gene is always in the second position. ########################################################################## # List of key genes which can be modified Photosynthesis = ["ssr2016", "ssl2542", "ssr2595", "ssl1633", "ssl0563", "slr1835", "sll0219", "sll0217", "sll0218", "sll0550", "sll1521"] Glycogen = ["sll1689", "sll1968", "sll1423", "sll1594", "ssl0564", "sll0750", "slr6040", "sll0789", "sll0790"] Alkane = ["sll0208", "sll0209", "slr0596", "sll0728", "slr1435"] # no pattern with manual check: ["slr1435", "sll0208", "slr0596", "sll0728", "sll0209"] NADPH = ["slr1239", "slr1434", "slr1843", "slr1289", "slr1643", "ssl0020"] Iron = ["sll0088", "slr0074", "slr0075", "slr0076", "slr0077", "slr1417"] FA = ["sll0542", "sll0728 ", "slr0435", "sll0053", "sll0336", "slr2023", "slr1511", "sll0330", "slr0886", "slr1994", "fabA", "slr1051", "sll1069", "slr1332", "fabB"] ########################################################################## # run these commands, change the option of "False" to "True" if want to include the direct "synecho" pattern Photosynthesisresult = wrapperTriangles(Photosynthesis, False, "Photosynthesis") Glycogenresult = wrapperTriangles(Glycogen, False, "Glycogen metabolism") Alkaneresult = wrapperTriangles(Alkane, False, "Alkane metabolism") NADPHresult = wrapperTriangles(NADPH, False, "NADPH metabolism") Ironresult = wrapperTriangles(Iron, False, "Iron metabolism") FAresult = wrapperTriangles(FA, False, "Fatty acid metabolism") ########################