pyLoopSage 1.0.22__tar.gz → 1.0.23__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 (25) hide show
  1. pyloopsage-1.0.23/MANIFEST.in +1 -0
  2. {pyloopsage-1.0.22/pyLoopSage.egg-info → pyloopsage-1.0.23}/PKG-INFO +2 -1
  3. {pyloopsage-1.0.22 → pyloopsage-1.0.23}/README.md +1 -0
  4. {pyloopsage-1.0.22 → pyloopsage-1.0.23}/loopsage/__init__.py +1 -1
  5. {pyloopsage-1.0.22 → pyloopsage-1.0.23}/loopsage/args_definition.py +1 -0
  6. {pyloopsage-1.0.22 → pyloopsage-1.0.23}/loopsage/md.py +24 -16
  7. {pyloopsage-1.0.22 → pyloopsage-1.0.23}/loopsage/run.py +1 -1
  8. {pyloopsage-1.0.22 → pyloopsage-1.0.23}/loopsage/stochastic_simulation.py +17 -17
  9. {pyloopsage-1.0.22 → pyloopsage-1.0.23/pyLoopSage.egg-info}/PKG-INFO +2 -1
  10. {pyloopsage-1.0.22 → pyloopsage-1.0.23}/pyLoopSage.egg-info/SOURCES.txt +1 -1
  11. {pyloopsage-1.0.22 → pyloopsage-1.0.23}/setup.py +1 -1
  12. pyloopsage-1.0.22/loopsage/knots.py +0 -193
  13. {pyloopsage-1.0.22 → pyloopsage-1.0.23}/LICENSE +0 -0
  14. {pyloopsage-1.0.22 → pyloopsage-1.0.23}/loopsage/em.py +0 -0
  15. {pyloopsage-1.0.22 → pyloopsage-1.0.23}/loopsage/forcefields/classic_sm_ff.xml +0 -0
  16. {pyloopsage-1.0.22 → pyloopsage-1.0.23}/loopsage/initial_structures.py +0 -0
  17. {pyloopsage-1.0.22 → pyloopsage-1.0.23}/loopsage/plots.py +0 -0
  18. {pyloopsage-1.0.22 → pyloopsage-1.0.23}/loopsage/preproc.py +0 -0
  19. {pyloopsage-1.0.22 → pyloopsage-1.0.23}/loopsage/utils.py +0 -0
  20. {pyloopsage-1.0.22 → pyloopsage-1.0.23}/loopsage/vizualization_tools.py +0 -0
  21. {pyloopsage-1.0.22 → pyloopsage-1.0.23}/pyLoopSage.egg-info/dependency_links.txt +0 -0
  22. {pyloopsage-1.0.22 → pyloopsage-1.0.23}/pyLoopSage.egg-info/entry_points.txt +0 -0
  23. {pyloopsage-1.0.22 → pyloopsage-1.0.23}/pyLoopSage.egg-info/requires.txt +0 -0
  24. {pyloopsage-1.0.22 → pyloopsage-1.0.23}/pyLoopSage.egg-info/top_level.txt +0 -0
  25. {pyloopsage-1.0.22 → pyloopsage-1.0.23}/setup.cfg +0 -0
@@ -0,0 +1 @@
1
+ exclude loopsage/knots.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyLoopSage
3
- Version: 1.0.22
3
+ Version: 1.0.23
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
@@ -305,6 +305,7 @@ An example, illustrated with Chimera software, simulated trajectory of structure
305
305
  | ANGLE_FF_STRENGTH | Angle force strength. | float | 200.0 |
306
306
  | LE_FF_LENGTH | Equilibrium distance of loop forces. | float | 0.1 |
307
307
  | LE_FF_STRENGTH | Interaction Strength of loop forces. | float | 50000.0 |
