mempro 0.2.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mempro
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Membrane Protein Orientation in Lipid Bilayers
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -14,6 +14,8 @@ Dynamic: license-file
14
14
  # MemPrO
15
15
  Membrane Protein Orientation in Lipid Bilayers. The paper associated with this code can be found [here](https://doi.org/10.1021/acs.jctc.5c01433).
16
16
 
17
+ [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/pstansfeld/MemPrO/blob/main/MemPrO_Colab.ipynb)
18
+
17
19
  Code and parameters from the following were used to help write this code:
18
20
 
19
21
  **Insane:**
@@ -56,7 +58,9 @@ pip install "matplotlib>=3.8.4"
56
58
 
57
59
  The Martini 3 force field file (`martini_v3.itp`) is included in `mempro/`. To use a different one, supply its path with the `-itp` flag; the standard file can be downloaded from [here](https://cgmartini.nl/docs/downloads/force-field-parameters/martini3/particle-definitions.html).
58
60
 
59
- MemPrO is also accessible on Google Colab via the link [ADD LINK]. The Colab will contain instructions for use. (Currently the Colab is WIP and this will be updated.)
61
+ ### Google Colab
62
+
63
+ MemPrO can also be run without installing anything locally, using the [MemPrO Colab notebook](https://colab.research.google.com/github/pstansfeld/MemPrO/blob/main/MemPrO_Colab.ipynb). It installs MemPrO from PyPI, orients a structure from the PDB, AlphaFold DB or your own upload, shows the results (including a 3D view of the top-ranked orientation), and can optionally predict membrane deformations with MemPrOD. It runs on both CPU and GPU runtimes.
60
64
 
61
65
  ## Running MemPrO
62
66
 
@@ -72,7 +76,7 @@ python PATH/TO/MemPrO.py -f input_file.pdb
72
76
  mempro -f input_file.pdb
73
77
  ```
74
78
 
75
- No environment variables are required. By default MemPrO will use all available CPU cores and look for `Insane4MemPrO.py` and `martini_v3.itp` in the same directory as the script (or the installed package directory). These paths can be overridden with the `-insane` and `-itp` flags if needed.
79
+ No environment variables are required. By default MemPrO will use a GPU if JAX can see one, otherwise one CPU device per performance core (see `-nc`), and will look for `Insane4MemPrO.py` and `martini_v3.itp` in the same directory as the script (or the installed package directory). These paths can be overridden with the `-insane` and `-itp` flags if needed.
76
80
 
77
81
  Compiled code is cached in `~/.cache/mempro/jax`, so runs after the first one start about 15 s faster. Set `JAX_COMPILATION_CACHE_DIR` to put the cache somewhere else, or `MEMPRO_NO_JAX_CACHE=1` to turn it off.
78
82
 
@@ -82,6 +86,8 @@ MemPrO is intended to be run on PDB files, and will output the following files:
82
86
 
83
87
  * **orientation.txt** — Contains general information about all of the final minima. In general rank 1 is the best orientation, however there are cases where this is not true. `orientation.txt` can then be used to determine the best orientation.
84
88
 
89
+ * **orientations.pdb** — All final orientations in a single PDB file, one model per rank, ordered by rank.
90
+
85
91
  * **local_minima_orientation.png** — A representation of how spread out the local minima are. The more white the image, the more confidence can be placed in rank 1. This is only true in general and there are some cases where this can be safely ignored.
86
92
 
87
93
  * **local_minima_potential.png** — A representation of the potential of the final configuration from a given starting configuration. This complements `local_minima_orientation.png` but in general does not provide much additional information.
@@ -93,7 +99,7 @@ MemPrO is intended to be run on PDB files, and will output the following files:
93
99
  * `PG_potential_curve.png` — The potential curve for the position of the PG cell wall. Only produced with `-pg`.
94
100
  * `cross-section_area_curve.png` — The cross-sectional area of the segment of the protein passing through the PG layer. Only produced with `-pg`.
95
101
  * `Z_potential_curve.png` — Potential against Z position of the final orientation. Gives an idea of the nature of the minima.
96
- * `curv_potential_curve.png` — Potential against global membrane curvature for the final orientation. Can indicate whether curvature minimisation should be used.
102
+ * `curv_potential_curve.png` — Potential against global membrane curvature for the final orientation. Can indicate whether curvature minimisation should be used. Not produced with `-dm`.
97
103
 
98
104
  ## Flags
99
105
 
@@ -107,7 +113,7 @@ MemPrO is intended to be run on PDB files, and will output the following files:
107
113
 
108
114
  **-ng, --grid_size** — Number of initial starting configurations. Defaults to 36. For best efficiency this should be a multiple of the number of CPUs being used. More starting configurations sample the space of minima better, but there are diminishing returns and generally no more than 40 are needed.
109
115
 
110
- **-nc, --num_cpus** — Number of CPU cores to use. Defaults to all available cores. For best performance with `-ng`, set `-nc` to a value that divides `-ng` evenly.
116
+ **-nc, --num_cpus** — Number of CPU cores to use. Defaults to the number of performance cores where the system reports them (currently macOS, e.g. 4 on an Apple M4, whose 6 efficiency cores would slow each batch down), otherwise all available cores. For best performance with `-ng`, set `-nc` to a value that divides `-ng` evenly.
111
117
 
112
118
  **-dev, --device** — Device to run on: `auto` (default; GPU if JAX can see one, otherwise CPU), `cpu` or `gpu`. On GPU, all starting configurations are minimised together in a single batch. GPU support needs `pip install "mempro[gpu]"` (Linux, NVIDIA).
113
119
 
@@ -115,6 +121,8 @@ MemPrO is intended to be run on PDB files, and will output the following files:
115
121
 
116
122
  **-rank, --rank** — Ranking method for minima: `h` ranks by percentage hits, `p` ranks by potential, `auto` (default) ranks by a value calculated from approximate minima depth and potential.
117
123
 
124
+ **-seed, --seed** — Seed for the random number generators. Runs with the same seed, input and flags give identical results, provided the number of devices (`-nc`, or the number of GPUs) is also the same. Default is unseeded.
125
+
118
126
  **-dm, --dual_membrane** — Toggles dual membrane minimisation. Splits the membrane into an inner and outer membrane and minimises the distance between them. Only use this if your protein spans the periplasm or is a gap junction etc.
119
127
 
120
128
  **-ch, --charge** — Partial charge of the (inner) membrane. The value corresponds to the average charge on a lipid divided by the average area per lipid, representing the average charge across the bilayer sheet. For an E. coli inner membrane this is around -0.008. Default is 0, which gives the best performance in most cases.
@@ -147,7 +155,7 @@ MemPrO is intended to be run on PDB files, and will output the following files:
147
155
 
148
156
  **-insane, --insane_path** — Path to `Insane4MemPrO.py`. Defaults to the same directory as `MemPrO.py` (or the installed package directory). Only required when using `-bd`.
149
157
 
150
- **-bd, --build_system** — Number of final ranked configurations to build as CG MD-ready systems using Insane4MemPrO. Currently only works with coarse grain input files.
158
+ **-bd, --build_system** — Number of final ranked configurations to build as CG MD-ready systems using Insane4MemPrO. Default is 0 (no systems built). Currently only works with coarse grain input files.
151
159
 
152
160
  **-bd_args, --build_arguments** — Arguments to pass to Insane4MemPrO, primarily lipid composition, solvent type etc. Required when using `-bd`.
153
161
 
@@ -167,10 +175,16 @@ Orient `input_file.pdb` on a grid of 40 starting configurations with 150 minimis
167
175
  python PATH/TO/MemPrO.py -f input_file.pdb -o Output_dir/ -ng 40 -ni 150
168
176
  ```
169
177
 
170
- Orient with global curvature minimisation and write per-bead potentials to b-factors:
178
+ Orient with global curvature minimisation:
179
+
180
+ ```
181
+ python PATH/TO/MemPrO.py -f input_file.pdb -o Output_dir/ -ng 40 -ni 150 -c
182
+ ```
183
+
184
+ Orient and write per-bead potentials to b-factors (`-wb` is not compatible with `-c`):
171
185
 
172
186
  ```
173
- python PATH/TO/MemPrO.py -f input_file.pdb -o Output_dir/ -ng 40 -ni 150 -c -wb
187
+ python PATH/TO/MemPrO.py -f input_file.pdb -o Output_dir/ -ng 40 -ni 150 -wb
174
188
  ```
175
189
 
176
190
  Orient with a dual membrane system, then build the top 2 ranked configurations as CG systems. The inner membrane contains POPE, POPG and CARD; the outer membrane contains LIPA:
@@ -255,29 +269,29 @@ MemPrO comes with Insane4MemPrO, a CG system builder based on [Insane](https://g
255
269
 
256
270
  #### Peptidoglycan layer options
257
271
 
258
- **-pgl** — (Optional) Number of PG layers to place at `-pgl_z`.
272
+ **-pgl** — (Default: 0) Number of PG layers to place at `-pgl_z`.
259
273
 
260
- **-pgl_z** — (Optional) Z position of the PG layer relative to the centre of the periplasmic space.
274
+ **-pgl_z** — (Default: 0) Z position of the PG layer relative to the centre of the periplasmic space.
261
275
 
262
- **-cper** — (Optional) Percentage of cross-links.
276
+ **-cper** — (Default: 0.4) Percentage of cross-links.
263
277
 
264
- **-lper** — (Optional) Percentage of cross-links that are between layers.
278
+ **-lper** — (Default: 0.1) Percentage of cross-links that are between layers.
265
279
 
266
- **-per33** — (Optional) Percentage of 3-3 cross-links; all other cross-links will be 3-4.
280
+ **-per33** — (Default: 0.03) Percentage of 3-3 cross-links; all other cross-links will be 3-4.
267
281
 
268
- **-oper** — (Optional) Percentage chance of a monomer linking with an oligomer. (Actual chance of link is `cper * oper`.)
282
+ **-oper** — (Default: 1) Percentage chance of a monomer linking with an oligomer. (Actual chance of link is `cper * oper`.)
269
283
 
270
284
  **-gdist** — (Default: 0.75,4,8.9,0.25,10,45) Distribution of glycan strand lengths. Format as `weight1,stddev1,mean1,weight2,...` where each triple describes a Gaussian. The sum of these Gaussians forms the distribution.
271
285
 
272
286
  #### Protein options
273
287
 
274
- **-fudge** — (Default: 0.3) Fudge factor for allowing lipid-protein overlap.
288
+ **-fudge** — (Default: 0.3) Fudge factor for allowing lipid-protein overlap. A value of 0.15 is recommended when using `-def`.
275
289
 
276
290
  #### Solvent options
277
291
 
278
292
  **-sol** — (Required) Solvent type.
279
293
 
280
- **-sold** — (Default: 0.5) Solvent packing density.
294
+ **-sold** — (Default: 0.5) Solvent diameter.
281
295
 
282
296
  **-solr** — (Default: 0.1) Magnitude of random deviations to solvent positions.
283
297
 
@@ -331,7 +345,7 @@ More detailed tutorials are available [here](Insane4MemPrO_tutorials.md).
331
345
 
332
346
  If your protein hasn't oriented correctly there are a number of things to try:
333
347
 
334
- * Check all orientations by loading `orientations.pdb` and reviewing `orientations.txt`. The correct orientation may not be rank 1.
348
+ * Check all orientations by loading `orientations.pdb` and reviewing `orientation.txt`. The correct orientation may not be rank 1.
335
349
  * Check `curv_potential_curve.png` to see if your protein prefers a curved membrane. If so, using `-c` will help.
336
350
  * Check the PDB file for missing atoms or residues, which can cause the surface to be incorrectly evaluated. Missing atoms should be fixed before orientation.
337
351
  * Using `-wb` can be helpful for diagnosing missing atoms — it writes the potential contribution of each residue as a b-factor, viewable in PyMOL.
@@ -1,6 +1,8 @@
1
1
  # MemPrO
2
2
  Membrane Protein Orientation in Lipid Bilayers. The paper associated with this code can be found [here](https://doi.org/10.1021/acs.jctc.5c01433).
3
3
 
4
+ [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/pstansfeld/MemPrO/blob/main/MemPrO_Colab.ipynb)
5
+
4
6
  Code and parameters from the following were used to help write this code:
5
7
 
6
8
  **Insane:**
@@ -43,7 +45,9 @@ pip install "matplotlib>=3.8.4"
43
45
 
44
46
  The Martini 3 force field file (`martini_v3.itp`) is included in `mempro/`. To use a different one, supply its path with the `-itp` flag; the standard file can be downloaded from [here](https://cgmartini.nl/docs/downloads/force-field-parameters/martini3/particle-definitions.html).
45
47
 
46
- MemPrO is also accessible on Google Colab via the link [ADD LINK]. The Colab will contain instructions for use. (Currently the Colab is WIP and this will be updated.)
48
+ ### Google Colab
49
+
50
+ MemPrO can also be run without installing anything locally, using the [MemPrO Colab notebook](https://colab.research.google.com/github/pstansfeld/MemPrO/blob/main/MemPrO_Colab.ipynb). It installs MemPrO from PyPI, orients a structure from the PDB, AlphaFold DB or your own upload, shows the results (including a 3D view of the top-ranked orientation), and can optionally predict membrane deformations with MemPrOD. It runs on both CPU and GPU runtimes.
47
51
 
48
52
  ## Running MemPrO
49
53
 
@@ -59,7 +63,7 @@ python PATH/TO/MemPrO.py -f input_file.pdb
59
63
  mempro -f input_file.pdb
60
64
  ```
61
65
 
62
- No environment variables are required. By default MemPrO will use all available CPU cores and look for `Insane4MemPrO.py` and `martini_v3.itp` in the same directory as the script (or the installed package directory). These paths can be overridden with the `-insane` and `-itp` flags if needed.
66
+ No environment variables are required. By default MemPrO will use a GPU if JAX can see one, otherwise one CPU device per performance core (see `-nc`), and will look for `Insane4MemPrO.py` and `martini_v3.itp` in the same directory as the script (or the installed package directory). These paths can be overridden with the `-insane` and `-itp` flags if needed.
63
67
 
64
68
  Compiled code is cached in `~/.cache/mempro/jax`, so runs after the first one start about 15 s faster. Set `JAX_COMPILATION_CACHE_DIR` to put the cache somewhere else, or `MEMPRO_NO_JAX_CACHE=1` to turn it off.
65
69
 
@@ -69,6 +73,8 @@ MemPrO is intended to be run on PDB files, and will output the following files:
69
73
 
70
74
  * **orientation.txt** — Contains general information about all of the final minima. In general rank 1 is the best orientation, however there are cases where this is not true. `orientation.txt` can then be used to determine the best orientation.
71
75
 
76
+ * **orientations.pdb** — All final orientations in a single PDB file, one model per rank, ordered by rank.
77
+
72
78
  * **local_minima_orientation.png** — A representation of how spread out the local minima are. The more white the image, the more confidence can be placed in rank 1. This is only true in general and there are some cases where this can be safely ignored.
73
79
 
74
80
  * **local_minima_potential.png** — A representation of the potential of the final configuration from a given starting configuration. This complements `local_minima_orientation.png` but in general does not provide much additional information.
@@ -80,7 +86,7 @@ MemPrO is intended to be run on PDB files, and will output the following files:
80
86
  * `PG_potential_curve.png` — The potential curve for the position of the PG cell wall. Only produced with `-pg`.
81
87
  * `cross-section_area_curve.png` — The cross-sectional area of the segment of the protein passing through the PG layer. Only produced with `-pg`.
82
88
  * `Z_potential_curve.png` — Potential against Z position of the final orientation. Gives an idea of the nature of the minima.
83
- * `curv_potential_curve.png` — Potential against global membrane curvature for the final orientation. Can indicate whether curvature minimisation should be used.
89
+ * `curv_potential_curve.png` — Potential against global membrane curvature for the final orientation. Can indicate whether curvature minimisation should be used. Not produced with `-dm`.
84
90
 
85
91
  ## Flags
86
92
 
@@ -94,7 +100,7 @@ MemPrO is intended to be run on PDB files, and will output the following files:
94
100
 
95
101
  **-ng, --grid_size** — Number of initial starting configurations. Defaults to 36. For best efficiency this should be a multiple of the number of CPUs being used. More starting configurations sample the space of minima better, but there are diminishing returns and generally no more than 40 are needed.
96
102
 
97
- **-nc, --num_cpus** — Number of CPU cores to use. Defaults to all available cores. For best performance with `-ng`, set `-nc` to a value that divides `-ng` evenly.
103
+ **-nc, --num_cpus** — Number of CPU cores to use. Defaults to the number of performance cores where the system reports them (currently macOS, e.g. 4 on an Apple M4, whose 6 efficiency cores would slow each batch down), otherwise all available cores. For best performance with `-ng`, set `-nc` to a value that divides `-ng` evenly.
98
104
 
99
105
  **-dev, --device** — Device to run on: `auto` (default; GPU if JAX can see one, otherwise CPU), `cpu` or `gpu`. On GPU, all starting configurations are minimised together in a single batch. GPU support needs `pip install "mempro[gpu]"` (Linux, NVIDIA).
100
106
 
@@ -102,6 +108,8 @@ MemPrO is intended to be run on PDB files, and will output the following files:
102
108
 
103
109
  **-rank, --rank** — Ranking method for minima: `h` ranks by percentage hits, `p` ranks by potential, `auto` (default) ranks by a value calculated from approximate minima depth and potential.
104
110
 
111
+ **-seed, --seed** — Seed for the random number generators. Runs with the same seed, input and flags give identical results, provided the number of devices (`-nc`, or the number of GPUs) is also the same. Default is unseeded.
112
+
105
113
  **-dm, --dual_membrane** — Toggles dual membrane minimisation. Splits the membrane into an inner and outer membrane and minimises the distance between them. Only use this if your protein spans the periplasm or is a gap junction etc.
106
114
 
107
115
  **-ch, --charge** — Partial charge of the (inner) membrane. The value corresponds to the average charge on a lipid divided by the average area per lipid, representing the average charge across the bilayer sheet. For an E. coli inner membrane this is around -0.008. Default is 0, which gives the best performance in most cases.
@@ -134,7 +142,7 @@ MemPrO is intended to be run on PDB files, and will output the following files:
134
142
 
135
143
  **-insane, --insane_path** — Path to `Insane4MemPrO.py`. Defaults to the same directory as `MemPrO.py` (or the installed package directory). Only required when using `-bd`.
136
144
 
137
- **-bd, --build_system** — Number of final ranked configurations to build as CG MD-ready systems using Insane4MemPrO. Currently only works with coarse grain input files.
145
+ **-bd, --build_system** — Number of final ranked configurations to build as CG MD-ready systems using Insane4MemPrO. Default is 0 (no systems built). Currently only works with coarse grain input files.
138
146
 
139
147
  **-bd_args, --build_arguments** — Arguments to pass to Insane4MemPrO, primarily lipid composition, solvent type etc. Required when using `-bd`.
140
148
 
@@ -154,10 +162,16 @@ Orient `input_file.pdb` on a grid of 40 starting configurations with 150 minimis
154
162
  python PATH/TO/MemPrO.py -f input_file.pdb -o Output_dir/ -ng 40 -ni 150
155
163
  ```
156
164
 
157
- Orient with global curvature minimisation and write per-bead potentials to b-factors:
165
+ Orient with global curvature minimisation:
166
+
167
+ ```
168
+ python PATH/TO/MemPrO.py -f input_file.pdb -o Output_dir/ -ng 40 -ni 150 -c
169
+ ```
170
+
171
+ Orient and write per-bead potentials to b-factors (`-wb` is not compatible with `-c`):
158
172
 
159
173
  ```
160
- python PATH/TO/MemPrO.py -f input_file.pdb -o Output_dir/ -ng 40 -ni 150 -c -wb
174
+ python PATH/TO/MemPrO.py -f input_file.pdb -o Output_dir/ -ng 40 -ni 150 -wb
161
175
  ```
162
176
 
163
177
  Orient with a dual membrane system, then build the top 2 ranked configurations as CG systems. The inner membrane contains POPE, POPG and CARD; the outer membrane contains LIPA:
@@ -242,29 +256,29 @@ MemPrO comes with Insane4MemPrO, a CG system builder based on [Insane](https://g
242
256
 
243
257
  #### Peptidoglycan layer options
244
258
 
245
- **-pgl** — (Optional) Number of PG layers to place at `-pgl_z`.
259
+ **-pgl** — (Default: 0) Number of PG layers to place at `-pgl_z`.
246
260
 
247
- **-pgl_z** — (Optional) Z position of the PG layer relative to the centre of the periplasmic space.
261
+ **-pgl_z** — (Default: 0) Z position of the PG layer relative to the centre of the periplasmic space.
248
262
 
249
- **-cper** — (Optional) Percentage of cross-links.
263
+ **-cper** — (Default: 0.4) Percentage of cross-links.
250
264
 
251
- **-lper** — (Optional) Percentage of cross-links that are between layers.
265
+ **-lper** — (Default: 0.1) Percentage of cross-links that are between layers.
252
266
 
253
- **-per33** — (Optional) Percentage of 3-3 cross-links; all other cross-links will be 3-4.
267
+ **-per33** — (Default: 0.03) Percentage of 3-3 cross-links; all other cross-links will be 3-4.
254
268
 
255
- **-oper** — (Optional) Percentage chance of a monomer linking with an oligomer. (Actual chance of link is `cper * oper`.)
269
+ **-oper** — (Default: 1) Percentage chance of a monomer linking with an oligomer. (Actual chance of link is `cper * oper`.)
256
270
 
257
271
  **-gdist** — (Default: 0.75,4,8.9,0.25,10,45) Distribution of glycan strand lengths. Format as `weight1,stddev1,mean1,weight2,...` where each triple describes a Gaussian. The sum of these Gaussians forms the distribution.
258
272
 
259
273
  #### Protein options
260
274
 
261
- **-fudge** — (Default: 0.3) Fudge factor for allowing lipid-protein overlap.
275
+ **-fudge** — (Default: 0.3) Fudge factor for allowing lipid-protein overlap. A value of 0.15 is recommended when using `-def`.
262
276
 
263
277
  #### Solvent options
264
278
 
265
279
  **-sol** — (Required) Solvent type.
266
280
 
267
- **-sold** — (Default: 0.5) Solvent packing density.
281
+ **-sold** — (Default: 0.5) Solvent diameter.
268
282
 
269
283
  **-solr** — (Default: 0.1) Magnitude of random deviations to solvent positions.
270
284
 
@@ -318,7 +332,7 @@ More detailed tutorials are available [here](Insane4MemPrO_tutorials.md).
318
332
 
319
333
  If your protein hasn't oriented correctly there are a number of things to try:
320
334
 
321
- * Check all orientations by loading `orientations.pdb` and reviewing `orientations.txt`. The correct orientation may not be rank 1.
335
+ * Check all orientations by loading `orientations.pdb` and reviewing `orientation.txt`. The correct orientation may not be rank 1.
322
336
  * Check `curv_potential_curve.png` to see if your protein prefers a curved membrane. If so, using `-c` will help.
323
337
  * Check the PDB file for missing atoms or residues, which can cause the surface to be incorrectly evaluated. Missing atoms should be fixed before orientation.
324
338
  * Using `-wb` can be helpful for diagnosing missing atoms — it writes the potential contribution of each residue as a b-factor, viewable in PyMOL.
@@ -13,6 +13,8 @@ from functools import partial, lru_cache
13
13
  import datetime
14
14
  from jax import tree_util
15
15
  import shutil
16
+ import shlex
17
+ import sys
16
18
  from collections import defaultdict
17
19
 
18
20
  PATH_TO_INSANE = os.environ.get("PATH_TO_INSANE", "")
@@ -75,11 +77,27 @@ def _pre_parse_device_args():
75
77
  _pre_parse_device_args()
76
78
 
77
79
  #We need to force JAX to fully utilize a multi-core cpu
78
- no_cpu = int(os.environ.get("NUM_CPU", os.cpu_count()))
80
+ #By default one device per performance core: each batch of minimisations waits for its slowest device, so on CPUs with
81
+ #efficiency cores (e.g. Apple Silicon, 4P+6E on an M4) using every core is slower (4G1U: 24 s on 10 cores, 16 s on 4)
82
+ def default_device_count():
83
+ try:
84
+ import subprocess
85
+ perf_cores = subprocess.run(["sysctl","-n","hw.perflevel0.physicalcpu"],capture_output=True,text=True,timeout=5)
86
+ if perf_cores.returncode == 0 and perf_cores.stdout.strip().isdigit():
87
+ return int(perf_cores.stdout.strip())
88
+ except (OSError,subprocess.SubprocessError):
89
+ pass
90
+ return os.cpu_count()
91
+ no_cpu = int(os.environ.get("NUM_CPU", default_device_count()))
79
92
  os.environ["XLA_FLAGS"] = (os.environ.get("XLA_FLAGS", "")+" --xla_force_host_platform_device_count="+str(no_cpu)).strip()
93
+ jax_version = tuple(int(v) for v in jax.__version__.split(".")[:3] if v.isdigit())
80
94
  #The legacy (non-thunk) CPU runtime is faster for MemPrO's scan/cond-heavy loops but only exists up to JAX 0.6.0
81
- if tuple(int(v) for v in jax.__version__.split(".")[:3] if v.isdigit()) <= (0,6,0):
95
+ if jax_version <= (0,6,0):
82
96
  os.environ["XLA_FLAGS"] += " --xla_cpu_use_thunk_runtime=false"
97
+ #XLA's YNNPACK CPU fusions (JAX >= 0.9) intermittently overflow a worker thread's stack on the large vectorised
98
+ #potentials in MemPrO/MemPrOD (SIGBUS in slinky::eval_non_inlined); turning them off costs no measurable speed
99
+ if jax_version >= (0,9,0) and "xla_cpu_experimental_ynn_fusion_type" not in os.environ["XLA_FLAGS"]:
100
+ os.environ["XLA_FLAGS"] += " --xla_cpu_experimental_ynn_fusion_type="
83
101
 
84
102
 
85
103
  #This is useful for debugging nans in grad
@@ -349,17 +367,21 @@ class PDB_helper:
349
367
  with open(os.path.join(self.fn), "r") as lfile:
350
368
  content = lfile.read().split("\n")
351
369
 
352
- # Group atom lines by (residue name, residue ID)
353
- residue_atoms = defaultdict(list)
370
+ # Group consecutive atom lines into residues. A residue ends when the residue name, chain or
371
+ # number changes, so repeated residue numbers in a chain (e.g. martinize2's default
372
+ # renumbering of multi-chain proteins) stay separate residues. write_oriented() writes one
373
+ # line per bead in file order, so there must be exactly one bead per valid ATOM line.
374
+ residues = []
375
+ prev_key = None
354
376
  for c in content:
355
377
  if len(c) > 54 and "[" not in c and c.startswith("ATOM"):
356
- resname = c[17:21].strip()
357
- resid = int(c[22:26].strip())
358
- chain_id = c[21].strip()
359
- residue_atoms[(resname, chain_id, resid)].append(c)
378
+ key = (c[17:21].strip(), c[21].strip(), c[22:27])
379
+ if key != prev_key:
380
+ residues.append((key[0], int(c[22:26].strip()), []))
381
+ prev_key = key
382
+ residues[-1][2].append(c)
360
383
 
361
- for (resname, chain_id, resid), atoms in residue_atoms.items():
362
- original_resname = resname
384
+ for resname, resid, atoms in residues:
363
385
  if resname not in Reses:
364
386
  print(f"Unknown residue {resname}, using UNK")
365
387
  resname = "UNK"
@@ -369,7 +391,7 @@ class PDB_helper:
369
391
  # Collect atom positions
370
392
  atom_pos_list = []
371
393
  atom_bval_list = []
372
- atom_nums = []
394
+ atom_names = []
373
395
 
374
396
  for c in atoms:
375
397
  try:
@@ -377,13 +399,12 @@ class PDB_helper:
377
399
  y = float(c[38:46].strip())
378
400
  z = float(c[46:54].strip())
379
401
  b = float(c[60:66].strip())
380
- atom_num = int(c[22:26].strip())
381
402
  pos = np.array([x, y, z])
382
403
 
383
404
  if not np.any(np.isnan(pos)):
384
405
  atom_pos_list.append(pos)
385
406
  atom_bval_list.append(b)
386
- atom_nums.append(atom_num)
407
+ atom_names.append(c[12:16].strip())
387
408
  except ValueError:
388
409
  continue
389
410
 
@@ -391,36 +412,25 @@ class PDB_helper:
391
412
  print(f"Skipping residue {resname} {resid} due to no valid positions.")
392
413
  continue
393
414
 
394
- # Fallback mean values
395
- mean_pos = np.mean(atom_pos_list, axis=0)
396
- mean_b = np.mean(atom_bval_list) if atom_bval_list else 0.0
397
-
398
- # Use quick hack: match bead types with atom positions by order
399
- if len(bead_list) != len(atom_pos_list):
400
- #print(f"Mismatch in bead count vs atom count for {resname} {resid}. Using mean positions. Expecting {len(bead_list)}, found {len(atom_pos_list)}")
401
- if resname == "UNK":
402
- bead_list = np.arange(len(atom_pos_list))
403
- else:
404
- print(atom_pos_list)
405
- print(f"Mismatch in bead count vs atom count for {resname} {resid}. Using mean positions. Expecting {len(bead_list)}, found {len(atom_pos_list)}")
406
- for bead_type in bead_list:
407
- if resname == "UNK":
408
- bead_index == 22
409
- else:
410
- bead_index = Beadtype.get(bead_type, -1)
411
- if bead_index == -1:
412
- raise ValueError(f"Unknown bead type: {bead_type}")
413
- self.bead_types.append(bead_index)
414
- self.reses.append(res_index)
415
- if resname == "UNK":
416
- self.poses.append(atom_pos_list[bead_type])
417
- self.b_vals.append(atom_bval_list[bead_type])
415
+ if resname == "UNK":
416
+ res_bead_types = ["GHOST"]*len(atom_pos_list)
417
+ elif len(bead_list) == len(atom_pos_list):
418
+ res_bead_types = bead_list
419
+ else:
420
+ # Match beads by Martini name (BB, SC1, SC2, ...), falling back to file order
421
+ print(f"Mismatch in bead count vs atom count for {resname} {resid}. Expecting {len(bead_list)}, found {len(atom_pos_list)}. Matching beads by name.")
422
+ res_bead_types = []
423
+ for j, name in enumerate(atom_names):
424
+ if name == "BB":
425
+ k = 0
426
+ elif name.startswith("SC") and name[2:].isdigit():
427
+ k = int(name[2:])
418
428
  else:
419
- self.poses.append(mean_pos)
420
- self.b_vals.append(mean_b)
421
- continue
429
+ k = j
430
+ res_bead_types.append(bead_list[min(k, len(bead_list)-1)])
422
431
 
423
- for bead_type, pos, b_val in zip(bead_list, atom_pos_list, atom_bval_list):
432
+ in_range = self.ranges.size != 0 and self.in_ranges(resid)
433
+ for bead_type, pos, b_val in zip(res_bead_types, atom_pos_list, atom_bval_list):
424
434
  bead_index = Beadtype.get(bead_type, -1)
425
435
  if bead_index == -1:
426
436
  raise ValueError(f"Unknown bead type: {bead_type}")
@@ -429,7 +439,6 @@ class PDB_helper:
429
439
  self.poses.append(pos)
430
440
 
431
441
  if self.ranges.size != 0:
432
- in_range = any(self.in_ranges(num) for num in atom_nums)
433
442
  self.b_vals.append(1.0 if in_range else 0.0)
434
443
  else:
435
444
  self.b_vals.append(b_val)
@@ -558,36 +567,19 @@ class PDB_helper:
558
567
  total = get_sph_circ(total,jnp.array([0,0]),jnp.pi)
559
568
  total_area = jnp.sum(total)
560
569
  #jax.debug.print("TA{x}",x=total_area)
561
- is_surf = jnp.zeros((points.shape[0]))
562
- def sloopv3_1(is_surf,ind):
563
- pind = ind
564
- ang_grid = jnp.zeros((40,20))
565
- def sloopv3_2(ang_grid,ind):
566
- direc = points[pind]-points[bpoints[pind,ind]]
567
- dist = jnp.linalg.norm(direc)
568
- def far(ang_grid):
569
- return ang_grid
570
- def nfar(ang_grid):
571
- #angle = (jnp.pi/3)*srad/jnp.max(jnp.array([4.0,dist]))#jnp.arccos(2*dist/srad)#0.6
572
- angle = jnp.arccos(jnp.clip(dist/(2*srad),0,1))
573
- cen_y = jnp.arctan2(direc[2],jnp.sqrt(direc[0]*direc[0]+direc[1]*direc[1]))
574
- cen_x = jnp.arctan2(direc[1],direc[0])
575
- ang_grid = get_sph_circ(ang_grid,jnp.array([cen_x,cen_y]),angle)
576
- return ang_grid
577
- ang_grid = jax.lax.cond((dist < srad*3)*(dist>1e-5),nfar,far,ang_grid)
578
- return ang_grid,ind
579
- ang_grid,_ = jax.lax.scan(sloopv3_2,ang_grid,jnp.arange(mrad))
580
- perc = jnp.sum(ang_grid)/total_area
581
- #jax.debug.print("Perc {x}",x=perc)
582
- def gthresh(is_surf):
583
- is_surf = is_surf.at[ind].set(1)
584
- return is_surf
585
- def lthresh(is_surf):
586
- return is_surf
587
- is_surf = jax.lax.cond(perc<thresh,gthresh,lthresh,is_surf)
588
- return is_surf,ind
589
- is_surf,_=jax.lax.scan(sloopv3_1,is_surf,jnp.arange(points.shape[0]))
590
- return is_surf
570
+ #Each nearby bead (closer than 3*srad) covers a circle of directions; a bead is on the surface if less than
571
+ #thresh of its directions are covered. get_sph_circ sets cells, so overlapping circles combine as a logical OR
572
+ def is_surf_fun(pind):
573
+ direc = points[pind]-points[bpoints[pind]]
574
+ dist = jnp.linalg.norm(direc,axis=1)
575
+ angle = jnp.arccos(jnp.clip(dist/(2*srad),0,1))
576
+ cen_y = jnp.arctan2(direc[:,2],jnp.sqrt(direc[:,0]*direc[:,0]+direc[:,1]*direc[:,1]))
577
+ cen_x = jnp.arctan2(direc[:,1],direc[:,0])
578
+ circles = jax.vmap(get_sph_circ,in_axes=(None,0,0))(jnp.zeros((40,20)),jnp.stack((cen_x,cen_y),axis=1),angle)
579
+ covered = jnp.any((circles > 0)*((dist < srad*3)*(dist>1e-5))[:,None,None],axis=0)
580
+ perc = jnp.sum(jnp.where(covered,total,0.0))/total_area
581
+ return jnp.where(perc<thresh,1.0,0.0)
582
+ return jax.lax.map(is_surf_fun,jnp.arange(points.shape[0]),batch_size=64)
591
583
 
592
584
  #This function gets the surface residues of a given cg protein
593
585
  def get_surface(self):
@@ -602,26 +594,13 @@ class PDB_helper:
602
594
 
603
595
  #Calculating normals at each point of the cg protein
604
596
  timer = time.time()
605
- @partial(jax.vmap)
597
+ #The normal at a bead points away from the mean of all beads within 32 A (beads processed in batches to bound memory)
606
598
  def normal_fun_2(ind):
607
- def normal_fun_1(ind_fix,ind):
608
- normal_count = jnp.zeros(4)
609
- def in_sphere(normal,posa,posb):
610
- normal = normal.at[3].set(normal[3]+1)
611
- normal = normal.at[:3].set(normal[:3]+(-posa+posb))
612
- return normal
613
- def not_in_sphere(normal,posa,posb):
614
- return normal
615
- normal = jax.lax.cond(jnp.linalg.norm(self.poses[ind]-self.poses[ind_fix])<32,in_sphere,not_in_sphere,normal_count,self.poses[ind],self.poses[ind_fix])#8####################<--------------------------
616
- return ind_fix,normal
617
-
618
- _,normals = jax.lax.scan(normal_fun_1,ind,jnp.arange(self.poses.shape[0]))
619
- normals_sum = jnp.sum(normals,axis=0)
620
- normal = normals_sum[:3]/normals_sum[3]
599
+ near = jnp.linalg.norm(self.poses-self.poses[ind],axis=1)<32
600
+ normal = jnp.sum(jnp.where(near[:,None],self.poses[ind]-self.poses,0.0),axis=0)/jnp.sum(near)
621
601
  normal = normal/jnp.linalg.norm(normal)
622
602
  return normal
623
- shard_nns = jax.device_put(jnp.arange(self.poses.shape[0]),sharding0)
624
- normals = normal_fun_2(shard_nns)
603
+ normals = jax.lax.map(normal_fun_2,jnp.arange(self.poses.shape[0]),batch_size=256)
625
604
 
626
605
  self.normals = jnp.array(jax.device_get(normals))#jax.device_put(normals)#jax.device_put_sharded([normals],[devices[0]])
627
606
 
@@ -646,27 +625,13 @@ class PDB_helper:
646
625
  timer = time.time()
647
626
  #Each point on the surface had an asocciated sphere indicating directions which are exposed
648
627
  #Here we flag the directions which are free
649
- @partial(jax.vmap)
628
+ #A direction is free if no bead lies within 4 A of the point 4 A out along the normal plus that direction
650
629
  def sphere_fun_3(ind):
651
- def sphere_fun_2(ind_fix,ind):
652
- def sphere_fun_1(ind_fix,ind):
653
- def in_prot():
654
- return 0
655
- def not_in_prot():
656
- return 1
657
- return ind_fix,jax.lax.cond(jnp.linalg.norm(self.poses[ind_fix[0]]+normals[ind_fix[0]]*4-self.poses[ind]+self.ball[ind_fix[1]])<4,in_prot,not_in_prot)
658
- _,on_surf_arr=jax.lax.scan(sphere_fun_1,jnp.array([ind_fix,ind]),jnp.arange(self.poses.shape[0]))
659
- on_surf_arr = jnp.array(jax.device_get(on_surf_arr))
660
-
661
- on_surf = jnp.prod(on_surf_arr)
662
- return ind_fix,on_surf
663
- _,on_surf_ball = jax.lax.scan(sphere_fun_2,ind,jnp.arange(self.ball.shape[0]))
664
- ball_arr = on_surf_ball*self.surface[ind]
665
- return ball_arr
666
-
667
-
668
- shard_nn = jax.device_put(jnp.arange(self.poses.shape[0]),sharding0)
669
- spheres = sphere_fun_3(shard_nn)
630
+ test_points = self.poses[ind]+normals[ind]*4+self.ball
631
+ dists = jnp.linalg.norm(test_points[:,None,:]-self.poses[None,:,:],axis=2)
632
+ on_surf_ball = jnp.all(jnp.logical_not(dists<4),axis=1).astype(int)
633
+ return on_surf_ball*self.surface[ind]
634
+ spheres = jax.lax.map(sphere_fun_3,jnp.arange(self.poses.shape[0]),batch_size=32)
670
635
 
671
636
  self.poses = self.poses[:pos_num]
672
637
  self.surface = self.surface[:pos_num,0]
@@ -680,12 +645,8 @@ class PDB_helper:
680
645
  jax.block_until_ready(self.poses)
681
646
 
682
647
  #Here we set the cartesean directions for each sphere (as before we only had flags)
683
- self.spheres = jnp.zeros((surface_number,self.ball.shape[0],3))
684
- def set_sphere_poses_fun_2(sphere_poses,ind):
685
- timeser = jnp.transpose(jnp.array([surface_sphere[ind],surface_sphere[ind],surface_sphere[ind]]))
686
- sphere_poses = sphere_poses.at[ind].set(sphere_poses[ind,:]+timeser*self.ball)
687
- return sphere_poses,ind
688
- self.spheres,_ = jax.lax.scan(set_sphere_poses_fun_2,self.spheres,jnp.arange(surface_sphere.shape[0]))
648
+ #surface_number counts the padding rows too (they match surface_poses), and their spheres stay zero
649
+ self.spheres = jnp.zeros((surface_number,self.ball.shape[0],3)).at[:surface_sphere.shape[0]].set(surface_sphere[:,:,None]*self.ball[None,:,:])
689
650
  surf_mean = jnp.mean(self.surface_poses,axis=0)
690
651
  self.surface_poses = self.surface_poses-surf_mean
691
652
  self.poses = self.poses-surf_mean
@@ -2603,7 +2564,7 @@ class MemBrain:
2603
2564
 
2604
2565
 
2605
2566
  #Builds a CG system using insane
2606
- def build_oriented(self,orient_dir,build_args):
2567
+ def build_oriented(self,orient_dir,build_args,insane_path):
2607
2568
  mins = np.array(self.minima)
2608
2569
  no_mins = (mins.shape[0]//5)
2609
2570
  min_poses = mins[:no_mins]
@@ -2626,14 +2587,14 @@ class MemBrain:
2626
2587
  curv_str = " -curv "+str(10*np.abs(min_curvs[i]))+",0.1,"+str(np.sign(min_curvs[i]-1e-9))
2627
2588
  print("Building rank "+str(i+1)+" with Insane4MemPrO:")
2628
2589
  cg_sys_dir = rank_dir+"CG_System_rank_"+str(i+1)+"/"
2629
- run_str = "python "+PATH_TO_INSANE+" "+build_args.strip()+" -o "+cg_sys_dir+"CG-system.gro -p "+cg_sys_dir+"topol.top -f "+cg_sys_dir+"protein-cg.pdb "+"-x "+str(x_range)+" -y "+str(y_range)+" -z "+str(z_range)+curv_str
2590
+ run_str = shlex.quote(sys.executable)+" "+shlex.quote(insane_path)+" "+build_args.strip()+" -o "+shlex.quote(cg_sys_dir+"CG-system.gro")+" -p "+shlex.quote(cg_sys_dir+"topol.top")+" -f "+shlex.quote(cg_sys_dir+"protein-cg.pdb")+" -x "+str(x_range)+" -y "+str(y_range)+" -z "+str(z_range)+curv_str
2630
2591
  if(zdist > 1e-6):
2631
2592
  run_str += " -ps "+str(zdist)
2632
2593
  if(self.pg_layer_pred):
2633
2594
  ps = self.pg_poses[i][0]
2634
2595
  xs_pg = self.pg_poses[i][1]
2635
2596
  pg_pos = -xs_pg[jnp.argmin(ps)]
2636
- run_str += " -pgl_z "+str(pg_pos/10.0)+" -o_f "+cg_sys_dir
2597
+ run_str += " -pgl_z "+str(pg_pos/10.0)+" -o_f "+shlex.quote(cg_sys_dir)
2637
2598
  #print()
2638
2599
  err_val = os.system(run_str)
2639
2600
  if(err_val != 0):
@@ -3154,13 +3115,28 @@ def read_martini_lines(martini_file):
3154
3115
  with open(martini_file,"r") as mfile:
3155
3116
  return tuple(mfile.readlines())
3156
3117
 
3157
- #A fucntion that reads a martini file to get interaction strengths
3118
+ #Index of the first line starting with each 12-character "%6s%6s" bead pair, as in [ nonbond_params ]
3119
+ @lru_cache(maxsize=None)
3120
+ def martini_pair_index(martini_file):
3121
+ index = {}
3122
+ for i,line in enumerate(read_martini_lines(martini_file)):
3123
+ index.setdefault(line[:12],i)
3124
+ return index
3125
+
3126
+ #A fucntion that reads a martini file to get interaction strengths (memoised: each pair is looked up once per run)
3127
+ #Pairs are found through martini_pair_index (one pass over the file); any pair not found at the start of a line
3128
+ #falls back to the original search for the first line containing it
3129
+ @lru_cache(maxsize=None)
3158
3130
  def get_int_strength(bead_1,bead_2,martini_file):
3159
3131
  if(bead_2 == "GHOST"):
3160
3132
  return 0
3161
3133
  string = " "*(6-len(bead_1))+bead_1+" "*(6-len(bead_2))+bead_2
3162
3134
  string2 = " "*(6-len(bead_2))+bead_2+" "*(6-len(bead_1))+bead_1
3163
3135
  content = read_martini_lines(martini_file)
3136
+ index = martini_pair_index(martini_file)
3137
+ found = [index[s] for s in (string,string2) if s in index]
3138
+ if found:
3139
+ return -float(content[min(found)][32:45])
3164
3140
  for i,line in enumerate(content):
3165
3141
  if(string in line or string2 in line):
3166
3142
  return -float(line[32:45])
@@ -3405,21 +3381,9 @@ def get_sph_circ(grid,cen,angle):
3405
3381
  dy = jnp.pi/n
3406
3382
  xs = jnp.linspace(0,2*jnp.pi-dx,2*n)+dx/2
3407
3383
  ys = jnp.linspace(0,jnp.pi-dy,n)+dy/2
3408
- def scloop1(grid,ind):
3409
- ind1 = ind
3410
- def scloop2(grid,ind):
3411
- dist = jnp.arccos(jnp.cos(cen[0])*jnp.cos(xs[ind1])+jnp.sin(cen[0])*jnp.sin(xs[ind1])*jnp.cos(cen[1]-ys[ind]))
3412
- def langle(grid):
3413
- grid = grid.at[ind1,ind].set(dx*(jnp.cos(ys[ind]-dy/2)-jnp.cos(ys[ind]+dy/2)))
3414
- return grid
3415
- def gangle(grid):
3416
- return grid
3417
- grid = jax.lax.cond(dist<angle,langle,gangle,grid)
3418
- return grid,ind
3419
- grid,_ = jax.lax.scan(scloop2,grid,jnp.arange(n))
3420
- return grid,ind
3421
- grid,_ = jax.lax.scan(scloop1,grid,jnp.arange(2*n))
3422
- return grid
3384
+ dist = jnp.arccos(jnp.cos(cen[0])*jnp.cos(xs)[:,None]+jnp.sin(cen[0])*jnp.sin(xs)[:,None]*jnp.cos(cen[1]-ys)[None,:])
3385
+ cell_area = dx*(jnp.cos(ys-dy/2)-jnp.cos(ys+dy/2))
3386
+ return jnp.where(dist<angle,jnp.broadcast_to(cell_area[None,:],grid.shape),grid)
3423
3387
 
3424
3388
  def add_Reses(Res, Resitp):
3425
3389
  current_res_no = len(Reses)
@@ -3507,14 +3471,14 @@ def main():
3507
3471
  os.environ["TF_CPP_MIN_LOG_LEVEL"]="3"
3508
3472
 
3509
3473
  #Reading command line args
3510
- parser = argparse.ArgumentParser(prog="MemPrO",description="Orients a protein in a membrane. Currently only for E-coli membranes, but this will change in future updates. Currently flags -c -c_ni -ps -fc are WIP, these can still be used at the your own peril.")
3474
+ parser = argparse.ArgumentParser(prog="MemPrO",description="Orients a protein in a membrane. Currently only for E-coli membranes, but this will change in future updates.")
3511
3475
  parser.add_argument("-f", "--file_name",help = "Input file name (.pdb)")
3512
3476
  parser.add_argument("-o","--output",help="Name of the output directory (Default: Orient)")
3513
3477
  parser.add_argument("-ni","--iters",help="Number of minimisation iterations (Default: 150)")
3514
3478
  parser.set_defaults(iters=150)
3515
3479
  parser.add_argument("-ng","--grid_size",help="Number of starting configurations (Default: 36)")
3516
3480
  parser.set_defaults(grid_size=36)
3517
- parser.add_argument("-nc","--num_cpus",help="Number of CPU cores to use (Default: all available)",type=int)
3481
+ parser.add_argument("-nc","--num_cpus",help="Number of CPU cores to use (Default: performance cores on macOS, otherwise all available)",type=int)
3518
3482
  parser.set_defaults(num_cpus=None)
3519
3483
  parser.add_argument("-dev","--device",help="Device to run on: auto (GPU if available, otherwise CPU), cpu or gpu (Default: auto)",choices=["auto","cpu","gpu"])
3520
3484
  parser.set_defaults(device="auto")
@@ -3537,7 +3501,7 @@ def main():
3537
3501
  parser.add_argument("-itp","--itp_file",help="Path to force field (martini_v3.itp)")
3538
3502
  parser.set_defaults(itp_file=_default_itp)
3539
3503
  parser.add_argument("-insane","--insane_path",help="Path to Insane4MemPrO.py")
3540
- parser.set_defaults(insane_path=_default_insane)
3504
+ parser.set_defaults(insane_path=PATH_TO_INSANE or _default_insane)
3541
3505
  parser.add_argument("-bd","--build_system",help = "Build a MD ready CG-system for ranks < n (Default: n=0)")
3542
3506
  parser.set_defaults(build_system=0)
3543
3507
  parser.add_argument("-bd_args","--build_arguments",help="Arguments to pass to insane when building system")
@@ -3564,12 +3528,21 @@ def main():
3564
3528
  parser.set_defaults(write_bfactors=False)
3565
3529
  parser.add_argument("-rank","--rank",help="Ranking method: auto (default), h (hits), p (potential)")
3566
3530
  parser.set_defaults(rank="auto")
3531
+ parser.add_argument("-seed","--seed",type=int,help="Seed for the random number generators, for reproducible runs. Results also depend on the number of devices (-nc) (Default: unseeded)")
3532
+ parser.set_defaults(seed=None)
3567
3533
  args = parser.parse_args()
3568
3534
 
3569
3535
  if args.file_name is None:
3570
3536
  parser.print_help()
3571
3537
  sys.exit(0)
3572
3538
 
3539
+ #All random draws in MemPrO use NumPy's global generator
3540
+ if args.seed is not None:
3541
+ if not 0 <= args.seed < 2**32:
3542
+ print("ERROR: Could not read value of -seed. Must be an integer between 0 and 2^32-1.")
3543
+ exit()
3544
+ np.random.seed(args.seed)
3545
+
3573
3546
  fliper = 1
3574
3547
  if args.flip:
3575
3548
  fliper = -1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mempro
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Membrane Protein Orientation in Lipid Bilayers
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -14,6 +14,8 @@ Dynamic: license-file
14
14
  # MemPrO
15
15
  Membrane Protein Orientation in Lipid Bilayers. The paper associated with this code can be found [here](https://doi.org/10.1021/acs.jctc.5c01433).
16
16
 
17
+ [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/pstansfeld/MemPrO/blob/main/MemPrO_Colab.ipynb)
18
+
17
19
  Code and parameters from the following were used to help write this code:
18
20
 
19
21
  **Insane:**
@@ -56,7 +58,9 @@ pip install "matplotlib>=3.8.4"
56
58
 
57
59
  The Martini 3 force field file (`martini_v3.itp`) is included in `mempro/`. To use a different one, supply its path with the `-itp` flag; the standard file can be downloaded from [here](https://cgmartini.nl/docs/downloads/force-field-parameters/martini3/particle-definitions.html).
58
60
 
59
- MemPrO is also accessible on Google Colab via the link [ADD LINK]. The Colab will contain instructions for use. (Currently the Colab is WIP and this will be updated.)
61
+ ### Google Colab
62
+
63
+ MemPrO can also be run without installing anything locally, using the [MemPrO Colab notebook](https://colab.research.google.com/github/pstansfeld/MemPrO/blob/main/MemPrO_Colab.ipynb). It installs MemPrO from PyPI, orients a structure from the PDB, AlphaFold DB or your own upload, shows the results (including a 3D view of the top-ranked orientation), and can optionally predict membrane deformations with MemPrOD. It runs on both CPU and GPU runtimes.
60
64
 
61
65
  ## Running MemPrO
62
66
 
@@ -72,7 +76,7 @@ python PATH/TO/MemPrO.py -f input_file.pdb
72
76
  mempro -f input_file.pdb
73
77
  ```
74
78
 
75
- No environment variables are required. By default MemPrO will use all available CPU cores and look for `Insane4MemPrO.py` and `martini_v3.itp` in the same directory as the script (or the installed package directory). These paths can be overridden with the `-insane` and `-itp` flags if needed.
79
+ No environment variables are required. By default MemPrO will use a GPU if JAX can see one, otherwise one CPU device per performance core (see `-nc`), and will look for `Insane4MemPrO.py` and `martini_v3.itp` in the same directory as the script (or the installed package directory). These paths can be overridden with the `-insane` and `-itp` flags if needed.
76
80
 
77
81
  Compiled code is cached in `~/.cache/mempro/jax`, so runs after the first one start about 15 s faster. Set `JAX_COMPILATION_CACHE_DIR` to put the cache somewhere else, or `MEMPRO_NO_JAX_CACHE=1` to turn it off.
78
82
 
@@ -82,6 +86,8 @@ MemPrO is intended to be run on PDB files, and will output the following files:
82
86
 
83
87
  * **orientation.txt** — Contains general information about all of the final minima. In general rank 1 is the best orientation, however there are cases where this is not true. `orientation.txt` can then be used to determine the best orientation.
84
88
 
89
+ * **orientations.pdb** — All final orientations in a single PDB file, one model per rank, ordered by rank.
90
+
85
91
  * **local_minima_orientation.png** — A representation of how spread out the local minima are. The more white the image, the more confidence can be placed in rank 1. This is only true in general and there are some cases where this can be safely ignored.
86
92
 
87
93
  * **local_minima_potential.png** — A representation of the potential of the final configuration from a given starting configuration. This complements `local_minima_orientation.png` but in general does not provide much additional information.
@@ -93,7 +99,7 @@ MemPrO is intended to be run on PDB files, and will output the following files:
93
99
  * `PG_potential_curve.png` — The potential curve for the position of the PG cell wall. Only produced with `-pg`.
94
100
  * `cross-section_area_curve.png` — The cross-sectional area of the segment of the protein passing through the PG layer. Only produced with `-pg`.
95
101
  * `Z_potential_curve.png` — Potential against Z position of the final orientation. Gives an idea of the nature of the minima.
96
- * `curv_potential_curve.png` — Potential against global membrane curvature for the final orientation. Can indicate whether curvature minimisation should be used.
102
+ * `curv_potential_curve.png` — Potential against global membrane curvature for the final orientation. Can indicate whether curvature minimisation should be used. Not produced with `-dm`.
97
103
 
98
104
  ## Flags
99
105
 
@@ -107,7 +113,7 @@ MemPrO is intended to be run on PDB files, and will output the following files:
107
113
 
108
114
  **-ng, --grid_size** — Number of initial starting configurations. Defaults to 36. For best efficiency this should be a multiple of the number of CPUs being used. More starting configurations sample the space of minima better, but there are diminishing returns and generally no more than 40 are needed.
109
115
 
110
- **-nc, --num_cpus** — Number of CPU cores to use. Defaults to all available cores. For best performance with `-ng`, set `-nc` to a value that divides `-ng` evenly.
116
+ **-nc, --num_cpus** — Number of CPU cores to use. Defaults to the number of performance cores where the system reports them (currently macOS, e.g. 4 on an Apple M4, whose 6 efficiency cores would slow each batch down), otherwise all available cores. For best performance with `-ng`, set `-nc` to a value that divides `-ng` evenly.
111
117
 
112
118
  **-dev, --device** — Device to run on: `auto` (default; GPU if JAX can see one, otherwise CPU), `cpu` or `gpu`. On GPU, all starting configurations are minimised together in a single batch. GPU support needs `pip install "mempro[gpu]"` (Linux, NVIDIA).
113
119
 
@@ -115,6 +121,8 @@ MemPrO is intended to be run on PDB files, and will output the following files:
115
121
 
116
122
  **-rank, --rank** — Ranking method for minima: `h` ranks by percentage hits, `p` ranks by potential, `auto` (default) ranks by a value calculated from approximate minima depth and potential.
117
123
 
124
+ **-seed, --seed** — Seed for the random number generators. Runs with the same seed, input and flags give identical results, provided the number of devices (`-nc`, or the number of GPUs) is also the same. Default is unseeded.
125
+
118
126
  **-dm, --dual_membrane** — Toggles dual membrane minimisation. Splits the membrane into an inner and outer membrane and minimises the distance between them. Only use this if your protein spans the periplasm or is a gap junction etc.
119
127
 
120
128
  **-ch, --charge** — Partial charge of the (inner) membrane. The value corresponds to the average charge on a lipid divided by the average area per lipid, representing the average charge across the bilayer sheet. For an E. coli inner membrane this is around -0.008. Default is 0, which gives the best performance in most cases.
@@ -147,7 +155,7 @@ MemPrO is intended to be run on PDB files, and will output the following files:
147
155
 
148
156
  **-insane, --insane_path** — Path to `Insane4MemPrO.py`. Defaults to the same directory as `MemPrO.py` (or the installed package directory). Only required when using `-bd`.
149
157
 
150
- **-bd, --build_system** — Number of final ranked configurations to build as CG MD-ready systems using Insane4MemPrO. Currently only works with coarse grain input files.
158
+ **-bd, --build_system** — Number of final ranked configurations to build as CG MD-ready systems using Insane4MemPrO. Default is 0 (no systems built). Currently only works with coarse grain input files.
151
159
 
152
160
  **-bd_args, --build_arguments** — Arguments to pass to Insane4MemPrO, primarily lipid composition, solvent type etc. Required when using `-bd`.
153
161
 
@@ -167,10 +175,16 @@ Orient `input_file.pdb` on a grid of 40 starting configurations with 150 minimis
167
175
  python PATH/TO/MemPrO.py -f input_file.pdb -o Output_dir/ -ng 40 -ni 150
168
176
  ```
169
177
 
170
- Orient with global curvature minimisation and write per-bead potentials to b-factors:
178
+ Orient with global curvature minimisation:
179
+
180
+ ```
181
+ python PATH/TO/MemPrO.py -f input_file.pdb -o Output_dir/ -ng 40 -ni 150 -c
182
+ ```
183
+
184
+ Orient and write per-bead potentials to b-factors (`-wb` is not compatible with `-c`):
171
185
 
172
186
  ```
173
- python PATH/TO/MemPrO.py -f input_file.pdb -o Output_dir/ -ng 40 -ni 150 -c -wb
187
+ python PATH/TO/MemPrO.py -f input_file.pdb -o Output_dir/ -ng 40 -ni 150 -wb
174
188
  ```
175
189
 
176
190
  Orient with a dual membrane system, then build the top 2 ranked configurations as CG systems. The inner membrane contains POPE, POPG and CARD; the outer membrane contains LIPA:
@@ -255,29 +269,29 @@ MemPrO comes with Insane4MemPrO, a CG system builder based on [Insane](https://g
255
269
 
256
270
  #### Peptidoglycan layer options
257
271
 
258
- **-pgl** — (Optional) Number of PG layers to place at `-pgl_z`.
272
+ **-pgl** — (Default: 0) Number of PG layers to place at `-pgl_z`.
259
273
 
260
- **-pgl_z** — (Optional) Z position of the PG layer relative to the centre of the periplasmic space.
274
+ **-pgl_z** — (Default: 0) Z position of the PG layer relative to the centre of the periplasmic space.
261
275
 
262
- **-cper** — (Optional) Percentage of cross-links.
276
+ **-cper** — (Default: 0.4) Percentage of cross-links.
263
277
 
264
- **-lper** — (Optional) Percentage of cross-links that are between layers.
278
+ **-lper** — (Default: 0.1) Percentage of cross-links that are between layers.
265
279
 
266
- **-per33** — (Optional) Percentage of 3-3 cross-links; all other cross-links will be 3-4.
280
+ **-per33** — (Default: 0.03) Percentage of 3-3 cross-links; all other cross-links will be 3-4.
267
281
 
268
- **-oper** — (Optional) Percentage chance of a monomer linking with an oligomer. (Actual chance of link is `cper * oper`.)
282
+ **-oper** — (Default: 1) Percentage chance of a monomer linking with an oligomer. (Actual chance of link is `cper * oper`.)
269
283
 
270
284
  **-gdist** — (Default: 0.75,4,8.9,0.25,10,45) Distribution of glycan strand lengths. Format as `weight1,stddev1,mean1,weight2,...` where each triple describes a Gaussian. The sum of these Gaussians forms the distribution.
271
285
 
272
286
  #### Protein options
273
287
 
274
- **-fudge** — (Default: 0.3) Fudge factor for allowing lipid-protein overlap.
288
+ **-fudge** — (Default: 0.3) Fudge factor for allowing lipid-protein overlap. A value of 0.15 is recommended when using `-def`.
275
289
 
276
290
  #### Solvent options
277
291
 
278
292
  **-sol** — (Required) Solvent type.
279
293
 
280
- **-sold** — (Default: 0.5) Solvent packing density.
294
+ **-sold** — (Default: 0.5) Solvent diameter.
281
295
 
282
296
  **-solr** — (Default: 0.1) Magnitude of random deviations to solvent positions.
283
297
 
@@ -331,7 +345,7 @@ More detailed tutorials are available [here](Insane4MemPrO_tutorials.md).
331
345
 
332
346
  If your protein hasn't oriented correctly there are a number of things to try:
333
347
 
334
- * Check all orientations by loading `orientations.pdb` and reviewing `orientations.txt`. The correct orientation may not be rank 1.
348
+ * Check all orientations by loading `orientations.pdb` and reviewing `orientation.txt`. The correct orientation may not be rank 1.
335
349
  * Check `curv_potential_curve.png` to see if your protein prefers a curved membrane. If so, using `-c` will help.
336
350
  * Check the PDB file for missing atoms or residues, which can cause the surface to be incorrectly evaluated. Missing atoms should be fixed before orientation.
337
351
  * Using `-wb` can be helpful for diagnosing missing atoms — it writes the potential contribution of each residue as a b-factor, viewable in PyMOL.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mempro"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  description = "Membrane Protein Orientation in Lipid Bilayers"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes