├── .gitignore
├── HPC_scripts
├── create_ipcluster_environment.sh
├── script_engines.sh
├── steps_to_submit_IGM.txt
└── submit_igm.sh
├── IGM_documentation.pdf
├── LICENSE
├── README.md
├── bin
├── igm-info-dir
├── igm-register-dir
├── igm-report
├── igm-run
├── igm-server
├── igm-stop
└── igm-unregister-dir
├── demo
├── .workers.restart
├── WTC11_HiC_2Mb.hcs
├── config_file.json
├── demo_sample_outputs
│ ├── igm-model.hss.T
│ ├── igm-model.hss.randomInit
│ └── stepdb.sqlite
└── deprecated
│ ├── ipworkernode.pbs
│ ├── startController.sh
│ └── startWorker.sh
├── html_history.py
├── igm-config_all.json
├── igm-run_scheme.pdf
├── igm
├── __init__.py
├── _preprocess.py
├── core
│ ├── __init__.py
│ ├── config.py
│ ├── defaults
│ │ ├── __init__.py
│ │ ├── config_default.json
│ │ └── config_schema.json
│ ├── job_tracking.py
│ └── step.py
├── cython_compiled
│ ├── __init.py__
│ ├── cpp_sprite_assignment.cpp
│ ├── cpp_sprite_assignment.h
│ ├── setup.py
│ ├── sprite.pyx
│ └── tests.py
├── model
│ ├── __init__.py
│ ├── forces.py
│ ├── kernel
│ │ ├── __init__.py
│ │ ├── lammps.py
│ │ ├── lammps_io.py
│ │ ├── lammps_model.py
│ │ └── util.py
│ ├── model.py
│ └── particle.py
├── parallel
│ ├── __init__.py
│ ├── async_file_operations.py
│ ├── dask_controller.py
│ ├── ipyparallel_controller.py
│ ├── parallel_controller.py
│ ├── slurm_controller.py
│ └── utils.py
├── report
│ ├── __init__.py
│ ├── damid.py
│ ├── distance_map.py
│ ├── hic.py
│ ├── html_report.py
│ ├── images.py
│ ├── plots.py
│ ├── radials.py
│ ├── radius_of_gyration.py
│ ├── shells.py
│ ├── utils.py
│ └── violations.py
├── restraints
│ ├── __init__.py
│ ├── damid.py
│ ├── envelope.py
│ ├── fish.py
│ ├── genenvelope.py
│ ├── hic.py
│ ├── inter_hic.py
│ ├── intra_hic.py
│ ├── nucleolus.py
│ ├── polymer.py
│ ├── restraint.py
│ ├── sprite.py
│ └── steric.py
├── steps
│ ├── ActivationDistanceStep.py
│ ├── DamidActivationDistanceStep.py
│ ├── FishAssignmentStep.py
│ ├── HicEvaluationStep.py
│ ├── ModelingStep.py
│ ├── RandomInit.py
│ ├── RelaxInit.py
│ ├── SpriteAssignmentStep.py
│ └── __init__.py
├── tasks
│ ├── __init__.py
│ └── modeling_step.py
├── ui
│ ├── __init__.py
│ ├── communication.py
│ ├── config_parse.py
│ ├── folders_database.py
│ ├── navigation.py
│ ├── static
│ │ ├── css
│ │ │ ├── filebrowser.css
│ │ │ ├── igm-edit-config.css
│ │ │ └── main.css
│ │ ├── favicon.ico
│ │ └── js
│ │ │ ├── filebrowser.js
│ │ │ ├── genome_visualizer
│ │ │ ├── filebrowser.js
│ │ │ ├── genomeapp.js
│ │ │ ├── interface.js
│ │ │ ├── main.js
│ │ │ ├── util.js
│ │ │ ├── viewer.js
│ │ │ └── visualizer.js
│ │ │ ├── igm-edit-config.js
│ │ │ └── main.js
│ ├── static_pages.py
│ ├── templates
│ │ ├── base.html
│ │ ├── cfg_form.html
│ │ ├── history.html
│ │ └── main.html
│ ├── utils.py
│ └── views.py
└── utils
│ ├── __init__.py
│ ├── actdist.py
│ ├── emails.py
│ ├── files.py
│ ├── kernel_testing.py
│ ├── log.py
│ └── sprite
│ └── sprite.py
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | #user added
2 | #
3 | *.hss
4 | *.pdf
5 | ipython-log*
6 |
7 | # Byte-compiled / optimized / DLL files
8 | __pycache__/
9 | *.py[cod]
10 | *$py.class
11 |
12 |
13 | # C extensions
14 | *.so
15 |
16 | # Distribution / packaging
17 | .Python
18 | env/
19 | build/
20 | develop-eggs/
21 | dist/
22 | downloads/
23 | eggs/
24 | .eggs/
25 | lib/
26 | lib64/
27 | parts/
28 | sdist/
29 | var/
30 | wheels/
31 | *.egg-info/
32 | .installed.cfg
33 | *.egg
34 | tmp/
35 | *.db
36 |
37 | # PyInstaller
38 | # Usually these files are written by a python script from a template
39 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
40 | *.manifest
41 | *.spec
42 |
43 | # Installer logs
44 | pip-log.txt
45 | pip-delete-this-directory.txt
46 |
47 | # Unit test / coverage reports
48 | htmlcov/
49 | .tox/
50 | .coverage
51 | .coverage.*
52 | .cache
53 | nosetests.xml
54 | coverage.xml
55 | *.cover
56 | .hypothesis/
57 |
58 | # Translations
59 | *.mo
60 | *.pot
61 |
62 | # Django stuff:
63 | *.log
64 | local_settings.py
65 |
66 | # Flask stuff:
67 | instance/
68 | .webassets-cache
69 |
70 | # Scrapy stuff:
71 | .scrapy
72 |
73 | # Sphinx documentation
74 | docs/_build/
75 |
76 | # PyBuilder
77 | target/
78 |
79 | # Jupyter Notebook
80 | .ipynb_checkpoints
81 |
82 | # pyenv
83 | .python-version
84 |
85 | # celery beat schedule file
86 | celerybeat-schedule
87 |
88 | # SageMath parsed files
89 | *.sage.py
90 |
91 | # dotenv
92 | .env
93 |
94 | # virtualenv
95 | .venv
96 | venv/
97 | ENV/
98 |
99 | # Spyder project settings
100 | .spyderproject
101 | .spyproject
102 |
103 | # Rope project settings
104 | .ropeproject
105 |
106 | # mkdocs documentation
107 | /site
108 |
109 | # mypy
110 | .mypy_cache/
111 |
112 | # mac temporary
113 | ._*
114 |
115 | # cython auto generated
116 | igm/cython_compiled/sprite.cpp
117 |
118 | # pycharm
119 |
120 | # pycharm
121 | .idea/
122 |
123 |
--------------------------------------------------------------------------------
/HPC_scripts/create_ipcluster_environment.sh:
--------------------------------------------------------------------------------
1 | # This script prepares the controller/engines environment, using the MONITOR option
2 | # which allows to keep track of how the tasks are split among the different engines
3 | # On the top of this, the serial IGM job will be submitted
4 |
5 | # number of workers
6 | NTASKS=100
7 | # memory per worker
8 | MEM=$2
9 | # walltime
10 | WTIME=$3
11 | # conda env to use
12 | CONDA=$4
13 | # scheduler memory, usual a little larger than workers
14 | SMEM=$5
15 |
16 | let NTOT=${NTASKS}+1
17 | if [[ -z "$2" ]]; then
18 | MEM=2G
19 | fi
20 | if [[ -z "$3" ]]; then
21 | WTIME=200:59:59
22 | fi
23 |
24 | if [[ -z "$4" ]]; then
25 | CONDA=py3
26 | fi
27 |
28 | if [[ -z "$5" ]]; then
29 | SMEM=10G
30 | fi
31 |
32 | echo "requesting ${NTASKS} processes, ${MEM} per cpu, walltime: ${WTIME}"
33 |
34 | CURRDIR=`pwd`
35 |
36 | TMPFILE=`mktemp` || exit 1
37 | # write the slurm script
38 | cat > $TMPFILE <<- EOF
39 | #!/bin/bash
40 | #$ -M bonimba@g.ucla.edu
41 | #$ -m ea
42 | #$ -N ipycontroller
43 | #$ -l h_data=${SMEM}
44 | #$ -l h_rt=${WTIME}
45 | #$ -l highp
46 | #$ -cwd
47 | #$ -o out_controller
48 | #$ -e err_controller
49 | #$ -V
50 |
51 | export PATH="$PATH"
52 | ulimit -s 8192
53 |
54 |
55 | cd $SGE_O_WORKDIR
56 |
57 | myip=\$(getent hosts \$(hostname) | awk '{print \$1}')
58 | MONITOR=$(command -v monitor_process)
59 | if [[ ! -z "$MONITOR" ]]; then
60 | monitor_process --wtype S ipcontroller --nodb --ip=\$myip
61 | else
62 | ipcontroller --nodb --ip=\$myip
63 | fi
64 | EOF
65 |
66 | #cat $TMPFILE >> 'script1.txt'
67 |
68 | SCHEDJOB=$(qsub $TMPFILE | awk '{print $4}')
69 | echo 'scheduler job submitted:' $SCHEDJOB
70 |
71 | TMPFILE=`mktemp` || exit 1
72 | # write the slurm script
73 | cat > $TMPFILE <<- EOF
74 | #!/bin/bash
75 | #$ -M bonimba@g.ucla.edu
76 | #$ -m ea
77 | #$ -N ipycluster
78 | #$ -l h_data=${MEM}
79 | #$ -l h_rt=${WTIME}
80 | #$ -l highp
81 | #$ -cwd
82 | #$ -o out_engines
83 | #$ -e err_engines
84 | #$ -V
85 | #$ -pe dc* ${NTASKS}
86 |
87 |
88 | export PATH="$PATH"
89 | ulimit -s 8192
90 |
91 | cd $SGE_O_WORKDIR
92 |
93 | # let the scheduler setup finish
94 | sleep 10
95 | MONITOR=$(command -v monitor_process)
96 | if [[ ! -z "$MONITOR" ]]; then
97 | mpirun --n=${NTASKS} monitor_process --wtype W ipengine
98 | else
99 | mpirun --n=${NTASKS} ipengine
100 | fi
101 |
102 | EOF
103 |
104 | #cat $TMPFILE
105 | sleep 1
106 | cat $TMPFILE >> 'script_engines.sh'
107 |
108 | echo "The engines will start only after the controller job $SCHEDJOB starts..."
109 |
110 | echo "Need to manually submit $NTASKS engines on the top of the controller, by executing the script_engines.sh script"
111 |
--------------------------------------------------------------------------------
/HPC_scripts/script_engines.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #$ -M bonimba@g.ucla.edu
3 | #$ -m ea
4 | #$ -N ipycluster
5 | #$ -l h_data=2G
6 | #$ -l h_rt=200:59:59
7 | #$ -l highp
8 | #$ -cwd
9 | #$ -o out_engines
10 | #$ -e err_engines
11 | #$ -V
12 | #$ -pe dc* 100
13 |
14 |
15 | export PATH="/u/home/b/bonimba/miniconda3/bin:/u/home/b/bonimba/miniconda3/condabin:/u/local/compilers/intel/18.0.4/parallel_studio_xe_2018/bin:/u/local/compilers/intel/18.0.4/compilers_and_libraries_2018.5.274/linux/mpi/intel64/bin:/u/local/compilers/intel/18.0.4/compilers_and_libraries_2018.5.274/linux/bin/intel64:/u/local/compilers/intel/18.0.4/clck/2018.3/bin/intel64:/u/local/compilers/intel/18.0.4/itac/2018.4.025/intel64/bin:/u/local/compilers/intel/18.0.4/inspector/bin64:/u/local/compilers/intel/18.0.4/vtune_amplifier_2018/bin64:/u/local/compilers/intel/18.0.4/advisor_2018/bin64:/u/systems/UGE8.6.4/bin/lx-amd64:/u/local/bin:/u/local/sbin:/usr/lib64/qt-3.3/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin:/u/home/b/bonimba/bin"
16 | ulimit -s 8192
17 |
18 | cd
19 |
20 | # let the scheduler setup finish
21 | sleep 10
22 | MONITOR=
23 | if [[ ! -z "" ]]; then
24 | mpirun --n=100 monitor_process --wtype W ipengine
25 | else
26 | mpirun --n=100 ipengine
27 | fi
28 |
29 |
30 |
--------------------------------------------------------------------------------
/HPC_scripts/steps_to_submit_IGM.txt:
--------------------------------------------------------------------------------
1 | How to create the parallel environment (step 1) and submit a successful parallel IGM calculation (step 2) on top of it.
2 |
3 | - remove the ```submit_engines.sh``` file (if there).
4 | - ```bash create_ipcluster_environment.sh```: this will submit the controller and create the ```submit_engines``` script, ready for you to submit (step 1a).
5 | - Wait for the controller job to start (and generate its own ```out`` and `err` files), and then submit the engines with ```qsub submit_engines``` (step 1b).
6 | - When the engines are also running, you can submit the actual IGM calculation on top of the (controller + engines) scaffold, using ```qsub submit_igm``` (step 2).
7 |
--------------------------------------------------------------------------------
/HPC_scripts/submit_igm.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #----------------------------------------#
4 | #- IGM SUBMISSION SCRIPT, multi-threaded JOB
5 | #----------------------------------------#
6 |
7 | #$ -M bonimba@g.ucla.edu
8 | #$ -m ea
9 | #$ -N jobname
10 | #$ -l h_data=40G
11 | #$ -l h_rt=30:00:00
12 | #$ -l highp
13 | #$ -cwd
14 | #$ -o out_igm
15 | #$ -e err_igm
16 | #$ -V
17 | #$ -pe shared 2
18 |
19 | export PATH="$PATH"
20 | ulimit -s 8192
21 |
22 | # -----------------------
23 | # print JOB ID, can be useful for keeping track of status
24 | echo $JOB_ID
25 |
26 | # print PATH pointing to directory the job is run from
27 | echo $SGE_O_WORKDIR
28 |
29 |
30 | # shared memory parallelization: same node, more cores, export number of threads
31 | export OMP_NUM_THREADS=2
32 | echo "submitting IGM optimization..."
33 |
34 | # execute job
35 | igm-run igm_config.json >> igm_output.txt
36 |
37 |
--------------------------------------------------------------------------------
/IGM_documentation.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alberlab/igm/edbb33135260443b8b41eb09d7ca4c37b19e330d/IGM_documentation.pdf
--------------------------------------------------------------------------------
/bin/igm-info-dir:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import argparse
4 | import sys
5 | import json
6 | from igm.ui.folders_database import folder_info
7 |
8 |
9 | if __name__ == "__main__":
10 |
11 | parser = argparse.ArgumentParser(description='Get a record from the IGM database in json format')
12 | parser.add_argument('folder', nargs='?', default=os.getcwd(), help='Optional. Folder to retrieve. If not specified, it defaults the current directory. Ignored if --all is specified.')
13 | parser.add_argument('--all', action='store_true', help='if specified, print info for all the folders')
14 |
15 | args = parser.parse_args()
16 |
17 | try:
18 | if args.all:
19 | sys.stdout.write(json.dumps(folder_info(), indent=4) + '\n')
20 | else:
21 | sys.stdout.write(json.dumps(folder_info(args.folder), indent=4) + '\n')
22 |
23 | except RuntimeError as e:
24 | sys.stderr.write(str(e))
25 | exit(1)
26 |
27 |
--------------------------------------------------------------------------------
/bin/igm-register-dir:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os.path
3 | import argparse
4 | import sys
5 | from igm.ui.folders_database import register_folder
6 |
7 | if __name__ == "__main__":
8 |
9 | parser = argparse.ArgumentParser(description='Register a directory in the IGM database')
10 | parser.add_argument('folder', nargs='?', default=os.getcwd(), help='Optional. Directory to register. If not specified, it register the current directory.')
11 | parser.add_argument('--cell-line', default='', help='(Optional) Cell line')
12 | parser.add_argument('--resolution', default='', help='(Optional) Model resolution')
13 | parser.add_argument('--notes', default='', help='(Optional) Additional notes')
14 | parser.add_argument('--tags', default='', help='(Optional) Comma separated list of tags')
15 |
16 | #ssh -f -N -T -M -S -R ::
17 |
18 | args = parser.parse_args()
19 |
20 | try:
21 | register_folder(**vars(args))
22 | except RuntimeError as e:
23 | sys.stderr.write(str(e))
24 | exit(1)
25 |
26 | sys.stdout.write('ok: ' + args.folder + '\n')
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/bin/igm-stop:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import socket, argparse, os, stat, sys
4 |
5 |
6 | def is_socket(path):
7 | mode = os.stat(path).st_mode
8 | return stat.S_ISSOCK(mode)
9 |
10 |
11 | if __name__ == "__main__":
12 |
13 | parser = argparse.ArgumentParser(description='Stop a running igm instance')
14 | parser.add_argument('dir', nargs='?', default=os.getcwd(), help='Optional. Directory where the IGM instance is running.')
15 |
16 | args = parser.parse_args()
17 |
18 | if not os.path.isdir(args.dir):
19 | sys.stderr.write('Cannot find directory {}\n'.format(args.dir))
20 | exit(1)
21 |
22 | socket_file = os.path.join(args.dir, '.igm-socket')
23 |
24 | try:
25 | if not is_socket(socket_file):
26 | sys.stderr.write('File "{}" does not appear to be a valid socket.\n'.format(socket_file))
27 | exit(1)
28 | except FileNotFoundError:
29 | sys.stderr.write('Cannot find socket file "{}"\nIs IGM running in {}?\n'.format(socket_file, args.dir))
30 | exit(1)
31 |
32 | s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
33 | s.connect(socket_file)
34 | s.send(b'{"q": "kill"}')
35 | s.close()
36 |
--------------------------------------------------------------------------------
/bin/igm-unregister-dir:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os.path
3 | import argparse
4 | import sys
5 |
6 | from igm.ui.folders_database import unregister_folder
7 |
8 | if __name__ == "__main__":
9 |
10 | parser = argparse.ArgumentParser(description='Unregister a directory in the IGM database')
11 | parser.add_argument('folder', nargs='?', default=os.getcwd(), help='Optional. Directory to delete. If not specified, it deletes the current directory.')
12 |
13 | args = parser.parse_args()
14 |
15 | try:
16 | unregister_folder(args.folder)
17 | sys.stdout.write('removed: ' + args.folder + '\n')
18 | except RuntimeError as e:
19 | sys.stderr.write(str(e))
20 | exit(1)
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/demo/.workers.restart:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alberlab/igm/edbb33135260443b8b41eb09d7ca4c37b19e330d/demo/.workers.restart
--------------------------------------------------------------------------------
/demo/WTC11_HiC_2Mb.hcs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alberlab/igm/edbb33135260443b8b41eb09d7ca4c37b19e330d/demo/WTC11_HiC_2Mb.hcs
--------------------------------------------------------------------------------
/demo/config_file.json:
--------------------------------------------------------------------------------
1 | {
2 | "genome": {
3 | "assembly": "hg38",
4 | "ploidy": "male",
5 | "usechr": [
6 | "#",
7 | "X",
8 | "Y"
9 | ],
10 | "segmentation": 2000000
11 | },
12 | "model": {
13 | "population_size": 100,
14 | "occupancy": 0.4,
15 | "init_radius": 7000,
16 | "restraints": {
17 | "excluded": {
18 | "evfactor": 1.0
19 | },
20 | "envelope": {
21 | "nucleus_shape": "sphere",
22 | "nucleus_radius": 5500,
23 | "nucleus_kspring": 1.0
24 | },
25 | "polymer": {
26 | "contact_range": 2.0,
27 | "polymer_bonds_style": "simple",
28 | "polymer_kspring": 1.0
29 | }
30 | }
31 | },
32 | "restraints": {
33 | "Hi-C": {
34 | "input_matrix": "WTC11_HiC_2Mb.hcs",
35 | "intra_sigma_list": [
36 | 1.0,
37 | 0.2,
38 | 0.1,
39 | 0.05,
40 | 0.02,
41 | 0.01
42 | ],
43 | "inter_sigma_list": [
44 | 1.0,
45 | 0.2,
46 | 0.1,
47 | 0.05,
48 | 0.02,
49 | 0.01
50 | ],
51 | "contact_range": 2.0,
52 | "contact_kspring": 1.0,
53 | "actdist_file": "actdist.h5",
54 | "tmp_dir": "actdist",
55 | "keep_temporary_files": false,
56 | "run_evaluation_step": false
57 | }
58 | },
59 | "optimization": {
60 | "min_iterations": 5,
61 | "force_last_iteration": true,
62 | "force_minimum_iterations_hic_cutoff": 0.0,
63 | "max_iterations": 10,
64 | "violation_tolerance": 0.05,
65 | "max_violations": 0.01,
66 | "structure_output": "igm-model.hss",
67 | "keep_intermediate_structures": true,
68 | "kernel": "lammps",
69 | "tmp_dir": "opt",
70 | "clean_restart": false,
71 | "keep_temporary_files": false,
72 | "kernel_opts": {
73 | "lammps": {
74 | "lammps_executable": "path to lammpgen/src/lmp_serial",
75 | "seed": 6535,
76 | "max_neigh": 6000,
77 | "use_gpu": false
78 | }
79 | },
80 | "optimizer_options": {
81 | "mdsteps": 45000,
82 | "timestep": 0.25,
83 | "tstart": 500.0,
84 | "tstop": 0.01,
85 | "custom_annealing_protocol": {
86 | "num_steps": 4,
87 | "mdsteps": [
88 | 5000,
89 | 15000,
90 | 15000,
91 | 10000
92 | ],
93 | "tstarts": [
94 | 5000.0,
95 | 500.0,
96 | 50.0,
97 | 1.0
98 | ],
99 | "tstops": [
100 | 500.0,
101 | 50.0,
102 | 1.0,
103 | 0.0
104 | ],
105 | "evfactors": [
106 | 0.5,
107 | 1.0,
108 | 1.0,
109 | 1.0
110 | ],
111 | "envelope_factors": [
112 | 1.2,
113 | 1.0,
114 | 1.0,
115 | 1.0
116 | ],
117 | "relax": {
118 | "mdsteps": 500,
119 | "temperature": 1.0,
120 | "max_velocity": 10.0
121 | }
122 | },
123 | "damp": 50.0,
124 | "max_velocity": 1000.0,
125 | "etol": 0.0001,
126 | "ftol": 1e-06,
127 | "max_cg_iter": 500,
128 | "max_cg_eval": 500,
129 | "thermo": 1000,
130 | "write": -1
131 | }
132 | },
133 | "parameters": {
134 | "step_db": "stepdb.sqlite",
135 | "tmp_dir": "tmp",
136 | "log": "igm.log",
137 | "log_level": "debug"
138 | },
139 | "parallel": {
140 | "controller": "ipyparallel",
141 | "controller_options": {
142 | "ipyparallel": {
143 | "max_tasks": 5000
144 | }
145 | }
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/demo/demo_sample_outputs/igm-model.hss.T:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alberlab/igm/edbb33135260443b8b41eb09d7ca4c37b19e330d/demo/demo_sample_outputs/igm-model.hss.T
--------------------------------------------------------------------------------
/demo/demo_sample_outputs/igm-model.hss.randomInit:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alberlab/igm/edbb33135260443b8b41eb09d7ca4c37b19e330d/demo/demo_sample_outputs/igm-model.hss.randomInit
--------------------------------------------------------------------------------
/demo/demo_sample_outputs/stepdb.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alberlab/igm/edbb33135260443b8b41eb09d7ca4c37b19e330d/demo/demo_sample_outputs/stepdb.sqlite
--------------------------------------------------------------------------------
/demo/deprecated/ipworkernode.pbs:
--------------------------------------------------------------------------------
1 |
2 | #!/bin/bash
3 | #PBS -N workernode
4 | #PBS -l nodes=1:Xeon:ppn=1
5 | #PBS -l walltime=100:00:00
6 | #PBS -q cmb
7 | #PBS -l pmem=3gb
8 | #PBS -l pvmem=3gb
9 | #PBS -l mem=3gb
10 | #PBS -l vmem=3gb
11 | #PBS -j oe
12 | #PBS -o /dev/null
13 | #PBS -m p
14 |
15 | cd ${PBS_O_WORKDIR}
16 |
17 | source /home/cmb-08/fa/local/setup.sh
18 | ulimit -s 8192
19 | #IPEID=$(uuidgen)
20 | #touch ".${IPEID}.restart"
21 | ipengine --quiet 2> logs/workernode_${PBS_JOBID}.log
22 |
23 | while [ -e ".workers.restart" ]
24 | do
25 | sleep 1
26 | ipengine --quiet 2> logs/workernode_${PBS_JOBID}.log
27 | done
--------------------------------------------------------------------------------
/demo/deprecated/startController.sh:
--------------------------------------------------------------------------------
1 | export MYIP=$(ifconfig eno1 | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1' | head -1)
2 |
3 | ipcontroller --ip=$MYIP --nodb
4 |
--------------------------------------------------------------------------------
/demo/deprecated/startWorker.sh:
--------------------------------------------------------------------------------
1 | mkdir logs
2 | mkdir tmp
3 | for i in `seq 280`
4 | do
5 | qsub ipworkernode.pbs >> worker_job_ids.log
6 | done
7 | touch .workers.restart
8 |
9 |
--------------------------------------------------------------------------------
/html_history.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import igm.ui.views as vw
3 |
4 | with open('history.html', 'w') as f:
5 | f.write( vw.history( sys.argv[1] ) )
--------------------------------------------------------------------------------
/igm-config_all.json:
--------------------------------------------------------------------------------
1 | {
2 | "genome": {
3 | "assembly": "hg38",
4 | "ploidy": "male",
5 | "usechr": [
6 | "#",
7 | "X",
8 | "Y"
9 | ],
10 | "segmentation": 200000
11 | },
12 | "model": {
13 | "population_size": 1000,
14 | "occupancy": 0.4,
15 | "restraints": {
16 | "excluded": {
17 | "evfactor": 1.0
18 | },
19 | "envelope": {
20 | "nucleus_shape": "ellipsoid",
21 | "nucleus_semiaxes": [7840.0, 6470.0, 2450.0],
22 | "nucleus_kspring": 1.0
23 | },
24 | "polymer": {
25 | "contact_range": 2.0,
26 | "polymer_bonds_style": "simple",
27 | "polymer_kspring": 1.0
28 | }
29 | }
30 | },
31 | "restraints": {
32 | "Hi-C": {
33 | "input_matrix": ".hcs file, Hi-C matrix input of contact probabilities",
34 | "intra_sigma_list": [
35 | 1.0,
36 | 0.5,
37 | 0.2,
38 | 0.1,
39 | 0.05,
40 | 0.01,
41 | 0.008
42 | ],
43 | "inter_sigma_list": [
44 | 1.0,
45 | 0.5,
46 | 0.2,
47 | 0.1,
48 | 0.05,
49 | 0.01,
50 | 0.008
51 | ],
52 | "contact_range": 2.0,
53 | "contact_kspring": 1.0,
54 | "actdist_file": "actdist.h5",
55 | "tmp_dir": "actdist",
56 | "keep_temporary_files": false,
57 | "run_evaluation_step": false
58 | },
59 | "FISH" : {
60 | "input_fish" : "h5py file, FISH input, list of distance distributions for radial or pairwise 3D HIPMap FISH data",
61 | "rtype" : "p",
62 | "kspring" : 1.0,
63 | "tmp_dir" : "fish_actdist",
64 | "batch_size": 200,
65 | "keep_temporary_files" : "true",
66 | "tol_list" : [200.0, 100.0, 100.0, 100.0, 50.0, 50.0, 50.0, 25.0],
67 | "fish_assignment_file": "fish_assignment.h5"
68 | },
69 | "sprite" : {
70 | "clusters": "h5py file, SPRITE input, list of loci colocalizing = list of clusters of loci",
71 | "volume_fraction_list": [0.001, 0.001, 0.005, 0.005, 0.005, 0.01, 0.01, 0.05],
72 | "radius_kt": 50.0,
73 | "assignment_file": "sprite_assignment.h5",
74 | "tmp_dir": "sprite_assign",
75 | "keep_temporary_files": false,
76 | "batch_size": 10,
77 | "kspring": 1.0
78 | },
79 |
80 | "DamID": {
81 | "input_profile": ".txt file, lamina DamID input, contact probabilities with the lamina",
82 | "sigma_list": [
83 | 0.8, 0.8, 0.6, 0.6, 0.45, 0.45, 0.3, 0.3
84 | ],
85 | "contact_range": 0.05,
86 | "contact_kspring": 1.0,
87 | "damid_actdist_file": "actdist.h5",
88 | "tmp_dir": "damid",
89 | "keep_temporary_files": false
90 | }
91 |
92 | },
93 | "optimization": {
94 | "iter_corr_knob": 1,
95 | "min_iterations": 5,
96 | "force_last_iteration": true,
97 | "force_minimum_iterations_hic_cutoff": 0.0,
98 | "max_iterations": 12,
99 | "violation_tolerance": 0.05,
100 | "max_violations": 0.01,
101 | "structure_output": "igm-model.hss",
102 | "keep_intermediate_structures": true,
103 | "kernel": "lammps",
104 | "tmp_dir": "opt",
105 | "clean_restart": false,
106 | "keep_temporary_files": false,
107 | "kernel_opts": {
108 | "lammps": {
109 | "lammps_executable": "/u/home/b/bonimba/lammpgen/src/lmp_serial",
110 | "seed": 6535,
111 | "max_neigh": 6000,
112 | "use_gpu": false
113 | }
114 | },
115 | "optimizer_options": {
116 | "mdsteps": 40000,
117 | "timestep": 0.25,
118 | "tstart": 500.0,
119 | "tstop": 0.01,
120 | "custom_annealing_protocol": {
121 | "num_steps": 4,
122 | "mdsteps": [
123 | 5000,
124 | 15000,
125 | 15000,
126 | 10000
127 | ],
128 | "tstarts": [
129 | 5000.0,
130 | 500.0,
131 | 50.0,
132 | 1.0
133 | ],
134 | "tstops": [
135 | 500.0,
136 | 50.0,
137 | 1.0,
138 | 0.0
139 | ],
140 | "evfactors": [
141 | 0.5,
142 | 1.0,
143 | 1.0,
144 | 1.0
145 | ],
146 | "envelope_factors": [
147 | 1.2,
148 | 1.0,
149 | 1.0,
150 | 1.0
151 | ],
152 | "relax": {
153 | "mdsteps": 200,
154 | "temperature": 1.0,
155 | "max_velocity": 10.0
156 | }
157 | },
158 | "damp": 50.0,
159 | "max_velocity": 1000.0,
160 | "etol": 0.0001,
161 | "ftol": 1e-06,
162 | "max_cg_iter": 500,
163 | "max_cg_eval": 500,
164 | "thermo": 1000,
165 | "write": -1
166 | }
167 | },
168 | "parameters": {
169 | "step_db": "stepdb.sqlite",
170 | "tmp_dir": "/u/scratch/b/bonimba/HFF_all_longer/tmp",
171 | "log": "igm.log",
172 | "log_level": "debug"
173 | },
174 | "parallel": {
175 | "controller": "ipyparallel",
176 | "controller_options": {
177 | "ipyparallel": {
178 | "max_tasks": 5000
179 | }
180 | }
181 | }
182 | }
183 |
184 |
--------------------------------------------------------------------------------
/igm-run_scheme.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alberlab/igm/edbb33135260443b8b41eb09d7ca4c37b19e330d/igm-run_scheme.pdf
--------------------------------------------------------------------------------
/igm/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import division, print_function
2 | import warnings
3 | warnings.simplefilter(action='ignore', category=FutureWarning)
4 |
5 | from . import model
6 | from . import restraints
7 | from . import parallel
8 |
9 |
10 | from .core import Config
11 |
12 |
13 | from .steps import *
14 | from ._preprocess import Preprocess
15 |
16 | from .utils import SetupLogging, logger
17 |
--------------------------------------------------------------------------------
/igm/core/__init__.py:
--------------------------------------------------------------------------------
1 |
2 | from __future__ import division, print_function
3 |
4 |
5 | from .config import Config
6 | from .step import Step, StructGenStep
7 |
8 |
--------------------------------------------------------------------------------
/igm/core/config.py:
--------------------------------------------------------------------------------
1 | from __future__ import division, print_function
2 |
3 | import json
4 | import hashlib
5 | from configparser import ConfigParser
6 | import os, os.path
7 | from copy import deepcopy
8 | from six import string_types, raise_from
9 | from ..utils.files import make_absolute_path
10 |
11 | from . import defaults
12 | schema_file = os.path.join(
13 | os.path.dirname(os.path.abspath(defaults.__file__)),
14 | 'config_schema.json'
15 | )
16 | schema = json.load(open(schema_file, 'r'))
17 |
18 | ##TODO
19 | #use walk_schema to validate user args
20 | def walk_schema(n, w, group_callback, post_group_callback,
21 | item_callback):
22 | if isinstance(w, dict):
23 | if w.get("role") is not None and "group" in w["role"]:
24 | group_callback(n, w)
25 | for x in w:
26 | walk_tree(n + '/' + x, w[x],
27 | group_callback, post_group_callback,
28 | item_callback)
29 | exit_group_callback(n, w)
30 | return
31 | if w.get("dtype") is not None:
32 | item_callback(n, w)
33 |
34 | def make_default_dict(group):
35 | out = dict()
36 | for k, item in group.items():
37 | if not isinstance(item, dict):
38 | continue
39 | if ('role' in item) and (item['role'] == 'group'):
40 | out[k] = make_default_dict(group[k])
41 | elif ('role' in item) and (item['role'] in ['optional-group', 'optional_input']):
42 | pass
43 | else:
44 | try:
45 | out[k] = item['default']
46 | except KeyError:
47 | pass
48 | return out
49 |
50 | RAISE = object()
51 |
52 | class Config(dict):
53 | """
54 | Config is the class which holds all static parameters
55 | e.g. # structures, hic file, SPRITE file etc.
56 | and dynamic parameters e.g. activation distance and cluster assignment.
57 | """
58 | RAISE = RAISE
59 |
60 | def __init__(self, cfg=None):
61 |
62 | super(Config, self).__init__()
63 |
64 | # create default dictionary
65 | self.update(make_default_dict(schema))
66 |
67 | # runtime is a required key, and is home to all the generated parameters
68 | self['runtime'] = dict()
69 |
70 | # update with user files in ${HOME}/.igm
71 | user_defaults_file = os.environ['HOME'] + '/.igm/user_defaults.cfg'
72 | if os.path.isfile(user_defaults_file):
73 | user_defaults = ConfigParser()
74 | user_defaults.read(user_defaults_file)
75 | for s in user_defaults.sections() + ['DEFAULT']:
76 | for k in user_defaults[s]:
77 | self.set(k, user_defaults[s][k])
78 |
79 | # update the default dictionary with the provided file
80 | if cfg is not None:
81 | if isinstance(cfg, string_types):
82 | with open(cfg) as f:
83 | self.update(json.load(f))
84 | elif isinstance(cfg, dict):
85 | self.update(deepcopy(cfg))
86 | else:
87 | raise TypeError('Config argument needs to be a path or a dictionary.')
88 |
89 | self.igm_parameter_abspath()
90 |
91 | # fix optimizer arguments
92 | self.preprocess_optimization_arguments()
93 |
94 | for k in self['restraints']:
95 | if k not in self['runtime']:
96 | self['runtime'][k] = dict()
97 |
98 | def get(self, keypath, default=RAISE):
99 | split_path = keypath.split("/")
100 |
101 | try:
102 | d = self
103 | for p in split_path:
104 | d = d[p]
105 | except KeyError:
106 | if default is not RAISE:
107 | return default
108 | d = schema
109 | for p in split_path:
110 | if p in d:
111 | d = d[p]
112 | else:
113 | raise_from(KeyError("{} does not exist".format(keypath)), None)
114 | d = d.get("default")
115 | return d
116 |
117 | def set(self, keypath, val):
118 | split_path = keypath.split("/")
119 |
120 | d = self
121 | for p in split_path[:-1]:
122 | if p not in d:
123 | d[p] = dict()
124 | d = d[p]
125 | d[split_path[-1]] = val
126 |
127 | return val
128 |
129 | def igm_parameter_abspath(self):
130 | # if a working directory is not specified, we set it to the
131 | # current directory.
132 | self['parameters']['workdir'] = make_absolute_path( self.get("parameters/workdir", os.getcwd()) )
133 |
134 | # We use absolute paths because workers may be running on different
135 | # directories.
136 | self['parameters']['tmp_dir'] = make_absolute_path( self.get("parameters/tmp_dir", 'tmp'), self['parameters']['workdir'] )
137 | self['parameters']['log'] = make_absolute_path( self.get("parameters/log", 'igm.log'), self['parameters']['workdir'] )
138 | self['optimization']['structure_output'] = make_absolute_path( self['optimization']['structure_output'], self['parameters']['workdir'])
139 |
140 | def preprocess_optimization_arguments(self):
141 |
142 | opt = self['optimization']['optimizer_options']
143 | opt['ev_step'] = 0
144 | try:
145 | if opt['write'] == -1:
146 | opt['write'] = opt['mdsteps'] # write only final step
147 | except:
148 | pass
149 |
150 | def preprocess_sprite_arguments(self):
151 | if 'sprite' not in self['restraints']:
152 | return
153 | self['restraints']['sprite'] = validate_user_args(self['restraints']['sprite'], SPRITE_DEFAULT)
154 | opt = self['restraints']['sprite']
155 | opt['tmp_dir'] = make_absolute_path(opt['tmp_dir'], self['tmp_dir'])
156 | opt['assignment_file'] = make_absolute_path(opt['assignment_file'], opt['tmp_dir'])
157 | opt['clusters'] = make_absolute_path(opt['clusters'], self['workdir'])
158 |
159 | def static_hash(self):
160 | '''
161 | Returns a hash for the static run options
162 | '''
163 | return hashlib.md5(
164 | json.dumps({
165 | self[k] for k in self if k != 'runtime'
166 | }).encode()
167 | ).hexdigest()
168 |
169 | def runtime_hash(self):
170 | '''
171 | Returns a hash for the current configuration,
172 | including runtime status.
173 | '''
174 | return hashlib.md5(
175 | json.dumps(self).encode()
176 | ).hexdigest()
177 |
178 | def save(self, fname):
179 | with open(fname, 'w') as f:
180 | json.dump(self, f, indent=4)
181 |
182 | #==
183 |
184 | def validate_user_args(inargs, defaults, strict=True):
185 | args = {k: v for k, v, _, _ in defaults}
186 | atypes = {k: t for k, _, t, _ in defaults}
187 | for k, v in inargs.items():
188 | if k not in args and strict is True:
189 | raise ValueError('Keywords argument \'%s\' not recognized.' % k)
190 | if v is not None:
191 | args[k] = atypes[k](v)
192 |
193 | return args
194 |
195 |
196 |
--------------------------------------------------------------------------------
/igm/core/defaults/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alberlab/igm/edbb33135260443b8b41eb09d7ca4c37b19e330d/igm/core/defaults/__init__.py
--------------------------------------------------------------------------------
/igm/core/defaults/config_default.json:
--------------------------------------------------------------------------------
1 | {
2 |
3 | "genome" : {
4 | "genome" : "hg38",
5 | "segmentation" : "tad.bed",
6 | "ploidy" : "diploid"
7 | },
8 |
9 | "model" : {
10 | "nucleus_shape" : "sphere",
11 | "nucleus_radius" : 5000.0,
12 | "occupancy" : 0.2,
13 | "contact_range" : 2.0,
14 | "evfactor" : 0.05
15 | },
16 |
17 | "restraints" : {
18 |
19 | },
20 |
21 | "optimization" : {
22 | "kernel" : "lammps",
23 | "tmp_dir" : "opt",
24 | "run_name" : "test",
25 | "keep_temporary_files" : true,
26 | "lammps_executable" : "lmp_serial_mod",
27 | "optimizer_options" : {
28 | "mdsteps" : 20000,
29 | "timestep" : 0.25,
30 | "tstart" : 250.0,
31 | "tstop" : 0.5,
32 | "damp" : 50.0,
33 | "max_velocity": 100,
34 | "ev_factor": 0.05
35 | }
36 |
37 | },
38 |
39 | "step_db" : "stepdb.sqlite",
40 | "max_iterations" : 10,
41 | "log" : "log.txt",
42 | "log_level" : "debug",
43 | "keep_intermediate_structures" : true,
44 | "violation_tolerance" : 0.01
45 |
46 | }
47 |
48 |
--------------------------------------------------------------------------------
/igm/core/job_tracking.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sqlite3
3 | import time
4 | import json
5 |
6 | from ..utils.log import logger
7 | from .config import Config
8 |
9 | class StepDB(object):
10 |
11 | SCHEMA = [
12 | ('uid', 'TEXT'),
13 | ('name', 'TEXT'),
14 | ('cfg', 'TEXT'),
15 | ('time', 'INT'),
16 | ('status', 'TEXT'),
17 | ('data', 'TEXT')
18 | ]
19 |
20 | JSONCOLS = ['cfg', 'data']
21 |
22 | COLUMNS = [x[0] for x in SCHEMA]
23 |
24 | def __init__(self, cfg, mode='a'):
25 | self.db = None
26 | if isinstance(cfg, str):
27 | try:
28 | cfg = Config(cfg)
29 | except:
30 | pass
31 | elif isinstance(cfg, Config):
32 | pass
33 | elif isinstance(cfg, dict):
34 | cfg = Config(cfg)
35 | else:
36 | raise ValueError('Invalid argument (needs to be Config, dict or string, got %s)' % type(cfg) )
37 | self.prepare_db(cfg, mode=mode)
38 |
39 | def prepare_db(self, cfg, mode='a'):
40 | if isinstance(cfg, str):
41 | self.db = cfg
42 | else:
43 | self.db = cfg.get('parameters/step_db', None)
44 | if self.db:
45 | if os.path.isfile(self.db):
46 | logger.debug('db file found')
47 | # check db follows the schema
48 | try:
49 | with sqlite3.connect(self.db) as conn:
50 | s = conn.execute('PRAGMA table_info(steps)').fetchall()
51 | assert s is not None
52 | for i, (n, t) in enumerate(StepDB.SCHEMA):
53 | if n != s[i][1] or t != s[i][2]:
54 | msg = 'invalid column %s %s ' % (s[i][1], s[i][2])
55 | msg += '(expected: %s %s)' % (n, t)
56 | raise AssertionError(msg)
57 |
58 | except AssertionError as e:
59 | msg = 'Invalid database file %s.' % self.db
60 | raise type(e)(e.message + '\n' + msg)
61 |
62 | else:
63 | if mode == 'r':
64 | raise OSError('File not found')
65 | with sqlite3.connect(self.db) as conn:
66 | conn.execute(
67 | 'CREATE TABLE steps (' +
68 | ','.join([
69 | ' '.join(x) for x in StepDB.SCHEMA
70 | ]) +
71 | ')'
72 | )
73 |
74 | def record(self, **kwargs):
75 |
76 | if self.db is None:
77 | return
78 |
79 | data = {}
80 |
81 | # prepare columns
82 | for c, t in StepDB.SCHEMA:
83 | if c == 'time':
84 | data['time'] = kwargs.get('time', time.time())
85 | elif c in StepDB.JSONCOLS:
86 | data[c] = json.dumps( kwargs.get(c, None) )
87 | else:
88 | data[c] = kwargs.get(c, '')
89 |
90 | with sqlite3.connect(self.db) as conn:
91 | conn.execute(
92 | 'INSERT INTO steps (' +
93 | ','.join(StepDB.COLUMNS) +
94 | ') VALUES (' +
95 | ','.join( ['?'] * len(StepDB.COLUMNS) ) +
96 | ')',
97 | tuple(data[c] for c in StepDB.COLUMNS)
98 | )
99 |
100 | def unpack(self, result):
101 | out = {}
102 | for i, c in enumerate(StepDB.COLUMNS):
103 | if c in StepDB.JSONCOLS:
104 | out[c] = json.loads(result[i])
105 | else:
106 | out[c] = result[i]
107 | return out
108 |
109 | def get_history(self, uid=None):
110 |
111 | with sqlite3.connect(self.db) as conn:
112 | if uid is None:
113 | query = 'SELECT * FROM steps ORDER BY time'
114 | r = conn.execute(
115 | query
116 | ).fetchall()
117 | else:
118 | query = 'SELECT * FROM steps WHERE uid=? ORDER BY time'
119 | r = conn.execute(
120 | query,
121 | (uid,)
122 | ).fetchall()
123 | return [ self.unpack(x) for x in r ]
124 |
--------------------------------------------------------------------------------
/igm/cython_compiled/__init.py__:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alberlab/igm/edbb33135260443b8b41eb09d7ca4c37b19e330d/igm/cython_compiled/__init.py__
--------------------------------------------------------------------------------
/igm/cython_compiled/cpp_sprite_assignment.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | #define INF (100000000);
5 |
6 | using namespace std;
7 |
8 | class Vec3{
9 | public:
10 | float X[3];
11 |
12 | Vec3() {X[0] = 0; X[1] = 0; X[2] = 0;}
13 | explicit Vec3(float* a) {X[0] = a[0]; X[1] = a[1]; X[2] = a[2];}
14 | Vec3(float x, float y, float z) {X[0] = x; X[1] = y; X[2] = z;}
15 |
16 | float operator[](int i){return X[i];}
17 | const float operator[](int i) const{return X[i];}
18 |
19 | Vec3& operator=(float* a) {X[0] = a[0]; X[1] = a[1]; X[2] = a[2]; return *this;}
20 | Vec3& operator=(Vec3& a) {X[0] = a[0]; X[1] = a[1]; X[2] = a[2]; return *this;}
21 |
22 | Vec3 operator+(const Vec3& w) const {return Vec3(X[0] + w.X[0], X[1] + w.X[1], X[2] + w.X[2]); }
23 | Vec3 operator-(const Vec3& w) const {return Vec3(X[0] - w.X[0], X[1] - w.X[1], X[2] - w.X[2]); }
24 |
25 | Vec3 operator-() const {return Vec3(-X[0], -X[1], -X[2]); }
26 |
27 | Vec3 operator*(const float w) const {return Vec3(X[0] * w, X[1] * w, X[2] * w); }
28 | Vec3 operator/(const float w) const {return Vec3(X[0] / w, X[1] / w, X[2] / w); }
29 |
30 | Vec3& operator+=(const Vec3& w){X[0] += w[0]; X[1] += w[1]; X[2] += w[2]; return *this;}
31 | Vec3& operator-=(const Vec3& w){X[0] -= w[0]; X[1] -= w[1]; X[2] -= w[2]; return *this;}
32 |
33 | Vec3& operator+=(float w){X[0] += w; X[1] += w; X[2] += w; return *this;}
34 | Vec3& operator-=(float w){X[0] -= w; X[1] -= w; X[2] -= w; return *this;}
35 |
36 | Vec3& operator*=(const float w) {X[0] *= w; X[1] *= w; X[2] *= w; return *this;}
37 | Vec3& operator/=(const float w) {X[0] /= w; X[1] /= w; X[2] /= w; return *this;}
38 |
39 | float dot(const Vec3& w) const { return X[0]*w[0] + X[1]*w[1] + X[2]*w[2]; }
40 | float normsq() const { return dot(*this); }
41 | float norm() const { return sqrt(normsq()); }
42 | };
43 |
44 | float dot(const Vec3& a, const Vec3& b) { return a.dot(b); }
45 | float normsq(const Vec3& a) { return a.normsq(); }
46 | float norm(const Vec3& a) { return a.norm(); }
47 |
48 |
49 | float gyration_radius_sq(const vector& crd)
50 | {
51 | Vec3 r_mean(0.0f, 0.0f, 0.0f);
52 | int n = crd.size();
53 | for (int i = 0; i < n; ++i)
54 | r_mean += crd[i];
55 | r_mean /= n;
56 |
57 | float rg = 0;
58 | for (int i = 0; i < n; ++i)
59 | rg += normsq(crd[i] - r_mean);
60 | return (rg / n);
61 | }
62 |
63 | void get_combination(const vector >& all_copies,
64 | int* copies_num,
65 | int n_regions,
66 | int k,
67 | vector& comb,
68 | vector& copy_idx)
69 | {
70 | for (int i = 0; i < n_regions; ++i)
71 | {
72 | int si = k % copies_num[i];
73 | k /= copies_num[i];
74 | comb[i] = all_copies[i][si];
75 | copy_idx[i] = si;
76 | }
77 | }
78 |
79 | void get_rg2s_cpp(float* crds,
80 | int n_struct,
81 | int n_bead,
82 | int n_regions,
83 | int* copies_num,
84 | float* rg2s,
85 | int* copy_idxs,
86 | int* min_struct)
87 | {
88 |
89 | vector > all_copies(n_bead); // all coordinates
90 | vector current(n_regions); // coords of only one combination
91 | vector copy_idx(n_regions); // copies selection for current combination
92 |
93 | float min_rg2 = INF; // absolute minimum
94 |
95 | // prepare all_copies and compute the total
96 | // number of possible combinations
97 | int n_combinations = 1;
98 | for (int i = 0; i < n_regions; ++i)
99 | {
100 | all_copies[i].resize(copies_num[i]);
101 | n_combinations *= copies_num[i];
102 | }
103 |
104 | // loop through structures
105 | for (int s = 0; s < n_struct; ++s)
106 | {
107 | // set all_copies coordinates
108 | int k = 0;
109 | for (int i = 0; i < n_regions; ++i)
110 | {
111 | for (int j = 0; j < copies_num[i]; ++j)
112 | {
113 | all_copies[i][j] = crds + k*n_struct*3 + s*3;
114 | k += 1;
115 | }
116 | }
117 |
118 | // explore all the combinations
119 | float min_struct_rg2 = INF;
120 | vector min_copy_idx(n_regions, -1);
121 | for (int i = 0; i < n_combinations; ++i)
122 | {
123 | get_combination(all_copies, copies_num, n_regions, i, current, copy_idx);
124 | float rg2 = gyration_radius_sq(current);
125 | if (rg2 < min_struct_rg2){
126 | min_struct_rg2 = rg2;
127 | min_copy_idx.swap(copy_idx);
128 | }
129 | }
130 |
131 | // save results and check minimum
132 | rg2s[s] = min_struct_rg2;
133 | for (int i = 0; i < n_regions; ++i)
134 | {
135 | copy_idxs[n_regions*s + i] = min_copy_idx[i];
136 | }
137 | if (min_struct_rg2 < min_rg2)
138 | {
139 | min_rg2 = min_struct_rg2;
140 | *min_struct = s;
141 | }
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/igm/cython_compiled/cpp_sprite_assignment.h:
--------------------------------------------------------------------------------
1 | void get_rg2s_cpp(float* crds,
2 | int n_struct,
3 | int n_bead,
4 | int n_regions,
5 | int* copies_num,
6 | float* rg2s,
7 | int* copy_idxs,
8 | int* min_struct);
--------------------------------------------------------------------------------
/igm/cython_compiled/setup.py:
--------------------------------------------------------------------------------
1 | from distutils.core import setup
2 | from Cython.Build import cythonize
3 | import numpy
4 |
5 | setup(
6 | name = 'sprite',
7 | ext_modules = cythonize(
8 | ['sprite.pyx', 'cpp_sprite_assignment.cpp']
9 | ),
10 | include_dirs=[numpy.get_include()],
11 | )
12 |
--------------------------------------------------------------------------------
/igm/cython_compiled/tests.py:
--------------------------------------------------------------------------------
1 | from sprite import *
2 |
3 | import numpy as np
4 |
5 | crd = np.array([ [ [1,0,0] ] , [ [-1, 0, 0] ], [ [0,-1,0] ], [ [0,1,0] ]], dtype=np.float32)
6 | copies_num = np.array([1,1,1,1], dtype=np.int32)
7 |
8 | get_rgs2(crd, copies_num)
9 | # (array([1.], dtype=float32), 0, array([[0, 0, 0, 0]], dtype=int32))
10 |
11 |
12 | crd = np.array([ [ [1,0,0] ] , [ [0.5, 0, 0] ], [ [0,-0.5,0] ], [ [0,0.5,0] ]], dtype=np.float32)
13 | copies_num = np.array([2,2], dtype=np.int32)
14 | get_rgs2(crd, copies_num)
15 |
16 | # (array([1.], dtype=float32), 0, array([[0, 0, 0, 0]], dtype=int32))
17 |
18 | crd = np.array([ [ [1,0,0], [0.1,0,0] ] , [ [0.5, 0, 0], [1,0,0] ], [ [0,-0.5,0], [-1,0,0] ], [ [0,0.5,0], [-0.1,0,0] ] ], dtype=np.float32)
19 | copies_num = np.array([2,2], dtype=np.int32)
20 | get_rgs2(crd, copies_num)
21 | # (array([0.125, 0.01 ], dtype=float32), 1, array([[1, 0], [0, 1]], dtype=int32))
--------------------------------------------------------------------------------
/igm/model/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import division, print_function
2 |
3 | from .model import Model
4 | from .particle import Particle
5 |
6 |
--------------------------------------------------------------------------------
/igm/model/kernel/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/igm/model/kernel/lammps_io.py:
--------------------------------------------------------------------------------
1 | from __future__ import division, absolute_import, print_function
2 | import numpy as np
3 |
4 | from .util import reverse_readline
5 |
6 | def get_info_from_log(output):
7 | ''' gets final energy, excluded volume energy and bond energy.
8 | TODO: get more info? '''
9 | info = {}
10 | generator = reverse_readline(output)
11 |
12 | for l in generator:
13 | if l[:9] == ' Force t':
14 | ll = next(generator)
15 | info['final-energy'] = float(ll.split()[2])
16 | break
17 |
18 | for l in generator:
19 | if l[:4] == 'Loop':
20 | ll = next(generator)
21 | vals = [float(s) for s in ll.split()]
22 | info['pair-energy'] = vals[2]
23 | info['bond-energy'] = vals[3]
24 | while not ll.startswith('Step'):
25 | ll = next(generator)
26 | keys = ll.split()
27 | info['thermo'] = {k: v for k, v in zip(keys[1:], vals[1:])}
28 | break
29 |
30 | for l in generator:
31 | if l[:4] == 'Loop':
32 | # MD minimization
33 | info['md-time'] = float(l.split()[3])
34 |
35 | # EN=`grep -A 1 "Energy initial, next-to-last, final =" $LAMMPSLOGTMP \
36 | # | tail -1 | awk '{print $3}'`
37 | return info
38 |
39 | def get_last_frame(fh):
40 |
41 | """ Quite self-explanatory: extract coordinates from last frame produced by simulation """
42 | atomlines = []
43 | for l in reverse_readline(fh):
44 | if 'ITEM: ATOMS' in l:
45 | v = l.split()
46 | ii = v.index('id') - 2
47 | ix = v.index('x') - 2
48 | iy = v.index('y') - 2
49 | iz = v.index('z') - 2
50 | break
51 | atomlines.append(l)
52 |
53 | crds = np.empty((len(atomlines), 3))
54 | for l in atomlines:
55 | v = l.split()
56 | i = int(v[ii]) - 1 # ids are in range 1-N
57 | x = float(v[ix])
58 | y = float(v[iy])
59 | z = float(v[iz])
60 | crds[i][0] = x
61 | crds[i][1] = y
62 | crds[i][2] = z
63 |
64 | return crds
65 |
--------------------------------------------------------------------------------
/igm/model/kernel/util.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | def reverse_readline(fh, buf_size=8192):
4 | """a generator that returns the lines of a file in reverse order"""
5 | segment = None
6 | offset = 0
7 | fh.seek(0, os.SEEK_END)
8 | file_size = remaining_size = fh.tell()
9 | while remaining_size > 0:
10 | offset = min(file_size, offset + buf_size)
11 | fh.seek(file_size - offset)
12 | buffer = fh.read(min(remaining_size, buf_size))
13 | remaining_size -= buf_size
14 | lines = buffer.split('\n')
15 | # the first line of the buffer is probably not a complete line so
16 | # we'll save it and append it to the last line of the next buffer
17 | # we read
18 | if segment is not None:
19 | # if the previous chunk starts right from the beginning of line
20 | # do not concact the segment to the last line of new chunk
21 | # instead, yield the segment first
22 | if buffer[-1] is not '\n':
23 | lines[-1] += segment
24 | else:
25 | yield segment
26 | segment = lines[0]
27 | for index in range(len(lines) - 1, 0, -1):
28 | if len(lines[index]):
29 | yield lines[index]
30 | # Don't yield None if the file was empty
31 | if segment is not None:
32 | yield segment
33 |
34 | def resolve_templates(templates, args):
35 | rt = {}
36 | for key, val in templates.items():
37 | rt[key] = val.format(*args)
38 | return rt
39 |
--------------------------------------------------------------------------------
/igm/model/model.py:
--------------------------------------------------------------------------------
1 | from __future__ import division, absolute_import, print_function
2 | import numpy as np
3 | from .particle import Particle
4 | try:
5 | from itertools import izip as zip
6 | except ImportError:
7 | pass
8 |
9 | class Model(object):
10 | """
11 |
12 | Modeling target system
13 |
14 | This is an abstract mid-layer between data-restraints (ASSIGNMENT) and minization kernel (LAMMPS).
15 |
16 | The Model holds all information about the target system by particles and
17 | harmonic forces.
18 |
19 | """
20 | def __init__(self, uid=0):
21 | self.particles = []
22 | self.forces = []
23 | self.id = uid
24 |
25 |
26 | #====Particle methods
27 | def addParticle(self, pos, r, t, **kwargs):
28 | """
29 | Add particle to system
30 | """
31 |
32 | self.particles.append(Particle(pos, r, t, **kwargs))
33 |
34 | return len(self.particles)-1
35 |
36 |
37 | def getParticle(self,i):
38 | """
39 | Get particle in the system
40 | """
41 | return self.particles[i]
42 |
43 | def setParticlePos(self, i, pos):
44 | """
45 | set particle coordinates
46 | """
47 |
48 | self.particles[i].setCoordinates(pos)
49 | #====
50 |
51 |
52 | #===bulk particle methods
53 | def initParticles(self, crd, rad):
54 | """
55 | initialize particles using numpy array
56 |
57 | Parameters
58 | ----------
59 | crd : 2D numpy array (float), N*3
60 | Numpy array of coordinates for each particle, N is the number of particles
61 | rad : 2D numpy array (float), N*1
62 | particle radius
63 | """
64 | assert crd.shape[0] == rad.shape[0]
65 |
66 | for pos, r in zip(crd, rad):
67 | self.addParticle(pos, r, Particle.NORMAL)
68 | #====
69 |
70 |
71 | #====bulk get methods
72 | def getCoordinates(self):
73 | """
74 | Get all particles' Coordinates in numpy array form
75 | """
76 | return np.array([p.pos for p in self.particles if p.ptype == Particle.NORMAL])
77 |
78 |
79 | def getRadii(self):
80 | """
81 | Get all particles' radii in numpy array vector form
82 | """
83 | return np.array([[p.r for p in self.particles if p.ptype == Particle.NORMAL]]).T
84 | #====
85 |
86 |
87 | #====force methods
88 | def addForce(self, f):
89 | """
90 | Add a basic force
91 | """
92 |
93 | self.forces.append(f)
94 |
95 | return len(self.forces)-1
96 |
97 | def getForce(self, i):
98 | """
99 | get a force
100 | """
101 |
102 | return self.forces[i]
103 |
104 | def evalForceScore(self, i):
105 | """
106 | get force score
107 | """
108 |
109 | return self.forces[i].getScore(self.particles)
110 |
111 | def evalForceViolationRatio(self, i):
112 | """
113 | get force violation ratio
114 | """
115 |
116 | return self.forces[i].getViolationRatio(self.particles)
117 | #====
118 |
119 |
120 | #====restraint methods
121 | def addRestraint(self, res, override=False):
122 | """
123 | Add a type of restraint to model
124 | """
125 | res._apply_model(self, override)
126 |
127 | res._apply(self)
128 |
129 |
130 | def optimize(self, cfg):
131 | """
132 | optimize the model by selected kernel
133 | """
134 |
135 | if cfg['optimization']["kernel"] == "lammps":
136 | from .kernel import lammps
137 | return lammps.optimize(self, cfg)
138 | #-
139 | #-
140 |
141 | def saveCoordinates(self, filename):
142 | """
143 | save xyz coordinates into numpy npy file
144 | """
145 |
146 | np.save(filename, self.getCoordinates())
147 | #-
148 |
149 | def saveXYZR(self, filename):
150 | """
151 | save xyzr into numpy npz file
152 | """
153 |
154 | np.savez(filename, xyz=self.getCoordinates(), r=self.getRadii())
155 | #-
156 |
157 | #=
158 |
159 |
160 |
--------------------------------------------------------------------------------
/igm/model/particle.py:
--------------------------------------------------------------------------------
1 | from __future__ import division, absolute_import, print_function
2 | import numpy as np
3 |
4 | POSDTYPE = np.float32
5 |
6 | # define "Particle" class
7 |
8 | class Particle(object):
9 |
10 | NORMAL = 0
11 | DUMMY_STATIC = 1
12 | DUMMY_DYNAMIC = 2
13 |
14 | PTYPES = ["NORMAL","DUMMY_STATIC","DUMMY_DYNAMIC"]
15 |
16 | def __init__(self, pos, r, t, **kwargs):
17 | self.pos = np.array(pos).astype(POSDTYPE) # particle coordinates
18 | self.r = POSDTYPE(r) # particle radius (if 0, then no excluded voluem)
19 | self.ptype = t # particle type (see PTYPES list)
20 | for k in kwargs:
21 | setattr(self, k, kwargs[k])
22 |
23 | def __str__(self):
24 | return "({} {} {}, {}):{}".format(self.pos[0], self.pos[1], self.pos[2],
25 | self.r,
26 | Particle.PTYPES[self.ptype])
27 | __repr__ = __str__
28 |
29 | def getCoordinates(self):
30 | return self.pos
31 |
32 | def setCoordinates(self, pos):
33 | self.pos = pos
34 |
35 | def __sub__(self, other):
36 | return np.linalg.norm(self.pos - other.pos)
37 |
38 |
39 |
--------------------------------------------------------------------------------
/igm/parallel/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import division, print_function
2 |
3 | from .parallel_controller import SerialController
4 |
5 | from .ipyparallel_controller import BasicIppController
6 |
7 | from .slurm_controller import SlurmController
8 |
9 | controller_class = {
10 | "serial" : SerialController,
11 | "slurm" : SlurmController,
12 | "ipyparallel" : BasicIppController,
13 | "ipyparallel_basic" : BasicIppController,
14 | }
15 |
16 | def Controller(cfg):
17 | parallel_cfg = cfg.get("parallel", dict())
18 | pctype = parallel_cfg.get("controller", "ipyparallel")
19 | pcopts = parallel_cfg.get("controller_options", dict()).get(pctype, dict())
20 | return controller_class[pctype](**pcopts)
21 |
--------------------------------------------------------------------------------
/igm/parallel/async_file_operations.py:
--------------------------------------------------------------------------------
1 | import os
2 | import os.path
3 | import time
4 | import traceback
5 | import multiprocessing
6 |
7 | POLL_INTERVAL = 30 # check 30 seconds
8 |
9 |
10 | class GeneratorLen(object):
11 | def __init__(self, gen, length):
12 | self.gen = gen
13 | self.length = length
14 |
15 | def __len__(self):
16 | return self.length
17 |
18 | def __iter__(self):
19 | return self.gen
20 |
21 |
22 | class FilePoller(object):
23 |
24 | """ Define polling function """
25 |
26 | def __init__(self, files, callback, args=None, kwargs=None, remove_after_callback=False,
27 | setup=None, setup_args=tuple(), setup_kwargs=dict(),
28 | teardown=None, teardown_args=tuple(), teardown_kwargs=dict()):
29 | self._manager = multiprocessing.Manager()
30 |
31 | self.files = files
32 | self.args = args
33 | if args is None:
34 | self.args = [list()] * len(files)
35 |
36 | self.kwargs = kwargs
37 | if kwargs is None:
38 | self.kwargs = [dict()] * len(files)
39 |
40 | self.callback = callback
41 |
42 | self.th = None
43 |
44 | self.remove_flag = remove_after_callback
45 | self.completed = self._manager.list()
46 | self.setup = setup
47 | self.setup_args = setup_args
48 | self.setup_kwargs = setup_kwargs
49 | self.teardown = teardown
50 | self.teardown_args = teardown_args
51 | self.teardown_kwargs = teardown_kwargs
52 | self._traceback = self._manager.list()
53 |
54 | # watch function, which scans over the 'to_poll' list
55 | def watch(self, completed, tb, timeout=None, interval=POLL_INTERVAL):
56 | try:
57 | if self.setup:
58 | self.setup(*self.setup_args, **self.setup_kwargs)
59 | to_poll = set(range(len(self.files)))
60 | start = time.time()
61 | while True:
62 | last_poll = time.time()
63 | for i in list(to_poll):
64 | if os.path.isfile(self.files[i]):
65 | self.callback(*self.args[i], **self.kwargs[i])
66 | if self.remove_flag:
67 | os.remove(self.files[i])
68 | to_poll.remove(i)
69 | completed.append(i)
70 |
71 | if len(to_poll) == 0:
72 | break
73 |
74 | now = time.time()
75 | if timeout is not None:
76 | if now - start > timeout:
77 | raise RuntimeError('Timeout expired (%f seconds)' % (timeout,))
78 |
79 | delta = now - last_poll
80 | if delta < interval:
81 | time.sleep(interval - delta)
82 |
83 | except KeyboardInterrupt:
84 | pass
85 |
86 | except:
87 | tb.append(traceback.format_exc())
88 |
89 | finally:
90 | if self.teardown:
91 | try:
92 | self.teardown(*self.teardown_args, **self.teardown_kwargs)
93 | except:
94 | stb = traceback.format_exc()
95 | try:
96 | # the tb manager could already be down
97 | tb.append(stb)
98 | except:
99 | print(stb)
100 |
101 | def watch_async(self, timeout=None, interval=POLL_INTERVAL):
102 | self.th = multiprocessing.Process(target=self.watch, args=(self.completed, self._traceback, timeout, interval))
103 | self.th.start()
104 |
105 | def wait(self, timeout=None):
106 | self.th.join(timeout)
107 | if len(self._traceback):
108 | raise RuntimeError('\n'.join(self._traceback))
109 |
110 | def _enumerate(self):
111 | lastc = 0
112 | while True:
113 | #print('lastdc = ' + str(lastc)) # LB
114 | if len(self._traceback):
115 | self.th.join()
116 | raise RuntimeError('\n'.join(self._traceback))
117 |
118 | if lastc == len(self.files):
119 | if self.th:
120 | self.th.join()
121 | break
122 |
123 | if len(self.completed) > lastc:
124 | lastc += 1
125 | yield self.completed[lastc - 1]
126 |
127 | else:
128 | time.sleep(POLL_INTERVAL)
129 |
130 | def enumerate(self):
131 | return GeneratorLen(self._enumerate(), len(self.files))
132 |
--------------------------------------------------------------------------------
/igm/parallel/dask_controller.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function, division
2 |
3 | from dask.distributed import Client
4 |
5 | import traceback
6 |
7 | from .parallel_controller import ParallelController
8 |
9 | class TimedFunction(object):
10 | '''
11 | Utility wrapper for a remote task. Allows to avoid
12 | '''
13 | def __init__(self, func, timeout=None):
14 | self.func = func
15 | self.timeout = timeout
16 |
17 | def run(self, *args, **kwargs):
18 | try:
19 | from time import time
20 | tstart = time()
21 | res = self.func(*args, **kwargs)
22 | self._q.put( (0, res, time()-tstart) )
23 | except:
24 | self._q.put( (-1, traceback.format_exc()) )
25 |
26 | def __call__(self, *args, **kwargs):
27 | # runs on a child process to terminate execution if timeout exceedes
28 | try:
29 | import multiprocessing
30 | try:
31 | from Queue import Empty
32 | except:
33 | from queue import Empty
34 |
35 | self._q = multiprocessing.Queue()
36 | p = multiprocessing.Process(target=self.run, args=args, kwargs=kwargs)
37 | p.start()
38 | rval = self._q.get(block=True, timeout=self.timeout)
39 | p.join()
40 | if rval[0] == -1:
41 | raise RuntimeError(rval[1])
42 |
43 | except Empty:
44 | p.terminate()
45 | raise RuntimeError('Processing time exceeded (%f)' % self.timeout)
46 |
47 | return rval[1]
48 |
49 |
50 | class DaskController(ParallelController):
51 | def __init__(self):
52 | self.client = None
53 | super(DaskController, self).__init__()
54 |
55 | def setup(self, cfg):
56 | if 'ip' not in cfg:
57 | pass
58 | # TODO: check some kind of default file
59 | self.client = Client(cfg['ip'])
60 |
61 | def map(self, parallel_task, args):
62 | futures = self.client.map(parallel_task, args)
63 | results = []
64 | for f in futures:
65 | results.append(f.result())
66 | return results
67 |
68 | def reduce(self, reduce_task, outs):
69 | return reduce_task(outs)
70 |
--------------------------------------------------------------------------------
/igm/parallel/parallel_controller.py:
--------------------------------------------------------------------------------
1 | '''
2 | The reduce function should always accept an iterable
3 | The map function should always return an iterable
4 | '''
5 | from tqdm import tqdm
6 |
7 | class ParallelController(object):
8 |
9 | def __init__(self):
10 | """
11 | A parallel controller that map parallel jobs into workers
12 | """
13 |
14 | def setup(self):
15 | pass
16 |
17 | def map(self, parallel_task, args):
18 | raise NotImplementedError()
19 |
20 | def reduce(self, reduce_task, outs):
21 | return reduce_task(outs)
22 |
23 | def map_reduce(self, parallel_task, reduce_task, args):
24 | return self.reduce(reduce_task, self.map(parallel_task, args))
25 |
26 | def teardown(self):
27 | pass
28 |
29 | class SerialController(ParallelController):
30 | def map(self, parallel_task, args):
31 | return [parallel_task(a) for a in tqdm(args, desc="(SERIAL)")]
32 |
33 |
34 |
35 | def map_reduce(parallel_task, reduce_function, args, controller):
36 | controller.setup()
37 | result = controller.map_reduce(parallel_task, reduce_function, args)
38 | controller.teardown()
39 | return result
40 |
--------------------------------------------------------------------------------
/igm/parallel/slurm_controller.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function, division
2 |
3 | from six import raise_from
4 |
5 | import os
6 | import time
7 | from .parallel_controller import ParallelController
8 | from tqdm import tqdm
9 | from .utils import split_evenly
10 | import subprocess
11 | import shutil
12 |
13 |
14 | import cloudpickle
15 | from uuid import uuid4
16 |
17 | base_template = '''#!/bin/bash
18 | #SBATCH --ntasks=1
19 | #SBATCH --mem-per-cpu={{mem}}
20 | #SBATCH --time={{walltime}}
21 | #SBATCH --partition=cmb
22 | #SBATCH --job-name={{jname}}
23 | #SBATCH --output={{out}}
24 | #SBATCH --error={{err}}
25 |
26 | echo $(which python)
27 | #source /auto/cmb-08/fa/local/setup.sh
28 | umask 002
29 |
30 | {{interpreter}} -c 'from igm.parallel.slurm_controller import SlurmController; SlurmController.execute("{{sfile}}", {{i}})'
31 |
32 | '''
33 |
34 | default_args = {
35 | 'mem' : '2GB',
36 | 'walltime' : '6:00:00',
37 | 'interpreter' : 'python'
38 | }
39 |
40 | def parse_template(template, **kwargs):
41 | for k, v in kwargs.items():
42 | template = template.replace('{{' + k + '}}', v)
43 | return template
44 |
45 | class SlurmController(ParallelController):
46 | def __init__(self, template=None, max_tasks=4000, tmp_dir='tmp', simultaneous_tasks=430, **kwargs):
47 |
48 | sargs = {}
49 | sargs.update(default_args)
50 | sargs.update(kwargs)
51 |
52 | self.max_tasks = max_tasks
53 | self.sim_tasks = simultaneous_tasks
54 | if template is not None:
55 | self.template = open(template).read()
56 | else:
57 | self.template = base_template
58 | self.template = parse_template(self.template, **sargs)
59 | if not os.path.isdir(tmp_dir):
60 | os.makedirs(tmp_dir)
61 | self.tmp_dir = os.path.abspath(tmp_dir)
62 |
63 |
64 | def send_job(self, outd, i):
65 | slurmscript = os.path.join(outd, '%d.slurm' % i)
66 | with open(slurmscript, 'w') as f:
67 | f.write(
68 | parse_template(
69 | self.template,
70 | i=str(i),
71 | jname=str(i),
72 | out=os.path.join(outd, '%d.out' % i),
73 | err=os.path.join(outd, '%d.err' % i)
74 | )
75 | )
76 |
77 | # writes "Submitted batch job XXXXXXX"
78 | jid = subprocess.check_output(['sbatch', slurmscript]).split()[3]
79 | return jid
80 |
81 | def job_was_successful(self, outd, i):
82 | if os.path.isfile(os.path.join(outd, '%d.complete' % i )):
83 | return True
84 | return False
85 |
86 | def job_is_completed(self, outd, i, jid):
87 | try:
88 | out = subprocess.check_output(['squeue', '-j', jid]).decode('utf-8').split('\n')
89 | keys, vals, _ = out
90 | kv = {k: v for k, v in zip(keys.split(), vals.split())}
91 | if kv['ST'] == 'CD':
92 | return True
93 | else:
94 | return False
95 | except (subprocess.CalledProcessError, ValueError):
96 | if not self.job_was_successful(outd, i):
97 | raise_from(RuntimeError('(SLURM): Remote error. Error file:' + os.path.join(outd, '%d.err' % i)), None)
98 | return True
99 | return False
100 |
101 |
102 | def poll_loop(self, n_tasks, outd, timeout=1):
103 | to_send = set(range(n_tasks))
104 | processing = dict()
105 |
106 | while len(to_send) > 0 or len(processing) > 0 :
107 | just_completed = set()
108 | for i, jid in processing.items():
109 | if self.job_is_completed(outd, i, jid):
110 | just_completed.add(i)
111 | yield i
112 |
113 | while len(processing) < self.sim_tasks and len(to_send) > 0:
114 | i = to_send.pop()
115 | processing[i] = self.send_job(outd, i)
116 |
117 | for i in just_completed:
118 | del processing[i]
119 |
120 | time.sleep(timeout)
121 |
122 | raise StopIteration
123 |
124 |
125 | def map(self, parallel_task, args):
126 | uid = 'slurmc.' + str(uuid4())
127 | outd = os.path.join(self.tmp_dir, uid)
128 | os.makedirs(outd)
129 |
130 | batches = list(split_evenly(args, self.max_tasks))
131 |
132 | sfile = os.path.join(outd, 'exdata.cloudpickle')
133 | with open(sfile, 'wb') as f:
134 | cloudpickle.dump({'f': parallel_task, 'args': batches, 'outd': outd}, f)
135 |
136 | self.template = parse_template(self.template, sfile=sfile)
137 |
138 | n_tasks = len(batches)
139 |
140 | ar = self.poll_loop(n_tasks, outd)
141 | for i in tqdm(ar, desc="(SLURM)", total=n_tasks):
142 | pass
143 | shutil.rmtree(outd)
144 |
145 | @staticmethod
146 | def execute(sfile, i):
147 | v = cloudpickle.load(open(sfile, 'rb'))
148 | for x in v['args'][i]:
149 | v['f'](x)
150 | open(os.path.join(v['outd'], '%d.complete' % i), 'w').close()
151 |
--------------------------------------------------------------------------------
/igm/parallel/utils.py:
--------------------------------------------------------------------------------
1 | def batch(sequence, n=1):
2 | l = len(sequence)
3 | for ndx in range(0, l, n):
4 | yield sequence[ndx:min(ndx + n, l)]
5 |
6 | def split_evenly(sequence, n=1):
7 | l = len(sequence)
8 | if l == 0:
9 | return []
10 | n = min(l, n)
11 |
12 | k = l // n
13 | if l % n != 0:
14 | k += 1
15 |
16 | for ndx in range(0, l, k):
17 | yield sequence[ndx:min(ndx + k, l)]
--------------------------------------------------------------------------------
/igm/report/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alberlab/igm/edbb33135260443b8b41eb09d7ca4c37b19e330d/igm/report/__init__.py
--------------------------------------------------------------------------------
/igm/report/damid.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import matplotlib.pyplot as plt
3 | import logging
4 | import traceback
5 | from alabtools import HssFile
6 | from scipy.stats import pearsonr
7 | from .utils import create_folder, snormsq_ellipse
8 |
9 |
10 | # noinspection PyTypeChecker
11 | def report_damid(hssfname, damid_file, contact_range, semiaxes=None, run_label=''):
12 | logger = logging.getLogger("DamID")
13 | logger.info('Executing DamID report')
14 |
15 | if run_label:
16 | run_label = '-' + run_label
17 | try:
18 | create_folder("damid")
19 |
20 | with HssFile(hssfname, 'r') as hss:
21 | index = hss.index
22 | radii = hss.radii
23 | if semiaxes is None:
24 | # see if we have information about semiaxes in the file
25 | try:
26 | semiaxes = hss['envelope']['params'][()]
27 | if len(semiaxes.shape) == 0: # is scalar
28 | semiaxes = np.array([semiaxes, semiaxes, semiaxes])
29 | except KeyError:
30 | semiaxes = np.array([5000., 5000., 5000.])
31 |
32 | out_damid_prob = np.zeros(len(index.copy_index))
33 | for locid in index.copy_index.keys():
34 | ii = index.copy_index[locid]
35 | n_copies = len(ii)
36 |
37 | r = radii[ii[0]]
38 |
39 | # rescale pwish considering the number of copies
40 | # pwish = np.clip(pwish/n_copies, 0, 1)
41 |
42 | d_sq = np.empty(n_copies * hss.nstruct)
43 |
44 | for i in range(n_copies):
45 | x = hss.get_bead_crd(ii[i])
46 | nuc_rad = np.array(semiaxes) * (1 - contact_range)
47 | d_sq[i * hss.nstruct:(i + 1) * hss.nstruct] = snormsq_ellipse(x, nuc_rad, r)
48 |
49 | contact_count = np.count_nonzero(d_sq >= 1)
50 | out_damid_prob[locid] = float(contact_count) / hss.nstruct / n_copies
51 | np.savetxt('damid/output.txt', out_damid_prob)
52 |
53 | if damid_file:
54 | damid_profile = np.loadtxt(damid_file, dtype='float32')
55 | np.savetxt(f'damid/input{run_label}.txt', damid_profile)
56 | fig = plt.figure(figsize=(4, 4))
57 | plt.title(f'DamID{run_label}')
58 | vmax = max(np.nanmax(damid_profile), np.nanmax(out_damid_prob))
59 | vmin = min(np.nanmin(damid_profile), np.nanmin(out_damid_prob))
60 | corr = pearsonr(damid_profile, out_damid_prob)
61 | np.savetxt(f'damid/pearsonr{run_label}.txt', corr)
62 | plt.scatter(damid_profile, out_damid_prob, s=6)
63 | plt.xlim(vmin, vmax)
64 | plt.ylim(vmin, vmax)
65 | plt.text(vmin * 1.01, vmax * 0.95, f'pearson correlation: {corr[0]:.5f}')
66 | plt.plot([vmin, vmax], [vmin, vmax], 'k--')
67 | plt.xlabel('input')
68 | plt.ylabel('output')
69 | fig.savefig(f'damid/scatter{run_label}.pdf')
70 | fig.savefig(f'damid/scatter{run_label}.png')
71 | plt.close(fig)
72 | logger.info('Done.')
73 |
74 | except KeyboardInterrupt:
75 | logger.error('User interrupt. Exiting.')
76 | exit(1)
77 |
78 | except Exception:
79 | traceback.print_exc()
80 | logger.error('Error in DamID step\n==============================')
81 |
--------------------------------------------------------------------------------
/igm/report/distance_map.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from alabtools import HssFile
3 |
4 |
5 | def create_average_distance_map(hssfname):
6 | with HssFile(hssfname, 'r') as h:
7 | crd = h.coordinates
8 | index = h.index
9 | n = len(index.copy_index)
10 | fmap = np.empty((n, n))
11 | for i in range(n):
12 | for j in range(i + 1, n):
13 | aves = []
14 | if index.chrom[i] == index.chrom[j]:
15 | for k, m in zip(index.copy_index[i], index.copy_index[j]):
16 | aves.append(np.linalg.norm(crd[k] - crd[m], axis=1).mean())
17 | else:
18 | for k in index.copy_index[i]:
19 | for m in index.copy_index[j]:
20 | aves.append(np.linalg.norm(crd[k] - crd[m], axis=1).mean())
21 | fmap[i, j] = np.mean(aves)
22 | return fmap
23 |
--------------------------------------------------------------------------------
/igm/report/html_report.py:
--------------------------------------------------------------------------------
1 | import os
2 | import os.path as op
3 | import base64
4 | from alabtools.utils import natural_sort
5 |
6 |
7 | css = '''
8 | table {
9 | border: 1px solid grey;
10 | }
11 |
12 | td, th {
13 | text-align: center;
14 | padding: .3em 1em .3em 1em;
15 | }
16 |
17 | .folder {
18 | padding-left:1em;
19 | border-left:1px solid grey;
20 | }
21 |
22 | .lvl-1 {
23 | padding-left:0;
24 | border-left:0;
25 | }
26 |
27 | pre {
28 | border: 1px dashed grey;
29 | background-color: #eeeeee;
30 | }
31 |
32 | th {
33 | font-weight:bold;
34 | }
35 |
36 | .pfile {
37 | padding: 1em;
38 | border: 1px dotted #dddddd;
39 | }
40 |
41 | img {
42 | max-width: 70vw;
43 | max-height: 80vh;
44 | width: auto;
45 | height: auto;
46 | }
47 | '''
48 |
49 |
50 | def looks_like_a_table(filename):
51 | lines = open(filename).readlines()
52 | if len(lines) < 2:
53 | return False
54 | if not lines[0].startswith('#'):
55 | return False
56 | n = len(lines[1].split())
57 | for l in lines[2:]:
58 | if l.strip() == '':
59 | continue
60 | if len(l.split()) != n:
61 | return False
62 | return True
63 |
64 |
65 | def to_html_table(textfile, attrs=''):
66 | lines = open(textfile).readlines()
67 | headers = lines[0].replace('#', '').strip().split()
68 | thead = '' + ' | '.join(headers) + ' | '
69 | tbody = ''
70 | for l in lines[1:]:
71 | values = l.strip().split()
72 | tbody += '' + ' | '.join(values) + ' |
'
73 | tbody += ''
74 | return ''
75 |
76 |
77 | def handle_file(filename, lines):
78 | lines.append(f''
79 | f'{op.basename(filename)}
')
80 | _, ext = op.splitext(filename)
81 | if ext in ['.png', '.jpg', '.bmp', '.gif']:
82 | data_uri = base64.b64encode(
83 | open(filename, 'rb').read()).decode('utf-8').replace('\n', '')
84 | lines.append(f'
')
85 | elif ext == '.txt':
86 | # check if it is a table
87 | if looks_like_a_table(filename):
88 | lines.append(to_html_table(filename, 'border="1"'))
89 | else:
90 | content = open(filename).read()
91 | clines = content.split('\n')
92 | nlines = len(clines)
93 | if nlines > 40:
94 | preview_text = '\n'.join(clines[:8]) + f'\n... (+ {nlines - 8} other lines)'
95 | lines.append(f'{preview_text}
')
96 | else:
97 | lines.append(f'{content}
')
98 |
99 | else:
100 | lines.append('Preview not available.
')
101 |
102 |
103 | def sort_items(base, items):
104 | files = []
105 | folders = []
106 | for it in items:
107 | if op.isfile(op.join(base, it)):
108 | files.append(it)
109 | elif op.isdir(op.join(base, it)):
110 | folders.append(it)
111 | folders.sort()
112 | files = natural_sort(files)
113 | return folders + files
114 |
115 |
116 | def process_dir(folder, lvl, lines):
117 | items = sort_items(folder, os.listdir(folder))
118 | lines.append(f'')
119 | lines.append(f'
{op.basename(folder)}')
120 |
121 | for it in items:
122 | if op.isfile(op.join(folder, it)):
123 | lines.append('
')
124 | handle_file(op.join(folder, it), lines)
125 | lines.append(f'
')
126 | elif op.isdir(op.join(folder, it)):
127 | process_dir(op.join(folder, it), lvl+1, lines)
128 |
129 | lines.append(f'
')
130 |
131 |
132 | def generate_html_report(folder):
133 | """
134 | Scrapes the output folder and generates a single, navigable file.
135 | Parameters
136 | ----------
137 | folder : str
138 | """
139 |
140 | while folder.endswith('/'):
141 | folder = folder[:-1]
142 |
143 | # get the label
144 | try:
145 | with open('label.txt', 'r') as f:
146 | run_label = f.read()
147 | except IOError:
148 | run_label = ''
149 |
150 | if run_label == '':
151 | run_label = op.basename(folder)
152 |
153 | lines = []
154 | process_dir(folder, 1, lines)
155 | head = f'IGM Report: {op.basename(folder)}'
156 |
157 | header = f'{run_label}
{op.abspath(folder)}
'
158 |
159 | body = '' + header + '\n'.join(lines) + ''
160 | return head + body
161 |
--------------------------------------------------------------------------------
/igm/report/images.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import logging
3 | import traceback
4 | from alabtools import HssFile
5 |
6 | from .utils import create_folder
7 |
8 |
9 | def render_structures(hssfname, n=4, random=True):
10 | logger = logging.getLogger('Render')
11 | logger.info('Starting render... (it may take a while)')
12 |
13 | try:
14 | create_folder('images')
15 | with HssFile(hssfname, 'r') as h:
16 |
17 | if random:
18 | ii = np.random.choice(range(h.nstruct), size=n, replace=False)
19 | else:
20 | ii = list(range(min(n, h.nstruct)))
21 |
22 | h.dump_pdb(ii, fname='images/structure_%d.pdb', render=True, high_quality=True)
23 | logger.info('Done.')
24 |
25 | except KeyboardInterrupt:
26 | logger.error('User interrupt. Exiting.')
27 | exit(1)
28 |
29 | except Exception:
30 | traceback.print_exc()
31 | logger.error('Error in rendering step\n==============================')
32 |
33 |
--------------------------------------------------------------------------------
/igm/report/plots.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import matplotlib.pyplot as plt
3 | from matplotlib.colors import LogNorm
4 | from scipy.ndimage import gaussian_filter
5 |
6 |
7 | def logloghist2d(d1, d2, bins=(100, 100), ranges=((1e-3, 1), (1e-3, 1)), outfile=None, vmin=1e2, vmax=1e5, nlevels=5,
8 | sigma=None, xlabel='in', ylabel='out', smooth=None, **kwargs):
9 |
10 | if ranges[0] is None:
11 | x0, x1 = np.min(d1), np.max(d1)
12 | else:
13 | x0, x1 = ranges[0]
14 |
15 | if ranges[1] is None:
16 | y0, y1 = np.min(d2), np.max(d2)
17 | else:
18 | y0, y1 = ranges[1]
19 |
20 | xx = np.logspace(np.log10(x0), np.log10(x1), bins[0] + 1, base=10)
21 | yy = np.logspace(np.log10(y0), np.log10(y1), bins[1] + 1, base=10)
22 | bottom_left = max(xx[0], yy[0])
23 | top_right = min(xx[-1], yy[-1])
24 |
25 | # Note: we invert axes, because in a plot, the x axis actually correspond to
26 | # columns, while the y to rows. np.histogram2d puts the value from the first dataset on
27 | # rows, and the ones from the second in columns.
28 | h, e1, e2 = np.histogram2d(d2, d1, bins=(yy, xx))
29 |
30 | # Smooth histogram with a gaussian kernel for visualization purposes
31 | if smooth:
32 | h = gaussian_filter(h, **smooth)
33 |
34 | # pad the histogram, so that we can extend the image to fit the whole space, and set the x and
35 | # y axes point to the midpoints in the plot (in this case we need to take into account the log
36 | # scaling of the axis)
37 | lex = np.log(e1)
38 | ley = np.log(e2)
39 | dx = lex[1] - lex[0]
40 | dy = ley[1] - ley[0]
41 | midx = np.exp(np.arange(lex[0] - dx / 2, lex[-1] + dx, dx))
42 | midy = np.exp(np.arange(ley[0] - dy / 2, ley[-1] + dy, dy))
43 |
44 | h = np.pad(h, 1, 'edge')
45 |
46 | f = plt.figure(figsize=(5, 5))
47 | # p = plt.pcolormesh(xx, yy, h, **kwargs)
48 | grid_x, grid_y = np.meshgrid(midx, midy)
49 | q = h.copy()
50 |
51 | # set the maximum histogram value to the cutoff, or we will have blank areas in the contour plots
52 | h[h > vmax] = vmax
53 |
54 | levels = np.logspace(np.log10(vmin), np.log10(vmax), nlevels, base=10)
55 | p = plt.contourf(grid_x, grid_y, h, norm=LogNorm(vmin, vmax), cmap='Reds', levels=levels, **kwargs)
56 | if sigma is not None:
57 | plt.axvline(x=sigma, ls='--', c='#dddddd')
58 |
59 | plt.plot([bottom_left, top_right], [bottom_left, top_right], 'k--')
60 | plt.xscale('log')
61 | plt.yscale('log')
62 | plt.xlim(xx[0], xx[-1])
63 | plt.ylim(yy[0], yy[-1])
64 | plt.xlabel(xlabel)
65 | plt.ylabel(ylabel)
66 | plt.tight_layout()
67 | if outfile is not None:
68 | plt.savefig(outfile)
69 | return f, p, q, e1, e2
70 |
71 |
72 | def density_histogram_2d(d1, d2, bins=(100, 100), ranges=((1e-3, 1), (1e-3, 1)),
73 | outfile=None, vmin=1e1, vmax=1e5, nlevels=5,
74 | xlabel='in', ylabel='out', smooth=None, **kwargs):
75 |
76 | if ranges is None:
77 | ranges = (None, None)
78 |
79 | if ranges[0] is None:
80 | x0, x1 = np.min(d1), np.max(d1)
81 | else:
82 | x0, x1 = ranges[0]
83 |
84 | if ranges[1] is None:
85 | y0, y1 = np.min(d2), np.max(d2)
86 | else:
87 | y0, y1 = ranges[1]
88 |
89 | xx = np.linspace(x0, x1, bins[0])
90 | yy = np.linspace(y0, y1, bins[1])
91 |
92 | bottom_left = max(xx[0], yy[0])
93 | top_right = min(xx[-1], yy[-1])
94 |
95 | # Note: we invert axes, because in a plot, the x axis actually correspond to
96 | # columns, while the y to rows. np.histogram2d puts the value from the first dataset on
97 | # rows, and the ones from the second in columns.
98 | h, e1, e2 = np.histogram2d(d2, d1, bins=(yy, xx))
99 |
100 | # Smooth histogram with a gaussian kernel for visualization purposes
101 | if smooth:
102 | h = gaussian_filter(h, **smooth)
103 |
104 | # pad the histogram, so that we can extend the image to fit the whole space, and set the x and
105 | # y axes point to the midpoints in the plot
106 | lex = e1
107 | ley = e2
108 | dx = lex[1] - lex[0]
109 | dy = ley[1] - ley[0]
110 | midx = np.arange(lex[0] - dx / 2, lex[-1] + dx, dx)
111 | midy = np.arange(ley[0] - dy / 2, ley[-1] + dy, dy)
112 |
113 | h = np.pad(h, 1, 'edge')
114 |
115 | f = plt.figure(figsize=(5, 5))
116 | # p = plt.pcolormesh(xx, yy, h, **kwargs)
117 | grid_x, grid_y = np.meshgrid(midx, midy)
118 | q = h.copy()
119 |
120 | h[h > vmax] = vmax
121 | levels = np.logspace(np.log10(vmin), np.log10(vmax), nlevels, base=10)
122 | p = plt.contourf(grid_x, grid_y, h, norm=LogNorm(vmin, vmax),
123 | cmap='Blues', levels=levels, **kwargs)
124 | plt.plot([bottom_left, top_right], [bottom_left, top_right], 'k--')
125 | plt.xlim(xx[0], xx[-1])
126 | plt.ylim(yy[0], yy[-1])
127 | plt.xlabel(xlabel)
128 | plt.ylabel(ylabel)
129 | plt.tight_layout()
130 | if outfile is not None:
131 | plt.savefig(outfile)
132 | return f, p, q, e1, e2
133 |
--------------------------------------------------------------------------------
/igm/report/radials.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import matplotlib.pyplot as plt
3 | from matplotlib.cm import get_cmap
4 | from matplotlib.colors import Colormap
5 | from matplotlib.patches import Circle
6 | import logging
7 | import traceback
8 | import os.path
9 | from alabtools import HssFile
10 | from alabtools.plots import plot_by_chromosome
11 |
12 | from .utils import create_folder, average_copies
13 |
14 |
15 | def get_radial_level(crd, index, semiaxes):
16 | '''
17 | Use coordinates in bead-major format to extract average radial levels.
18 | Levels are defined for a point (x, y, z) as the square root of
19 | (x/a)^2 + (y/b)^2 + (z/c)^2
20 | where a, b, c are the three semiaxes.
21 | That generalizes to the fraction of the nucleus radius for spheres
22 |
23 | Parameters
24 | ----------
25 | crd: np.ndarray
26 | coordinates in bead-major format (n_beads x n_structures x 3)
27 | index: alabtools.Index
28 | index of genomic locations
29 | semiaxes: np.ndarray
30 | the 3 semiaxes of the envelope
31 | '''
32 |
33 | semiaxes = np.array(semiaxes)
34 |
35 | radials = np.array([
36 | np.sqrt(np.sum(np.square(crd[i] / semiaxes), axis=1)).mean() for i in range(len(index))
37 | ])
38 |
39 | return average_copies(radials, index)
40 |
41 |
42 | def radial_plot_p(edges, val, cmap='Greys', **kwargs):
43 | '''
44 | Plots radial densities on a sphere, colorcoded
45 | '''
46 | fig = plt.figure()
47 | ax = fig.gca()
48 | vmax = kwargs.get('vmax', max(val))
49 | vmin = kwargs.get('vmin', min(val))
50 | maxe = edges[-1]
51 | plt.axis('equal')
52 | plt.xlim(-maxe, maxe)
53 | plt.ylim(-maxe, maxe)
54 |
55 | if not isinstance(cmap, Colormap):
56 | cmap = get_cmap(cmap)
57 |
58 | def get_color(v):
59 | rng = vmax - vmin
60 | d = np.clip((v - vmin) / rng, 0, 0.999)
61 | idx = int(d * cmap.N)
62 | return cmap.colors[idx]
63 |
64 | for i in reversed(range(len(val))):
65 | c = Circle((0, 0), edges[i + 1], facecolor=get_color(val[i]))
66 | ax.add_patch(c)
67 |
68 |
69 | def plot_radial_density(hssfname, semiaxes, n=11, vmax=1.1, run_label=''):
70 |
71 | with HssFile(hssfname, 'r') as hss:
72 |
73 | crd = hss.coordinates.reshape((hss.nstruct * hss.nbead, 3))
74 | radials = np.sqrt(np.sum(np.square(crd / semiaxes), axis=1))
75 |
76 | counts, edges = np.histogram(radials, bins=n, range=(0, vmax))
77 | volumes = np.array([edges[i + 1]**3 - edges[i]**3 for i in range(n)])
78 | fig = plt.figure()
79 | plt.title(f'Radial density distribution {run_label}')
80 | plt.bar(np.arange(n) + 0.5, height=counts / volumes, width=1)
81 | plt.xticks(range(n + 1), ['{:.2f}'.format(x) for x in edges], rotation=60)
82 | plt.tight_layout()
83 | fig.savefig(f'radials/density_histo{run_label}.pdf')
84 | fig.savefig(f'radials/density_histo{run_label}.png')
85 | plt.close(fig)
86 |
87 | np.savetxt(f'radials/density_histo{run_label}.txt', counts / volumes)
88 |
89 |
90 | def report_radials(hssfname, semiaxes=None, run_label=''):
91 | if run_label:
92 | run_label = '-' + run_label
93 | logger = logging.getLogger('Radials')
94 | logger.info('Executing Radials report...')
95 | try:
96 | create_folder("radials")
97 | with HssFile(hssfname, 'r') as hss:
98 | index = hss.index
99 | if semiaxes is None:
100 | # see if we have information about semiaxes in the file
101 | try:
102 | semiaxes = hss['envelope']['params'][()]
103 | if len(semiaxes.shape) == 0: # is scalar
104 | semiaxes = np.array([semiaxes, semiaxes, semiaxes])
105 | except KeyError:
106 | semiaxes = np.array([5000., 5000., 5000.])
107 | radials = get_radial_level(hss.coordinates, index, semiaxes)
108 | np.savetxt(f'radials/radials{run_label}.txt', radials)
109 | fig, _ = plot_by_chromosome(radials, index.get_haploid(), vmin=.4, vmax=1.0,
110 | suptitle=f'Radial position per bead {run_label}')
111 |
112 | fig.savefig(f'radials/radials{run_label}.pdf')
113 | fig.savefig(f'radials/radials{run_label}.png')
114 |
115 | plt.close(fig)
116 |
117 | plot_radial_density(hssfname, semiaxes, run_label=run_label)
118 |
119 | if os.path.isfile(f'shells/ave_radial{run_label}.txt'):
120 | logger.info('Note: normalizing with respect to last shell')
121 | n = np.loadtxt(f'shells/ave_radial{run_label}.txt')[-1]
122 | np.savetxt(f'radials/radials_norm{run_label}.txt', radials / n)
123 | fig, _ = plot_by_chromosome(radials / n, index.get_haploid(), vmin=.4, vmax=1.0)
124 | fig.savefig(f'radials/radials_norm{run_label}.pdf')
125 | fig.savefig(f'radials/radials_norm{run_label}.png')
126 | plt.close(fig)
127 | logger.info('Done.')
128 |
129 | except KeyboardInterrupt:
130 | logger.error('User interrupt. Exiting.')
131 | exit(1)
132 |
133 | except Exception:
134 | traceback.print_exc()
135 | logger.error('Error in radials step\n==============================')
136 |
--------------------------------------------------------------------------------
/igm/report/radius_of_gyration.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import matplotlib.pyplot as plt
3 | import logging
4 | import traceback
5 | from alabtools import HssFile
6 | from .utils import create_folder
7 |
8 |
9 | def rg(points):
10 | '''
11 | Get the radius of gyration for a set of points
12 | :param points: np.ndarray
13 | set of points (n_points x 3)
14 | :return: float
15 | Rg(points)
16 | '''
17 | ave = np.average(points, axis=0)
18 | v = points - ave
19 | return np.sqrt(np.sum(np.square(v)) / len(v))
20 |
21 |
22 | def get_chroms_rgs(crds, index):
23 | '''
24 | Get the average radius of gyration for each chromosome chain in the
25 | index. Returns a list of size n_struct*n_copies for each chromosome.
26 | :param crds: np.ndarray
27 | coordinates in bead-major format
28 | :param index: alabtools.Index
29 | index of genomic locations
30 | :return: list[np.ndarray]
31 | '''
32 | rgs = []
33 | for chrom in index.get_chromosomes():
34 | copies = index.get_chrom_copies(chrom)
35 | data = list()
36 | for copy in copies:
37 | ii = index.get_chrom_pos(chrom, copy)
38 | for crd in crds[ii, :].swapaxes(0, 1):
39 | data.append(rg(crd))
40 | rgs.append(np.array(data))
41 | return rgs
42 |
43 |
44 | def set_box_colors(bp, clr):
45 | if not isinstance(clr, (list, tuple, np.ndarray)):
46 | clr = [clr] * len(bp['boxes'])
47 | for i in range(len(clr)):
48 | plt.setp(bp['boxes'][i], facecolor=clr[i])
49 | plt.setp(bp['medians'][i], color='black')
50 |
51 |
52 | def boxplots_group(data, group_labels, n_per_row=6, subplot_width=10,
53 | subplot_height=2.5, vmin=800, vmax=3500, outfile=None,
54 | title='', color='#fb7b04'):
55 | '''
56 | Splits a large number of boxex across multiple rows
57 | '''
58 | n_groups = len(group_labels)
59 | n_rows = n_groups // n_per_row if n_groups % n_per_row == 0 else n_groups // n_per_row + 1
60 | f, plots = plt.subplots(n_rows, 1, figsize=(subplot_width, subplot_height * n_rows), sharey=True)
61 | f.suptitle(title)
62 | for ip, i in enumerate(range(0, n_groups, n_per_row)):
63 |
64 | # select data subset
65 | boxdata = data[i:i + n_per_row]
66 | plots[ip].set_ylim(vmin, vmax)
67 | bp = plots[ip].boxplot(boxdata, labels=group_labels[i:i + n_per_row], patch_artist=True, showfliers=False)
68 | set_box_colors(bp, color)
69 |
70 | plt.tight_layout(rect=[0, 0.03, 1, 0.95])
71 | if outfile is not None:
72 | plt.savefig(outfile)
73 | return f, plots
74 |
75 |
76 | def report_radius_of_gyration(hssfname, run_label=''):
77 |
78 | if run_label:
79 | run_label = '-' + run_label
80 |
81 | logger = logging.getLogger('GyrRadius')
82 | try:
83 |
84 | logger.info('Executing Radius of Gyration report...')
85 | create_folder("radius_of_gyration")
86 | with HssFile(hssfname, 'r') as hss:
87 | chroms = hss.genome.chroms
88 | rgs = get_chroms_rgs(hss.coordinates, hss.index)
89 | np.savez(f'radius_of_gyration/chromosomes{run_label}.npz',
90 | **{c: arr for c, arr in zip(hss.genome.chroms, rgs)})
91 | fig, _ = boxplots_group(rgs, chroms,
92 | title=f'Chromosomes Radii of Gyration {run_label}',
93 | outfile=f'radius_of_gyration/rgs{run_label}.pdf')
94 | fig.savefig(f'radius_of_gyration/rgs{run_label}.png')
95 | plt.close(fig)
96 | logger.info('Done.')
97 |
98 | except KeyboardInterrupt:
99 | logger.error('User interrupt. Exiting.')
100 | exit(1)
101 |
102 | except Exception:
103 | traceback.print_exc()
104 | logger.error('Error in radius of gyration step\n==============================')
105 |
106 |
--------------------------------------------------------------------------------
/igm/report/shells.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import matplotlib.pyplot as plt
3 | import logging
4 | import traceback
5 | from alabtools import HssFile
6 | from .utils import create_folder
7 |
8 |
9 | def report_shells(hssfname, semiaxes=None, nshell=5, hvmax=1.5, hnbins=150, run_label=''):
10 | if run_label:
11 | run_label = '-' + run_label
12 | logger = logging.getLogger('Shells')
13 |
14 | try:
15 | logger.info('Executiong Shells report')
16 | create_folder("shells")
17 | with HssFile(hssfname, 'r') as hss:
18 | crd = np.swapaxes(hss.coordinates, 0, 1)
19 | if semiaxes is None:
20 | # see if we have information about semiaxes in the file
21 | try:
22 | semiaxes = hss['envelope']['params'][()]
23 | if len(semiaxes.shape) == 0: # is scalar
24 | semiaxes = np.array([semiaxes, semiaxes, semiaxes])
25 | except KeyError:
26 | semiaxes = np.array([5000., 5000., 5000.])
27 |
28 | n = hss.nbead
29 | kth = [int(k * n / nshell) for k in range(0, nshell)]
30 | bds = kth + [n]
31 | ave_shell_rad = np.empty((hss.nstruct, nshell))
32 | pos_histos = np.zeros((nshell, hnbins))
33 | edges = None
34 | for i in range(hss.nstruct):
35 | radials = np.sqrt(np.sum(np.square(crd[i] / semiaxes), axis=1))
36 | radials = np.partition(radials, kth)
37 | for j in range(nshell):
38 | h, edges = np.histogram(radials[bds[j]:bds[j + 1]], bins=hnbins,
39 | range=(0, hvmax))
40 | pos_histos[j] += h
41 | ave_shell_rad[i][j] = np.average(radials[bds[j]:bds[j + 1]])
42 |
43 | np.savetxt(f'shells/ave_radial{run_label}.txt', np.average(ave_shell_rad, axis=0))
44 | midpoints = (edges[:-1] + edges[1:]) / 2
45 | fig = plt.figure()
46 | plt.title('Radial position distributions per shell')
47 | for j in range(nshell):
48 | plt.bar(midpoints, height=pos_histos[j], alpha=.6, width=hvmax/hnbins, label='shell {:d}'.format(j+1))
49 | plt.legend()
50 | fig.savefig(f'shells/positions_histograms_by_shell{run_label}.pdf')
51 | fig.savefig(f'shells/positions_histograms_by_shell{run_label}.png')
52 | plt.close(fig)
53 | logger.info('Done.')
54 |
55 | except KeyboardInterrupt:
56 | logger.error('User interrupt. Exiting.')
57 | exit(1)
58 |
59 | except Exception:
60 | traceback.print_exc()
61 | logger.error('Error in shells step\n==============================')
62 |
--------------------------------------------------------------------------------
/igm/report/utils.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import os.path
3 |
4 |
5 | def aggregate_copies(v, index, fun=np.nanmean, *args, **kwargs):
6 | '''
7 | Aggregate genomic regions if more copies are present
8 | :param v: np.ndarray
9 | the values for all the regions/beads
10 | :param index: alabtools.Index
11 | the index
12 | :param fun: callable
13 | the function to apply (default: np.nanmean). It should take one vector
14 | and return a scalar
15 | :return: np.ndarray
16 | '''
17 | v = np.array(v)
18 | assert len(v) == len(index)
19 | ci = index.copy_index
20 | x = np.zeros(len(ci))
21 | for i in range(len(ci)):
22 | x[i] = fun(v[ci[i]], *args, **kwargs)
23 | return x
24 |
25 |
26 | def average_copies(v, index):
27 | return aggregate_copies(v, index)
28 |
29 |
30 | def sum_copies(v, index):
31 | return aggregate_copies(v, index, np.sum)
32 |
33 |
34 | def create_folder(folder):
35 | if not os.path.isdir(folder):
36 | os.makedirs(folder)
37 |
38 |
39 | def snormsq_ellipse(x, semiaxes, r=0):
40 | '''
41 | Returns the level of a point inside an ellipsoid
42 | (0 -> center, ]0, 1[ -> inside, 1 -> surface, >1 outside).
43 | :param x: np.ndarray
44 | the coordinates of the point
45 | :param semiaxes: np.ndarray
46 | the 3 semiaxes of the ellipsoid
47 | :param r: float
48 | if specified, the point is considered a sphere and level=1 means
49 | the surface is touching the ellipsoid.
50 | :return:
51 | '''
52 | a, b, c = np.array(semiaxes) - r
53 | sq = np.square(x)
54 | return sq[:, 0] / (a**2) + sq[:, 1] / (b**2) + sq[:, 2] / (c**2)
55 |
--------------------------------------------------------------------------------
/igm/report/violations.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from matplotlib.patches import Rectangle
3 | from matplotlib.ticker import PercentFormatter
4 | import matplotlib.pyplot as plt
5 | import logging
6 | import traceback
7 | import json
8 | from alabtools import HssFile
9 |
10 | from .utils import create_folder
11 |
12 |
13 | def plot_violation_histogram(h, edges, tol=0.05, nticks=20, title='', figsize=(10, 4), outfile=None, log=False):
14 | fig = plt.figure(figsize=figsize)
15 | step = edges[1] - edges[0]
16 | tick_step = int(len(edges) / nticks)
17 |
18 | if log:
19 | z = h.copy()
20 | for i in range(len(h)):
21 | if h[i] > 0:
22 | z[i] = np.log(h[i])
23 | h = z
24 | else:
25 | # transform to percentage
26 | totsum = np.sum(h)
27 | h = h / totsum
28 |
29 | xx = np.arange(len(h) - 1) + 0.5
30 | xx = np.concatenate([xx, [len(h) + tick_step + 0.5]])
31 |
32 | tick_pos = list(range(len(edges))[::tick_step])
33 | tick_labels = ['{:.2f}'.format(edges[i]) for i in tick_pos]
34 | tick_pos.append(len(h) + tick_step + 0.5)
35 | tick_labels.append('>{:.2f}'.format(edges[-2]))
36 |
37 | # ignore the first bin to determine height
38 | # vmax = np.max(h[1:]) * 1.1
39 | vmax = max(np.max(h) * 1.05, 1)
40 | plt.title(title)
41 |
42 | plt.xlabel('Relative restraint violation')
43 | if log:
44 | plt.ylabel('Restraints count (Log)')
45 | else:
46 | plt.ylabel('Percentage of restraints')
47 | plt.gca().yaxis.set_major_formatter(PercentFormatter(1.0))
48 |
49 | plt.axvline(x=tol / step, ls='--', c='green')
50 | plt.gca().add_patch(Rectangle((tol / step, 0), width=tick_pos[-1] - tol / step + tick_step,
51 | height=vmax, fill=True, facecolor='darkred', alpha=.3))
52 | plt.ylim(0, vmax)
53 |
54 | plt.bar(xx, height=h, width=1, color='grey')
55 | plt.xticks(tick_pos, tick_labels, rotation=60)
56 | plt.xlim(0, tick_pos[-1] + tick_step)
57 | plt.tight_layout()
58 | if outfile is not None:
59 | plt.savefig(outfile)
60 | return fig
61 |
62 |
63 | def report_violations(hssfname, violation_tolerance, run_label=''):
64 | logger = logging.getLogger('Violations')
65 | logger.info('Executing violation report...')
66 | if run_label:
67 | run_label = '-' + run_label
68 | try:
69 |
70 | create_folder('violations')
71 |
72 | with HssFile(hssfname, 'r') as hss:
73 | stats = json.loads(hss['summary'][()])
74 |
75 | # save a copy of the data
76 | with open(f'violations/stats{run_label}.json', 'w') as f:
77 | json.dump(stats, f, indent=4)
78 |
79 | with open(f'violations/restraints_summary{run_label}.txt', 'w') as f:
80 | f.write('# type imposed violated\n')
81 | f.write('"all" {} {}\n'.format(
82 | stats['n_imposed'],
83 | stats['n_violations'],
84 | ))
85 | for k, ss in stats['byrestraint'].items():
86 | f.write('"{}" {} {}\n'.format(
87 | k,
88 | ss['n_imposed'],
89 | ss['n_violations'],
90 | ))
91 |
92 | create_folder('violations/histograms')
93 |
94 | h = stats['histogram']['counts']
95 | edges = stats['histogram']['edges']
96 | fig = plot_violation_histogram(h, edges, violation_tolerance, nticks=10,
97 | title="Histogram of all Violations",
98 | outfile=f"violations/histograms/summary{run_label}.pdf")
99 | fig.savefig(f"violations/histograms/summary{run_label}.png")
100 | plt.close(fig)
101 |
102 | fig = plot_violation_histogram(h, edges, violation_tolerance, nticks=10, log=True,
103 | title="Histogram of all Violations (Log)",
104 | outfile=f"violations/histograms/summary_log-{run_label}.pdf")
105 | fig.savefig(f"violations/histograms/summary_log{run_label}.png")
106 | plt.close(fig)
107 |
108 | for k, v in stats['byrestraint'].items():
109 | h = v['histogram']['counts']
110 | fig = plot_violation_histogram(h, edges, violation_tolerance, nticks=10,
111 | title='Histogram of Violations for ' + k,
112 | outfile=f"violations/histograms/{k}{run_label}.pdf")
113 | fig.savefig(f"violations/histograms/{k}{run_label}.png")
114 | plt.close(fig)
115 |
116 | fig = plot_violation_histogram(h, edges, violation_tolerance, nticks=10, log=True,
117 | title='Histogram of Violations (Log) for ' + k,
118 | outfile=f"violations/histograms/{k}_log{run_label}.pdf")
119 | fig.savefig(f"violations/histograms/{k}_log{run_label}.png")
120 | plt.close(fig)
121 |
122 | # TODO: energies and stuff
123 | logger.info('Done.')
124 |
125 | except KeyboardInterrupt:
126 | logger.error('User interrupt. Exiting.')
127 | exit(1)
128 |
129 | except Exception:
130 | traceback.print_exc()
131 | logger.error('Error trying to compute violation statistics\n==============================')
132 |
--------------------------------------------------------------------------------
/igm/restraints/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import division, print_function
2 |
3 | from .envelope import Envelope
4 | from .polymer import Polymer
5 | from .steric import Steric
6 | from .hic import HiC
7 | from .sprite import Sprite
8 | from .damid import Damid
9 | from .nucleolus import Nucleolus
10 | from .genenvelope import GenEnvelope
11 | from .fish import Fish
12 | from .inter_hic import interHiC
13 | from .intra_hic import intraHiC
14 |
--------------------------------------------------------------------------------
/igm/restraints/damid.py:
--------------------------------------------------------------------------------
1 | ## ---------------------------
2 | # Allocate DamID restraints (using the "DamIDActdist.hdf5" file from the Assignment Step) as "EllipticEnvelope" forces between particles to the model object in IGM
3 | ## ---------------------------
4 |
5 |
6 |
7 | from __future__ import division, print_function
8 |
9 | import numpy as np
10 | import h5py
11 |
12 | from .restraint import Restraint
13 | from ..model.forces import HarmonicLowerBound, EllipticEnvelope # restraint forces associated with damid
14 | from ..model import Particle
15 |
16 | try:
17 | UNICODE_EXISTS = bool(type(unicode))
18 | except NameError:
19 | unicode = lambda s: str(s)
20 |
21 |
22 | # ---- AUXILIARY FUNCTIONS FOR DAMID CALCULATIONS ----#
23 |
24 | def snormsq_sphere(x, R, r):
25 |
26 | """
27 | Compute radial distance of a bead to spherical nuclear envelope
28 |
29 | INPUT
30 | x (float), bead/locus coordinates
31 | R (float), radius of nuclear envelope, when spherical
32 | r (float), radius of bead
33 |
34 | OUTPUT (normalized) distance between bead surface and nuclear envelope
35 | d = 1 if bead surface touches the envelope
36 | d < 1 otherwise
37 | """
38 |
39 | # return np.square(
40 | # (np.linalg.norm(x, axis=1) + r) / R**2
41 | # )
42 | return np.sum(np.square(x), axis=1) / (R-r)**2
43 |
44 |
45 |
46 |
47 |
48 | def snormsq_ellipsoid(x, semiaxes, r):
49 |
50 | """
51 | Compute radial distance of a bead to ellipsoidal nuclear envelope
52 |
53 | INPUT
54 | x (float), bead/locus coordinates
55 | r (float), radius of bead
56 | semiaxes (float, float, float), semiaxes of nuclear envelope, if ellipsoidal
57 |
58 | OUTPUT (normalized) distance between bead surface and nuclear envelope: x**2/(a-r)**2 + y**2/(b-r)**2 + z**2/(c-r)**2
59 | d = 1 if bead surface touches the envelope (this means the bead center is laying on a concentric ellipsoid
60 | of semiaxes (a - r, b - r, c - r))
61 | d < 1 otherwise (the bead center is laying on a concentric ellipsoid with even shorter semiaxes)
62 | """
63 |
64 | a, b, c = np.array(semiaxes) - r
65 | sq = np.square(x)
66 | return sq[0]/(a**2) + sq[1]/(b**2) + sq[2]/(c**2)
67 |
68 |
69 | def snorm(x, shape=u"sphere", **kwargs):
70 | if shape == u"sphere":
71 | return np.linalg.norm(x) / kwargs['a']
72 | elif shape == u"ellipsoid":
73 | a, b, c = kwargs['a'], kwargs['b'], kwargs['c']
74 | return np.sqrt( (x[0]/a)**2 + (x[1]/b)**2 + (x[2]/c)**2 )
75 |
76 |
77 | class Damid(Restraint):
78 | """
79 | Object handles Hi-C restraint
80 |
81 | Parameters
82 | ----------
83 | damid_file : activation distance file for damid
84 |
85 | contact_range : int
86 | defining contact range between 2 particles as contactRange*(r1+r2)
87 |
88 |
89 | """
90 |
91 | def __init__(self, damid_file, contact_range=0.05, nuclear_radius=5000.0, shape="sphere",
92 | semiaxes=(5000, 5000, 5000), k=1.0):
93 |
94 | self.shape = unicode(shape)
95 |
96 | if self.shape == u"sphere":
97 | self.a = self.b = self.c = nuclear_radius
98 | elif self.shape == u"ellipsoid":
99 | self.a, self.b, self.c = semiaxes
100 |
101 | # recapitulate parameters
102 | self.contact_range = contact_range
103 | self.nuclear_radius = nuclear_radius
104 | self.k = k
105 | self.forceID = []
106 | self._load_actdist(damid_file)
107 | #-
108 |
109 |
110 | def _load_actdist(self,damid_actdist):
111 |
112 | """ Read in file containing current DAMID activation distances """
113 | self.damid_actdist = DamidActivationDistanceDB(damid_actdist)
114 |
115 |
116 | def _apply_envelope(self, model):
117 |
118 | """ Effectively apply damid restraints to the different beads, if distance is smaller than activation distance """
119 |
120 | center = model.addParticle([0., 0., 0.], 0., Particle.DUMMY_STATIC)
121 | cutoff = 1 - self.contact_range
122 |
123 | affected_particles = [
124 | i for i, d in self.damid_actdist
125 | if snormsq_ellipsoid(
126 | model.particles[i].pos,
127 | np.array([self.a, self.b, self.c]) * cutoff,
128 | model.particles[i].r
129 | ) >= d**2
130 | ]
131 |
132 | f = model.addForce(
133 | EllipticEnvelope(
134 | affected_particles,
135 | center,
136 | np.array([self.a, self.b, self.c])*cutoff,
137 | -self.k,
138 | scale=cutoff*np.mean([self.a, self.b, self.c])
139 | )
140 | )
141 |
142 | self.forceID.append(f)
143 |
144 |
145 | def _apply(self, model):
146 | return self._apply_envelope(model)
147 |
148 | #==
149 |
150 | class DamidActivationDistanceDB(object):
151 | """
152 | HDF5 activation distance iterator: read in damid activation distance file in chunks
153 | """
154 |
155 | def __init__(self, damid_file, chunk_size = 10000):
156 | self.h5f = h5py.File(damid_file,'r')
157 | self._n = len(self.h5f['loc'])
158 |
159 | self.chunk_size = min(chunk_size, self._n)
160 |
161 | self._i = 0
162 | self._chk_i = chunk_size
163 | self._chk_end = 0
164 |
165 | def __len__(self):
166 | return self._n
167 |
168 | def __iter__(self):
169 | return self
170 |
171 | def __next__(self):
172 | if self._i < self._n:
173 | self._i += 1
174 | self._chk_i += 1
175 |
176 | if self._chk_i >= self.chunk_size:
177 | self._load_next_chunk()
178 |
179 | return (int(self._chk_row[self._chk_i]),
180 | self._chk_data[self._chk_i])
181 | else:
182 | self._i = 0
183 | self._chk_i = self.chunk_size
184 | self._chk_end = 0
185 | raise StopIteration()
186 |
187 | def next(self):
188 | return self.__next__()
189 |
190 | def _load_next_chunk(self):
191 | self._chk_row = self.h5f['loc'][self._chk_end : self._chk_end + self.chunk_size]
192 | self._chk_data = self.h5f['dist'][self._chk_end : self._chk_end + self.chunk_size]
193 | self._chk_end += self.chunk_size
194 | self._chk_i = 0
195 |
196 | def __del__(self):
197 | try:
198 | self.h5f.close()
199 | except:
200 | pass
201 |
202 |
--------------------------------------------------------------------------------
/igm/restraints/envelope.py:
--------------------------------------------------------------------------------
1 | from __future__ import division, print_function
2 |
3 | import numpy as np
4 |
5 | from .restraint import Restraint
6 | from ..model.particle import Particle
7 | from ..model.forces import HarmonicUpperBound, EllipticEnvelope
8 | try:
9 | UNICODE_EXISTS = bool(type(unicode))
10 | except NameError:
11 | unicode = lambda s: str(s)
12 |
13 | class Envelope(Restraint):
14 | """
15 | A object handles nuclear envelope restraints
16 |
17 | Parameters
18 | ----------
19 | nucRadius : float or list
20 | nuclear radius or the length of the three semiaxes, in nm
21 | k : float
22 | spring constant
23 | """
24 | def __init__(self, shape="sphere", nuclear_radius=5000.0, k=1.0):
25 | self.shape = unicode(shape)
26 |
27 | if self.shape == u"sphere":
28 | self.a = self.b = self.c = nuclear_radius
29 | elif self.shape == u"ellipsoid":
30 | self.a, self.b, self.c = nuclear_radius
31 |
32 | self.k = k
33 | self.forceID = []
34 |
35 |
36 | def _apply_sphere_envelop(self, model):
37 |
38 | """ add a dummy dimensionless particle in the geometric center, to be used in force modeling """
39 | center = model.addParticle([0., 0., 0.], 0., Particle.DUMMY_STATIC)
40 |
41 | normal_particles = [
42 | i for i, p in enumerate(model.particles)
43 | if p.ptype == Particle.NORMAL
44 | ]
45 | f = model.addForce(
46 | EllipticEnvelope(
47 | normal_particles,
48 | center,
49 | (self.a, self.b, self.c),
50 | self.k,
51 | scale=0.1*np.mean([self.a, self.b, self.c])
52 | # set arbitrary scale: 100% violation ratio if extend inside by
53 | # 1/10 of the nucleus radius. With usual parameters, it means
54 | # that will be noticed as violated when the bond is stretched
55 | # by 25nm.
56 | )
57 | )
58 | self.forceID.append(f)
59 |
60 |
61 | def _apply(self, model):
62 | self._apply_sphere_envelop(model)
63 |
64 |
65 | def __repr__(self):
66 | return 'Envelope[shape={},k={},a={},b={},c={}]'.format(self.shape, self.k, self.a, self.b, self.c)
67 | #=
68 |
--------------------------------------------------------------------------------
/igm/restraints/genenvelope.py:
--------------------------------------------------------------------------------
1 | from __future__ import division, print_function
2 |
3 | import numpy as np
4 |
5 | from .restraint import Restraint
6 | from ..model.particle import Particle
7 | from ..model.forces import ExpEnvelope
8 |
9 | try:
10 | UNICODE_EXISTS = bool(type(unicode))
11 | except NameError:
12 | unicode = lambda s: str(s)
13 |
14 | class GenEnvelope(Restraint):
15 | """
16 | This class handles a (non ideal) nuclear envelope restraint from a density map
17 |
18 | Parameters
19 | ----------
20 | volume_file: string
21 | name of file containing the binary density map we would like to fit the genome into
22 | k : float
23 | spring constant
24 | """
25 |
26 | def __init__(self, shape, volume_file="", k=1.0):
27 |
28 | self.shape = unicode(shape)
29 | self.volume_file = volume_file # file containing the binary density map information
30 | self.k = k # elastic restraining constant (>0 attractive, <0 repulsive)
31 |
32 | self.forceID = []
33 |
34 |
35 | def _apply_sphere_envelop(self, model):
36 |
37 | normal_particles = [
38 | i for i, p in enumerate(model.particles)
39 | if p.ptype == Particle.NORMAL
40 | ]
41 |
42 | f = model.addForce(
43 | ExpEnvelope(
44 | normal_particles,
45 | self.volume_file,
46 | self.k,
47 | scale= 0.5
48 | # set arbitrary scale: 100% violation ratio if extend inside by
49 | # 1/10 of the nucleus radius. With usual parameters, it means
50 | # that will be noticed as violated when the bond is stretched
51 | # by 25nm.
52 | )
53 | )
54 | self.forceID.append(f)
55 |
56 |
57 | def _apply(self, model):
58 | self._apply_sphere_envelop(model)
59 |
60 |
61 | def __repr__(self):
62 | return 'ExpEnvelope[shape={},k={}]'.format(self.shape, self.k)
63 | #=
64 |
--------------------------------------------------------------------------------
/igm/restraints/hic.py:
--------------------------------------------------------------------------------
1 | ## ---------------------------
2 | # Allocate Hi-C restraints (using the "Actdist.hdf5" file from the Assignment Step) as forces between particles to the model object in IGM
3 | ## ---------------------------
4 |
5 | from __future__ import division, print_function
6 |
7 | import numpy as np
8 |
9 | from .restraint import Restraint
10 | from ..model.forces import HarmonicUpperBound
11 |
12 | import h5py
13 |
14 | class HiC(Restraint):
15 |
16 | """
17 | Object handles Hi-C restraint
18 |
19 | Parameters
20 | ----------
21 | actdist_file : activation distance file (generated from ActivationDistanceStep.py)
22 |
23 | contactRange : int
24 | defining contact range between 2 particles as contactRange*(r1+r2)
25 |
26 | k: float
27 | elastic constant for harmonic restraint
28 | """
29 |
30 | def __init__(self, actdist_file, contactRange=2, k=1.0):
31 |
32 | self.contactRange = contactRange
33 | self.k = k
34 | self.forceID = []
35 | self._load_actdist(actdist_file)
36 | #-
37 |
38 | def _load_actdist(self,actdist):
39 | self.actdist = ActivationDistanceDB(actdist)
40 |
41 | def _apply(self, model):
42 |
43 | """ Apply harmonic restraint to those distances smaller than d_{act}"""
44 |
45 | for (i, j, d) in self.actdist:
46 |
47 | # calculate particle distances for i, j
48 | # if ||i-j|| <= d then assign a bond for i, j
49 | if model.particles[i] - model.particles[j] <= d :
50 |
51 | # harmonic mean distance
52 | dij = self.contactRange*(model.particles[i].r +
53 | model.particles[j].r)
54 |
55 | # add harmonic bond between i-th and j-th beads
56 | f = model.addForce(HarmonicUpperBound((i, j), dij, self.k,
57 | note=Restraint.HIC))
58 | self.forceID.append(f)
59 | #-
60 | #-
61 | #=
62 |
63 |
64 | #==
65 |
66 | class ActivationDistanceDB(object):
67 |
68 | """ HDF5 activation distance iterator: read in file of activation distances, in chunks """
69 |
70 | def __init__(self, actdist_file, chunk_size = 10000):
71 | self.h5f = h5py.File(actdist_file,'r')
72 | self._n = len(self.h5f['row'])
73 |
74 | self.chunk_size = min(chunk_size, self._n)
75 |
76 | self._i = 0
77 | self._chk_i = chunk_size
78 | self._chk_end = 0
79 |
80 | def __len__(self):
81 | return self._n
82 |
83 | def __iter__(self):
84 | return self
85 |
86 | def __next__(self):
87 | if self._i < self._n:
88 | self._i += 1
89 | self._chk_i += 1
90 |
91 | if self._chk_i >= self.chunk_size:
92 | self._load_next_chunk()
93 |
94 | return (self._chk_row[self._chk_i],
95 | self._chk_col[self._chk_i],
96 | self._chk_data[self._chk_i])
97 | else:
98 | self._i = 0
99 | self._chk_i = self.chunk_size
100 | self._chk_end = 0
101 | raise StopIteration()
102 | def next(self):
103 | return self.__next__()
104 |
105 | # probabilities are not needed now, since they are already encoded in the list of activation distances
106 | def _load_next_chunk(self):
107 | self._chk_row = self.h5f['row'][self._chk_end : self._chk_end + self.chunk_size]
108 | self._chk_col = self.h5f['col'][self._chk_end : self._chk_end + self.chunk_size]
109 | self._chk_data = self.h5f['dist'][self._chk_end : self._chk_end + self.chunk_size]
110 | self._chk_end += self.chunk_size
111 | self._chk_i = 0
112 |
113 | def __del__(self):
114 | self.h5f.close()
115 |
116 |
--------------------------------------------------------------------------------
/igm/restraints/inter_hic.py:
--------------------------------------------------------------------------------
1 | ## ---------------------------
2 | # Allocate INTER Hi-C restraints (using the "Actdist.hdf5" file from the Assignment Step) as forces between particles to the model object in IGM
3 | ## ---------------------------
4 |
5 |
6 |
7 | from __future__ import division, print_function
8 |
9 | import numpy as np
10 |
11 | from .restraint import Restraint
12 | from ..model.forces import HarmonicUpperBound
13 | from ..utils.log import logger
14 |
15 | import h5py
16 |
17 | class interHiC(Restraint):
18 |
19 | """
20 | Object handles Hi-C restraint
21 |
22 | Parameters
23 | ----------
24 | actdist_file : activation distance file (generated from ActivationDistanceStep.py)
25 |
26 | contactRange : int
27 | defining contact range between 2 particles as contactRange*(r1+r2)
28 |
29 | k: float
30 | elastic constant for harmonic restraint
31 | """
32 |
33 | def __init__(self, actdist_file, chrom, contactRange=2, k=1.0):
34 |
35 | self.contactRange = contactRange
36 | self.k = k
37 | self.chrom = chrom
38 | self.forceID = []
39 | self._load_actdist(actdist_file)
40 | #-
41 |
42 | def _load_actdist(self,actdist):
43 | self.actdist = ActivationDistanceDB(actdist)
44 |
45 | def _apply(self, model):
46 |
47 | """ Apply harmonic restraint to those distances smaller than d_{act}"""
48 |
49 | for (i, j, d) in self.actdist:
50 |
51 | # calculate particle distances for i, j
52 | # if ||i-j|| <= d then assign a bond for i, j
53 | if ((model.particles[i] - model.particles[j] <= d) and (self.chrom[i] != self.chrom[j])) :
54 |
55 | # harmonic mean distance
56 | dij = self.contactRange*(model.particles[i].r +
57 | model.particles[j].r)
58 |
59 | # add harmonic bond between i-th and j-th beads
60 | f = model.addForce(HarmonicUpperBound((i, j), dij, self.k,
61 | note=Restraint.INTER_HIC))
62 | self.forceID.append(f)
63 | #-
64 | #-
65 | #=
66 |
67 |
68 | #==
69 |
70 | class ActivationDistanceDB(object):
71 |
72 | """ HDF5 activation distance iterator: read in file of activation distances, in chunks """
73 |
74 | def __init__(self, actdist_file, chunk_size = 10000):
75 | self.h5f = h5py.File(actdist_file,'r')
76 | self._n = len(self.h5f['row'])
77 |
78 | self.chunk_size = min(chunk_size, self._n)
79 |
80 | self._i = 0
81 | self._chk_i = chunk_size
82 | self._chk_end = 0
83 |
84 | def __len__(self):
85 | return self._n
86 |
87 | def __iter__(self):
88 | return self
89 |
90 | def __next__(self):
91 | if self._i < self._n:
92 | self._i += 1
93 | self._chk_i += 1
94 |
95 | if self._chk_i >= self.chunk_size:
96 | self._load_next_chunk()
97 |
98 | return (self._chk_row[self._chk_i],
99 | self._chk_col[self._chk_i],
100 | self._chk_data[self._chk_i])
101 | else:
102 | self._i = 0
103 | self._chk_i = self.chunk_size
104 | self._chk_end = 0
105 | raise StopIteration()
106 | def next(self):
107 | return self.__next__()
108 |
109 | # probabilities are not needed now, since they are already encoded in the list of activation distances
110 | def _load_next_chunk(self):
111 | self._chk_row = self.h5f['row'][self._chk_end : self._chk_end + self.chunk_size]
112 | self._chk_col = self.h5f['col'][self._chk_end : self._chk_end + self.chunk_size]
113 | self._chk_data = self.h5f['dist'][self._chk_end : self._chk_end + self.chunk_size]
114 | self._chk_end += self.chunk_size
115 | self._chk_i = 0
116 |
117 | def __del__(self):
118 | self.h5f.close()
119 |
120 |
--------------------------------------------------------------------------------
/igm/restraints/intra_hic.py:
--------------------------------------------------------------------------------
1 | ## ---------------------------
2 | # Allocate INTRA Hi-C restraints (using the "Actdist.hdf5" file from the Assignment Step) as "HarmonicUpperBound" forces between particles to the model object in IGM
3 | ## ---------------------------
4 |
5 |
6 |
7 |
8 | from __future__ import division, print_function
9 |
10 | import numpy as np
11 |
12 | from .restraint import Restraint
13 | from ..model.forces import HarmonicUpperBound
14 | from ..utils.log import logger
15 |
16 | import h5py
17 |
18 | class intraHiC(Restraint):
19 |
20 | """
21 | Object handles Hi-C restraint
22 |
23 | Parameters
24 | ----------
25 | actdist_file : activation distance file (generated from ActivationDistanceStep.py)
26 |
27 | contactRange : int
28 | defining contact range between 2 particles as contactRange*(r1+r2)
29 |
30 | k: float
31 | elastic constant for harmonic restraint
32 | """
33 |
34 | def __init__(self, actdist_file, chrom, contactRange=2, k=1.0):
35 |
36 | self.contactRange = contactRange
37 | self.k = k
38 | self.chrom = chrom
39 | self.forceID = []
40 | self._load_actdist(actdist_file)
41 | #-
42 |
43 | def _load_actdist(self,actdist):
44 | self.actdist = ActivationDistanceDB(actdist)
45 |
46 | def _apply(self, model):
47 |
48 | """ Apply harmonic restraint to those distances smaller than d_{act}"""
49 |
50 | for (i, j, d) in self.actdist:
51 |
52 | # calculate particle distances for i, j
53 | # if ||i-j|| <= d then assign a bond for i, j
54 | if ((model.particles[i] - model.particles[j] <= d) and (self.chrom[i] == self.chrom[j])) :
55 |
56 | # harmonic mean distance
57 | dij = self.contactRange*(model.particles[i].r +
58 | model.particles[j].r)
59 |
60 | # add harmonic bond between i-th and j-th beads
61 | f = model.addForce(HarmonicUpperBound((i, j), dij, self.k,
62 | note=Restraint.INTRA_HIC))
63 | self.forceID.append(f)
64 | #-
65 | #-
66 | #=
67 |
68 |
69 | #==
70 |
71 | class ActivationDistanceDB(object):
72 |
73 | """ HDF5 activation distance iterator: read in file of activation distances, in chunks """
74 |
75 | def __init__(self, actdist_file, chunk_size = 10000):
76 | self.h5f = h5py.File(actdist_file,'r')
77 | self._n = len(self.h5f['row'])
78 |
79 | self.chunk_size = min(chunk_size, self._n)
80 |
81 | self._i = 0
82 | self._chk_i = chunk_size
83 | self._chk_end = 0
84 |
85 | def __len__(self):
86 | return self._n
87 |
88 | def __iter__(self):
89 | return self
90 |
91 | def __next__(self):
92 | if self._i < self._n:
93 | self._i += 1
94 | self._chk_i += 1
95 |
96 | if self._chk_i >= self.chunk_size:
97 | self._load_next_chunk()
98 |
99 | return (self._chk_row[self._chk_i],
100 | self._chk_col[self._chk_i],
101 | self._chk_data[self._chk_i])
102 | else:
103 | self._i = 0
104 | self._chk_i = self.chunk_size
105 | self._chk_end = 0
106 | raise StopIteration()
107 | def next(self):
108 | return self.__next__()
109 |
110 | # probabilities are not needed now, since they are already encoded in the list of activation distances
111 | def _load_next_chunk(self):
112 | self._chk_row = self.h5f['row'][self._chk_end : self._chk_end + self.chunk_size]
113 | self._chk_col = self.h5f['col'][self._chk_end : self._chk_end + self.chunk_size]
114 | self._chk_data = self.h5f['dist'][self._chk_end : self._chk_end + self.chunk_size]
115 | self._chk_end += self.chunk_size
116 | self._chk_i = 0
117 |
118 | def __del__(self):
119 | self.h5f.close()
120 |
121 |
--------------------------------------------------------------------------------
/igm/restraints/nucleolus.py:
--------------------------------------------------------------------------------
1 | from __future__ import division, print_function
2 |
3 | from .restraint import Restraint
4 | from ..model.particle import Particle
5 | from ..model.forces import NuclExcludedVolume
6 |
7 | class Nucleolus(Restraint):
8 | """
9 | A object handles nucleolus excluded volume restraints
10 |
11 | Parameters
12 | ----------
13 | body_pos, body_r: center coordinates and radius of nucleolus
14 | k : float
15 | spring constant
16 | """
17 |
18 | def __init__(self, body_pos, body_r, k=1.0):
19 |
20 | """ Class initialization"""
21 |
22 | self.body_pos = body_pos # coordinates of nucleolus center
23 | self.body_r = body_r # radius of spherical nucleulus
24 | self.k = k
25 | self.forceID = []
26 |
27 | def _apply(self, model):
28 |
29 | """ Apply force """
30 |
31 | plist = [i for i, p in enumerate(model.particles) if p.ptype == Particle.NORMAL]
32 |
33 | f = model.addForce(NuclExcludedVolume(plist, self.body_pos, self.body_r, self.k))
34 | self.forceID.append(f)
35 | #-
36 | #=
37 |
--------------------------------------------------------------------------------
/igm/restraints/polymer.py:
--------------------------------------------------------------------------------
1 | from __future__ import division, print_function
2 |
3 | import numpy as np
4 | from .restraint import Restraint
5 | from ..model.forces import HarmonicUpperBound
6 |
7 | MIN_CONSECUTIVE = 0.5
8 |
9 | class Polymer(Restraint):
10 | """
11 | Object handles consecutive bead restraint
12 |
13 | Parameters
14 | ----------
15 | index : alabtools.index object
16 | chromosome chain index
17 | contactRange : int
18 | defining contact range between 2 particles as contactRange*(r1+r2)
19 | k : float
20 | spring constant
21 |
22 | """
23 |
24 | def __init__(self, index, contactRange=2, k=1.0, contact_probabilities=None):
25 | self.index = index
26 | self.contactRange = contactRange
27 | self.k = k
28 | self.forceID = []
29 | self.cp = np.load(contact_probabilities) if (contact_probabilities is not None) else None
30 |
31 | def _apply(self, model):
32 |
33 | for i in range(len(self.index) - 1):
34 |
35 | # if i and i+1 belong to the same chromosome (and copy)
36 | if (self.index.chrom[i] == self.index.chrom[i+1] and
37 | self.index.copy[i] == self.index.copy[i+1]):
38 |
39 | # do we have contact probabiities?
40 | if self.cp is None:
41 | dij = self.contactRange*(model.particles[i].r +
42 | model.particles[i+1].r)
43 | else:
44 | d0 = (model.particles[i].r + model.particles[i+1].r)
45 | d1 = self.contactRange * d0
46 | f = self.cp[i]
47 | # if we have inconsistent or no data, just assume MIN_CONSECUTIVE contact
48 | if f < MIN_CONSECUTIVE or f > 1:
49 | f = MIN_CONSECUTIVE
50 | x3 = ( d1**3 + ( f - 1 )*d0**3 ) / f
51 | dij = x3**(1./3)
52 |
53 | f = model.addForce(HarmonicUpperBound((i, i+1), dij, self.k,
54 | note=Restraint.CONSECUTIVE))
55 | self.forceID.append(f)
56 | #-
57 | #--
58 | #=
59 |
60 |
61 |
--------------------------------------------------------------------------------
/igm/restraints/restraint.py:
--------------------------------------------------------------------------------
1 | from __future__ import division, print_function
2 | import numpy as np
3 |
4 | class Restraint(object):
5 | """
6 |
7 | Restraint object, takes care of data and translate to forces in model.
8 |
9 | Also keep track of forces added and can evaluate
10 |
11 | """
12 |
13 | OTHER = -1
14 | CONSECUTIVE = 0
15 | HIC = 1
16 | INTRA_HIC = 9
17 | INTER_HIC = 10
18 | DAMID = 2
19 | FISH_RADIAL = 3
20 | FISH_PAIR = 4
21 | SPRITE = 5
22 | ENVELOPE = 6
23 | EXCLUDED_VOLUME = 7
24 | NUCL_EXCLUDED_VOLUME = 8
25 |
26 | def __init__(self, data, args):
27 | self.forceID = []
28 |
29 | def _apply_model(self, model, override=False):
30 | """
31 | Attach restraint to model
32 | """
33 | try:
34 | self.model
35 | if override:
36 | self.model = model
37 | else:
38 | raise(RuntimeError("Restraint alreay applyed to model! Set override to true to proceed."))
39 | except AttributeError:
40 | self.model = model
41 |
42 | def _apply(self, model, override=False):
43 | self._apply_model(model, override)
44 |
45 |
46 | def __len__(self):
47 | return len(self.forceID)
48 |
49 | def __getitem__(self, key):
50 | return self.forceID[key]
51 |
52 | def evaluate(self):
53 | """
54 | evaluate the restraint violations
55 | """
56 | score = 0
57 | violations = 0
58 |
59 | for fid in self.forceID:
60 | s = self.model.evalForceScore(fid)
61 | if s > 0:
62 | violations += 1
63 | #-
64 | score += s
65 |
66 | return (violations, score)
67 |
68 | def get_violations(self, tolerance):
69 | violations = []
70 | ratios = []
71 |
72 | for fid in self.forceID:
73 | s = self.model.evalForceViolationRatio(fid)
74 | if s > tolerance:
75 | violations.append(repr(self.model.forces[fid]))
76 | ratios.append(s)
77 |
78 | return (violations, ratios)
79 |
80 | def get_violation_histogram(self, nbins=100, vmax=1, epsilon=1e-4):
81 | v = self.get_violations(tolerance=epsilon)
82 | over = len(v>vmax)
83 | inner = v[v<=vmax]
84 | H, edges = np.histogram(inner, bins=nbins)
85 | H = np.concatenate([H, [over]])
86 | edges.append(np.array([vmax, float('inf')]))
87 | return H, edges
88 |
89 | def __repr__(self):
90 | return type(self).__name__
91 |
92 |
--------------------------------------------------------------------------------
/igm/restraints/sprite.py:
--------------------------------------------------------------------------------
1 | ## ---------------------------
2 | # Allocate single cell SPRITE restraints (using the "SpriteActdist.hdf5" file from the Assignment Step) as "HarmonicUpperBound" forces between particles to the model object in IGM
3 | ## ---------------------------
4 |
5 |
6 | from __future__ import division, print_function
7 |
8 | import numpy as np
9 | import h5py
10 |
11 | from ..model.particle import Particle
12 | from .restraint import Restraint
13 | from ..model.forces import HarmonicUpperBound
14 |
15 | class Sprite(Restraint):
16 | """
17 | Add SPRITE restraint as a DUMMY DYNAMIC particle is introduced and all beads assigned to a given
18 | cluster are connected to it via harmonic springs
19 |
20 | Parameters
21 | ----------
22 | assignment_file : SPRITE activation distance file
23 |
24 | volume_occupancy : float
25 | defining contact range between 2 particles as contactRange*(r1+r2)
26 |
27 | k (float): elastic constant for restraining
28 | """
29 |
30 | def __init__(self, assignment_file, volume_occupancy, struct_id, k=1.0):
31 |
32 | """ Initialize SPRITE parameters and input file """
33 |
34 | self.volume_occupancy = volume_occupancy
35 | self.struct_id = struct_id
36 | self.k = k
37 | self.forceID = []
38 | self.assignment_file = h5py.File(assignment_file, 'r')
39 | #-
40 |
41 | def _apply(self, model):
42 |
43 | """ Apply SPRITE restraints """
44 |
45 | assignment = self.assignment_file['assignment'][()]
46 | indptr = self.assignment_file['indptr'][()]
47 | selected_beads = self.assignment_file['selected']
48 | clusters_ids = np.where(assignment == self.struct_id)[0]
49 | radii = model.getRadii()
50 | coord = model.getCoordinates()
51 |
52 | # loop over indexes in 'assignment' (see SpriteAcrivationStep.py) referring to structure 'struct_id'
53 | for i in clusters_ids:
54 | beads = selected_beads[ indptr[i] : indptr[i+1] ]
55 | crad = radii[beads]
56 | ccrd = coord[beads]
57 | csize = len(beads)
58 | csize = get_cluster_size(crad, self.volume_occupancy)
59 |
60 | # add a centroid for the cluster, in the geometric center
61 | centroid_pos = np.mean(ccrd, axis=0)
62 | centroid = model.addParticle(centroid_pos, 0, Particle.DUMMY_DYNAMIC) # no excluded volume
63 |
64 | for b in beads:
65 | # apply harmonic restraint(s) to all beads making up the cluster, using the position of the centroid as "origin" (one-body terms)
66 | f = model.addForce(HarmonicUpperBound(
67 | (b, centroid), float(csize-radii[b]), self.k,
68 | note=Restraint.SPRITE))
69 | self.forceID.append(f)
70 |
71 |
72 | def cbrt(x):
73 | return (x)**(1./3.)
74 |
75 | def get_cluster_size(radii, volume_occupancy):
76 | return cbrt(np.sum(radii**3)/volume_occupancy)
77 |
--------------------------------------------------------------------------------
/igm/restraints/steric.py:
--------------------------------------------------------------------------------
1 | from __future__ import division, print_function
2 |
3 | from .restraint import Restraint
4 | from ..model.particle import Particle
5 | from ..model.forces import ExcludedVolume
6 |
7 | class Steric(Restraint):
8 | """
9 | A object handles nuclear envelope restraints
10 |
11 | Parameters
12 | ----------
13 | nucRadius : float
14 | nuclear radius in unit of nm.
15 | k : float
16 | spring constant
17 | """
18 |
19 | def __init__(self, k=1.0):
20 | self.k = k
21 | self.forceID = []
22 |
23 | def _apply(self, model):
24 |
25 | plist = [i for i, p in enumerate(model.particles) if p.ptype == Particle.NORMAL]
26 |
27 | f = model.addForce(ExcludedVolume(plist, self.k))
28 |
29 | self.forceID.append(f)
30 | #-
31 | #=
32 |
--------------------------------------------------------------------------------
/igm/steps/RandomInit.py:
--------------------------------------------------------------------------------
1 | from __future__ import division, print_function
2 | import numpy as np
3 | import os, os.path
4 |
5 | from math import acos, sin, cos, pi
6 |
7 | from ..core import StructGenStep
8 | from ..utils import HmsFile
9 | from alabtools.analysis import HssFile
10 |
11 | class RandomInit(StructGenStep):
12 |
13 | def setup(self):
14 | self.tmp_file_prefix = "random"
15 | self.argument_list = range(self.cfg["model"]["population_size"])
16 |
17 | @staticmethod
18 | def task(struct_id, cfg, tmp_dir):
19 | """
20 | generate one random structure with territories
21 | """
22 | k = np.random.randint(0, 2**32)
23 | np.random.seed( (k*struct_id) % (2**32) )
24 | hssfilename = cfg["optimization"]["structure_output"]
25 | nucleus_radius = cfg.get("model/init_radius")
26 |
27 | with HssFile(hssfilename,'r') as hss:
28 | index = hss.index
29 |
30 | crd = generate_territories(index, nucleus_radius)
31 |
32 | ofname = os.path.join(tmp_dir, 'random_%d.hms' % struct_id)
33 | with HmsFile(ofname, 'w') as hms:
34 | hms.saveCoordinates(struct_id, crd)
35 |
36 | def intermediate_name(self):
37 | return '.'.join([
38 | self.cfg["optimization"]["structure_output"],
39 | 'randomInit'
40 | ])
41 | #-
42 |
43 |
44 | def uniform_sphere(R):
45 | """
46 | Generates uniformly distributed points in a sphere
47 |
48 | Arguments:
49 | R (float): radius of the sphere
50 | Returns:
51 | np.array:
52 | triplet of coordinates x, y, z
53 | """
54 | phi = np.random.uniform(0, 2 * pi)
55 | costheta = np.random.uniform(-1, 1)
56 | u = np.random.uniform(0, 1)
57 |
58 | theta = acos( costheta )
59 | r = R * ( u**(1./3.) )
60 |
61 | x = r * sin( theta) * cos( phi )
62 | y = r * sin( theta) * sin( phi )
63 | z = r * cos( theta )
64 |
65 | return np.array([x,y,z])
66 |
67 |
68 |
69 | def generate_territories(index, R=5000.0):
70 | '''
71 | Creates a single random structure with chromosome territories.
72 | Each "territory" is a sphere with radius 0.75 times the average
73 | expected radius of a chromosome.
74 | Arguments:
75 | chrom : alabtools.utils.Index
76 | the bead index for the system.
77 | R : float
78 | radius of the cell
79 |
80 | Returns:
81 | np.array : structure coordinates
82 | '''
83 |
84 | # chromosome ends are detected when
85 | # the name is changed
86 | n_tot = len(index)
87 | n_chrom = len(index.chrom_sizes)
88 |
89 | crds = np.empty((n_tot, 3))
90 | # the radius of the chromosome is set as 75% of its
91 | # "volumetric sphere" one. This is totally arbitrary.
92 | # Note: using float division of py3
93 | chr_radii = [0.75 * R * (float(nb)/n_tot)**(1./3) for nb in index.chrom_sizes]
94 | crad = np.average(chr_radii)
95 | k = 0
96 | for i in range(n_chrom):
97 | center = uniform_sphere(R - crad)
98 | for j in range(index.chrom_sizes[i]):
99 | crds[k] = uniform_sphere(crad) + center
100 | k += 1
101 |
102 | return crds
103 |
104 |
105 | def generate_random_in_sphere(radii, R=5000.0):
106 | '''
107 | Returns:
108 | np.array : structure coordinates
109 | '''
110 | return np.array([uniform_sphere(R-r) for r in radii])
111 |
--------------------------------------------------------------------------------
/igm/steps/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import division, print_function
2 | from .RandomInit import RandomInit
3 | from .RelaxInit import RelaxInit
4 | from .ActivationDistanceStep import ActivationDistanceStep
5 | from .DamidActivationDistanceStep import DamidActivationDistanceStep
6 | from .ModelingStep import ModelingStep
7 | from .SpriteAssignmentStep import SpriteAssignmentStep
8 | from .HicEvaluationStep import HicEvaluationStep
9 | from .FishAssignmentStep import FishAssignmentStep
10 |
11 |
12 |
--------------------------------------------------------------------------------
/igm/tasks/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alberlab/igm/edbb33135260443b8b41eb09d7ca4c37b19e330d/igm/tasks/__init__.py
--------------------------------------------------------------------------------
/igm/tasks/modeling_step.py:
--------------------------------------------------------------------------------
1 | from __future__ import division, print_function
2 |
3 | import numpy as np
4 | import os
5 |
6 | from functools import partial
7 | from alabtools.analysis import HssFile, COORD_DTYPE
8 |
9 | from ..model import Model, Particle
10 | from ..parallel.ipyparallel_controller import BasicIppController, AdvancedIppController
11 | from ..parallel.parallel_controller import SerialController
12 | from ..core.config import Config
13 | from ..restraints import Polymer, Envelope, Steric
14 | from ..kernel import lammps
15 | from ..util import resolve_templates
16 |
17 | controller_class = {
18 | u'serial' : SerialController,
19 | u'ipyparallel' : AdvancedIppController,
20 | u'ipyparallel_basic' : BasicIppController,
21 | }
22 |
23 | kernel_class = {
24 | u'lammps' : lammps
25 | }
26 |
27 |
28 | def modeling_task(struct_id, cfg_file):
29 | '''
30 | Serial function to be mapped in parallel. //
31 | It is a wrapper intended to be used only internally by the parallel map
32 | function. Will be called as a partial with all the constant variables
33 | set, except i.
34 | Resolve the templates, obtains input data,
35 | runs the minimization routines and finally communicates back results.
36 |
37 | Parameters
38 | ----------
39 | i : int
40 | number of the structure
41 | cfg_file : str
42 | configuration filename for the task
43 |
44 | Returns
45 | -------
46 | None
47 | '''
48 | cfg = Config(cfg_file)
49 |
50 | # importing here so it will be called on the parallel workers
51 | local_vars = resolve_templates(cfg['mstep']['templates'], [struct_id])
52 |
53 | model = Model()
54 | with HssFile(cfg['mstep']['input_hss'], 'r') as f:
55 | radii = f.radii
56 | index = f.index
57 | crd = f['coordinates'][:, struct_id, :][()]
58 |
59 | n_particles = len(crd)
60 | for i in range(n_particles):
61 | model.addParticle(crd[i], radii[i], Particle.NORMAL)
62 |
63 | ee = Envelope(cfg['model']['nucleus_geometry'])
64 | model.addRestraint(ee)
65 |
66 | ex = Steric(cfg['model']['evfactor'])
67 | model.addRestraint(ex)
68 |
69 | pp = Polymer(index,
70 | cfg['model']['contact_range'],
71 | cfg['model']['contact_kspring'])
72 | model.addRestraint(pp)
73 |
74 | kernel = kernel_class[cfg['mstep']['kernel']]
75 | info = kernel.optimize(model, cfg['optimization'])
76 |
77 | new_crd = np.array([p.pos for p in model.particles], dtype=COORD_DTYPE)
78 | np.save(local_vars['crd_out'], new_crd)
79 |
80 | # make sure that is readable
81 | np.load(local_vars['crd_out'])
82 |
83 | with open(local_vars['info_out'], 'w') as f:
84 | for k in kernel.INFO_KEYS:
85 | if isinstance(info[k], float):
86 | out_str = '{:9.2f}'.format(info[k])
87 | elif isinstance(info[k], int):
88 | out_str = '{:7d}'.format(info[k])
89 | else:
90 | out_str = str(info[k])
91 | f.write(out_str + '\t')
92 |
93 | def modeling_step(model, cfg):
94 |
95 | with HssFile(cfg['mstep']['input_hss'], 'r') as f:
96 | radii = f.radii
97 | index = f.index
98 | genome = f.genome
99 |
100 | n_struct = cfg['mstep']['n_struct']
101 | n_beads = cfg['mstep']['n_beads']
102 |
103 | basepath = os.path.join(cfg['mstep']['workdir'], cfg['mstep']['run_name'])
104 | cfg['mstep']['templates'] = {
105 | 'crd_out' : basepath + '.outcrd.{}.npy',
106 | 'info_out' : basepath + '.info.{}.txt'
107 | }
108 | cfg.save(basepath + '.config')
109 |
110 | serial_function = partial(modeling_task, cfg_file=basepath + '.config')
111 | pctype = cfg['parallel_controller']
112 | pcopts = cfg['parallel_controller_options']
113 | controller = controller_class[pctype](**pcopts)
114 | argument_list = list(range(n_struct))
115 |
116 | controller.map(serial_function, argument_list)
117 |
118 | # write coordinates
119 | crd_shape = (n_beads, n_struct, 3)
120 | with HssFile(cfg['mstep']['output_hss'], 'w') as hss:
121 | hss.index = index
122 | hss.genome = genome
123 | hss.radii = radii
124 | all_crd = hss.create_dataset('coordinates', shape=crd_shape, dtype=COORD_DTYPE)
125 | for i in range(n_struct):
126 | local_vars = resolve_templates(cfg['mstep']['templates'], [i])
127 | crd = np.load(local_vars['crd_out'])
128 | all_crd[:, i, :] = crd # note that we discard all the positions of added atoms
129 |
130 | # write info
131 | kernel = kernel_class[cfg['mstep']['kernel']]
132 | with open(cfg['mstep']['info_out'], 'w') as outf:
133 | outf.write('#')
134 | for k in kernel.INFO_KEYS:
135 | outf.write(k + '\t')
136 | outf.write('\n')
137 | for i in range(n_struct):
138 | local_vars = resolve_templates(cfg['mstep']['templates'], [i])
139 | with open(local_vars['info_out']) as inf:
140 | outf.write(inf.read() + '\n')
141 |
142 | # cleanup
143 | for i in range(n_struct):
144 | local_vars = resolve_templates(cfg['mstep']['templates'], [i])
145 | os.remove(local_vars['crd_out'])
146 | os.remove(local_vars['info_out'])
147 |
148 |
149 |
150 |
151 |
152 |
153 |
--------------------------------------------------------------------------------
/igm/ui/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alberlab/igm/edbb33135260443b8b41eb09d7ca4c37b19e330d/igm/ui/__init__.py
--------------------------------------------------------------------------------
/igm/ui/communication.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os, os.path
3 | from alabtools.analysis import HssFile
4 | from igm.core.job_tracking import StepDB
5 | from igm import Config
6 | from .folders_database import register_folder
7 | import glob
8 |
9 |
10 | def history(folder='.'):
11 | cfg = json.load(open(os.path.join(folder, 'igm-config.json'), 'r'))
12 | try:
13 | db = StepDB(cfg, mode='r')
14 | h = db.get_history()
15 | except OSError:
16 | h = []
17 | # to avoid excessive data exchange
18 | for i in range(len(h)):
19 | del h[i]['cfg']
20 | return h
21 |
22 | def readlog(folder='.'):
23 |
24 | # select the file
25 | logf = os.path.join(folder, 'igm-log.txt')
26 | cfgf = os.path.join(folder, 'igm-config.json')
27 | if os.path.isfile(logf):
28 | f = logf
29 | elif os.path.isfile(cfgf):
30 | current_cfg = json.load(open(cfgf))
31 | f = current_cfg['log']
32 | else:
33 | return None
34 |
35 | # read in binary mode, so will keep carriage returns
36 | lines = open(f, 'rb').readlines()
37 | # remove all the unnecessary carriage returns and reassemble
38 | log = '\n'.join([ l.decode('utf-8').split('\r')[-1].strip('\n') for l in lines ])
39 | return log
40 |
41 | def igm_is_running(folder='.'):
42 | pidf = os.path.join(folder, '.igm-pid.txt')
43 | if os.path.isfile(pidf):
44 | pid = int(open(pidf).read())
45 | try:
46 | # sending a 0 signal fails if the process does not exist
47 | # does nothing otherwise
48 | os.kill(pid, 0)
49 | status = 'yes'
50 | except OSError:
51 | # In this case the machine running the server
52 | # may be different from the machine running the igm script,
53 | # or something exploded before calling the atexit functions
54 | status = 'maybe'
55 | else:
56 | status = 'no'
57 | return status
58 |
59 | def kill_igm(folder='.'):
60 | '''
61 | Try to kindly ask to terminate, then kill the process
62 | if it is not done in 5 seconds.
63 | '''
64 | pidf = os.path.join(folder, '.igm-pid.txt')
65 | import time, multiprocessing
66 | if os.path.isfile(pidf):
67 | pid = int(open(pidf).read())
68 | os.kill(pid, 2)
69 | def real_kill(pid):
70 | time.sleep(5)
71 | if os.path.isfile(pidf):
72 | os.kill(pid, 9)
73 | os.remove(pidf)
74 | p = multiprocessing.Process(target=real_kill, args=(pid,), daemon=True)
75 | p.start()
76 |
77 |
78 | def clear_previous_runs(folder='.'):
79 | cfgf = os.path.join(folder, 'igm-config.json')
80 | # this is ultra dangerous, one could change the config and
81 | # remove arbitrary files, should check they are files
82 | # inside the current directory
83 | cfg = Config(cfgf)
84 | os.remove(cfg['step_db'])
85 | os.remove(cfg['structure_output'])
86 | os.remove(cfg['structure_output'] + '.tmp')
87 | os.remove(cfg['log'])
88 |
89 | def get_structure(path, n, folder='.'):
90 | path = os.path.join(folder, path)
91 |
92 | with HssFile(path, 'r') as f:
93 | crd = f.get_struct_crd(n).tolist()
94 | chrom = f.genome.chroms[f.index.chrom].tolist()
95 | radius = f.radii.tolist()
96 | nstruct = f.nstruct
97 | cstarts = f.index.offset.tolist()
98 |
99 | return {
100 | 'crd' : [crd[cstarts[i]:cstarts[i+1]] for i in range(len(cstarts)-1)],
101 | 'idx' : chrom,
102 | 'rad' : [radius[cstarts[i]:cstarts[i+1]] for i in range(len(cstarts)-1)],
103 | 'n' : int(nstruct),
104 | 'cstarts': cstarts,
105 | 'chroms': [str(v) for i, v in enumerate(chrom) if i == 0 or v != chrom[i-1]],
106 | }
107 |
108 | def save_metadata(data, cwd):
109 | folder = data.pop('folder')
110 | if os.path.realpath(folder) != os.path.realpath(cwd):
111 | raise ValueError('Invalid folder %s' % folder)
112 | if 'notes' in data:
113 | if data['notes']:
114 | with open(os.path.realpath(folder) + '/IGM_NOTES.TXT', 'w') as f:
115 | f.write(data['notes'])
116 | register_folder(cwd, **data)
117 |
--------------------------------------------------------------------------------
/igm/ui/config_parse.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os, os.path
3 |
4 | import igm.core.defaults
5 |
6 |
7 | schema_file = os.path.join(
8 | os.path.dirname( os.path.abspath(igm.core.defaults.__file__) ),
9 | 'config_schema.json'
10 | )
11 | schema = json.load(open(schema_file, 'r'))
12 |
13 | def set_item(d, path, val):
14 | if len(path) == 1:
15 | d[path[0]] = val
16 | else:
17 | if path[0] not in d:
18 | d[path[0]] = dict()
19 | set_item(d[path[0]], path[1:], val)
20 |
21 | def get_item(d, path):
22 | if len(path) == 1:
23 | return d[ path[0] ]
24 | return get_item(d[ path[0] ], path[1:])
25 |
26 | def split_path(path, sep='__'):
27 | return path.split(sep)
28 |
29 | def type_or_json(vtype, val):
30 | from six import raise_from
31 | if isinstance(val, vtype):
32 | return val
33 | if not isinstance(val, str):
34 | raise ValueError()
35 | try:
36 | v = json.loads(val)
37 | return v
38 | except json.JSONDecodeError:
39 | raise_from(ValueError(), None)
40 | if isinstance(val, vtype):
41 | return val
42 | raise ValueError()
43 |
44 | def validate_value(value, dtype, subdtype=None, alen=None):
45 | if dtype == 'int':
46 | return int(value)
47 | elif dtype == 'float':
48 | return float(value)
49 | elif dtype == 'bool':
50 | return bool(value)
51 | elif dtype == 'list':
52 | sval = type_or_json(list, value)
53 | for i in range(len(sval)):
54 | if subdtype == 'list':
55 | if not isinstance(sval[i], list):
56 | raise ValueError()
57 | else:
58 | sval[i] = validate_value(sval[i], subdtype)
59 | return sval
60 | elif dtype == 'array':
61 | sval = type_or_json(list, value)
62 | if len(sval) != alen:
63 | raise ValueError('array length does not match')
64 | for i in range(len(sval)):
65 | sval[i] = validate_value(sval[i], subdtype)
66 | return sval
67 | elif dtype == 'dict':
68 | return type_or_json(dict, value)
69 | elif dtype == 'str' or dtype == 'path' or dtype == 'path-dir':
70 | return(str(value))
71 | else:
72 | raise ValueError()
73 |
74 | def save_cfg(data, folder='.'):
75 | warnings = []
76 | errors = []
77 | cfg = {}
78 | for path, value in data.items():
79 |
80 | try:
81 | sitem = get_item(schema, split_path(path))
82 | except KeyError:
83 | set_item(cfg, split_path(path), value)
84 | warnings.append('key "%s" not in schema' % path.replace('__', ' > '))
85 | continue
86 |
87 | if sitem.get('blank', False):
88 | if value is None or (isinstance(value, str) and value.strip() == ''):
89 | continue
90 |
91 | dtypes = sitem['dtype']
92 |
93 | if not isinstance(dtypes, list):
94 | dtypes = [dtypes]
95 |
96 | sval = None
97 | for dtype in dtypes:
98 | try:
99 | sval = validate_value(value, dtype,
100 | subdtype=sitem.get('subdtype'),
101 | alen=sitem.get('length'))
102 | break
103 | except ValueError:
104 | pass
105 | if sval is None:
106 | errors.append('%s: invalid data "%s". Valid data type are "%s" ' % (
107 | path.replace('__', ' > '), value, dtypes))
108 | continue
109 |
110 | if dtype in ['path', 'path-dir', 'str'] and not sitem.get('required', False):
111 | if sval.strip() == "":
112 | continue
113 |
114 | if dtype == 'path' and sitem.get('role', '') == 'input':
115 | if not os.path.isfile(sval):
116 | warnings.append('%s: cannot find input file "%s"' % (path.replace('__', ' > '), sval))
117 |
118 | if sitem.get('allowed_values') is not None:
119 | if sval not in sitem['allowed_values']:
120 | errors.append('%s: invalid value "%s". Valid values are "%s" ' % (
121 | path.replace('__', ' > '), value, sitem['allowed_values']))
122 | continue
123 |
124 | set_item(cfg, split_path(path), sval)
125 |
126 | r = {'errors': errors, 'warnings': warnings}
127 | if len(errors):
128 | r['status'] = 'failed'
129 | r['cfg'] = None
130 | return r
131 | else:
132 | cfgf = os.path.join(folder, 'igm-config.json')
133 | with open(cfgf, 'w') as f:
134 | json.dump(cfg, f, indent=4)
135 | r['status'] = 'ok'
136 | r['cfg'] = cfg
137 | return r
138 |
--------------------------------------------------------------------------------
/igm/ui/folders_database.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | from os.path import normpath, abspath, isfile, isdir
4 | import sqlite3
5 | import time
6 |
7 | def create_db(db_file):
8 | with sqlite3.connect(db_file) as db:
9 | q = 'CREATE TABLE paths (folder TEXT, name TEXT, cell_line TEXT, resolution TEXT, notes TEXT, tags TEXT, created INT, last_modified INT)'
10 | db.execute(q)
11 |
12 | def register_folder(folder, name="", cell_line="", resolution="", notes="", tags=""):
13 |
14 | folder = normpath( abspath(folder) )
15 |
16 | if not isdir(folder):
17 | raise RuntimeError('Error: the directory `%s` does not exist or it is not readable\n' % folder)
18 |
19 | db_file = os.environ['HOME'] + '/.igm/paths.db'
20 |
21 | if not isfile(db_file):
22 | create_db(db_file)
23 |
24 | with sqlite3.connect(db_file) as db:
25 |
26 | c = db.execute('SELECT count(folder) FROM paths WHERE folder=?', (folder, )).fetchall()[0][0]
27 | if c > 0:
28 | db.execute('UPDATE paths SET name=?, cell_line=?, resolution=?, notes=?, tags=?, last_modified=? WHERE folder=?', (name, cell_line, resolution, notes, tags, int(time.time()), folder) )
29 |
30 | else:
31 | db.execute('INSERT into paths (folder, name, cell_line, resolution, notes, tags, created, last_modified) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
32 | (folder, name, cell_line, resolution, notes, tags, int(time.time()), int(time.time())) )
33 |
34 | return True
35 |
36 | def unregister_folder(folder):
37 | folder = os.path.normpath( os.path.abspath(folder) )
38 |
39 | db_file = os.environ['HOME'] + '/.igm/paths.db'
40 |
41 | if not os.path.isfile(db_file):
42 | create_db(db_file)
43 |
44 | with sqlite3.connect(db_file) as db:
45 | x = db.execute('DELETE FROM paths WHERE folder=?', (folder,) )
46 | if x.rowcount == 0:
47 | raise RuntimeError('The folder %s is not in the database\n' % folder)
48 |
49 | return True
50 |
51 | def folder_info(folder=None):
52 | if folder is not None:
53 | folder = os.path.normpath( os.path.abspath(folder) )
54 |
55 | db_file = os.environ['HOME'] + '/.igm/paths.db'
56 |
57 | if not os.path.isfile(db_file):
58 | create_db(db_file)
59 |
60 | with sqlite3.connect(db_file) as db:
61 | if folder is not None:
62 | # return only specified folder
63 | c = db.execute('SELECT folder, name, cell_line, resolution, notes, tags, created, last_modified FROM paths WHERE folder=?', (folder, )).fetchall()
64 | if len(c) == 0:
65 | raise RuntimeError('Error: folder `%s` not in the database\n' % folder)
66 | else:
67 | folder, name, cell_line, resolution, notes, tags, ct, mt = c[0]
68 | return {
69 | 'folder' : folder,
70 | 'name': name,
71 | 'cell_line' : cell_line,
72 | 'resolution' : resolution,
73 | 'notes' : notes,
74 | 'tags' : tags,
75 | 'created' : ct,
76 | 'last_modified' : mt
77 | }
78 | else:
79 | # return all the folders
80 | c = db.execute('SELECT folder, name, cell_line, resolution, notes, tags, created, last_modified FROM paths').fetchall()
81 | res = []
82 | for folder, name, cell_line, resolution, notes, tags, ct, mt in c:
83 | res.append({
84 | 'folder' : folder,
85 | 'name': name,
86 | 'cell_line' : cell_line,
87 | 'resolution' : resolution,
88 | 'notes' : notes,
89 | 'tags' : tags,
90 | 'created' : ct,
91 | 'last_modified' : mt
92 | })
93 | return res
94 |
--------------------------------------------------------------------------------
/igm/ui/navigation.py:
--------------------------------------------------------------------------------
1 | import glob
2 | from os.path import basename, normpath, abspath, isdir, isfile
3 |
4 | def list_directory(path, root='/', ext=None):
5 |
6 | root = normpath(root)
7 | dname = normpath( abspath(root + '/' + path) )
8 |
9 | if not dname.startswith(root) or not isdir(dname):
10 | return {}
11 |
12 | content = glob.glob(dname + '/*')
13 | dirs = []
14 |
15 | if abspath(dname) != abspath(root):
16 | dirs += [ '..' ]
17 |
18 | rootlen = len(root)+1
19 | dirs += [basename( normpath(c) ) for c in content if isdir(c)]
20 | files = [basename( normpath(c) ) for c in content if isfile(c)]
21 |
22 | if ext is not None:
23 | if not isinstance(ext, list):
24 | ext = [ext]
25 | nfiles = []
26 | for e in ext:
27 | nfiles += [c for c in files if c.endswith(e)]
28 | files = nfiles
29 |
30 | return {
31 | 'path': dname[rootlen:],
32 | 'dirs': dirs,
33 | 'files': files,
34 | }
35 |
--------------------------------------------------------------------------------
/igm/ui/static/css/filebrowser.css:
--------------------------------------------------------------------------------
1 | #fb_u_loading {
2 | background: white;
3 | z-index:85;
4 | position:absolute;
5 | top:0px;
6 | left:0px;
7 | width:100%;
8 | height:100%;
9 | opacity:.3;
10 | }
11 |
12 | .fb_c_dir, .fb_c_file {
13 | line-height: 2.5em;
14 | position:relative;
15 | display: block;
16 | }
17 |
18 | .fb_l_dir, .fb_l_file {
19 | display: block-inline;
20 | width: 100%;
21 | height: 100%;
22 | word-break: break-all;
23 | }
24 |
25 | .fb_icon {
26 | width: 1.6em;
27 | display: inline-block;
28 | text-align: center;
29 | margin-right:1em;
30 | }
31 |
32 | .fb_c_dir:hover, .fb_c_file:hover {
33 | background-color: #eeeeee;
34 | }
35 |
36 | /*.fb_c_dir:before {
37 | content: "\F07B";
38 | font-family: FontAwesome;
39 | left:-25px;
40 | position:absolute;
41 | top:0;
42 | }
43 |
44 | .fb_c_file:before {
45 | content: "\F15B";
46 | font-family: FontAwesome;
47 | left:-25px;
48 | position:absolute;
49 | top:0;
50 | color: 'darkgreen';
51 | }*/
52 |
53 |
54 | .fb_l_dir {
55 | color: black;
56 | text-decoration: none;
57 | }
58 |
59 | .fb_l_dir:hover{
60 | color: darkblue;
61 | text-decoration: none;
62 | }
63 |
64 | .fb_l_file {
65 | color: 'darkgreen';
66 | }
67 |
68 | #fb_u_dirs, #fb_u_files {
69 | list-style: none;
70 | }
71 |
--------------------------------------------------------------------------------
/igm/ui/static/css/igm-edit-config.css:
--------------------------------------------------------------------------------
1 | .igm-group {
2 | padding: 1em;
3 | border-radius:5px;
4 | margin: 1em;
5 | }
6 |
7 | .igm-group-1 {
8 | background-color: #ffffff;
9 | }
10 |
11 |
12 | .igm-group-2 {
13 | background-color: #e1e8d9;
14 | }
15 |
16 |
17 | .igm-group-3 {
18 | background-color: #eeeeee;
19 |
20 | }
21 |
22 |
23 | .igm-group-4 {
24 | background-color: #dddfec;
25 | }
--------------------------------------------------------------------------------
/igm/ui/static/css/main.css:
--------------------------------------------------------------------------------
1 | html,body { height:100%; }
2 |
3 | #help-wrapper {
4 | background-color: white;
5 | position: fixed;
6 | width: 100%;
7 | height: 30%;
8 | bottom: 0px;
9 | left:0px;
10 | }
11 |
12 | #bottom-spacer {
13 | width: 100%;
14 | }
15 |
16 |
17 | #help-content {
18 | padding:2em;
19 | }
20 |
21 | #dragbar{
22 | background-color: #cccccc;
23 | width: :100%;
24 | height: 4px;
25 | cursor: row-resize;
26 | }
27 |
28 | #log-ta {
29 | font-family: Inconsolata, Consolas, Monaco, monospace;
30 | color: #eeeeee;
31 | background-color: #282923;
32 | -webkit-box-sizing: border-box;
33 | -moz-box-sizing: border-box;
34 | box-sizing: border-box;
35 | width: 100%;
36 | height: 80vh;
37 | overflow-y: scroll;
38 | white-space: pre;
39 | }
40 |
41 | #load-screen {
42 | position: absolute;
43 | top: 0;
44 | left: 0;
45 | width: 100%;
46 | height: 100%;
47 | z-index: 90;
48 | }
49 |
50 | #load-screen > .transparent-background {
51 | position: relative;
52 | top: 0;
53 | left: 0;
54 | width: 100%;
55 | height: 100%;
56 | background:white;
57 | opacity:.3;
58 | }
59 |
60 |
61 | #load-screen > span {
62 | position: fixed;
63 | top: 50%;
64 | left: 50%;
65 | -webkit-transform: translate(-50%, -50%);
66 | transform: translate(-50%, -50%);
67 | color: darkgrey;
68 | }
69 | /*
70 | .vertical-left-text {
71 | -ms-transform: rotate(-90deg);
72 | -webkit-transform: rotate(-90deg);
73 | transform: rotate(-90deg);
74 | }
75 | */
76 | #left-menu {
77 | height: 100%;
78 | float: left;
79 | padding-left: 0.5em;
80 | }
81 |
82 | #left-menu > button {
83 | clear:both;
84 | }
85 |
86 | #folder-navigator {
87 | border-right: solid 1px #cccccc;
88 | font-size: 13px;
89 | height: 100%;
90 | }
91 |
92 | .rotation-wrapper-outer {
93 | display: table;
94 | }
95 | .rotation-wrapper-inner {
96 | padding: 50% 0;
97 | height: 0;
98 | }
99 | .left-rotated {
100 | display: block;
101 | transform-origin: top left;
102 | /* Note: for a CLOCKWISE rotation, use the commented-out
103 | transform instead of this one. */
104 | transform: rotate(-90deg) translate(-100%);
105 | /* transform: rotate(90deg) translate(0, -100%); */
106 | margin-top: -50%;
107 |
108 | /* Not vital, but possibly a good idea if the element you're rotating contains
109 | text and you want a single long vertical line of text and the pre-rotation
110 | width of your element is small enough that the text wraps: */
111 | white-space: nowrap;
112 | }
113 |
114 | #folder-notes-edt {
115 | font-family: Inconsolata, Consolas, Monaco, monospace;
116 | }
117 |
118 | #igm-content {
119 | }
120 |
--------------------------------------------------------------------------------
/igm/ui/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alberlab/igm/edbb33135260443b8b41eb09d7ca4c37b19e330d/igm/ui/static/favicon.ico
--------------------------------------------------------------------------------
/igm/ui/static/js/filebrowser.js:
--------------------------------------------------------------------------------
1 |
2 | function baseName(str)
3 | {
4 | var base = new String(str).substring(str.lastIndexOf('/') + 1);
5 | // if(base.lastIndexOf(".") != -1)
6 | // base = base.substring(0, base.lastIndexOf("."));
7 | return base;
8 | }
9 |
10 | var FileBrowser = function(dom_element, onFileSelect) {
11 |
12 | var self = this;
13 | var root_path = '.';
14 | self.current_path = root_path;
15 | self.dom_element = $(dom_element);
16 |
17 | self.loading_div = $('');
18 | self.dom_element.append(self.loading_div);
19 | self.loading_div.hide();
20 |
21 | self.path_div = $('');
22 | self.dom_element.append(self.path_div);
23 |
24 | self.content_div = $('');
25 | self.dom_element.append(self.content_div);
26 |
27 | self.updateView = function(path, dirs, files) {
28 |
29 | self.current_path = path;
30 |
31 | self.loading_div.hide();
32 | self.path_div.html('Path' + path + '');
33 |
34 | var dirs_html = '';
39 |
40 | var files_html = '';
45 |
46 | self.content_div.html(dirs_html + files_html);
47 |
48 | $.each( self.content_div.find('.fb_l_dir'), function(index, item) {
49 | $(item).on( 'click', function() {
50 | self.navigate( $(item).attr('fb-target') );
51 | });
52 | });
53 |
54 | $.each( self.content_div.find('.fb_l_file'), function(index, item) {
55 | $(item).on( 'click', function() {
56 | onFileSelect( $(item).attr('fb-target') );
57 | });
58 | });
59 |
60 | }
61 |
62 | self.navigate = function(path) {
63 | self.loading_div.show();
64 | req = {
65 | request: 'listdir',
66 | path: path,
67 | };
68 | console.log(req);
69 | $.ajax({
70 | type: "POST",
71 | url: '/ajax/',
72 | dataType: 'json',
73 | data: JSON.stringify(req),
74 | contentType: 'application/json',
75 | success: function(data){
76 | console.log(data);
77 | self.updateView(data.path, data.dirs, data.hss);
78 | },
79 | error: function(x, e, t){
80 | console.log(x,e,t);
81 | alert('error!');
82 | }
83 | });
84 | }
85 |
86 | self.navigate(root_path);
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/igm/ui/static/js/genome_visualizer/filebrowser.js:
--------------------------------------------------------------------------------
1 |
2 | function baseName(str)
3 | {
4 | var base = new String(str).substring(str.lastIndexOf('/') + 1);
5 | // if(base.lastIndexOf(".") != -1)
6 | // base = base.substring(0, base.lastIndexOf("."));
7 | return base;
8 | }
9 |
10 | var FileBrowser = function(dom_element, onFileSelect, root_path='') {
11 |
12 | var self = this;
13 | self.current_path = root_path;
14 | self.dom_element = $(dom_element);
15 |
16 | self.loading_div = $('');
17 | self.dom_element.append(self.loading_div);
18 | self.loading_div.hide();
19 |
20 | self.path_div = $('');
21 | self.dom_element.append(self.path_div);
22 |
23 | self.content_div = $('');
24 | self.dom_element.append(self.content_div);
25 |
26 | self.dom_element.append($(''));
27 |
28 | self.updateView = function(path, dirs, files) {
29 |
30 | self.current_path = path;
31 |
32 | self.loading_div.hide();
33 | self.path_div.html('' + path + '');
34 |
35 | var dirs_html = '';
40 |
41 | var files_html = '';
46 |
47 | self.content_div.html(dirs_html + files_html);
48 |
49 | $.each( self.content_div.find('.fb_l_dir'), function(index, item) {
50 | $(item).on( 'click', function() {
51 | self.navigate( $(item).attr('fb-target') );
52 | });
53 | });
54 |
55 | $.each( self.content_div.find('.fb_l_file'), function(index, item) {
56 | $(item).on( 'click', function() {
57 | onFileSelect( $(item).attr('fb-target') );
58 | });
59 | });
60 |
61 | }
62 |
63 | self.navigate = function(path) {
64 | self.loading_div.show();
65 | req = {
66 | request: 'listhss',
67 | path: self.current_path + '/' + path,
68 | };
69 | $.ajax({
70 | type: "POST",
71 | url: '/ajax/',
72 | data : {
73 | 'data' : JSON.stringify(req)
74 | },
75 | dataType: 'json',
76 | success: function(data){
77 | self.updateView(data.path, data.dirs, data.files);
78 | self.current_path = data.path;
79 | },
80 | error: function(x, e, t){
81 | console.log(x,e,t);
82 | alert('error!');
83 | }
84 | });
85 | }
86 |
87 | self.navigate(root_path);
88 |
89 | $("#fb_u_reload_btn").click( function() {
90 | self.navigate(self.current_path);
91 | });
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/igm/ui/static/js/genome_visualizer/genomeapp.js:
--------------------------------------------------------------------------------
1 |
2 | var GenomeApp = function(){
3 |
4 | var self = this;
5 |
6 | // set some constants
7 |
8 | // set the size for the viewer
9 | var el = $('#viewer'); //record the elem so you don't crawl the DOM everytime
10 | var bottom = el.offset().top + el.outerHeight(true);
11 | var height = $(window).height() - bottom;
12 | var container = document.getElementById('viewer')
13 | $(container).height(Math.round($(window).height()*0.8));
14 |
15 | // create the viewer with default options
16 | var viewer = new Viewer(container);
17 |
18 | // create the app status
19 | var status = {};
20 |
21 | var interface = new InterfaceControl(self);
22 |
23 | this.viewer = viewer;
24 | this.status = status;
25 | this.interface = interface;
26 | this.fileBrowser = null;
27 |
28 | var newFileFlag = false;
29 |
30 |
31 | // sets up the file loader
32 |
33 | var fileField = document.getElementById('file-field');
34 |
35 | function getCopies( chroms ) {
36 |
37 | var copies = {};
38 | copies.ordered_keys = [];
39 |
40 | for (var i = 0; i < chroms.length; i++) {
41 |
42 | var cname = chroms[i];
43 |
44 | if ( copies[cname] === undefined ) {
45 |
46 | copies[cname] = [ i ];
47 | copies.ordered_keys.push(cname);
48 |
49 | } else {
50 |
51 | copies[cname].push( i );
52 |
53 | }
54 |
55 | }
56 |
57 | return copies;
58 |
59 | }
60 |
61 | self.gotFileData = function(data) {
62 |
63 | if (data.status === 'failed') {
64 | alert('ERROR:\n' + data.reason);
65 | interface.activate();
66 | return false;
67 | }
68 | status.coordinates = data.crd;
69 |
70 | if (newFileFlag) {
71 |
72 | status.chroms = data.chroms;
73 | status.cstarts = data.cstarts;
74 | status.idx = data.idx;
75 | status.rad = data.rad;
76 | status.num_frames = data.n;
77 | status.num_beads = data.crd[0].length;
78 | status.num_chains = data.crd.length;
79 | status.copies = getCopies( status.chroms );
80 | status.num_chrom = status.copies.ordered_keys.length;
81 | status.current_chains = [];
82 |
83 | for (var i = 0; i < status.num_chains; i++) {
84 | status.current_chains.push(i);
85 | }
86 |
87 | var original_palette = viewer.generatePalette( status.num_chrom );
88 | status.palette = new Array(status.num_chains);
89 |
90 | for (var i = 0; i < status.num_chrom; i++) {
91 | var cname = status.copies.ordered_keys[i];
92 | var copies = status.copies[ cname ];
93 | var hsl = new THREE.Color();
94 | original_palette[ i ].getHSL(hsl);
95 | var offset = 1.0;
96 | for (var j = 0; j < copies.length; j++) {
97 |
98 | var chain = copies[ j ];
99 | var newhsl = { h: parseInt(hsl.h*360), s: parseInt(hsl.s*100.0), l: parseInt(hsl.l*offset*100.0) };
100 | status.palette[ chain ] = new THREE.Color(`hsl(${newhsl.h},${newhsl.s}%,${newhsl.l}%)`);
101 |
102 | offset *= 0.66;
103 |
104 | }
105 |
106 | }
107 |
108 | interface.setChains( status );
109 |
110 |
111 | }
112 |
113 | viewer.onWindowResize();
114 | viewer.redraw(status);
115 | interface.activate();
116 | interface.updateControls( status, 'traj' );
117 |
118 |
119 |
120 | }
121 |
122 | self.requestData = function(fname, i) {
123 |
124 | interface.deactivate();
125 | var req = {
126 | request: 'get_structure',
127 | path: fname,
128 | n : i
129 | };
130 | console.log(req)
131 | $.ajax({
132 | type: "POST",
133 | url: '/ajax/',
134 | data : {
135 | 'data' : JSON.stringify(req)
136 | },
137 | success: self.gotFileData,
138 | dataType: 'json',
139 | error: function(x, err, et){
140 |
141 | console.log(x, err, et);
142 | interface.showLoad(false);
143 | interface.activate()
144 | interface.errorMessage('failed');
145 |
146 | },
147 | });
148 |
149 | }
150 |
151 | self.setFile = function(fname) {
152 |
153 | viewer.clear();
154 | status.current_frame = 0;
155 | status.current_chain = -1;
156 | newFileFlag = true;
157 | status.fname = fname;
158 |
159 | interface.showLoad(false);
160 |
161 | self.requestData(fname, 0);
162 | interface.updateControls(status, 'traj');
163 |
164 | }
165 |
166 | self.changeFrame = function( i ) {
167 |
168 | if ( i < 0 || i >= status.num_frames )
169 | return false;
170 |
171 |
172 | newFileFlag = false;
173 |
174 | viewer.clear();
175 |
176 | self.requestData(status.fname, i)
177 |
178 | status.current_frame = i;
179 |
180 | interface.updateControls(status, 'traj');
181 |
182 | //viewer.redraw(status, true);
183 |
184 | }
185 |
186 | self.changeChain = function( i ) {
187 |
188 | if ( i < 0 || i >= status.num_chains )
189 | return false;
190 |
191 | status.current_chain = i;
192 |
193 | interface.updateControls(status, 'all');
194 |
195 | viewer.redraw(status, true);
196 |
197 | }
198 |
199 | self.updateView = function(request) {
200 |
201 | interface.deactivate();
202 |
203 | if ( request.tube !== undefined )
204 | viewer.tubeFlag = request.tube;
205 |
206 | if ( request.sphere !== undefined )
207 | viewer.sphereFlag = request.sphere;
208 |
209 | if ( request.chain !== undefined ) {
210 |
211 | if ( request.chain === -1 ) {
212 |
213 | request.chain = [];
214 | for (var i = 0; i < status.num_chains; i++) {
215 | request.chain.push([i, true]);
216 | }
217 |
218 | }
219 |
220 | for (var i = 0; i < request.chain.length; i++) {
221 |
222 | var chain = request.chain[ i ][ 0 ];
223 | var visible = request.chain[ i ][ 1 ];
224 | var idx = status.current_chains.indexOf( chain );
225 |
226 | if ( visible && idx === -1 ) {
227 | status.current_chains.push( chain );
228 | }
229 | if ( !visible && idx !== -1 ) {
230 | status.current_chains.splice( idx, 1 );
231 | }
232 |
233 | }
234 |
235 | }
236 |
237 | viewer.redraw(status);
238 | interface.activate();
239 |
240 | }
241 |
242 | }
243 |
244 |
--------------------------------------------------------------------------------
/igm/ui/static/js/genome_visualizer/interface.js:
--------------------------------------------------------------------------------
1 |
2 | var InterfaceControl = function(app) {
3 |
4 | var self = this;
5 | self.app = app;
6 |
7 |
8 | self.loadScreen = $('');
9 | self.loadScreen.append( $('') );
10 | self.loadScreen.append( $('') );
11 |
12 | $('#structure-visualizer-tab').append(self.loadScreen);
13 | self.loadScreen.hide();
14 |
15 |
16 | self.deactivate = function() {
17 | self.loadScreen.show()
18 | // disable all action while computing
19 | $("button#select-file-btn").prop('disabled', true);
20 | $("#download-link").off("click");
21 | $(".toolbar-link").prop('disabled', true);
22 | $(".toolbar-link:not(.disabled)").addClass('disabled');
23 | $("input, select").prop('disabled', true);
24 | $("#traj-next, #traj-prev").prop('disabled', true);
25 |
26 | }
27 |
28 | self.activate = function() {
29 |
30 | self.loadScreen.hide();
31 | $("button#select-file-btn").prop('disabled', false);
32 | $(".toolbar-link").prop('disabled', false);
33 | $('.nav-link.disabled').removeClass("disabled");
34 | $("input, select").prop('disabled', false);
35 | $("#traj-next, #traj-prev").prop('disabled', false);
36 |
37 | }
38 |
39 | self.updateControls = function(status, selector){
40 |
41 |
42 | // trajectory controls
43 | if ( selector === 'all' || selector === 'traj' ) {
44 |
45 | if ( status.num_frames > 1 )
46 | $('#trajectory-controls').show();
47 | else
48 | $('#trajectory-controls').hide();
49 | $('#traj-frame').html( 'Structure ' + ( status.current_frame + 1 ) + ' of ' + status.num_frames );
50 |
51 | $('#structure-select-ctrl').val( status.current_frame );
52 | $('#chromosome-ctrl').val( status.current_chain );
53 |
54 | }
55 |
56 | }
57 |
58 |
59 | self.clearMessageBoard = function() {
60 | $("#result-info").html("");
61 | }
62 |
63 | self.errorMessage = function(err, preformatted=false) {
64 |
65 | var ediv = ''+ err + '
';
66 | var html = preformatted ? ''+ ediv + '
' : ediv;
67 | $("#result-info").append(html);
68 |
69 | }
70 |
71 | self.infoMessage = function(msg, preformatted=false) {
72 |
73 | var ediv = ''+ msg + '
';
74 | var html = preformatted ? ''+ ediv + '
' : ediv;
75 | $("#result-info").append(html);
76 |
77 | }
78 |
79 |
80 | self.setDownloadLink = function( data ) {
81 |
82 | var zip = new JSZip();
83 | zip.file("output.txt", data.result.out);
84 | zip.file("err.txt", data.result.err);
85 | zip.file("log.txt", data.result.log);
86 |
87 | $("#download-link").on("click", function () {
88 |
89 | zip.generateAsync( {type: "blob"} ).then( function ( blob ) { // 1) generate the zip file
90 |
91 | saveAs(blob, "results.zip"); // 2) trigger the download
92 |
93 | }, function (err) {
94 |
95 | self.errorMessage(err, true);
96 |
97 | });
98 |
99 | });
100 |
101 | }
102 |
103 |
104 | self.setLogs = function( data ) {
105 |
106 | var parmtxt = JSON.stringify( data.param, null, 2 );
107 | $('#log-prm').html( parmtxt );
108 | $('#log-cmd').html( data.cmd );
109 | $('#log-out').html( data.result.out );
110 | $('#log-err').html( data.result.err );
111 |
112 | }
113 |
114 |
115 | self.showLoad = function( yes=true ) {
116 |
117 | if (yes)
118 | $('#upload-dialog').modal('show');
119 | else
120 | $('#upload-dialog').modal('hide') ;
121 |
122 | }
123 |
124 | self.setChains = function( status ) {
125 |
126 | var chains = status.chroms;
127 | var palette = status.palette;
128 | var chrctrl = $('#chromosome-ctrl-div');
129 | var chromosomes = status.copies.ordered_keys;
130 |
131 | chrctrl.empty(); // remove old options
132 |
133 | var select = $('#chromosome-ctrl');
134 | select.empty(); // remove old options
135 | select.append($("").attr("value", "-1")
136 | .text('-- all --'));
137 |
138 | for (var i = 0; i < chromosomes.length; i++) {
139 |
140 | select.append($("").attr("value", status.copies[ chromosomes[ i ] ])
141 | .text(chromosomes[ i ]));
142 |
143 | }
144 |
145 | for (var i = 0; i < status.copies.ordered_keys.length; i++) {
146 |
147 | var cname = status.copies.ordered_keys[ i ];
148 | var copies = status.copies[ cname ];
149 | var inner_html = `${cname}
`;
150 |
151 | for (var j = 0; j < copies.length; j++) {
152 |
153 | var chain = copies[ j ];
154 | var color = '#' + palette[ chain ].getHexString();
155 |
156 | inner_html += `
157 |
158 |
159 |
163 |
164 |
165 | `;
166 |
167 | }
168 |
169 | chrctrl.append($("").addClass("form-row").html(inner_html));
170 |
171 | }
172 |
173 | $('input[name=chain_select]').on('change', function(event) {
174 |
175 | var caller = $(event.target)
176 |
177 | var selected = caller.prop('checked');
178 | var chain_id = parseInt(caller.attr('chain'));
179 |
180 | self.app.updateView({
181 |
182 | chain: [ [chain_id, selected] ]
183 |
184 | });
185 |
186 | });
187 |
188 | }
189 |
190 |
191 |
192 | }
193 |
--------------------------------------------------------------------------------
/igm/ui/static/js/genome_visualizer/main.js:
--------------------------------------------------------------------------------
1 | var app = null;
2 |
3 | $(document).ready(function() {
4 |
5 | // setup the app
6 | app = new GenomeApp();
7 |
8 | // actually disable links
9 | $(".toolbar-link.disabled").prop('disabled', true);
10 |
11 | //
12 | $("#bead-interval-invert").on("change", function() {
13 |
14 | app.changeBeadInterval();
15 |
16 | });
17 |
18 | $("#traj-next").on('click', function() {
19 |
20 | app.changeFrame( app.status.current_frame + 1 );
21 |
22 | });
23 |
24 | $("#traj-prev").on('click', function() {
25 |
26 | app.changeFrame( app.status.current_frame - 1 );
27 |
28 | });
29 |
30 | $("button#select-file-btn").click(function(){
31 |
32 | app.setFile($('#file-field').val());
33 | return false;
34 |
35 | });
36 |
37 |
38 | $('#chromosome-ctrl').on('change', function(){
39 |
40 | if ( $('#chromosome-ctrl').val() == "-1" ) {
41 |
42 | $('input[name=chain_select]').prop('checked', true);
43 | app.updateView({chain: -1});
44 |
45 | }
46 |
47 | else if ( $('#chromosome-ctrl').val() == "-2" ) {
48 |
49 | app.status.current_chains = [];
50 | $('input[name=chain_select]').prop('checked', false);
51 |
52 | }
53 |
54 | else {
55 |
56 | var ids = $('#chromosome-ctrl').val().split(',');
57 | for (var i = 0; i < ids.length; i++) {
58 |
59 | ids[ i ] = parseInt(ids[ i ]);
60 |
61 | }
62 |
63 | var cc = [];
64 | for (var i = 0; i < app.status.num_chains; i++) {
65 | if ( ids.indexOf(i) !== -1 ) {
66 | cc.push([i, true]);
67 | $('input#chain_select_' + i).prop('checked', true);
68 | } else {
69 | cc.push([i, false]);
70 | $('input#chain_select_' + i).prop('checked', false);
71 | }
72 | }
73 |
74 | app.updateView({chain: cc});
75 |
76 | }
77 |
78 | });
79 |
80 | $('#structure-select-ctrl').on('change', function(){
81 |
82 | app.changeFrame( parseInt( $('#structure-select-ctrl').val() ) - 1 );
83 |
84 | });
85 |
86 | $('#jobform').submit( function() {
87 |
88 | return false;
89 |
90 | });
91 |
92 | $('#tube-box').on('change', function(){
93 |
94 | app.updateView({
95 |
96 | tube: $('#tube-box').prop('checked')
97 |
98 | });
99 |
100 | });
101 |
102 | $('#sphere-box').on('change', function(){
103 |
104 | app.updateView({
105 |
106 | sphere: $('#sphere-box').prop('checked')
107 |
108 | });
109 |
110 | });
111 |
112 | app.fileBrowser = new FileBrowser('#file-browser', function(path) {
113 |
114 | app.setFile(path);
115 | return false;
116 |
117 | });
118 |
119 | });
120 |
--------------------------------------------------------------------------------
/igm/ui/static/js/genome_visualizer/util.js:
--------------------------------------------------------------------------------
1 | // deepcopy an object
2 | function deepcopy(obj){
3 | return JSON.parse(JSON.stringify(obj));
4 | }
5 |
6 | // python style range
7 | function range(start, end, increment) {
8 | var array = [];
9 | var current = start;
10 |
11 | increment = increment || 1;
12 | if (increment > 0) {
13 | while (current < end) {
14 | array.push(current);
15 | current += increment;
16 | }
17 | } else {
18 | while (current > end) {
19 | array.push(current);
20 | current += increment;
21 | }
22 | }
23 | return array;
24 | }
25 |
--------------------------------------------------------------------------------
/igm/ui/static/js/genome_visualizer/visualizer.js:
--------------------------------------------------------------------------------
1 | var app = null;
2 |
3 | $(document).ready(function() {
4 |
5 | // setup the app
6 | app = new GenomeApp();
7 |
8 | // actually disable links
9 | $(".toolbar-link.disabled").prop('disabled', true);
10 |
11 | //
12 | $("#bead-interval-invert").on("change", function() {
13 |
14 | app.changeBeadInterval();
15 |
16 | });
17 |
18 | $("#traj-next").on('click', function() {
19 |
20 | app.changeFrame( app.status.current_frame + 1 );
21 |
22 | });
23 |
24 | $("#traj-prev").on('click', function() {
25 |
26 | app.changeFrame( app.status.current_frame - 1 );
27 |
28 | });
29 |
30 | $("button#select-file-btn").click(function(){
31 |
32 | app.setFile($('#file-field').val());
33 | return false;
34 |
35 | });
36 |
37 |
38 | window.scrollTo(0, 0);
39 |
40 | $('#chromosome-ctrl').on('change', function(){
41 |
42 | if ( $('#chromosome-ctrl').val() == "-1" ) {
43 |
44 | $('input[name=chain_select]').prop('checked', true);
45 | app.updateView({chain: -1});
46 |
47 | }
48 |
49 | else if ( $('#chromosome-ctrl').val() == "-2" ) {
50 |
51 | app.status.current_chains = [];
52 | $('input[name=chain_select]').prop('checked', false);
53 |
54 | }
55 |
56 | else {
57 |
58 | var ids = $('#chromosome-ctrl').val().split(',');
59 | for (var i = 0; i < ids.length; i++) {
60 |
61 | ids[ i ] = parseInt(ids[ i ]);
62 |
63 | }
64 |
65 | var cc = [];
66 | for (var i = 0; i < app.status.num_chains; i++) {
67 | if ( ids.indexOf(i) !== -1 ) {
68 | cc.push([i, true]);
69 | $('input#chain_select_' + i).prop('checked', true);
70 | } else {
71 | cc.push([i, false]);
72 | $('input#chain_select_' + i).prop('checked', false);
73 | }
74 | }
75 |
76 | app.updateView({chain: cc});
77 |
78 | }
79 |
80 | });
81 |
82 | // show the file dialog as soon as the page is ready.
83 | $('#upload-dialog').modal("show");
84 |
85 | $('#structure-select-ctrl').on('change', function(){
86 |
87 | app.changeFrame( parseInt( $('#structure-select-ctrl').val() ) - 1 );
88 |
89 | });
90 |
91 | $('#jobform').submit( function() {
92 |
93 | return false;
94 |
95 | });
96 |
97 | $('#tube-box').on('change', function(){
98 |
99 | app.updateView({
100 |
101 | tube: $('#tube-box').prop('checked')
102 |
103 | });
104 |
105 | });
106 |
107 | $('#sphere-box').on('change', function(){
108 |
109 | app.updateView({
110 |
111 | sphere: $('#sphere-box').prop('checked')
112 |
113 | });
114 |
115 | });
116 |
117 | app.fileBrowser = new FileBrowser('#file-browser', function(path) {
118 |
119 | app.setFile(path);
120 | return false;
121 | });
122 |
123 | });
124 |
125 |
--------------------------------------------------------------------------------
/igm/ui/static_pages.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/igm/ui/templates/base.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {% block title %}Default title{% end %}
5 |
6 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | {% block head %} {% end %}
17 |
18 |
19 |
20 | {% block body %}Nothing to show{% end %}
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/igm/ui/templates/cfg_form.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block head %}
4 |
10 |
11 |
57 |
58 | {% end %}
59 |
60 | {% block title %}Generation status{% end %}
61 |
62 | {% block body %}
63 |
64 | IGM run configuration
65 |
66 |
67 |
71 | {% end %}
--------------------------------------------------------------------------------
/igm/ui/templates/history.html:
--------------------------------------------------------------------------------
1 | {% extends "base.html" %}
2 |
3 | {% block head %}{% end %}
4 |
5 | {% block title %}Generation status{% end %}
6 |
7 | {% block body %}
8 |
9 | Job history
10 |
11 |
12 | Directory: {{ directory }}
13 | Configuration file: {{ cfg_fname }}
14 |
15 |
16 | Cluster status:
17 |
18 |
19 | workers: {{ cstatus[0] }}, ({{ cstatus[2] }} active, {{ cstatus[3] }} idle). queued jobs: {{ cstatus[1] }}
20 |
21 |
22 |
23 |
24 | Step
25 | |
26 |
27 | Name
28 | |
29 |
30 | Time started
31 | |
32 |
33 | Time consumed
34 | |
35 |
36 | Total time
37 | |
38 |
39 | Status
40 | |
41 |
42 |
43 |
44 | {% for h in history %}
45 |
47 |
48 | {{ h['step_no'] }}
49 | |
50 |
51 | {{ h['name'] }}
52 | |
53 |
54 | {{ h['strtime'] }}
55 | |
56 |
57 | {{ h['consumed'] }}
58 | |
59 |
60 | +{{ h['elapsed'] }}
61 | |
62 |
63 | {{ h['status'] }}
64 | |
65 |
66 | {% end %}
67 |
68 |
69 |
70 |
71 | {% end %}
--------------------------------------------------------------------------------
/igm/ui/views.py:
--------------------------------------------------------------------------------
1 | from ipyparallel import Client
2 | import os, os.path
3 | from tornado import template
4 | from ..core.config import Config
5 | from ..core.defaults.defaults import igm_options
6 | from ..core.job_tracking import StepDB
7 | from ..utils.log import pretty_time
8 | from .utils import generate_form, parse_post_config
9 | import time
10 | import igm
11 |
12 | template_dir = os.path.join( os.path.dirname( os.path.abspath(__file__) ), 'templates' )
13 | loader = template.Loader(template_dir)
14 |
15 | def render(template, data):
16 | return loader.load(template).generate(**data)
17 |
18 |
19 |
20 |
21 | def cluster_status():
22 | try:
23 | rcl = Client()
24 | nworkers = len(rcl[:])
25 | qstat = rcl.queue_status()
26 | queued = qstat[u'unassigned']
27 | working = sum([ qstat[w][u'tasks'] for w in rcl.ids ])
28 | idle = nworkers - working
29 | rcl.close()
30 | except:
31 | nworkers, queued, working, idle = 0, 0, 0, 0
32 | return nworkers, queued, working, idle
33 |
34 | def history(cfgf):
35 |
36 | cfgf = os.path.abspath(cfgf)
37 | cfg = Config(cfgf)
38 | try:
39 | db = StepDB(cfg, mode='r')
40 | h = db.get_history()
41 | except OSError:
42 | h = []
43 | for i in range(len(h)):
44 | h[i]['elapsed'] = pretty_time(h[i]['time'] - h[0]['time'])
45 | h[i]['step_no'] = i
46 | h[i]['consumed'] = pretty_time(h[i]['time'] - h[max(i-1, 0)]['time'])
47 | h[i]['strtime'] = time.strftime('%c', time.localtime(h[i]['time']))
48 |
49 | return render( 'history.html', {
50 | 'history': h,
51 | 'directory': os.path.dirname(cfgf),
52 | 'cfg_fname': os.path.basename(cfgf),
53 | 'cstatus': cluster_status(),
54 | })
55 |
56 | def config_form():
57 | form_data = generate_form(igm_options)
58 | return render( 'cfg_form.html', {
59 | 'form_data': form_data
60 | })
61 |
62 | import traceback
63 | def config_form_process(post):
64 | try:
65 | out = parse_post_config(igm_options, post, 'igm')
66 | except:
67 | out = '' + traceback.format_exc()
68 | out += str(post)
69 | out += '
'
70 | return out
71 |
72 | def index():
73 | schema_file = os.path.join(
74 | os.path.dirname( os.path.abspath(igm.__file__) ),
75 | 'config_schema.json'
76 | )
77 | return render( 'index.html', {
78 | 'schema': open(schema_file).read()
79 | })
80 |
--------------------------------------------------------------------------------
/igm/utils/__init__.py:
--------------------------------------------------------------------------------
1 | from __future__ import division, print_function
2 |
3 | from .actdist import get_actdist
4 | from .files import HmsFile
5 |
6 | from .log import SetupLogging, logger
7 |
8 |
--------------------------------------------------------------------------------
/igm/utils/actdist.py:
--------------------------------------------------------------------------------
1 | from __future__ import division, print_function
2 |
3 | try:
4 | from itertools import izip as zip
5 | except ImportError:
6 | pass
7 |
8 | def cleanProbability(pij, pexist):
9 | if pexist < 1:
10 | pclean = (pij - pexist) / (1.0 - pexist)
11 | else:
12 | pclean = pij
13 | return max(0, pclean)
14 |
15 | def get_actdist(i, j, pwish, plast, hss, contactRange=2, option=0):
16 | '''
17 | Serial function to compute the activation distances for a pair of loci
18 | It expects some variables to be defined in its scope:
19 |
20 | Parameters
21 | ----------
22 | i, j : int
23 | index of the first, second locus
24 | pwish : float
25 | target contact probability
26 | plast : float
27 | the last refined probability
28 | hss : alabtools.analysis.HssFile
29 | file containing coordinates
30 | contactRange : int
31 | contact range of sum of radius of beads
32 | option : int
33 | calculation option:
34 | (0) intra chromosome contacts are considered intra
35 | (1) intra chromosome contacts are assigned intra/inter equally
36 | Returns
37 | -------
38 | i (int)
39 | j (int)
40 | ad (float): the activation distance
41 | p (float): the corrected probability
42 | '''
43 |
44 | # import here in case is executed on a remote machine
45 | import numpy as np
46 |
47 | if (i==j):
48 | return []
49 |
50 | n_struct = hss.get_nstruct()
51 | copy_index = hss.get_index().copy_index
52 | chrom = hss.get_index().chrom
53 |
54 | ii = copy_index[i]
55 | jj = copy_index[j]
56 |
57 | n_combinations = len(ii) * len(jj)
58 | n_possible_contacts = min(len(ii), len(jj))
59 | #for diploid cell n_combinations = 2*2 =4
60 | #n_possible_contacts = 2
61 |
62 | radii = hss.get_radii()
63 | ri, rj = radii[ii[0]], radii[jj[0]]
64 |
65 | d_sq = np.empty((n_combinations, n_struct))
66 |
67 | it = 0
68 | for k in ii:
69 | for m in jj:
70 | x = hss.get_bead_crd(k)
71 | y = hss.get_bead_crd(m)
72 | d_sq[it] = np.sum(np.square(x - y), axis=1)
73 | it += 1
74 | #=
75 |
76 | rcutsq = np.square(contactRange * (ri + rj))
77 | d_sq.sort(axis=0)
78 |
79 | contact_count = np.count_nonzero(d_sq[0:n_possible_contacts, :] <= rcutsq)
80 | pnow = float(contact_count) / (n_possible_contacts * n_struct)
81 | sortdist_sq = np.sort(d_sq[0:n_possible_contacts, :].ravel())
82 |
83 | t = cleanProbability(pnow, plast)
84 | p = cleanProbability(pwish, t)
85 |
86 | res = []
87 | if p>0:
88 | o = min(n_possible_contacts * n_struct - 1,
89 | int(round(n_possible_contacts * p * n_struct)))
90 | activation_distance = np.sqrt(sortdist_sq[o])
91 |
92 | if (chrom[i] == chrom[j]) and (option == 0):
93 | res = [(i0, i1, activation_distance, p) for i0,i1 in zip(ii,jj)]
94 | else:
95 | res = [(i0, i1, activation_distance, p) for i0 in ii for i1 in jj]
96 | return res
97 |
--------------------------------------------------------------------------------
/igm/utils/emails.py:
--------------------------------------------------------------------------------
1 | from .log import logger
2 | from email.message import EmailMessage
3 | import smtplib
4 | import traceback
5 | from ..core.config import Config
6 |
7 | '''
8 | Works only with localhost for now. Using other smtp will require to setup the connection, with its own security issues
9 | "smtp" : {
10 | "label" : "SMTP server",
11 | "dtype" : "str",
12 | "role" : "optional-input",
13 | "blank": true,
14 | "description" : "SMTP server to use. If not specified, localhost is used"
15 | }
16 | '''
17 |
18 | def send_email(to, sender=None, smtpserver='localhost', subject="", content=""):
19 | try:
20 | if isinstance(to, (Config, dict)):
21 | cfg = to
22 | to = cfg['address']
23 | sender = cfg.get('from', sender)
24 | smtpserver = cfg.get('smtp', smtpserver)
25 | msg = EmailMessage()
26 | msg.set_content(content)
27 | msg['Subject'] = subject
28 | if sender is None:
29 | sender = to
30 | msg['From'] = sender
31 | msg['To'] = to
32 | with smtplib.SMTP(smtpserver) as s:
33 | s.send_message(msg)
34 |
35 | except:
36 | logger.error('Cannot send email')
37 | logger.error(traceback.format_exc())
38 |
--------------------------------------------------------------------------------
/igm/utils/files.py:
--------------------------------------------------------------------------------
1 | from __future__ import division, print_function
2 | import numpy as np
3 | import h5py
4 | from ..model import Model
5 | from ..restraints.restraint import Restraint
6 | __hms_version__ = 1
7 |
8 | class HmsFile(h5py.File):
9 | """
10 | h5py.File like object for hms model structure files.
11 | """
12 |
13 | def __init__(self, *args, **kwargs):
14 | h5py.File.__init__(self, *args, **kwargs)
15 | try:
16 | self._version = self.attrs['version']
17 | except(KeyError):
18 | self._version = __hms_version__
19 | self.attrs.create('version', __hms_version__, dtype='int32')
20 | try:
21 | self._nbead = self.attrs['nbead']
22 | except(KeyError):
23 | self._nbead = 0
24 | self.attrs.create('nbead', 0, dtype='int32')
25 | try:
26 | self._struct_id = self.attrs['struct_id']
27 | except(KeyError):
28 | self._struct_id = 0
29 | self.attrs.create('struct_id', 0, dtype='int32')
30 |
31 | try:
32 | self._restraints_grp = self['/restraints/']
33 | except(KeyError):
34 | self._restraints_grp = self.create_group("restraints")
35 | #===
36 | def get_version(self):
37 | return self._version
38 |
39 | def get_nbead(self):
40 | return self._nbead
41 |
42 | def get_struct_id(self):
43 | return self._struct_id
44 |
45 | def get_coordinates(self, read_to_memory=True):
46 |
47 | '''
48 | Parameters
49 | ----------
50 | read_to_memory (bool) :
51 | If True (default), the coordinates will be read and returned
52 | as a numpy.ndarray. If False, a h5py dataset object will be
53 | returned. In the latter case, note that the datased is valid
54 | only while the file is open.
55 | '''
56 |
57 | if read_to_memory:
58 | return self['coordinates'][:]
59 | return self['coordinates']
60 |
61 | def get_total_restraints(self):
62 | total = 0
63 | for r in self._restraints_grp.keys():
64 | total += self._restraints_grp[r].attrs["total"]
65 | return total
66 |
67 | def get_total_violations(self):
68 | total = 0
69 | for r in self._restraints_grp.keys():
70 | total += self._restraints_grp[r].attrs["nvios"]
71 | return total
72 |
73 | def set_nbead(self, n):
74 | self.attrs['nbead'] = self._nbead = n
75 |
76 | def set_struct_id(self, n):
77 | self.attrs['struct_id'] = self._struct_id = n
78 |
79 | def saveModel(self, struct_id, model):
80 | assert isinstance(model, Model), "Parameter has to be a Model instance"
81 |
82 | crd = model.getCoordinates()
83 | self.saveCoordinates(struct_id, crd)
84 |
85 | def saveCoordinates(self, struct_id, crd):
86 | if "coordinates" in self:
87 | self["coordinates"][...] = crd
88 | else:
89 | self.create_dataset("coordinates", data = crd,
90 | chunks=True, compression="gzip")
91 |
92 | self.set_nbead(len(crd))
93 | self.set_struct_id(struct_id)
94 |
95 | def saveViolations(self, restraint, tolerance=0.05):
96 | assert isinstance(restraint, Restraint), "Parameter has to be a Restraint instance"
97 |
98 | violations, ratios = restraint.get_violations(tolerance)
99 | name = type(restraint).__name__
100 |
101 | if name in self._restraints_grp:
102 | self._restraints_grp[name]["violations"] = np.array(violations).astype("S50")
103 | self._restraints_grp[name]["ratios"] = np.array(ratios)
104 | self._restraints_grp[name].attrs["total"] = len(restraint.forceID)
105 | self._restraints_grp[name].attrs["nvios"] = len(ratios)
106 | else:
107 | subgrp = self._restraints_grp.create_group(name)
108 | subgrp.create_dataset("violations", data = np.array(violations).astype("S50"),
109 | chunks = True, compression = "gzip")
110 | subgrp.create_dataset("ratios", data = np.array(ratios),
111 | chunks = True, compression = "gzip")
112 | subgrp.attrs.create("total", len(restraint.forceID), dtype='int32')
113 | subgrp.attrs.create("nvios", len(ratios), dtype='int32')
114 | #-
115 | return len(ratios)
116 |
117 | def make_absolute_path(path, basedir='.'):
118 | import os.path
119 | if os.path.isabs(path):
120 | return path
121 | return os.path.abspath( os.path.join(basedir, path) )
122 |
123 | def h5_create_group_if_not_exist(root, groupname):
124 | if groupname in root:
125 | return root[groupname]
126 | else:
127 | return root.create_group(groupname)
128 |
129 | def h5_create_or_replace_dataset(root, dataname, data, **kwargs):
130 | if dataname in root:
131 | root[dataname][...] = data
132 | else:
133 | root.create_dataset(dataname, data=data, **kwargs)
134 | return root[dataname]
135 |
--------------------------------------------------------------------------------
/igm/utils/kernel_testing.py:
--------------------------------------------------------------------------------
1 | import os
2 | import numpy as np
3 |
4 | from igm.model import *
5 | from igm.model.kernel.lammps_model import LammpsModel
6 | from igm.steps.ModelingStep import ModelingStep
7 | from igm.core import Config
8 | from igm.core.job_tracking import StepDB
9 | from alabtools.analysis import HssFile
10 | from igm.restraints import Polymer, Envelope, Steric, HiC, Sprite
11 | from igm.utils import HmsFile
12 |
13 | def debug_minimization(cfg, struct_id, rname, **kwargs):
14 | if not isinstance(cfg, dict):
15 | cfg = Config(cfg)
16 | if os.path.isfile(cfg['step_db']):
17 | db = StepDB(cfg)
18 | h = db.get_history()
19 | cfg.update(h[-1])
20 |
21 | cfg['optimization']['optimizer_options'].update(kwargs)
22 | cfg['optimization']['keep_temporary_files'] = True
23 |
24 | step_id = rname
25 |
26 | hssfilename = cfg['structure_output']
27 |
28 | #read index, radii, coordinates
29 | with HssFile(hssfilename,'r') as hss:
30 | index = hss.index
31 | radii = hss.radii
32 | if cfg.get('random_shuffling', False):
33 | crd = generate_random_in_sphere(radii, cfg['model']['nucleus_radius'])
34 | else:
35 | crd = hss.get_struct_crd(struct_id)
36 |
37 | #init Model
38 | model = Model(uid=struct_id)
39 |
40 | # get the chain ids
41 | chain_ids = np.concatenate( [ [i]*s for i, s in enumerate(index.chrom_sizes) ] )
42 |
43 | #add particles into model
44 | n_particles = len(crd)
45 | for i in range(n_particles):
46 | model.addParticle(crd[i], radii[i], Particle.NORMAL, chainID=chain_ids[i])
47 |
48 | #========Add restraint
49 | monitored_restraints = []
50 |
51 | #add excluded volume restraint
52 | ex = Steric(cfg['model']['evfactor'])
53 | model.addRestraint(ex)
54 |
55 | #add nucleus envelop restraint
56 | if cfg['model']['nucleus_shape'] == 'sphere':
57 | ev = Envelope(cfg['model']['nucleus_shape'],
58 | cfg['model']['nucleus_radius'],
59 | cfg['model']['contact_kspring'])
60 | elif cfg['model']['nucleus_shape'] == 'ellipsoid':
61 | ev = Envelope(cfg['model']['nucleus_shape'],
62 | cfg['model']['nucleus_semiaxes'],
63 | cfg['model']['contact_kspring'])
64 | else:
65 | raise NotImplementedError('Invalid nucleus shape')
66 | model.addRestraint(ev)
67 |
68 | #add consecutive polymer restraint
69 | pp = Polymer(index,
70 | cfg['model']['contact_range'],
71 | cfg['model']['contact_kspring'])
72 | model.addRestraint(pp)
73 | monitored_restraints.append(pp)
74 |
75 | #add Hi-C restraint
76 | # if "Hi-C" in cfg['restraints']:
77 | # dictHiC = cfg['restraints']['Hi-C']
78 | # actdist_file = cfg['runtime']['Hi-C']['actdist_file']
79 | # contact_range = dictHiC.get( 'contact_range', 2.0 )
80 | # k = dictHiC.get( 'contact_kspring', 1.0 )
81 |
82 | # hic = HiC(actdist_file, contact_range, k)
83 | # model.addRestraint(hic)
84 | # monitored_restraints.append(hic)
85 |
86 | # if "sprite" in cfg['restraints']:
87 | # sprite_opt = cfg['restraints']['sprite']
88 | # sprite = Sprite(
89 | # sprite_opt['assignment_file'],
90 | # sprite_opt['volume_fraction'],
91 | # struct_id,
92 | # sprite_opt['kspring']
93 | # )
94 | # model.addRestraint(sprite)
95 | # monitored_restraints.append(sprite)
96 |
97 | #========Optimization
98 | #optimize model
99 | cfg['runtime']['run_name'] = rname
100 | model.optimize(cfg)
101 |
102 | tol = cfg.get('violation_tolerance', 0.01)
103 | lockfile = os.path.join('.', '%s.%d.ready' % (step_id, struct_id) )
104 | with FileLock(lockfile):
105 | open(lockfile, 'w').close() # touch the ready-file
106 | ofname = os.path.join('.', 'mstep_%d.hms' % struct_id)
107 | with HmsFile(ofname, 'w') as hms:
108 | hms.saveModel(struct_id, model)
109 |
110 | for r in monitored_restraints:
111 | hms.saveViolations(r, tolerance=tol)
112 |
113 | # double check it has been written correctly
114 | with HmsFile(ofname, 'r') as hms:
115 | if np.all( hms.get_coordinates() == model.getCoordinates() ):
116 | raise RuntimeError('error writing the file %s' % ofname)
117 |
--------------------------------------------------------------------------------
/igm/utils/log.py:
--------------------------------------------------------------------------------
1 | from time import time, strftime, localtime
2 | import sys
3 | import logging
4 | import os.path
5 |
6 | # clear root logger handler
7 | FORMAT = '(%(name)s) %(asctime)-15s [%(levelname)s] %(message)s'
8 | logging.basicConfig(format=FORMAT, level=logging.INFO)
9 | logger = logging.getLogger('IGM')
10 |
11 | class bcolors:
12 | HEADER = '\033[95m'
13 | OKBLUE = '\033[94m'
14 | OKGREEN = '\033[92m'
15 | WARNING = '\033[93m'
16 | FAIL = '\033[91m'
17 | ENDC = '\033[0m'
18 | BOLD = '\033[1m'
19 | UNDERLINE = '\033[4m'
20 |
21 | def SetupLogging(cfg):
22 | if 'log' in cfg['parameters']:
23 | loglevel=logging.INFO
24 | set_log(cfg['parameters']['log'], loglevel=loglevel)
25 |
26 |
27 | def set_log(fname, loglevel=logging.INFO):
28 |
29 | fname = os.path.abspath(fname)
30 |
31 | # ensure the file is not double added
32 | hs = [h.baseFilename for h in logger.handlers if isinstance(h, logging.FileHandler)]
33 | if fname in hs:
34 | return
35 |
36 | fh = logging.FileHandler(fname)
37 | fh.setFormatter(logging.Formatter(FORMAT))
38 | logger.addHandler(fh)
39 |
40 |
41 | def pretty_time(seconds):
42 | '''
43 | Prints the *seconds* in the format h mm ss
44 | '''
45 | seconds = int(seconds)
46 | m, s = divmod(seconds, 60)
47 | h, m = divmod(m, 60)
48 | return "%dh %02dm %02ds" % (h, m, s)
49 |
50 | def print_progress(iterable,
51 | length=None,
52 | every=1,
53 | timeout=None,
54 | size=12,
55 | fmt='(IGM) {bar} {percent:6.2f}% ({completed}/{total}) | {elapsed:12s} | ETA: {remaining:12s}',
56 | timefmt='%c',
57 | fd=sys.stdout ):
58 |
59 | if hasattr(iterable, '__len__') and length is None:
60 | length = len(iterable)
61 |
62 | last = 0
63 | fill = 0
64 | if hasattr(fd, 'isatty') and (not fd.isatty()):
65 | fd.write('0 |' + ' ' * size + '| 100\n |')
66 | fd.flush()
67 | lastfill = fill
68 | start = time()
69 | for i, v in enumerate(iterable):
70 | if fd.isatty():
71 | print_flag = ( i == 0 )
72 | if timeout is not None and time() - last > timeout:
73 | last = time()
74 | print_flag = True
75 | elif every is not None and (i+1) % every == 0:
76 | print_flag = True
77 | elif length is not None and i == length - 1:
78 | print_flag = True
79 |
80 | if print_flag:
81 | vals = {}
82 | vals['completed'] = i + 1
83 | vals['total'] = length
84 | if length is not None:
85 | vals['percent'] = (i + 1) * 100.0 / length
86 | fill = int(size * float(i+1) / length)
87 | else:
88 | vals['percent'] = 0.0
89 | fill += 1
90 |
91 | now = time()
92 | elapsed = now - start
93 | vals['elapsed'] = pretty_time(elapsed)
94 | if i == 0 or length is None:
95 | vals['remaining'] = 'N/A'
96 | vals['eta'] = 'N/A'
97 | else:
98 | remaining = elapsed / i * ( length - i )
99 | vals['remaining'] = pretty_time(remaining)
100 | eta = now + remaining
101 | vals['eta'] = strftime(timefmt, localtime(eta))
102 |
103 | if length:
104 | pb = '[' + '=' * fill + ' ' * (size-fill) + '] '
105 | else:
106 | pos = fill % (2*(size-1))
107 | if pos >= size:
108 | pos = 2*size - pos - 2
109 | pb = '[' + ' ' * (pos) + '=' + ' ' * (size-pos-1) + '] '
110 | vals['bar'] = pb
111 | fd.write( '\r' + fmt.format(**vals) )
112 | fd.flush()
113 | else:
114 | if length:
115 | fill = ( (i + 1) * size ) // length
116 | if fill > lastfill:
117 | lastfill = fill
118 | fd.write('=')
119 |
120 | yield v
121 |
122 | if not fd.isatty():
123 | fd.write('|')
124 | fd.write('\n')
125 | fd.flush()
126 |
--------------------------------------------------------------------------------
/igm/utils/sprite/sprite.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import h5py
3 |
4 | SPRITE_H5_CHUNKSIZE = 1000000
5 |
6 | def sprite_clusters_txt_to_h5(textfile, outfile):
7 | with h5py.File(outfile, 'w') as h5f:
8 | indptr = [ 0 ]
9 | lastwrite = 0
10 | h5f.create_dataset('data', shape=(0,), maxshape=(None,), chunks=(SPRITE_H5_CHUNKSIZE,) , dtype=np.int32)
11 | data = np.empty(shape=(0,), dtype=np.int32)
12 | for line in open(textfile, 'r'):
13 | cluster = np.array([ int(x) for x in line.split()[1:] ], dtype=np.int32)
14 | indptr.append(indptr[-1] + len(cluster))
15 | data = np.concatenate([data, cluster])
16 | if indptr[-1] - lastwrite > SPRITE_H5_CHUNKSIZE:
17 | h5f['data'].resize( (indptr[-1], ) )
18 | h5f['data'][lastwrite:] = data
19 | lastwrite = indptr[-1]
20 | data = np.empty(shape=(0,), dtype=np.int32)
21 |
22 | h5f['data'].resize( (indptr[-1], ) )
23 | h5f['data'][lastwrite:] = data
24 | h5f.create_dataset('indptr', data=indptr, dtype=np.int32)
25 |
26 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from distutils.core import setup, Extension
3 | from Cython.Build import cythonize
4 | import numpy
5 | #from setuptools import setup, find_packages
6 | import re, os
7 | def find_packages(path='.'):
8 | ret = []
9 | for root, dirs, files in os.walk(path):
10 | if '__init__.py' in files:
11 | ret.append(re.sub('^[^A-z0-9_]+', '', root.replace('/', '.')))
12 | return ret
13 |
14 | install_requires = [
15 | 'numpy>=1.9',
16 | 'scipy>=0.16',
17 | 'h5py>=2.5',
18 | 'alabtools>=0.0.1',
19 | 'tqdm', 'tornado',
20 | 'cloudpickle',
21 | 'cython'
22 | ]
23 |
24 | tests_require = [
25 | 'mock'
26 | ]
27 |
28 |
29 | extras_require = {
30 | 'docs': [
31 | 'Sphinx>=1.1',
32 | ]
33 | }
34 |
35 | extensions = [
36 | Extension("igm.cython_compiled.sprite",
37 | ["igm/cython_compiled/sprite.pyx", "igm/cython_compiled/cpp_sprite_assignment.cpp"]),
38 | ]
39 |
40 | extensions = cythonize(extensions)
41 |
42 | setup(
43 | name = 'igm',
44 | version = '1.0.0',
45 | author = 'Guido Polles, Nan Hua',
46 | author_email = 'polles@usc.edu nhua@usc.edu',
47 | url = 'https://github.com/alberlab/igm',
48 | description = 'Integrated Genome Modeling',
49 |
50 | packages=find_packages('./igm/'),
51 | package_data={'igm.core' : ['defaults/*.json'],
52 | 'igm.ui': ['static/css/*', 'static/js/*', 'static/js/genome_visualizer/*', 'templates/*']},
53 | #package_data={'' : ['core/defaults/*', 'ui/static/css/*', 'ui/static/js/*', 'ui/static/js/genome_visualizer/*', 'ui/templates/*']},
54 | include_package_data=True,
55 | install_requires=install_requires,
56 | tests_require=tests_require,
57 | extras_require=extras_require,
58 |
59 | ext_modules=extensions,
60 | include_dirs=[numpy.get_include()],
61 | scripts=['bin/igm-run', 'bin/igm-server', 'bin/igm-register-dir', 'bin/igm-info-dir',
62 | 'bin/igm-unregister-dir', 'bin/igm-stop', 'bin/igm-report'],
63 | )
64 |
--------------------------------------------------------------------------------