pyLoopSage 1.1.3__tar.gz → 1.1.5__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.
- pyloopsage-1.1.5/MANIFEST.in +1 -0
- {pyloopsage-1.1.3/pyLoopSage.egg-info → pyloopsage-1.1.5}/PKG-INFO +47 -1
- {pyloopsage-1.1.3 → pyloopsage-1.1.5}/README.md +45 -0
- {pyloopsage-1.1.3 → pyloopsage-1.1.5}/loopsage/__init__.py +1 -1
- {pyloopsage-1.1.3 → pyloopsage-1.1.5}/loopsage/args_definition.py +1 -0
- pyloopsage-1.1.5/loopsage/knots.py +210 -0
- {pyloopsage-1.1.3 → pyloopsage-1.1.5}/loopsage/plots.py +62 -52
- {pyloopsage-1.1.3 → pyloopsage-1.1.5}/loopsage/run.py +5 -2
- {pyloopsage-1.1.3 → pyloopsage-1.1.5}/loopsage/stochastic_simulation.py +10 -1
- {pyloopsage-1.1.3 → pyloopsage-1.1.5}/loopsage/utils.py +11 -2
- {pyloopsage-1.1.3 → pyloopsage-1.1.5/pyLoopSage.egg-info}/PKG-INFO +47 -1
- {pyloopsage-1.1.3 → pyloopsage-1.1.5}/pyLoopSage.egg-info/SOURCES.txt +1 -0
- {pyloopsage-1.1.3 → pyloopsage-1.1.5}/pyLoopSage.egg-info/requires.txt +1 -0
- {pyloopsage-1.1.3 → pyloopsage-1.1.5}/setup.py +2 -1
- pyloopsage-1.1.3/MANIFEST.in +0 -1
- {pyloopsage-1.1.3 → pyloopsage-1.1.5}/LICENSE +0 -0
- {pyloopsage-1.1.3 → pyloopsage-1.1.5}/loopsage/em.py +0 -0
- {pyloopsage-1.1.3 → pyloopsage-1.1.5}/loopsage/forcefields/classic_sm_ff.xml +0 -0
- {pyloopsage-1.1.3 → pyloopsage-1.1.5}/loopsage/initial_structures.py +0 -0
- {pyloopsage-1.1.3 → pyloopsage-1.1.5}/loopsage/md.py +0 -0
- {pyloopsage-1.1.3 → pyloopsage-1.1.5}/loopsage/preproc.py +0 -0
- {pyloopsage-1.1.3 → pyloopsage-1.1.5}/loopsage/vizualization_tools.py +0 -0
- {pyloopsage-1.1.3 → pyloopsage-1.1.5}/pyLoopSage.egg-info/dependency_links.txt +0 -0
- {pyloopsage-1.1.3 → pyloopsage-1.1.5}/pyLoopSage.egg-info/entry_points.txt +0 -0
- {pyloopsage-1.1.3 → pyloopsage-1.1.5}/pyLoopSage.egg-info/top_level.txt +0 -0
- {pyloopsage-1.1.3 → pyloopsage-1.1.5}/setup.cfg +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
exclude Dockerfile
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyLoopSage
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.5
|
|
4
4
|
Summary: An energy-based stochastic model of loop extrusion in chromatin.
|
|
5
5
|
Home-page: https://github.com/SFGLab/pyLoopSage
|
|
6
6
|
Author: Sebastian Korsak
|
|
@@ -32,6 +32,7 @@ Requires-Dist: imageio
|
|
|
32
32
|
Requires-Dist: imageio[ffmpeg]
|
|
33
33
|
Requires-Dist: pillow
|
|
34
34
|
Requires-Dist: pyBigWig
|
|
35
|
+
Requires-Dist: topoly
|
|
35
36
|
Dynamic: author
|
|
36
37
|
Dynamic: author-email
|
|
37
38
|
Dynamic: classifier
|
|
@@ -126,6 +127,51 @@ In general the user can run simulation in two different ways:
|
|
|
126
127
|
|
|
127
128
|
Can be easily installed with `pip install pyLoopSage`. To have CUDA acceleration, it is needed to have cuda-toolkit installed in case that you use nvidia drivers (otherwise you can use OpenCL or parallelization across CPU cores).
|
|
128
129
|
|
|
130
|
+
## 🐳 Running LoopSage with Docker
|
|
131
|
+
|
|
132
|
+
To use LoopSage in a fully containerized and reproducible way, you can build and run it using Docker. This is a very efficient way when you want to use CUDA.
|
|
133
|
+
|
|
134
|
+
### Step 1: Build the Docker Image
|
|
135
|
+
|
|
136
|
+
Clone the repository and build the image:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
docker build -t pyloopsage-cuda .
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The `Dockerfile` can be found in the GitHub repo of pyLoopSage.
|
|
143
|
+
|
|
144
|
+
### Step 2: Run the Simulation
|
|
145
|
+
|
|
146
|
+
Use the following command to run your simulation:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
docker run --rm -it --gpus all \
|
|
150
|
+
-v "$PWD/config.ini:/app/config.ini:ro" \
|
|
151
|
+
-v "$PWD/tmp:/app/output" \
|
|
152
|
+
-v "$HOME/Data:/home/blackpianocat/Data:ro" \
|
|
153
|
+
pyloopsage-cuda \
|
|
154
|
+
python -m loopsage.run -c /app/config.ini
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**What this does:**
|
|
158
|
+
|
|
159
|
+
* `--rm`: Automatically removes the container after it finishes.
|
|
160
|
+
* `--gpus all`: It detects the gpus of the system.
|
|
161
|
+
* `-it`: Runs with an interactive terminal.
|
|
162
|
+
* `-v "$PWD/config.ini:/app/config.ini:ro"`: Mounts your local `config.ini` as read-only inside the container.
|
|
163
|
+
* `-v "$PWD/tmp:/app/output"`: Maps the `tmp/` directory for outputs.
|
|
164
|
+
* `-v "$HOME/Data:/home/blackpianocat/Data:ro"`: Mounts your full data directory so LoopSage can access input files.
|
|
165
|
+
* The final command runs LoopSage with your config file.
|
|
166
|
+
|
|
167
|
+
You do **not** need to manually stop or clean up anything—the container is temporary and self-destructs after it completes. The image (`pyloopsage-cuda`) remains available on your system and can be deleted anytime using:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
docker rmi pyloopsage-cuda
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Note:** Install `nvidia-container-toolkit` in your system if you want to use the container with CUDA: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html
|
|
174
|
+
|
|
129
175
|
## How to use?
|
|
130
176
|
|
|
131
177
|
### Python Implementation
|
|
@@ -80,6 +80,51 @@ In general the user can run simulation in two different ways:
|
|
|
80
80
|
|
|
81
81
|
Can be easily installed with `pip install pyLoopSage`. To have CUDA acceleration, it is needed to have cuda-toolkit installed in case that you use nvidia drivers (otherwise you can use OpenCL or parallelization across CPU cores).
|
|
82
82
|
|
|
83
|
+
## 🐳 Running LoopSage with Docker
|
|
84
|
+
|
|
85
|
+
To use LoopSage in a fully containerized and reproducible way, you can build and run it using Docker. This is a very efficient way when you want to use CUDA.
|
|
86
|
+
|
|
87
|
+
### Step 1: Build the Docker Image
|
|
88
|
+
|
|
89
|
+
Clone the repository and build the image:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
docker build -t pyloopsage-cuda .
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The `Dockerfile` can be found in the GitHub repo of pyLoopSage.
|
|
96
|
+
|
|
97
|
+
### Step 2: Run the Simulation
|
|
98
|
+
|
|
99
|
+
Use the following command to run your simulation:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
docker run --rm -it --gpus all \
|
|
103
|
+
-v "$PWD/config.ini:/app/config.ini:ro" \
|
|
104
|
+
-v "$PWD/tmp:/app/output" \
|
|
105
|
+
-v "$HOME/Data:/home/blackpianocat/Data:ro" \
|
|
106
|
+
pyloopsage-cuda \
|
|
107
|
+
python -m loopsage.run -c /app/config.ini
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**What this does:**
|
|
111
|
+
|
|
112
|
+
* `--rm`: Automatically removes the container after it finishes.
|
|
113
|
+
* `--gpus all`: It detects the gpus of the system.
|
|
114
|
+
* `-it`: Runs with an interactive terminal.
|
|
115
|
+
* `-v "$PWD/config.ini:/app/config.ini:ro"`: Mounts your local `config.ini` as read-only inside the container.
|
|
116
|
+
* `-v "$PWD/tmp:/app/output"`: Maps the `tmp/` directory for outputs.
|
|
117
|
+
* `-v "$HOME/Data:/home/blackpianocat/Data:ro"`: Mounts your full data directory so LoopSage can access input files.
|
|
118
|
+
* The final command runs LoopSage with your config file.
|
|
119
|
+
|
|
120
|
+
You do **not** need to manually stop or clean up anything—the container is temporary and self-destructs after it completes. The image (`pyloopsage-cuda`) remains available on your system and can be deleted anytime using:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
docker rmi pyloopsage-cuda
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Note:** Install `nvidia-container-toolkit` in your system if you want to use the container with CUDA: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html
|
|
127
|
+
|
|
83
128
|
## How to use?
|
|
84
129
|
|
|
85
130
|
### Python Implementation
|
|
@@ -196,6 +196,7 @@ args = ListOfArgs([
|
|
|
196
196
|
Arg('BIND_COEFF', help="CTCF binding coefficient.", type=float, default='1.0', val='1.0'),
|
|
197
197
|
Arg('SAVE_PLOTS', help="It should be true in case that you would like to save diagnostic plots. In case that you use small MC_STEP or large N_STEPS is better to mark it as False.", type=bool, default='True', val='True'),
|
|
198
198
|
Arg('SAVE_MDT', help="In case that you would liketo save metadata of the stochastic simulation.", type=bool, default='True', val='True'),
|
|
199
|
+
Arg('DETECT_KNOTS', help="In case that you would like to find out if there are knots in the structure.", type=bool, default='False', val='False'),
|
|
199
200
|
|
|
200
201
|
# Molecular Dynamic Properties
|
|
201
202
|
Arg('INITIAL_STRUCTURE_TYPE', help="you can choose between: rw, confined_rw, self_avoiding_rw, helix, circle, spiral, sphere.", type=str, default='rw', val='rw'),
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
from scipy.interpolate import CubicSpline, interp1d
|
|
2
|
+
from scipy.spatial.distance import pdist, squareform
|
|
3
|
+
from scipy.integrate import dblquad
|
|
4
|
+
import topoly as tp
|
|
5
|
+
import numpy as np
|
|
6
|
+
from tqdm import tqdm
|
|
7
|
+
from .utils import *
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
################ Initial Knotted Structure Creation ######################
|
|
11
|
+
|
|
12
|
+
def resample_curve(points, N):
|
|
13
|
+
deltas = np.diff(points, axis=0)
|
|
14
|
+
dists = np.linalg.norm(deltas, axis=1)
|
|
15
|
+
cumulative = np.insert(np.cumsum(dists), 0, 0)
|
|
16
|
+
uniform_samples = np.linspace(0, cumulative[-1], N)
|
|
17
|
+
interp = interp1d(cumulative, points, axis=0)
|
|
18
|
+
return interp(uniform_samples)
|
|
19
|
+
|
|
20
|
+
def trefoil_knot(N=200, scale=0.5, offset=(0, 0, 0), rotation=None):
|
|
21
|
+
dense_N = 5000
|
|
22
|
+
t = np.linspace(0, 2 * np.pi, dense_N)
|
|
23
|
+
x = scale * (np.sin(t) + 2 * np.sin(2 * t)) + offset[0]
|
|
24
|
+
y = scale * (np.cos(t) - 2 * np.cos(2 * t)) + offset[1]
|
|
25
|
+
z = scale * (-np.sin(3 * t)) + offset[2]
|
|
26
|
+
|
|
27
|
+
V = np.vstack((x, y, z)).T
|
|
28
|
+
if rotation is not None:
|
|
29
|
+
V = np.dot(V, rotation)
|
|
30
|
+
return resample_curve(V, N)
|
|
31
|
+
|
|
32
|
+
def cinquefoil_knot(N=200, scale=0.5, offset=(0, 0, 0), rotation=None):
|
|
33
|
+
dense_N = 5000
|
|
34
|
+
t = np.linspace(0, 2 * np.pi, dense_N)
|
|
35
|
+
x = scale * (np.sin(2*t) + 2*np.sin(3*t))
|
|
36
|
+
y = scale * (np.cos(2*t) - 2*np.cos(3*t))
|
|
37
|
+
z = scale * (-np.sin(5*t))
|
|
38
|
+
|
|
39
|
+
V = np.vstack((x, y, z)).T
|
|
40
|
+
if rotation is not None:
|
|
41
|
+
V = np.dot(V, rotation)
|
|
42
|
+
return resample_curve(V + np.array(offset), N)
|
|
43
|
+
|
|
44
|
+
def random_rotation_matrix():
|
|
45
|
+
"""Generate a random 3D rotation matrix."""
|
|
46
|
+
theta, phi, z = np.random.uniform(0, 2*np.pi-np.pi/36, 3)
|
|
47
|
+
r = np.sqrt(z)
|
|
48
|
+
V = np.array([
|
|
49
|
+
[np.cos(theta) * np.cos(phi), -np.sin(theta), np.cos(theta) * np.sin(phi)],
|
|
50
|
+
[np.sin(theta) * np.cos(phi), np.cos(theta), np.sin(theta) * np.sin(phi)],
|
|
51
|
+
[-np.sin(phi), 0, np.cos(phi)]
|
|
52
|
+
])
|
|
53
|
+
return V
|
|
54
|
+
|
|
55
|
+
def smooth_linkage(start, end, N=50):
|
|
56
|
+
"""Generate a smooth linking curve between two knots."""
|
|
57
|
+
t = np.linspace(0, 1, N)
|
|
58
|
+
x = (1 - t) * start[0] + t * end[0]
|
|
59
|
+
y = (1 - t) * start[1] + t * end[1]
|
|
60
|
+
z = (1 - t) * start[2] + t * end[2]
|
|
61
|
+
return np.vstack((x, y, z)).T
|
|
62
|
+
|
|
63
|
+
def smooth_spline_linkage(start, start_dir, end, end_dir, N=100):
|
|
64
|
+
"""Generate a smooth cubic spline from `start` to `end` with given directional derivatives."""
|
|
65
|
+
t = np.array([0, 1])
|
|
66
|
+
x = np.array([start[0], end[0]])
|
|
67
|
+
y = np.array([start[1], end[1]])
|
|
68
|
+
z = np.array([start[2], end[2]])
|
|
69
|
+
|
|
70
|
+
# Use directional derivative constraints
|
|
71
|
+
cs_x = CubicSpline(t, x, bc_type=((1, start_dir[0]), (1, end_dir[0])))
|
|
72
|
+
cs_y = CubicSpline(t, y, bc_type=((1, start_dir[1]), (1, end_dir[1])))
|
|
73
|
+
cs_z = CubicSpline(t, z, bc_type=((1, start_dir[2]), (1, end_dir[2])))
|
|
74
|
+
|
|
75
|
+
tt = np.linspace(0, 1, N)
|
|
76
|
+
return np.vstack((cs_x(tt), cs_y(tt), cs_z(tt))).T
|
|
77
|
+
|
|
78
|
+
def random_unit_vector():
|
|
79
|
+
"""Uniformly sample a unit vector on the 3D sphere."""
|
|
80
|
+
phi = np.random.uniform(0, 2*np.pi)
|
|
81
|
+
costheta = np.random.uniform(-0.1, 0.1)
|
|
82
|
+
sintheta = np.sqrt(1 - costheta**2)
|
|
83
|
+
return np.array([sintheta * np.cos(phi), sintheta * np.sin(phi), costheta])
|
|
84
|
+
|
|
85
|
+
def generate_knotted_structure(N=1000, num_knots=5):
|
|
86
|
+
"""Generate a structure with `num_knots` along a random walk, total `N` points."""
|
|
87
|
+
if num_knots >= N:
|
|
88
|
+
raise ValueError("N must be much larger than num_knots")
|
|
89
|
+
|
|
90
|
+
points_per_knot = N // num_knots
|
|
91
|
+
structure = []
|
|
92
|
+
position = np.zeros(3)
|
|
93
|
+
radius_buffer = 2.0 # Step size for knot placement
|
|
94
|
+
|
|
95
|
+
for _ in range(num_knots):
|
|
96
|
+
# Move to next knot position
|
|
97
|
+
direction = random_unit_vector()
|
|
98
|
+
position = position + direction * radius_buffer
|
|
99
|
+
|
|
100
|
+
# Random rotation and scale
|
|
101
|
+
rotation = random_rotation_matrix()
|
|
102
|
+
scale = np.random.uniform(0.6, 1.0)
|
|
103
|
+
|
|
104
|
+
# Generate knot
|
|
105
|
+
knot_type = np.random.choice(["trefoil", "figure_eight"])
|
|
106
|
+
if knot_type == "trefoil":
|
|
107
|
+
knot = trefoil_knot(N=points_per_knot, scale=scale, offset=position, rotation=rotation)
|
|
108
|
+
else:
|
|
109
|
+
knot = cinquefoil_knot(N=points_per_knot, scale=scale, offset=position, rotation=rotation)
|
|
110
|
+
|
|
111
|
+
structure.append(knot)
|
|
112
|
+
|
|
113
|
+
curve = np.vstack(structure)
|
|
114
|
+
return resample_curve(curve, N) if len(curve) != N else curve
|
|
115
|
+
|
|
116
|
+
############## Identification of Knots and Links ######################
|
|
117
|
+
|
|
118
|
+
def smooth_knot_spline(V, num_interp=200, closed=True):
|
|
119
|
+
"""
|
|
120
|
+
Smoothly interpolates a 3D knot using a cubic spline.
|
|
121
|
+
|
|
122
|
+
Parameters:
|
|
123
|
+
V (numpy.ndarray): Nx3 array representing the 3D knot.
|
|
124
|
+
num_interp (int): Number of points in the interpolated knot.
|
|
125
|
+
closed (bool): Whether to enforce periodic boundary conditions for closed knots.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
numpy.ndarray: Smoothed Nx3 array of the interpolated knot.
|
|
129
|
+
"""
|
|
130
|
+
if V.shape[1] != 3:
|
|
131
|
+
raise ValueError("Input array V must have shape (N, 3)")
|
|
132
|
+
|
|
133
|
+
if len(V) < 4:
|
|
134
|
+
raise ValueError("At least 4 points are required for spline fitting.")
|
|
135
|
+
|
|
136
|
+
# Define the parameter t along the curve
|
|
137
|
+
t = np.linspace(0, 1, len(V))
|
|
138
|
+
|
|
139
|
+
# Interpolation points
|
|
140
|
+
t_new = np.linspace(0, 1, num_interp)
|
|
141
|
+
|
|
142
|
+
# Fit cubic splines separately for each coordinate
|
|
143
|
+
cs_x = CubicSpline(t, V[:, 0], bc_type='periodic' if closed else 'not-a-knot')
|
|
144
|
+
cs_y = CubicSpline(t, V[:, 1], bc_type='periodic' if closed else 'not-a-knot')
|
|
145
|
+
cs_z = CubicSpline(t, V[:, 2], bc_type='periodic' if closed else 'not-a-knot')
|
|
146
|
+
|
|
147
|
+
# Evaluate the splines
|
|
148
|
+
x_new = cs_x(t_new)
|
|
149
|
+
y_new = cs_y(t_new)
|
|
150
|
+
z_new = cs_z(t_new)
|
|
151
|
+
|
|
152
|
+
return np.vstack((x_new, y_new, z_new)).T
|
|
153
|
+
|
|
154
|
+
def calculate_linking_number(V,ms,ns):
|
|
155
|
+
links = list()
|
|
156
|
+
for i in range(len(ms)):
|
|
157
|
+
for j in range(i+1,len(ms)):
|
|
158
|
+
if (ns[i]-ms[i])>5 and (ns[j]-ms[j])>5 and (((ms[i]<ns[i]) and (ns[i]<ms[j])) or ((ms[j]<ns[j]) and (ns[j]<ms[i]))):
|
|
159
|
+
loop1 = V[ms[i]:ns[i]]
|
|
160
|
+
loop1 = np.vstack((loop1,loop1[0,:]))
|
|
161
|
+
loop1 = smooth_knot_spline(loop1,2*len(loop1))
|
|
162
|
+
l1 = [list(loop1[i]) for i in range(len(loop1))]
|
|
163
|
+
loop2 = V[ms[j]:ns[j]]
|
|
164
|
+
loop2 = np.vstack((loop2,loop2[0,:]))
|
|
165
|
+
loop2 = smooth_knot_spline(loop2,2*len(loop2))
|
|
166
|
+
l2 = [list(loop2[i]) for i in range(len(loop2))]
|
|
167
|
+
links.append(tp.gln(l1,l2))
|
|
168
|
+
links = np.array(links)
|
|
169
|
+
links = links[links>0.5]
|
|
170
|
+
N_links = len(links)
|
|
171
|
+
|
|
172
|
+
return N_links, links
|
|
173
|
+
|
|
174
|
+
def link_number_ensemble(path, step=1, mode='MD', viz=False):
|
|
175
|
+
nlinks = list()
|
|
176
|
+
Ms = np.load(path+'/other/Ms.npy')
|
|
177
|
+
Ns = np.load(path+'/other/Ns.npy')
|
|
178
|
+
|
|
179
|
+
# Automatically determine the number of ensembles
|
|
180
|
+
ensemble_files = [f for f in os.listdir(path+'/ensemble') if f.startswith('MDLE_') and f.endswith('.cif')]
|
|
181
|
+
max_index = max(int(f.split('_')[1].split('.')[0]) for f in ensemble_files)
|
|
182
|
+
|
|
183
|
+
print('\nCalculating linking number of structures....')
|
|
184
|
+
for i in tqdm(range(step, max_index + 1, step)):
|
|
185
|
+
V = get_coordinates_cif(path+f'/ensemble/{mode}LE_{i}.cif')
|
|
186
|
+
ms, ns = Ms[:, i-1], Ns[:, i-1]
|
|
187
|
+
N_links, links = calculate_linking_number(V, ms, ns)
|
|
188
|
+
nlinks.append(N_links)
|
|
189
|
+
print('Done!')
|
|
190
|
+
|
|
191
|
+
avg_link_number = np.mean(nlinks)
|
|
192
|
+
std_link_number = np.std(nlinks)
|
|
193
|
+
if viz:
|
|
194
|
+
plt.figure()
|
|
195
|
+
plt.plot(np.arange(0, len(nlinks)*step, step), nlinks)
|
|
196
|
+
plt.xlabel('Step')
|
|
197
|
+
plt.ylabel('Number of links')
|
|
198
|
+
plt.savefig(path+'/plots/links.png')
|
|
199
|
+
plt.close()
|
|
200
|
+
|
|
201
|
+
plt.figure()
|
|
202
|
+
plt.hist(nlinks, bins=20, alpha=0.75, color='blue', edgecolor='black')
|
|
203
|
+
plt.xlabel('Number of links')
|
|
204
|
+
plt.ylabel('Frequency')
|
|
205
|
+
plt.title('Histogram of Number of Links')
|
|
206
|
+
plt.savefig(path+'/plots/links_histogram.png')
|
|
207
|
+
plt.close()
|
|
208
|
+
np.save(path+'/other/links.npy', nlinks)
|
|
209
|
+
print(f'Average number of links: {avg_link_number:.2f} ± {std_link_number:.2f}')
|
|
210
|
+
return avg_link_number, std_link_number, nlinks
|
|
@@ -101,60 +101,70 @@ def average_pooling(mat,dim_new):
|
|
|
101
101
|
im_resized = np.array(im.resize(size))
|
|
102
102
|
return im_resized
|
|
103
103
|
|
|
104
|
-
def
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
spearmans[i] = scipy.stats.spearmanr(a, b).correlation
|
|
124
|
-
kendals[i] = scipy.stats.kendalltau(a, b).correlation
|
|
125
|
-
print(f'\nTemperature:{T}, Pearson Correlation coefficient:{pearsons[i]}, Spearman:{spearmans[i]}, Kendal:{kendals[i]}\n\n')
|
|
126
|
-
|
|
127
|
-
figure(figsize=(10, 8), dpi=600)
|
|
128
|
-
plt.plot(T_range,pearsons,'bo-')
|
|
129
|
-
plt.plot(T_range,spearmans,'ro-')
|
|
130
|
-
plt.plot(T_range,kendals,'go-')
|
|
131
|
-
# plt.plot(T_range,Cross,'go-')
|
|
132
|
-
plt.ylabel('Correlation with Experimental Heatmap', fontsize=16)
|
|
133
|
-
plt.xlabel('Temperature', fontsize=16)
|
|
134
|
-
# plt.yscale('symlog')
|
|
135
|
-
plt.legend(['Pearson','Spearman','Kendall Tau'])
|
|
136
|
-
plt.grid()
|
|
137
|
-
save_path = path+'/plots/pearson_plot.pdf' if path!=None else 'pearson_plot.pdf'
|
|
138
|
-
plt.savefig(save_path,dpi=600)
|
|
139
|
-
plt.close()
|
|
140
|
-
|
|
141
|
-
def coh_traj_plot(ms,ns,N_beads,path):
|
|
104
|
+
def coh_traj_plot(ms, ns, N_beads, path, jump_threshold=200, min_stable_time=10):
|
|
105
|
+
"""
|
|
106
|
+
Plots the trajectories of cohesins as filled regions between their two ends over time.
|
|
107
|
+
|
|
108
|
+
Parameters:
|
|
109
|
+
ms (list of arrays): List where each element is an array of left-end positions of a cohesin over time.
|
|
110
|
+
ns (list of arrays): List where each element is an array of right-end positions of a cohesin over time.
|
|
111
|
+
N_beads (int): Total number of beads (simulation sites) in the system.
|
|
112
|
+
path (str): Directory path where the plots will be saved.
|
|
113
|
+
jump_threshold (int, optional): Maximum allowed jump (in bead units) between consecutive time points for both ends.
|
|
114
|
+
If the jump between two consecutive positions exceeds this threshold for either end, that segment is considered a jump and is masked out.
|
|
115
|
+
Lower values make the criterion for erasing (masking) trajectories more strict (more segments are erased), higher values make it less strict.
|
|
116
|
+
min_stable_time (int, optional): Minimum number of consecutive time points required for a region to be considered stable and shown.
|
|
117
|
+
Shorter stable regions (less than this value) are erased (masked out).
|
|
118
|
+
Higher values make the criterion more strict (only longer stable regions are shown), lower values make it less strict.
|
|
119
|
+
|
|
120
|
+
The function highlights only stable, non-jumping regions of cohesin trajectories.
|
|
121
|
+
"""
|
|
122
|
+
print('\nPlotting trajectories of cohesins...')
|
|
142
123
|
N_coh = len(ms)
|
|
143
|
-
figure(figsize=(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
124
|
+
figure(figsize=(10, 10), dpi=200)
|
|
125
|
+
cmap = plt.get_cmap('prism')
|
|
126
|
+
colors = [cmap(i / N_coh) for i in range(N_coh)]
|
|
127
|
+
|
|
128
|
+
for nn in tqdm(range(N_coh)):
|
|
129
|
+
tr_m, tr_n = np.array(ms[nn]), np.array(ns[nn])
|
|
130
|
+
steps = np.arange(len(tr_m))
|
|
131
|
+
|
|
132
|
+
# Calculate jump size for tr_m and tr_n independently
|
|
133
|
+
jumps_m = np.abs(np.diff(tr_m))
|
|
134
|
+
jumps_n = np.abs(np.diff(tr_n))
|
|
135
|
+
|
|
136
|
+
# Create mask: True = good point, False = jump
|
|
137
|
+
jump_mask = np.ones_like(tr_m, dtype=bool)
|
|
138
|
+
jump_mask[1:] = (jumps_m < jump_threshold) & (jumps_n < jump_threshold) # both must be below threshold
|
|
139
|
+
|
|
140
|
+
# Now we want to detect stable regions
|
|
141
|
+
stable_mask = np.copy(jump_mask)
|
|
142
|
+
|
|
143
|
+
# Find connected regions
|
|
144
|
+
current_length = 0
|
|
145
|
+
for i in range(len(stable_mask)):
|
|
146
|
+
if jump_mask[i]:
|
|
147
|
+
current_length += 1
|
|
148
|
+
else:
|
|
149
|
+
if current_length < min_stable_time:
|
|
150
|
+
stable_mask[i-current_length:i] = False
|
|
151
|
+
current_length = 0
|
|
152
|
+
# Handle last region
|
|
153
|
+
if current_length < min_stable_time:
|
|
154
|
+
stable_mask[len(stable_mask)-current_length:] = False
|
|
155
|
+
|
|
156
|
+
# Apply mask
|
|
157
|
+
tr_m_masked = np.ma.masked_array(tr_m, mask=~stable_mask)
|
|
158
|
+
tr_n_masked = np.ma.masked_array(tr_n, mask=~stable_mask)
|
|
159
|
+
|
|
160
|
+
plt.fill_between(steps, tr_m_masked, tr_n_masked,
|
|
161
|
+
color=colors[nn], alpha=0.6, interpolate=False, linewidth=0)
|
|
162
|
+
plt.xlabel('MC Step', fontsize=16)
|
|
163
|
+
plt.ylabel('Simulation Beads', fontsize=16)
|
|
153
164
|
plt.gca().invert_yaxis()
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
save_path =
|
|
157
|
-
plt.savefig(save_path, format='svg', dpi=200)
|
|
165
|
+
plt.ylim((0, N_beads))
|
|
166
|
+
save_path = path + '/plots/LEFs.png'
|
|
167
|
+
plt.savefig(save_path, format='png',dpi=200)
|
|
158
168
|
plt.close()
|
|
159
169
|
|
|
160
170
|
def coh_probdist_plot(ms,ns,N_beads,path):
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from .stochastic_simulation import *
|
|
2
2
|
from .args_definition import *
|
|
3
|
-
import
|
|
4
|
-
import time
|
|
3
|
+
from .knots import *
|
|
5
4
|
import argparse
|
|
6
5
|
import configparser
|
|
7
6
|
from typing import List
|
|
@@ -95,5 +94,9 @@ def main():
|
|
|
95
94
|
else:
|
|
96
95
|
IndentationError('Uknown simulation type. It can be either MD or EM.')
|
|
97
96
|
|
|
97
|
+
# Knoting
|
|
98
|
+
if args.DETECT_KNOTS:
|
|
99
|
+
link_number_ensemble(path=args.OUT_PATH, viz=args.SAVE_PLOTS, mode=args.SIMULATION_TYPE)
|
|
100
|
+
|
|
98
101
|
if __name__=='__main__':
|
|
99
102
|
main()
|
|
@@ -198,7 +198,16 @@ def run_simulation(N_beads, N_steps, MC_step, burnin, T, T_min, fold_norm, fold_
|
|
|
198
198
|
Es, Ks, Fs, Bs, ufs = np.zeros(N_steps // MC_step, dtype=np.float64), np.zeros(N_steps // MC_step, dtype=np.float64), np.zeros(N_steps // MC_step, dtype=np.float64), np.zeros(N_steps // MC_step, dtype=np.float64), np.zeros(N_steps // MC_step, dtype=np.float64)
|
|
199
199
|
Ms, Ns = np.zeros((N_lef + N_lef2, N_steps // MC_step), dtype=np.int64), np.zeros((N_lef + N_lef2, N_steps // MC_step), dtype=np.int64)
|
|
200
200
|
|
|
201
|
+
last_percent = -1
|
|
202
|
+
|
|
201
203
|
for i in range(N_steps):
|
|
204
|
+
# Print progress every 5%
|
|
205
|
+
percent = int(100 * i / N_steps)
|
|
206
|
+
if percent % 5 == 0 and percent != last_percent:
|
|
207
|
+
# Numba can't use print with flush, so just print
|
|
208
|
+
print(f"Progress: {percent} % completed.")
|
|
209
|
+
last_percent = percent
|
|
210
|
+
|
|
202
211
|
Ti = T - (T - T_min) * (i + 1) / N_steps if mode == 'Annealing' else T
|
|
203
212
|
for j in range(N_lef + N_lef2):
|
|
204
213
|
# Randomly choose a move (sliding or rebinding)
|
|
@@ -275,7 +284,7 @@ class StochasticSimulation:
|
|
|
275
284
|
r = np.full(self.N_bws, -self.N_beads / 10) if not r and self.N_bws > 0 else (None if not r else r)
|
|
276
285
|
|
|
277
286
|
# Run simulation
|
|
278
|
-
print('\nRunning simulation (with
|
|
287
|
+
print('\nRunning simulation (with numba acceleration)...')
|
|
279
288
|
start = time.time()
|
|
280
289
|
self.burnin = burnin
|
|
281
290
|
self.Ms, self.Ns, self.Es, self.Ks, self.Fs, self.Bs, self.ufs = run_simulation(self.N_beads, N_steps, MC_step, burnin, T, T_min, fold_norm, fold_norm2, bind_norm, k_norm, self.N_lef, self.N_lef2, self.L, self.R, mode, lef_rw, lef_drift, cross_loop, r, self.N_bws, self.BWs, self.lef_track, between_families_penalty)
|
|
@@ -13,14 +13,23 @@ from scipy.stats.stats import pearsonr, spearmanr, kendalltau
|
|
|
13
13
|
from tqdm import tqdm
|
|
14
14
|
|
|
15
15
|
def make_folder(folder_name):
|
|
16
|
+
created = False
|
|
16
17
|
if not os.path.exists(folder_name):
|
|
17
18
|
os.mkdir(folder_name)
|
|
19
|
+
created = True
|
|
18
20
|
elif os.path.isdir(folder_name):
|
|
19
|
-
print(f'
|
|
21
|
+
print(f'\033[94mDirectory with name "{folder_name}" already exists! No problem, let\'s continue!\033[0m')
|
|
20
22
|
else:
|
|
21
23
|
raise IOError(f'File with name "{folder_name}" already exists! Please change the name of the folder!')
|
|
24
|
+
|
|
22
25
|
for subfolder in ['plots', 'other', 'ensemble']:
|
|
23
|
-
os.
|
|
26
|
+
subfolder_path = os.path.join(folder_name, subfolder)
|
|
27
|
+
if not os.path.exists(subfolder_path):
|
|
28
|
+
os.makedirs(subfolder_path, exist_ok=True)
|
|
29
|
+
created = True
|
|
30
|
+
|
|
31
|
+
if created:
|
|
32
|
+
print(f'\033[92mAt least one directory was created!\033[0m')
|
|
24
33
|
return folder_name
|
|
25
34
|
|
|
26
35
|
############# Creation of mmcif and psf files #############
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyLoopSage
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.5
|
|
4
4
|
Summary: An energy-based stochastic model of loop extrusion in chromatin.
|
|
5
5
|
Home-page: https://github.com/SFGLab/pyLoopSage
|
|
6
6
|
Author: Sebastian Korsak
|
|
@@ -32,6 +32,7 @@ Requires-Dist: imageio
|
|
|
32
32
|
Requires-Dist: imageio[ffmpeg]
|
|
33
33
|
Requires-Dist: pillow
|
|
34
34
|
Requires-Dist: pyBigWig
|
|
35
|
+
Requires-Dist: topoly
|
|
35
36
|
Dynamic: author
|
|
36
37
|
Dynamic: author-email
|
|
37
38
|
Dynamic: classifier
|
|
@@ -126,6 +127,51 @@ In general the user can run simulation in two different ways:
|
|
|
126
127
|
|
|
127
128
|
Can be easily installed with `pip install pyLoopSage`. To have CUDA acceleration, it is needed to have cuda-toolkit installed in case that you use nvidia drivers (otherwise you can use OpenCL or parallelization across CPU cores).
|
|
128
129
|
|
|
130
|
+
## 🐳 Running LoopSage with Docker
|
|
131
|
+
|
|
132
|
+
To use LoopSage in a fully containerized and reproducible way, you can build and run it using Docker. This is a very efficient way when you want to use CUDA.
|
|
133
|
+
|
|
134
|
+
### Step 1: Build the Docker Image
|
|
135
|
+
|
|
136
|
+
Clone the repository and build the image:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
docker build -t pyloopsage-cuda .
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The `Dockerfile` can be found in the GitHub repo of pyLoopSage.
|
|
143
|
+
|
|
144
|
+
### Step 2: Run the Simulation
|
|
145
|
+
|
|
146
|
+
Use the following command to run your simulation:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
docker run --rm -it --gpus all \
|
|
150
|
+
-v "$PWD/config.ini:/app/config.ini:ro" \
|
|
151
|
+
-v "$PWD/tmp:/app/output" \
|
|
152
|
+
-v "$HOME/Data:/home/blackpianocat/Data:ro" \
|
|
153
|
+
pyloopsage-cuda \
|
|
154
|
+
python -m loopsage.run -c /app/config.ini
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**What this does:**
|
|
158
|
+
|
|
159
|
+
* `--rm`: Automatically removes the container after it finishes.
|
|
160
|
+
* `--gpus all`: It detects the gpus of the system.
|
|
161
|
+
* `-it`: Runs with an interactive terminal.
|
|
162
|
+
* `-v "$PWD/config.ini:/app/config.ini:ro"`: Mounts your local `config.ini` as read-only inside the container.
|
|
163
|
+
* `-v "$PWD/tmp:/app/output"`: Maps the `tmp/` directory for outputs.
|
|
164
|
+
* `-v "$HOME/Data:/home/blackpianocat/Data:ro"`: Mounts your full data directory so LoopSage can access input files.
|
|
165
|
+
* The final command runs LoopSage with your config file.
|
|
166
|
+
|
|
167
|
+
You do **not** need to manually stop or clean up anything—the container is temporary and self-destructs after it completes. The image (`pyloopsage-cuda`) remains available on your system and can be deleted anytime using:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
docker rmi pyloopsage-cuda
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Note:** Install `nvidia-container-toolkit` in your system if you want to use the container with CUDA: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html
|
|
174
|
+
|
|
129
175
|
## How to use?
|
|
130
176
|
|
|
131
177
|
### Python Implementation
|
|
@@ -6,7 +6,7 @@ with open('README.md', 'r', encoding='utf-8') as f:
|
|
|
6
6
|
|
|
7
7
|
setup(
|
|
8
8
|
name='pyLoopSage', # Package name
|
|
9
|
-
version='1.1.
|
|
9
|
+
version='1.1.5', # Version of the software
|
|
10
10
|
description='An energy-based stochastic model of loop extrusion in chromatin.',
|
|
11
11
|
long_description=long_description,
|
|
12
12
|
long_description_content_type='text/markdown',
|
|
@@ -40,6 +40,7 @@ setup(
|
|
|
40
40
|
'imageio[ffmpeg]',
|
|
41
41
|
'pillow',
|
|
42
42
|
'pyBigWig',
|
|
43
|
+
'topoly',
|
|
43
44
|
],
|
|
44
45
|
entry_points={
|
|
45
46
|
'console_scripts': [
|
pyloopsage-1.1.3/MANIFEST.in
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
exclude loopsage/knots.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|