308
+ | CONTINUOUS_TOP | True if the topological constraints are applied continuously during the simulation. | bool | False |
308
309
  | EV_P | Probability that excluded volume is disabled. Enable it only in case of topoisomerase activity simulation. | float | 0.0 |
309
310
  | EV_FF_STRENGTH | Excluded-volume strength. | float | 100.0 |
310
311
  | EV_FF_POWER | Excluded-volume power. | float | 3.0 |
@@ -260,6 +260,7 @@ An example, illustrated with Chimera software, simulated trajectory of structure
260
260
  | ANGLE_FF_STRENGTH | Angle force strength. | float | 200.0 |
261
261
  | LE_FF_LENGTH | Equilibrium distance of loop forces. | float | 0.1 |
262
262
  | LE_FF_STRENGTH | Interaction Strength of loop forces. | float | 50000.0 |
263
+ | CONTINUOUS_TOP | True if the topological constraints are applied continuously during the simulation. | bool | False |
263
264
  | EV_P | Probability that excluded volume is disabled. Enable it only in case of topoisomerase activity simulation. | float | 0.0 |
264
265
  | EV_FF_STRENGTH | Excluded-volume strength. | float | 100.0 |
265
266
  | EV_FF_POWER | Excluded-volume power. | float | 3.0 |
@@ -1,4 +1,4 @@
1
1
  # Importing specific functions or classes from submodules
2
2
  from .run import main
3
3
 
4
- __version__ = "1.0.22"
4
+ __version__ = "1.0.23"
@@ -170,6 +170,7 @@ args = ListOfArgs([
170
170
  Arg('ANGLE_FF_STRENGTH', help="Angle force strength.", type=float, default='200.0', val='200.0'),
171
171
  Arg('LE_FF_LENGTH', help="Equillibrium distance of loop forces.", type=float, default='0.1', val='0.1'),
172
172
  Arg('LE_FF_STRENGTH', help="Interaction Strength of loop forces.", type=float, default='50000.0', val='50000.0'),
173
+ Arg('CONTINUOUS_TOP', help="True if topoisomerase disables EV in a continuous region rather than a discrete set of points.", type=bool, default='False', val='False'),
173
174
  Arg('EV_P', help="Probability that randomly excluded volume may be disabled.", type=float, default='0.0', val='0.0'),
174
175
  Arg('EV_FF_STRENGTH', help="Excluded-volume strength.", type=float, default='100.0', val='100.0'),
175
176
  Arg('EV_FF_POWER', help="Excluded-volume power.", type=float, default='3.0', val='3.0'),
@@ -36,18 +36,19 @@ class MD_LE:
36
36
  self.ev_ff_power = ev_ff_power
37
37
  self.tolerance = tolerance
38
38
 
39
- def run_pipeline(self,run_MD=True, friction=0.1, integrator_step=10 * mm.unit.femtosecond, sim_step=100, ff_path = 'forcefields/classic_sm_ff.xml', init_struct='rw', temperature=310, p_ev=0, plots=False):
39
+ def run_pipeline(self, run_MD=True, friction=0.1, integrator_step=10 * mm.unit.femtosecond, sim_step=100, ff_path='forcefields/classic_sm_ff.xml', init_struct='rw', temperature=310, p_ev=0, plots=False, continuous_topoisomerase=False):
40
40
  '''
41
41
  This is the basic function that runs the molecular simulation pipeline.
42
42
  '''
43
43
  # Parameters
44
44
  self.p_ev = p_ev
45
+ self.continuous_topoisomerase = continuous_topoisomerase
45
46
 
46
47
  # Define initial structure
47
48
  print('Building initial structure...')
48
- points = compute_init_struct(self.N_beads,mode='rw')
49
- write_mmcif(points,self.path+'/LE_init_struct.cif')
50
- generate_psf(self.N_beads,self.path+'/other/LE_init_struct.psf')
49
+ points = compute_init_struct(self.N_beads, mode='rw')
50
+ write_mmcif(points, self.path+'/LE_init_struct.cif')
51
+ generate_psf(self.N_beads, self.path+'/other/LE_init_struct.psf')
51
52
  print('Done brother ;D\n')
52
53
 
53
54
  # Define System
@@ -79,32 +80,39 @@ class MD_LE:
79
80
  start = time.time()
80
81
  heats = list()
81
82
  for i in range(self.N_steps):
82
- # Define probabilities that EV would be disabled
83
- if p_ev>0: self.ps_ev = np.random.rand(self.N_beads)
83
+ # Define probabilities or regions that EV would be disabled
84
+ if self.continuous_topoisomerase:
85
+ region_length = max(1, int(self.p_ev * self.N_beads)) # Determine region length from p_ev
86
+ start_idx = np.random.randint(0, self.N_beads - region_length -1)
87
+ end_idx = start_idx + region_length
88
+ self.ps_ev = np.zeros(self.N_beads)
89
+ self.ps_ev[start_idx:end_idx] = 1
90
+ elif p_ev > 0:
91
+ self.ps_ev = np.random.rand(self.N_beads)
84
92
  self.change_loop(i)
85
93
  self.change_ev()
86
94
  self.simulation.step(sim_step)
87
95
  self.state = self.simulation.context.getState(getPositions=True)
88
96
  PDBxFile.writeFile(pdb.topology, self.state.getPositions(), open(self.path+f'/ensemble/MDLE_{i+1}.cif', 'w'))
89
- heats.append(get_heatmap(self.state.getPositions(),save=False))
97
+ heats.append(get_heatmap(self.state.getPositions(), save=False))
90
98
  end = time.time()
91
99
  elapsed = end - start
92
- print(f'Everything is done! Simulation finished succesfully!\nMD finished in {elapsed/60:.2f} minutes.\n')
100
+ print(f'Everything is done! Simulation finished successfully!\nMD finished in {elapsed/60:.2f} minutes.\n')
93
101
 
94
- self.avg_heat = np.average(heats,axis=0)
95
- self.std_heat = np.std(heats,axis=0)
102
+ self.avg_heat = np.average(heats, axis=0)
103
+ self.std_heat = np.std(heats, axis=0)
96
104
 
97
105
  if plots:
98
- np.save(self.path+f'/other/avg_heatmap.npy',self.avg_heat)
99
- np.save(self.path+f'/other/std_heatmap.npy',self.std_heat)
100
- self.plot_heat(self.avg_heat,f'/plots/avg_heatmap.svg')
101
- self.plot_heat(self.std_heat,f'/plots/std_heatmap.svg')
106
+ np.save(self.path+f'/other/avg_heatmap.npy', self.avg_heat)
107
+ np.save(self.path+f'/other/std_heatmap.npy', self.std_heat)
108
+ self.plot_heat(self.avg_heat, f'/plots/avg_heatmap.svg')
109
+ self.plot_heat(self.std_heat, f'/plots/std_heatmap.svg')
102
110
  return self.avg_heat
103
111
 
104
112
  def change_ev(self):
105
- ev_strength = (self.ps_ev>self.p_ev).astype(int)*np.sqrt(self.ev_ff_strength) if self.p_ev>0 else np.sqrt(self.ev_ff_strength)*np.ones(self.N_beads)
113
+ ev_strength = (self.ps_ev > self.p_ev).astype(int) * np.sqrt(self.ev_ff_strength) if self.p_ev > 0 else np.sqrt(self.ev_ff_strength) * np.ones(self.N_beads)
106
114
  for n in range(self.N_beads):
107
- self.ev_force.setParticleParameters(n,[ev_strength[n],0.05])
115
+ self.ev_force.setParticleParameters(n, [ev_strength[n], 0.05])
108
116
  self.ev_force.updateParametersInContext(self.simulation.context)
109
117
 
110
118
  def change_loop(self,i):
@@ -86,7 +86,7 @@ def main():
86
86
  if args.SIMULATION_TYPE=='EM':
87
87
  sim.run_EM(args.PLATFORM,args.ANGLE_FF_STRENGTH,args.LE_FF_LENGTH,args.LE_FF_STRENGTH,args.EV_FF_STRENGTH,args.EV_FF_POWER,args.TOLERANCE,args.FRICTION,args.INTEGRATOR_STEP,args.SIM_TEMP,args.INITIAL_STRUCTURE_TYPE,args.VIZ_HEATS,args.FORCEFIELD_PATH)
88
88
  elif args.SIMULATION_TYPE=='MD':
89
- sim.run_MD(args.PLATFORM,args.ANGLE_FF_STRENGTH,args.LE_FF_LENGTH,args.LE_FF_STRENGTH,args.EV_FF_STRENGTH,args.EV_FF_POWER,args.TOLERANCE,args.FRICTION,args.INTEGRATOR_STEP,args.SIM_TEMP,args.INITIAL_STRUCTURE_TYPE,args.SIM_STEP,args.VIZ_HEATS,args.FORCEFIELD_PATH,args.EV_P)
89
+ sim.run_MD(args.PLATFORM,args.ANGLE_FF_STRENGTH,args.LE_FF_LENGTH,args.LE_FF_STRENGTH,args.EV_FF_STRENGTH,args.EV_FF_POWER,args.TOLERANCE,args.FRICTION,args.INTEGRATOR_STEP,args.SIM_TEMP,args.INITIAL_STRUCTURE_TYPE,args.SIM_STEP,args.VIZ_HEATS,args.FORCEFIELD_PATH,args.EV_P,args.CONTINUOUS_TOPOISOMERASE)
90
90
  elif args.SIMULATION_TYPE==None:
91
91
  print('\n3D simulation did not run because it was not specified. Please specify argument SIMULATION_TYPE as EM or MD.')
92
92
  else:
@@ -251,21 +251,21 @@ class StochasticSimulation:
251
251
  end = time.time()
252
252
  elapsed = end - start
253
253
  print(f'Computation finished succesfully in {elapsed//3600:.0f} hours, {elapsed%3600//60:.0f} minutes and {elapsed%60:.0f} seconds.')
254
-
254
+
255
255
  # Save simulation info
256
256
  if save:
257
257
  save_dir = os.path.join(self.path, 'other') + '/'
258
- with open(save_dir + 'info.txt', "w") as f:
259
- f.write(f'Number of beads {self.N_beads}.\n')
260
- f.write(f'Number of cohesins {self.N_lef}. Number of cohesins in second family {self.N_lef2}. Number of CTCFs {self.N_CTCF}. \n')
261
- f.write(f'Bedpe file for CTCF binding is {self.bedpe_file}.\n')
262
- f.write(f'Initial temperature {T}. Minimum temperature {T_min}.\n')
263
- f.write(f'Monte Carlo optimization method: {mode}.\n')
264
- f.write(f'Monte Carlo steps {N_steps}. Sampling frequency {self.MC_step}. Burnin period {burnin}.\n')
265
- f.write(f'Crossing energy in equilibrium is {np.average(self.Ks[burnin//MC_step:]):.2f}. Crossing coefficient kappa={kappa}.\n')
266
- f.write(f'Folding energy in equilibrium is {np.average(self.Fs[burnin//MC_step:]):.2f}. Folding coefficient f={f}. Folding coefficient for the second family f2={f2}\n')
267
- f.write(f'Binding energy in equilibrium is {np.average(self.Bs[burnin//MC_step:]):.2f}. Binding coefficient b={b}.\n')
268
- f.write(f'Energy at equillibrium: {np.average(self.Es[self.burnin//MC_step:]):.2f}.\n')
258
+ with open(save_dir + 'info.txt', "w") as file:
259
+ file.write(f'Number of beads {self.N_beads}.\n')
260
+ file.write(f'Number of cohesins {self.N_lef}. Number of cohesins in second family {self.N_lef2}. Number of CTCFs {self.N_CTCF}. \n')
261
+ file.write(f'Bedpe file for CTCF binding is {self.bedpe_file}.\n')
262
+ file.write(f'Initial temperature {T}. Minimum temperature {T_min}.\n')
263
+ file.write(f'Monte Carlo optimization method: {mode}.\n')
264
+ file.write(f'Monte Carlo steps {N_steps}. Sampling frequency {self.MC_step}. Burnin period {burnin}.\n')
265
+ file.write(f'Crossing energy in equilibrium is {np.average(self.Ks[burnin//MC_step:]):.2f}. Crossing coefficient kappa={kappa}.\n')
266
+ file.write(f'Folding energy in equilibrium is {np.average(self.Fs[burnin//MC_step:]):.2f}. Folding coefficient f={f}. Folding coefficient for the second family f2={f2}\n')
267
+ file.write(f'Binding energy in equilibrium is {np.average(self.Bs[burnin//MC_step:]):.2f}. Binding coefficient b={b}.\n')
268
+ file.write(f'Energy at equillibrium: {np.average(self.Es[self.burnin//MC_step:]):.2f}.\n')
269
269
  np.save(save_dir + 'Ms.npy', self.Ms)
270
270
  np.save(save_dir + 'Ns.npy', self.Ns)
271
271
  np.save(save_dir + 'ufs.npy', self.ufs)
@@ -292,9 +292,9 @@ class StochasticSimulation:
292
292
  sim_heat = em.run_pipeline(plots=save_plots,friction=friction,integrator_step=integrator_step,temperature=temperature,ff_path=ff_path,init_struct=init_struct)
293
293
  corr_exp_heat(sim_heat,self.bedpe_file,self.region,self.chrom,self.N_beads,self.path)
294
294
 
295
- def run_MD(self,platform='CPU',angle_ff_strength=200,le_distance=0.1,le_ff_strength=50000.0,ev_ff_strength=100.0,ev_ff_power=3.0,tolerance=0.001,friction=0.1,integrator_step=100*mm.unit.femtosecond,temperature=310,init_struct='rw',sim_step=1000,save_plots=True,ff_path=default_xml_path,p_ev=0):
295
+ def run_MD(self,platform='CPU',angle_ff_strength=200,le_distance=0.1,le_ff_strength=50000.0,ev_ff_strength=100.0,ev_ff_power=3.0,tolerance=0.001,friction=0.1,integrator_step=100*mm.unit.femtosecond,temperature=310,init_struct='rw',sim_step=1000,save_plots=True,ff_path=default_xml_path,p_ev=0,continuous_topoisomerase=False):
296
296
  md = MD_LE(self.Ms,self.Ns,self.N_beads,self.path,platform,angle_ff_strength,le_distance,le_ff_strength,ev_ff_strength,ev_ff_power,tolerance)
297
- sim_heat = md.run_pipeline(plots=save_plots,sim_step=sim_step,friction=friction,integrator_step=integrator_step,temperature=temperature,ff_path=ff_path,p_ev=p_ev,init_struct=init_struct)
297
+ sim_heat = md.run_pipeline(plots=save_plots,sim_step=sim_step,friction=friction,integrator_step=integrator_step,temperature=temperature,ff_path=ff_path,p_ev=p_ev,init_struct=init_struct,continuous_topoisomerase=continuous_topoisomerase)
298
298
  corr_exp_heat(sim_heat,self.bedpe_file,self.region,self.chrom,self.N_beads,self.path)
299
299
 
300
300
  def main():
@@ -311,12 +311,12 @@ def main():
311
311
  region, chrom = [15550000,16850000], 'chr6'
312
312
 
313
313
  # Definition of data
314
- output_name='../HiChIP_Annealing_T1_MD_region'
314
+ output_name='tmp'
315
315
  bedpe_file = '/home/skorsak/Data/HiChIP/Maps/hg00731_smc1_maps_2.bedpe'
316
316
 
317
317
  sim = StochasticSimulation(region,chrom,bedpe_file,out_dir=output_name,N_beads=1000,N_lef=N_lef,N_lef2=N_lef2)
318
318
  Es, Ms, Ns, Bs, Ks, Fs, ufs = sim.run_energy_minimization(N_steps,MC_step,burnin,T,T_min,mode=mode,viz=True,save=True,f=f,f2=f2, b=b, kappa=kappa, lef_rw=lew_rw)
319
- sim.run_EM('CUDA')
319
+ sim.run_MD('CUDA',continuous_topoisomerase=True, p_ev=0.1)
320
320
 
321
321
  if __name__=='__main__':
322
- main()
322
+ main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyLoopSage
3
- Version: 1.0.22
3
+ Version: 1.0.23
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
@@ -305,6 +305,7 @@ An example, illustrated with Chimera software, simulated trajectory of structure
305
305
  | ANGLE_FF_STRENGTH | Angle force strength. | float | 200.0 |
306
306
  | LE_FF_LENGTH | Equilibrium distance of loop forces. | float | 0.1 |
307
307
  | LE_FF_STRENGTH | Interaction Strength of loop forces. | float | 50000.0 |
308
+ | CONTINUOUS_TOP | True if the topological constraints are applied continuously during the simulation. | bool | False |
308
309
  | EV_P | Probability that excluded volume is disabled. Enable it only in case of topoisomerase activity simulation. | float | 0.0 |
309
310
  | EV_FF_STRENGTH | Excluded-volume strength. | float | 100.0 |
310
311
  | EV_FF_POWER | Excluded-volume power. | float | 3.0 |
@@ -1,11 +1,11 @@
1
1
  LICENSE
2
+ MANIFEST.in
2
3
  README.md
3
4
  setup.py
4
5
  loopsage/__init__.py
5
6
  loopsage/args_definition.py
6
7
  loopsage/em.py
7
8
  loopsage/initial_structures.py
8
- loopsage/knots.py
9
9
  loopsage/md.py
10
10
  loopsage/plots.py
11
11
  loopsage/preproc.py
@@ -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.0.22', # Version of the software
9
+ version='1.0.23', # 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',
@@ -1,193 +0,0 @@
1
- import topoly as tp
2
- import numpy as np
3
- import matplotlib
4
- from tqdm import tqdm
5
- import matplotlib
6
- import matplotlib.pyplot as plt
7
- from mpl_toolkits.mplot3d import Axes3D
8
- from mpl_toolkits.mplot3d.art3d import Line3DCollection
9
- import matplotlib.colors as mcolors
10
- import scipy.interpolate as si
11
- from scipy.spatial.distance import pdist, squareform
12
- from scipy.integrate import dblquad
13
- from scipy.interpolate import splprep, splev, CubicSpline
14
- from .uts import *
15
-
16
- def trivial_knot(N=100, radius=1.0):
17
- """
18
- Generate a 3D trivial (unknot) structure as a simple circle.
19
-
20
- Parameters:
21
- N (int): Number of points in the loop.
22
- radius (float): Radius of the circular unknot.
23
-
24
- Returns:
25
- numpy.ndarray: Nx3 array representing the 3D coordinates of the unknot.
26
- """
27
- t = np.linspace(0, 2 * np.pi, N) # Parameter along the circle
28
- x = radius * np.cos(t) # Circle in the xy-plane
29
- y = radius * np.sin(t)
30
- z = np.zeros_like(t) # No variation in z (flat circle)
31
-
32
- return np.vstack((x, y, z)).T # Return as Nx3 matrix
33
-
34
- def toroidal_knot(p, q, num_points=500, R=2, r=1):
35
- """
36
- Generate a toroidal (p, q) knot.
37
-
38
- Parameters:
39
- p (int): Number of turns around the torus' central axis.
40
- q (int): Number of turns around the torus' inner axis.
41
- num_points (int): Number of points to plot.
42
- R (float): Major radius of the torus (distance from center to torus tube).
43
- r (float): Minor radius of the torus tube.
44
-
45
- Returns:
46
- np.ndarray: Array of shape (num_points, 3) representing 3D coordinates of the knot.
47
- """
48
- t = np.linspace(0, 2 * np.pi, num_points, endpoint=False)
49
-
50
- # Parametric equations for the toroidal knot
51
- x = (R + r * np.cos(q * t)) * np.cos(p * t)
52
- y = (R + r * np.cos(q * t)) * np.sin(p * t)
53
- z = r * np.sin(q * t)
54
- V = np.column_stack((x, y, z))
55
- V = np.vstack((V,[x[0],y[0],z[0]]))
56
- return V
57
-
58
- def trefoil_knot(N=200, scale=0.8, offset=(0, 0, 0), rotation=None):
59
- """Generate a 3D Trefoil Knot with transformations."""
60
- t = np.linspace(0, 2 * np.pi-np.pi/36, N)
61
- x = scale * (np.sin(t) + 2 * np.sin(2 * t)) + offset[0]
62
- y = scale * (np.cos(t) - 2 * np.cos(2 * t)) + offset[1]
63
- z = scale * (-np.sin(3 * t)) + offset[2]
64
-
65
- V = np.vstack((x, y, z)).T
66
-
67
- if rotation is not None:
68
- V = np.dot(V, rotation) # Apply rotation matrix
69
-
70
- return V
71
-
72
- def cinquefoil_knot(N=300, scale=0.8, offset=(0, 0, 0), rotation=None):
73
- """Generate a 3D Cinquefoil (5_1) Knot with transformations."""
74
- t = np.linspace(0, 2 * np.pi, N)
75
- x = scale * (np.sin(2*t) + 2*np.sin(3*t))
76
- y = scale * (np.cos(2*t) - 2*np.cos(3*t))
77
- z = scale * (-np.sin(5*t))
78
-
79
- V = np.vstack((x, y, z)).T
80
- if rotation is not None:
81
- V = np.dot(V, rotation) # Apply rotation
82
- return V + np.array(offset) # Apply position offset
83
-
84
- def random_rotation_matrix():
85
- """Generate a random 3D rotation matrix."""
86
- theta, phi, z = np.random.uniform(0, 2*np.pi-np.pi/36, 3)
87
- r = np.sqrt(z)
88
- V = np.array([
89
- [np.cos(theta) * np.cos(phi), -np.sin(theta), np.cos(theta) * np.sin(phi)],
90
- [np.sin(theta) * np.cos(phi), np.cos(theta), np.sin(theta) * np.sin(phi)],
91
- [-np.sin(phi), 0, np.cos(phi)]
92
- ])
93
- return V
94
-
95
- def smooth_linkage(start, end, N=50):
96
- """Generate a smooth linking curve between two knots."""
97
- t = np.linspace(0, 1, N)
98
- x = (1 - t) * start[0] + t * end[0]
99
- y = (1 - t) * start[1] + t * end[1]
100
- z = (1 - t) * start[2] + t * end[2]
101
- return np.vstack((x, y, z)).T
102
-
103
- def generate_knotted_structure(num_knots=5, N_per_knot=300):
104
- """Generate a large 3D knotted structure by linking multiple knots."""
105
- knots = []
106
- previous_end = None
107
-
108
- for i in range(num_knots):
109
- knot_type = np.random.choice(["trefoil", "figure_eight"])
110
- offset = previous_end + np.random.uniform(-3.0, 3.0, size=3) if previous_end is not None else np.random.uniform(-5, 5, size=3)
111
- scale = np.random.uniform(0.5, 1.0) # Vary knot sizes
112
- rotation = random_rotation_matrix()
113
-
114
- if knot_type == "trefoil":
115
- knot = trefoil_knot(N=N_per_knot, scale=scale, offset=offset, rotation=rotation)
116
- else:
117
- knot = cinquefoil_knot(N=N_per_knot, scale=scale, offset=offset, rotation=rotation)
118
-
119
- if previous_end is not None:
120
- linkage = smooth_linkage(previous_end, knot[0], N=50) # Smooth connection
121
- knots.append(linkage)
122
-
123
- knots.append(knot)
124
- previous_end = knot[-1]
125
-
126
- return np.vstack(knots)
127
-
128
- def smooth_knot_spline(V, num_interp=100, closed=True):
129
- """
130
- Smoothly interpolates a 3D knot using a cubic spline.
131
-
132
- Parameters:
133
- V (numpy.ndarray): Nx3 array representing the 3D knot.
134
- num_interp (int): Number of points in the interpolated knot.
135
- closed (bool): Whether to enforce periodic boundary conditions for closed knots.
136
-
137
- Returns:
138
- numpy.ndarray: Smoothed Nx3 array of the interpolated knot.
139
- """
140
- if V.shape[1] != 3:
141
- raise ValueError("Input array V must have shape (N, 3)")
142
-
143
- if len(V) < 4:
144
- raise ValueError("At least 4 points are required for spline fitting.")
145
-
146
- # Define the parameter t along the curve
147
- t = np.linspace(0, 1, len(V))
148
-
149
- # Interpolation points
150
- t_new = np.linspace(0, 1, num_interp)
151
-
152
- # Fit cubic splines separately for each coordinate
153
- cs_x = CubicSpline(t, V[:, 0], bc_type='periodic' if closed else 'not-a-knot')
154
- cs_y = CubicSpline(t, V[:, 1], bc_type='periodic' if closed else 'not-a-knot')
155
- cs_z = CubicSpline(t, V[:, 2], bc_type='periodic' if closed else 'not-a-knot')
156
-
157
- # Evaluate the splines
158
- x_new = cs_x(t_new)
159
- y_new = cs_y(t_new)
160
- z_new = cs_z(t_new)
161
-
162
- return np.vstack((x_new, y_new, z_new)).T
163
-
164
- def link_number_ensemble(path,N_ens=1250,step=100):
165
- nlinks = list()
166
- Ms = np.load(path+'/other/Ms.npy')
167
- Ns = np.load(path+'/other/Ns.npy')
168
- for i in range(N_ens//step,N_ens,step):
169
- V = uts.get_coordinates_cif(path+f'/ensemble/MDLE_{i+1}.cif')
170
- ms, ns = Ms[:,i], Ns[:,i]
171
- N_links, links = calculate_linking_number(V,ms,ns)
172
- nlinks.append(N_links)
173
- return nlinks
174
-
175
- def calculate_linking_number(V,ms,ns):
176
- links = list()
177
- for i in tqdm(range(len(ms))):
178
- for j in range(i+1,len(ms)):
179
- 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]))):
180
- loop1 = V[ms[i]:ns[i]]
181
- loop1 = np.vstack((loop1,loop1[0,:]))
182
- loop1 = smooth_knot_spline(loop1,2*len(loop1))
183
- l1 = [list(loop1[i]) for i in range(len(loop1))]
184
- loop2 = V[ms[j]:ns[j]]
185
- loop2 = np.vstack((loop2,loop2[0,:]))
186
- loop2 = smooth_knot_spline(loop2,2*len(loop2))
187
- l2 = [list(loop2[i]) for i in range(len(loop2))]
188
- links.append(tp.gln(l1,l2))
189
- links = np.array(links)
190
- links = links[links>0.5]
191
- N_links = len(links)
192
-
193
- return N_links, links
File without changes
File without changes
File without changes