dynordg 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. dynordg-0.1.2/LICENSE +19 -0
  2. dynordg-0.1.2/MANIFEST.in +1 -0
  3. dynordg-0.1.2/PKG-INFO +76 -0
  4. dynordg-0.1.2/README.md +54 -0
  5. dynordg-0.1.2/pyproject.toml +40 -0
  6. dynordg-0.1.2/setup.cfg +4 -0
  7. dynordg-0.1.2/src/dynordg/__init__.py +2 -0
  8. dynordg-0.1.2/src/dynordg/classes/__init__.py +5 -0
  9. dynordg-0.1.2/src/dynordg/classes/core/__init__.py +3 -0
  10. dynordg-0.1.2/src/dynordg/classes/core/events.py +147 -0
  11. dynordg-0.1.2/src/dynordg/classes/core/nodes.py +45 -0
  12. dynordg-0.1.2/src/dynordg/classes/core/transitions.py +49 -0
  13. dynordg-0.1.2/src/dynordg/classes/graph/__init__.py +1 -0
  14. dynordg-0.1.2/src/dynordg/classes/graph/ribograph.py +93 -0
  15. dynordg-0.1.2/src/dynordg/classes/simulation/__init__.py +3 -0
  16. dynordg-0.1.2/src/dynordg/classes/simulation/fluxgraph.py +579 -0
  17. dynordg-0.1.2/src/dynordg/classes/simulation/transcript.py +178 -0
  18. dynordg-0.1.2/src/dynordg/classes/simulation/transitionmap.py +123 -0
  19. dynordg-0.1.2/src/dynordg/classes/viz/__init__.py +1 -0
  20. dynordg-0.1.2/src/dynordg/classes/viz/data/__init__.py +2 -0
  21. dynordg-0.1.2/src/dynordg/classes/viz/data/edges.py +246 -0
  22. dynordg-0.1.2/src/dynordg/classes/viz/data/layout.py +42 -0
  23. dynordg-0.1.2/src/dynordg/classes/viz/render/__init__.py +2 -0
  24. dynordg-0.1.2/src/dynordg/classes/viz/render/engine.py +833 -0
  25. dynordg-0.1.2/src/dynordg/classes/viz/render/view.py +428 -0
  26. dynordg-0.1.2/src/dynordg/classes/viz/ribographviz.py +190 -0
  27. dynordg-0.1.2/src/dynordg/constants.py +5 -0
  28. dynordg-0.1.2/src/dynordg/data/__init__.py +1 -0
  29. dynordg-0.1.2/src/dynordg/data/aug.csv +65537 -0
  30. dynordg-0.1.2/src/dynordg/data/noderer.py +9 -0
  31. dynordg-0.1.2/src/dynordg/data/non_aug.csv +9217 -0
  32. dynordg-0.1.2/src/dynordg/functions/__init__.py +1 -0
  33. dynordg-0.1.2/src/dynordg/functions/context_score.py +23 -0
  34. dynordg-0.1.2/src/dynordg.egg-info/PKG-INFO +76 -0
  35. dynordg-0.1.2/src/dynordg.egg-info/SOURCES.txt +36 -0
  36. dynordg-0.1.2/src/dynordg.egg-info/dependency_links.txt +1 -0
  37. dynordg-0.1.2/src/dynordg.egg-info/requires.txt +5 -0
  38. dynordg-0.1.2/src/dynordg.egg-info/top_level.txt +1 -0
dynordg-0.1.2/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2018 The Python Packaging Authority
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ include dynordg/data/*.csv
dynordg-0.1.2/PKG-INFO ADDED
@@ -0,0 +1,76 @@
1
+ Metadata-Version: 2.4
2
+ Name: dynordg
3
+ Version: 0.1.2
4
+ Summary: Simulate and render the flux of Ribosomes through Ribosomal Phase Space
5
+ Author-email: "Kyle A. Meiklejohn" <kyle.meiklejohn314@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/k-meiklejohn/dynordg
8
+ Project-URL: Issues, https://github.com/k-meiklejohn/dynordg/issues
9
+ Keywords: translation,ribosome decision graph,RDG,simulation,graph
10
+ Classifier: Programming Language :: Python
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.12
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: biopython>=1.86
17
+ Requires-Dist: levenshtein>=0.27.3
18
+ Requires-Dist: matplotlib>=3.10.6
19
+ Requires-Dist: networkx>=3.5
20
+ Requires-Dist: pandas>=2.3.3
21
+ Dynamic: license-file
22
+
23
+ # DYNORDG
24
+
25
+ Dynamic Ribosome Decision Graphs (RDGs) for simulating and visualizing ribosome flux along transcripts.
26
+
27
+ ## Overview
28
+
29
+ A Ribosome Decision Graph (RDG) models the possible paths a ribosome can take along an mRNA transcript.
30
+
31
+ **Dynamic RDGs** extend this by:
32
+ - Representing ribosome flux using edge thickness
33
+ - Implicitly encoding overlapping translons via flow rather than explicit separation
34
+ - Modeling ribosomal phase states based on downstream potential
35
+
36
+ This package provides tools to:
37
+ - Build RDGs from transcript sequences, or from user defined phase transistions
38
+ - Simulate ribosome movement
39
+ - Render dynamic flux graphs
40
+
41
+ ## Installation
42
+
43
+ ```bash
44
+ pip install dynordg
45
+ ```
46
+
47
+ ## Quick Start
48
+
49
+ ```python
50
+ from dynordg import Transcript, RiboGraphFlux, RiboGraphVis
51
+
52
+ # Create transcript
53
+ t = Transcript("AUGGCCAUGGCGCCCAGAACUGGGUAA")
54
+
55
+ # Automatically detect start/stop events
56
+ t.auto_stop_starts()
57
+
58
+ # Build flux graph
59
+ graph = RiboGraphFlux(t.transition_map)
60
+
61
+ # Create render object
62
+ plot = RiboGraphVis(graph)
63
+
64
+ # Render
65
+ plot.show()
66
+ ```
67
+
68
+ ## Example Output
69
+
70
+ Below is example dynamic RDG (if not a realistic one):
71
+
72
+ ![Example RDG](docs/example.png)
73
+
74
+
75
+
76
+
@@ -0,0 +1,54 @@
1
+ # DYNORDG
2
+
3
+ Dynamic Ribosome Decision Graphs (RDGs) for simulating and visualizing ribosome flux along transcripts.
4
+
5
+ ## Overview
6
+
7
+ A Ribosome Decision Graph (RDG) models the possible paths a ribosome can take along an mRNA transcript.
8
+
9
+ **Dynamic RDGs** extend this by:
10
+ - Representing ribosome flux using edge thickness
11
+ - Implicitly encoding overlapping translons via flow rather than explicit separation
12
+ - Modeling ribosomal phase states based on downstream potential
13
+
14
+ This package provides tools to:
15
+ - Build RDGs from transcript sequences, or from user defined phase transistions
16
+ - Simulate ribosome movement
17
+ - Render dynamic flux graphs
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ pip install dynordg
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ```python
28
+ from dynordg import Transcript, RiboGraphFlux, RiboGraphVis
29
+
30
+ # Create transcript
31
+ t = Transcript("AUGGCCAUGGCGCCCAGAACUGGGUAA")
32
+
33
+ # Automatically detect start/stop events
34
+ t.auto_stop_starts()
35
+
36
+ # Build flux graph
37
+ graph = RiboGraphFlux(t.transition_map)
38
+
39
+ # Create render object
40
+ plot = RiboGraphVis(graph)
41
+
42
+ # Render
43
+ plot.show()
44
+ ```
45
+
46
+ ## Example Output
47
+
48
+ Below is example dynamic RDG (if not a realistic one):
49
+
50
+ ![Example RDG](docs/example.png)
51
+
52
+
53
+
54
+
@@ -0,0 +1,40 @@
1
+ # pyproject.toml
2
+
3
+ [build-system]
4
+ requires = ["setuptools>=61.0.0", "wheel"]
5
+ build-backend = "setuptools.build_meta"
6
+
7
+ [project]
8
+ name = "dynordg"
9
+ version = "0.1.2"
10
+ description = "Simulate and render the flux of Ribosomes through Ribosomal Phase Space"
11
+ readme = "README.md"
12
+ authors = [{name = "Kyle A. Meiklejohn", email="kyle.meiklejohn314@gmail.com"}]
13
+ license = 'MIT'
14
+ license-files = ["LICEN[CS]E*"]
15
+ classifiers = [
16
+ "Programming Language :: Python",
17
+ "Programming Language :: Python :: 3",
18
+ "Operating System :: OS Independent",
19
+ ]
20
+
21
+
22
+
23
+ keywords = ["translation", "ribosome decision graph", "RDG", "simulation", "graph"]
24
+
25
+ dependencies = [
26
+ "biopython >=1.86",
27
+ "levenshtein >= 0.27.3",
28
+ "matplotlib >= 3.10.6",
29
+ "networkx >= 3.5",
30
+ "pandas >= 2.3.3",
31
+ ]
32
+
33
+ requires-python = ">=3.12"
34
+
35
+ [project.urls]
36
+ Homepage = "https://github.com/k-meiklejohn/dynordg"
37
+ Issues = "https://github.com/k-meiklejohn/dynordg/issues"
38
+
39
+ [tool.setuptools.package-data]
40
+ "dynordg" = ['data/*.csv']
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,2 @@
1
+ from .classes import RiboNode, RiboEvent, RiboGraph, RiboTransition, TransitionMap, RiboGraphFlux, RiboGraphVis, Transcript
2
+ from .functions import start_score
@@ -0,0 +1,5 @@
1
+ from .core import RiboEvent, RiboNode, RiboTransition
2
+ from .graph import RiboGraph
3
+ from .simulation import RiboGraphFlux, Transcript, TransitionMap
4
+ from .viz import RiboGraphVis
5
+ from ..data import AUG_SCORE, NON_AUG_SCORE
@@ -0,0 +1,3 @@
1
+ from .nodes import RiboNode
2
+ from .transitions import RiboTransition
3
+ from .events import RiboEvent
@@ -0,0 +1,147 @@
1
+ from .nodes import RiboNode
2
+ from .transitions import RiboTransition
3
+ import re
4
+
5
+ class RiboEvent(tuple):
6
+ """
7
+ Tuple in the form position:int, type: str, probability: float, drop_probability: float
8
+ Probabilities must be 0 <= p <= 1 and must not add to be greater than 1 (except termination which \
9
+ may have a probability (of reinitiation) and a drop_probability).
10
+ """
11
+
12
+
13
+ def _initiation_transition(self):
14
+ transitions = []
15
+ if self.probability > 0:
16
+ transitions.append(RiboTransition(RiboNode(self.position, 0),
17
+ RiboNode(self.position, self.frame),
18
+ self.probability))
19
+ if self.drop_probability > 0:
20
+ transitions.append(self.bulk_transition(0))
21
+ return transitions
22
+
23
+ def _termination_transition(self):
24
+ transitions = []
25
+ if self.probability > 0:
26
+ transitions.append(RiboTransition(RiboNode(self.position, self.frame),
27
+ RiboNode(self.position, 0),
28
+ self.probability))
29
+ if self.drop_probability > 0:
30
+ transitions.append(self.bulk_transition(self.frame))
31
+
32
+ return transitions
33
+
34
+ def _frameshift_transition(self):
35
+ transitions = []
36
+ shift_to_pos = self.position + self.shift
37
+ shift_to_frame = shift_to_pos % 3 + 1
38
+ if self.probability > 0:
39
+ transitions.append(RiboTransition(RiboNode(self.position, self.frame),
40
+ RiboNode(shift_to_pos, shift_to_frame ),
41
+ self.probability))
42
+ if self.drop_probability > 0:
43
+ transitions.append(self.bulk_transition(self.frame))
44
+ return transitions
45
+
46
+ def _ires_transition(self):
47
+ transitions = []
48
+ if self.probability > 0:
49
+ transitions.append(RiboTransition(RiboNode(self.position, -1),
50
+ RiboNode(self.position, self.frame),
51
+ self.probability))
52
+ return transitions
53
+
54
+ def _cap_transition(self):
55
+ transitions = []
56
+ if self.probability > 0:
57
+ transitions.append(RiboTransition(RiboNode(self.position, -1),
58
+ RiboNode(self.position, 0),
59
+ self.probability))
60
+ return transitions
61
+
62
+
63
+ def _end_transition(self):
64
+ transitions = []
65
+ if self.drop_probability > 0:
66
+ for phase in range(4):
67
+ transitions.append(self.bulk_transition(phase))
68
+ return transitions
69
+
70
+
71
+ TRANSITION_MAP = {
72
+ 'initiation': _initiation_transition,
73
+ 'termination': _termination_transition,
74
+ 'frameshift+1': _frameshift_transition,
75
+ 'frameshift-1': _frameshift_transition,
76
+ 'ires': _ires_transition,
77
+ 'cap': _cap_transition,
78
+ 'end': _end_transition
79
+ }
80
+
81
+
82
+ def bulk_transition(self, phase):
83
+ return RiboTransition(RiboNode(self.position, phase), RiboNode(self.position, -1), self.drop_probability)
84
+
85
+
86
+ @property
87
+ def frame(self):
88
+ """
89
+ Provides the frame (1,2,3) of the event
90
+ """
91
+ return (self.position % 3) + 1
92
+
93
+
94
+
95
+ @property
96
+ def shift(self):
97
+ """
98
+ If event is a frameshift type, this gives the direction and magnitude of the frameshift
99
+ """
100
+ match = re.search(r'shift([+-]?\d+)', self.type)
101
+ return int(match.group(1)) if match else 0
102
+
103
+ def __new__(cls, *args):
104
+
105
+ if len(args) == 1 and isinstance(args[0], (tuple, RiboEvent)):
106
+ data = args[0]
107
+ elif len(args) == 4:
108
+ data = args
109
+ else:
110
+ raise ValueError(f'RiboEvent requires either or a length-4 tuple, got: {args}')
111
+
112
+ if len(data) != 4:
113
+ raise ValueError(f'RiboEvent tuple must be of length 4, got length: {len(data)}')
114
+
115
+
116
+ if not isinstance(data[0], int):
117
+ raise ValueError(f"Position of RiboEvent tuple must be 'int', got {type(data[0]).__name__!r}")
118
+ elif not isinstance(data[1], str):
119
+ raise ValueError(f"Type of RiboEvent tuple must be 'str', got {type(data[1]).__name__!r}")
120
+ elif not all(isinstance(x, (float, int)) for x in data[2:]):
121
+ raise ValueError("Probabilities must be float or int")
122
+ elif all(x == 0 for x in data[2:]):
123
+ raise ValueError(f'At least one probability must be non-zero')
124
+ if not data[1] in ('cap', 'ires', 'termination'):
125
+ if sum(data[2:]) > 1:
126
+ raise ValueError(f"Sum of Probabilities for non-loading ('cap', 'ires') RiboEvent cannot exceed 1 or be 0, got {sum(data[2:])}")
127
+
128
+ return super().__new__(cls, data)
129
+
130
+ def __init__(self, *args):
131
+ super().__init__()
132
+ self.position = self[0]
133
+ self.type = self[1]
134
+ self.probability = self[2]
135
+ self.drop_probability = self[3]
136
+
137
+ def __repr__(self):
138
+ return f"(Pos:{self.position}, type:{self.type}, prob:{self.probability}, drop:{self.drop_probability})"
139
+
140
+ def to_transition(self) -> list[RiboTransition]:
141
+ """
142
+ Returns a list of RiboTransitions coresponding to the event
143
+ """
144
+ handler = self.TRANSITION_MAP.get(self.type)
145
+ if handler is None:
146
+ raise ValueError(f"No transition defined for event type {self.type!r}")
147
+ return handler(self)
@@ -0,0 +1,45 @@
1
+ class RiboNode(tuple):
2
+ """
3
+ Special 2-tuple that refers to a position in simplified Ribosomal phase space, the first integer \
4
+ refers to the nucleotide position on a transcript, while the second refers to the phase \
5
+ of the ribosome: -1 for not associated, 0 for scanning and 1,2,3 for translating in the frame \
6
+ where frame = position % 3 + 1
7
+
8
+ """
9
+ def __new__(cls, *args):
10
+ if len(args) == 1 and isinstance(args[0], (tuple, RiboNode)):
11
+ coords = args[0]
12
+ elif len(args) == 2:
13
+ coords = args
14
+ else:
15
+ raise ValueError(f'RiboNode requires 2 ints or a length-2 tuple, got: {args}')
16
+
17
+ if len(coords) != 2:
18
+ raise ValueError(f'RiboNode tuple must be of length 2, got length: {len(coords)}')
19
+
20
+ x, y = coords
21
+
22
+ if not isinstance(x, int) or not isinstance(y, int):
23
+ raise ValueError("RiboNode coordinates must be ints")
24
+
25
+ if not (-1 <= y < 4):
26
+ raise ValueError("position must be between -1 and 3")
27
+
28
+ return super().__new__(cls, (x, y))
29
+
30
+ @property
31
+ def position(self) -> int:
32
+ """
33
+ Nucleotide position of the node
34
+ """
35
+ return self[0]
36
+
37
+ @property
38
+ def phase(self) -> int:
39
+ """
40
+ Simplified phase of the node -1 is not reading, 0 is scanning, 1, 2, 3 are translating in one of those frames
41
+ """
42
+ return self[1]
43
+
44
+ def __repr__(self):
45
+ return f"(Pos:{self.position}, Phase:{self.phase})"
@@ -0,0 +1,49 @@
1
+ from .nodes import RiboNode
2
+
3
+ class RiboTransition(tuple):
4
+ """
5
+ A RiboTransition is an edge of a graph that shows a possible transition between phases, \
6
+ it consists of a initial position in ribosomal phase space, (source, RiboNode), a terminal position (target, RiboNode), \
7
+ and the probability that a ribosome reaching the source position will choose that path.
8
+ """
9
+
10
+ def __new__(cls, *args):
11
+ if len(args) == 1 and isinstance(args[0], (tuple, RiboTransition)):
12
+ data = args[0]
13
+ elif len(args) == 3:
14
+ data = args
15
+ else:
16
+ raise ValueError(f'RiboTransition requires either 3 arguments or a length-3 tuple, got: {args}')
17
+
18
+ if len(data) != 3:
19
+ raise ValueError(f'RiboTransition must be of length 3, got length: {len(data)}')
20
+
21
+ source, target, prob = data
22
+
23
+ # Coerce RiboNode-like tuples
24
+ if not isinstance(source, RiboNode):
25
+ if isinstance(source, tuple):
26
+ source = RiboNode(source)
27
+ else:
28
+ raise TypeError(f"Source must be 'RiboNode' or RiboNode-like tuple, got {type(source).__name__!r}")
29
+ if not isinstance(target, RiboNode):
30
+ if isinstance(target, tuple):
31
+ target = RiboNode(target)
32
+ else:
33
+ raise TypeError(f"Target must be 'RiboNode' or RiboNode-like tuple, got {type(target).__name__!r}")
34
+
35
+ if not isinstance(prob, (int, float)):
36
+ raise TypeError(f"Probability must be 'float' or 'int', got {type(prob).__name__!r}")
37
+ if not 0 < prob <= 1 and source.phase != -1:
38
+ raise ValueError(f"Probability must be in range (0, 1], got {prob}")
39
+
40
+ return super().__new__(cls, (source, target, prob))
41
+
42
+ def __init__(self, *args):
43
+ super().__init__()
44
+ self.source = self[0]
45
+ self.target = self[1]
46
+ self.probability = self[2]
47
+
48
+ def __repr__(self):
49
+ return f"(Source:{self.source}, Target:{self.target}, Probability:{self.probability})"
@@ -0,0 +1 @@
1
+ from .ribograph import RiboGraph
@@ -0,0 +1,93 @@
1
+ from ..core import RiboNode
2
+ from networkx import DiGraph
3
+
4
+ class RiboGraph(DiGraph):
5
+ """
6
+ A NetworkX Digraph that only accepts RiboNode instances as nodes
7
+ """
8
+
9
+ def __init__(self, incoming_graph_data = None, **attr):
10
+ super().__init__(incoming_graph_data, **attr)
11
+ self.bulk_node = RiboNode((-1,-1))
12
+ self.add_node(self.bulk_node)
13
+
14
+ def add_node(self, node_for_adding: RiboNode, **attr):
15
+ """
16
+ Add a new RiboNode to the graph
17
+ """
18
+ if isinstance(node_for_adding, RiboNode):
19
+ pass
20
+ elif isinstance(node_for_adding, tuple):
21
+ node_for_adding = RiboNode(node_for_adding)
22
+ else:
23
+ raise TypeError(f"Ribograph only accepts RiboNodes or RiboNode-like tuples, got {type(node_for_adding).__name__!r}")
24
+ super().add_node(node_for_adding, **attr)
25
+
26
+
27
+ def add_nodes_from(self, nodes_for_adding, **attr):
28
+ """
29
+ Add multiple RiboNodes to a graph
30
+ """
31
+ for node in nodes_for_adding:
32
+ n = node[0] if isinstance(node, tuple) else node
33
+ if not isinstance(n, RiboNode):
34
+ if isinstance(n, tuple):
35
+ pass # will be coerced by add_node
36
+ else:
37
+ raise TypeError(f'RiboGraph only accepts RiboNodes or RiboNode-like tuples, got {type(n).__name__!r}')
38
+ super().add_nodes_from(nodes_for_adding, **attr)
39
+
40
+
41
+ def add_edge(self, u, v, **attr):
42
+ """
43
+ Add an edge consisting of 2 RiboNodes to a graph
44
+ """
45
+ if not isinstance(u, RiboNode):
46
+ u = RiboNode(u)
47
+ if not isinstance(v, RiboNode):
48
+ v = RiboNode(v)
49
+
50
+ if self.has_edge(u, v):
51
+ existing = self.edges[u, v]
52
+ merged = {}
53
+ for key in set(existing) | set(attr):
54
+ existing_val = existing.get(key)
55
+ new_val = attr.get(key)
56
+ if existing_val is None:
57
+ merged[key] = new_val
58
+ elif new_val is None:
59
+ merged[key] = existing_val
60
+ elif key.startswith('flux'): #this may change
61
+ merged[key] = existing_val + new_val
62
+ else:
63
+ merged[key] = new_val # overwrite non-flux attributes
64
+ super().add_edge(u, v, **merged)
65
+ else:
66
+ super().add_edge(u, v, **attr)
67
+
68
+ def horizontal_in_edge(self, node: RiboNode, data:bool|str|list=False):
69
+ """
70
+ Returns the in edge in the same phase as the node
71
+ """
72
+ if data:
73
+ for u, v, data in self.in_edges(node, data=data):
74
+ if u.phase == node.phase:
75
+ return (u,v,data)
76
+ else:
77
+ for u, v in self.in_edges(node):
78
+ if u.phase == node.phase:
79
+ return (u,v)
80
+ def horizontal_out_edge(self, node: RiboNode, data:bool|str|list=False):
81
+ """
82
+ Returns the out edge in the same phase as the node
83
+ """
84
+
85
+ if data:
86
+ for u, v, data in self.out_edges(node, data=data):
87
+ if v.phase == node.phase:
88
+ return (u,v, data)
89
+
90
+ else:
91
+ for u, v in self.in_edges(node):
92
+ if u.phase == node.phase:
93
+ return (u,v)
@@ -0,0 +1,3 @@
1
+ from .fluxgraph import RiboGraphFlux
2
+ from .transitionmap import TransitionMap
3
+ from .transcript import Transcript