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.
- dynordg-0.1.2/LICENSE +19 -0
- dynordg-0.1.2/MANIFEST.in +1 -0
- dynordg-0.1.2/PKG-INFO +76 -0
- dynordg-0.1.2/README.md +54 -0
- dynordg-0.1.2/pyproject.toml +40 -0
- dynordg-0.1.2/setup.cfg +4 -0
- dynordg-0.1.2/src/dynordg/__init__.py +2 -0
- dynordg-0.1.2/src/dynordg/classes/__init__.py +5 -0
- dynordg-0.1.2/src/dynordg/classes/core/__init__.py +3 -0
- dynordg-0.1.2/src/dynordg/classes/core/events.py +147 -0
- dynordg-0.1.2/src/dynordg/classes/core/nodes.py +45 -0
- dynordg-0.1.2/src/dynordg/classes/core/transitions.py +49 -0
- dynordg-0.1.2/src/dynordg/classes/graph/__init__.py +1 -0
- dynordg-0.1.2/src/dynordg/classes/graph/ribograph.py +93 -0
- dynordg-0.1.2/src/dynordg/classes/simulation/__init__.py +3 -0
- dynordg-0.1.2/src/dynordg/classes/simulation/fluxgraph.py +579 -0
- dynordg-0.1.2/src/dynordg/classes/simulation/transcript.py +178 -0
- dynordg-0.1.2/src/dynordg/classes/simulation/transitionmap.py +123 -0
- dynordg-0.1.2/src/dynordg/classes/viz/__init__.py +1 -0
- dynordg-0.1.2/src/dynordg/classes/viz/data/__init__.py +2 -0
- dynordg-0.1.2/src/dynordg/classes/viz/data/edges.py +246 -0
- dynordg-0.1.2/src/dynordg/classes/viz/data/layout.py +42 -0
- dynordg-0.1.2/src/dynordg/classes/viz/render/__init__.py +2 -0
- dynordg-0.1.2/src/dynordg/classes/viz/render/engine.py +833 -0
- dynordg-0.1.2/src/dynordg/classes/viz/render/view.py +428 -0
- dynordg-0.1.2/src/dynordg/classes/viz/ribographviz.py +190 -0
- dynordg-0.1.2/src/dynordg/constants.py +5 -0
- dynordg-0.1.2/src/dynordg/data/__init__.py +1 -0
- dynordg-0.1.2/src/dynordg/data/aug.csv +65537 -0
- dynordg-0.1.2/src/dynordg/data/noderer.py +9 -0
- dynordg-0.1.2/src/dynordg/data/non_aug.csv +9217 -0
- dynordg-0.1.2/src/dynordg/functions/__init__.py +1 -0
- dynordg-0.1.2/src/dynordg/functions/context_score.py +23 -0
- dynordg-0.1.2/src/dynordg.egg-info/PKG-INFO +76 -0
- dynordg-0.1.2/src/dynordg.egg-info/SOURCES.txt +36 -0
- dynordg-0.1.2/src/dynordg.egg-info/dependency_links.txt +1 -0
- dynordg-0.1.2/src/dynordg.egg-info/requires.txt +5 -0
- 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
|
+

|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
dynordg-0.1.2/README.md
ADDED
|
@@ -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
|
+

|
|
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']
|
dynordg-0.1.2/setup.cfg
ADDED
|
@@ -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)
|