mempro 0.3.2__tar.gz → 0.3.3__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.
- {mempro-0.3.2 → mempro-0.3.3}/PKG-INFO +1 -1
- {mempro-0.3.2 → mempro-0.3.3}/mempro/Insane4MemPrO.py +50 -6
- {mempro-0.3.2 → mempro-0.3.3}/mempro/MemPrO.py +5 -0
- {mempro-0.3.2 → mempro-0.3.3}/mempro.egg-info/PKG-INFO +1 -1
- {mempro-0.3.2 → mempro-0.3.3}/mempro.egg-info/SOURCES.txt +1 -0
- {mempro-0.3.2 → mempro-0.3.3}/pyproject.toml +1 -1
- {mempro-0.3.2 → mempro-0.3.3}/tests/test_footprint.py +4 -24
- {mempro-0.3.2 → mempro-0.3.3}/tests/test_insane4mempro.py +58 -0
- mempro-0.3.3/tests/test_residues.py +46 -0
- {mempro-0.3.2 → mempro-0.3.3}/LICENSE +0 -0
- {mempro-0.3.2 → mempro-0.3.3}/README.md +0 -0
- {mempro-0.3.2 → mempro-0.3.3}/mempro/__init__.py +0 -0
- {mempro-0.3.2 → mempro-0.3.3}/mempro/_cli.py +0 -0
- {mempro-0.3.2 → mempro-0.3.3}/mempro/leaflet_check.py +0 -0
- {mempro-0.3.2 → mempro-0.3.3}/mempro/lipidome_m3.dat +0 -0
- {mempro-0.3.2 → mempro-0.3.3}/mempro/martini_v3.itp +0 -0
- {mempro-0.3.2 → mempro-0.3.3}/mempro.egg-info/dependency_links.txt +0 -0
- {mempro-0.3.2 → mempro-0.3.3}/mempro.egg-info/entry_points.txt +0 -0
- {mempro-0.3.2 → mempro-0.3.3}/mempro.egg-info/requires.txt +0 -0
- {mempro-0.3.2 → mempro-0.3.3}/mempro.egg-info/top_level.txt +0 -0
- {mempro-0.3.2 → mempro-0.3.3}/setup.cfg +0 -0
- {mempro-0.3.2 → mempro-0.3.3}/tests/test_sort.py +0 -0
|
@@ -2780,10 +2780,15 @@ def get_charge(lz,uz):
|
|
|
2780
2780
|
pcharge = 0
|
|
2781
2781
|
for ind,j in enumerate(protein.atoms):
|
|
2782
2782
|
pos = protein.coord[ind]
|
|
2783
|
-
if
|
|
2783
|
+
if protein_q is not None:
|
|
2784
|
+
if(pos[2] > lz and pos[2] < uz):
|
|
2785
|
+
pcharge += protein_q[ind]
|
|
2786
|
+
elif not j[0].strip().startswith('v') and j[1:3] != last:
|
|
2784
2787
|
if(pos[2] > lz and pos[2] < uz):
|
|
2785
2788
|
pcharge += charges.get(j[1].strip(),0)
|
|
2786
2789
|
last = j[1:3]
|
|
2790
|
+
if protein_q is not None:
|
|
2791
|
+
pcharge = int(round(pcharge))
|
|
2787
2792
|
total_charge = mcharge+pgcharge+pcharge
|
|
2788
2793
|
return total_charge,pcharge,mcharge,pgcharge
|
|
2789
2794
|
|
|
@@ -3363,6 +3368,7 @@ Input/output related options
|
|
|
3363
3368
|
("-o", Option(str, 1, None, "Output GRO file: Membrane with Protein")),
|
|
3364
3369
|
("-o_f", Option(str, 1, None, "Folder for additional files (Default: InsaneData)")),
|
|
3365
3370
|
("-p", Option(str, 1, None, "Optional rudimentary topology file")),
|
|
3371
|
+
("-p_top", Option(str, 1, None, "Topology (.top) of the protein in -f, e.g. from martinize2. Protein charges are taken from it, including the termini, instead of from residue names")),
|
|
3366
3372
|
("-ct", Option(str, 1, None, "This will create a template of the membrane only.")),
|
|
3367
3373
|
("-in_t", Option(str, 1, None, "Input template for placement of multiple proteins.")),
|
|
3368
3374
|
("-fs", Option(str, 1, None, "Input text file with multiple proteins")),
|
|
@@ -3607,17 +3613,39 @@ def read_lipid_itp(fn):
|
|
|
3607
3613
|
if not lines:
|
|
3608
3614
|
continue
|
|
3609
3615
|
name = lines[0][0]
|
|
3610
|
-
atoms,
|
|
3616
|
+
atoms, resnames, qs, sec = [], [], [], None
|
|
3611
3617
|
for l in lines[1:]:
|
|
3612
3618
|
if l[0].startswith("["):
|
|
3613
3619
|
sec = "".join(l).strip("[]")
|
|
3614
3620
|
elif sec == "atoms" and len(l) >= 7 and not l[0].startswith("#"):
|
|
3615
3621
|
atoms.append(l[4])
|
|
3616
|
-
|
|
3617
|
-
|
|
3622
|
+
resnames.append(l[3])
|
|
3623
|
+
qs.append(float(l[6]))
|
|
3624
|
+
mols[name] = {"atoms": atoms, "resnames": resnames, "charges": qs, "charge": int(round(sum(qs))), "build": builds.get(name)}
|
|
3618
3625
|
return mols
|
|
3619
3626
|
|
|
3620
3627
|
|
|
3628
|
+
# Charge of each bead of the molecules in a topology (.top), in order, with its residue name
|
|
3629
|
+
def read_top_charges(fn):
|
|
3630
|
+
top_dir = os.path.dirname(os.path.abspath(fn))
|
|
3631
|
+
with open(fn) as f:
|
|
3632
|
+
txt = f.read()
|
|
3633
|
+
mols = read_lipid_itp(fn)
|
|
3634
|
+
for inc in re.findall(r'^\s*#include\s+"([^"]+)"', txt, re.M):
|
|
3635
|
+
path = inc if os.path.isabs(inc) else os.path.join(top_dir, inc)
|
|
3636
|
+
if os.path.exists(path):
|
|
3637
|
+
mols.update(read_lipid_itp(path))
|
|
3638
|
+
beads = []
|
|
3639
|
+
for line in txt.split("[ molecules ]")[-1].splitlines() if "[ molecules ]" in txt else []:
|
|
3640
|
+
l = line.split(";")[0].split()
|
|
3641
|
+
if len(l) != 2:
|
|
3642
|
+
continue
|
|
3643
|
+
if l[0] not in mols:
|
|
3644
|
+
raise KeyError("molecule %s in %s has no [ atoms ] in the included itps" % (l[0], fn))
|
|
3645
|
+
beads += list(zip(mols[l[0]]["resnames"], mols[l[0]]["charges"]))*int(l[1])
|
|
3646
|
+
return beads
|
|
3647
|
+
|
|
3648
|
+
|
|
3621
3649
|
# The Martini 3 lipidome, for lipids without a built-in template
|
|
3622
3650
|
lipidome_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), "lipidome_m3.dat")
|
|
3623
3651
|
if os.path.exists(lipidome_file):
|
|
@@ -3638,9 +3666,13 @@ for fn in lipid_itps:
|
|
|
3638
3666
|
if mol["build"]:
|
|
3639
3667
|
kv = mol["build"]
|
|
3640
3668
|
define_lipid(name, kv.get("alhead",""), kv.get("allink",""), kv.get("altail",""))
|
|
3641
|
-
elif name in lipidsa
|
|
3669
|
+
elif name in lipidsa:
|
|
3642
3670
|
beads = [b for b in lipidsa[name][1].split() if b != "-"]
|
|
3643
|
-
if beads != mol["atoms"]:
|
|
3671
|
+
if beads != mol["atoms"] and len(beads) == len(mol["atoms"]):
|
|
3672
|
+
# Same beads under other names (e.g. CARD in the Martini 3 lipidome): take the names from the topology
|
|
3673
|
+
names = iter(mol["atoms"])
|
|
3674
|
+
lipidsa[name] = (lipidsa[name][0], " ".join(b if b == "-" else next(names) for b in lipidsa[name][1].split()))
|
|
3675
|
+
elif beads != mol["atoms"] and name in used_lipids:
|
|
3644
3676
|
print("WARNING: The bead names of %s in %s differ from the template (%s); the built lipid will not match the topology" % (name, fn, " ".join(beads)), file=sys.stderr)
|
|
3645
3677
|
|
|
3646
3678
|
# Add specified lipid definition to insane lipid library
|
|
@@ -3730,6 +3762,7 @@ elif options["-z"].value:
|
|
|
3730
3762
|
|
|
3731
3763
|
protein = Structure()
|
|
3732
3764
|
protein_lip = Structure()
|
|
3765
|
+
protein_q = None # Charge of each protein bead, from -p_top
|
|
3733
3766
|
prot = []
|
|
3734
3767
|
xshifts = [0] # Shift in x direction per protein
|
|
3735
3768
|
|
|
@@ -4940,6 +4973,17 @@ if(Num_PGL > 0):
|
|
|
4940
4973
|
|
|
4941
4974
|
# Charge of the system so far
|
|
4942
4975
|
|
|
4976
|
+
# Residue names miss charges that are not on side chains, e.g. the termini, so take them from the protein topology if given
|
|
4977
|
+
if options["-p_top"].value and protein:
|
|
4978
|
+
try:
|
|
4979
|
+
beads = read_top_charges(options["-p_top"].value)
|
|
4980
|
+
except (OSError, KeyError) as e:
|
|
4981
|
+
beads = []
|
|
4982
|
+
print("; WARNING: Could not read protein charges from %s (%s)" % (options["-p_top"].value, e), file=sys.stderr)
|
|
4983
|
+
if len(beads) == len(protein.atoms) and all(r[:4] == a[1].strip()[:4] for (r, _), a in zip(beads, protein.atoms)):
|
|
4984
|
+
protein_q = [q for _, q in beads]
|
|
4985
|
+
elif beads:
|
|
4986
|
+
print("; WARNING: The %d beads in %s do not match the %d protein beads in -f, so protein charges are inferred from residue names" % (len(beads), options["-p_top"].value, len(protein.atoms)), file=sys.stderr)
|
|
4943
4987
|
|
|
4944
4988
|
charge,pcharge,mcharge,pgcharge = get_charge(0,pbcz)
|
|
4945
4989
|
|
|
@@ -3436,6 +3436,11 @@ def add_Reses(Res, Resitp):
|
|
|
3436
3436
|
Beadtype[bt] = current_beadtype_no
|
|
3437
3437
|
current_beadtype_no += 1
|
|
3438
3438
|
|
|
3439
|
+
if len(restobead_add) == 0:
|
|
3440
|
+
# Without bead types the residue cannot be placed, and load_cg_pdb would fail with an IndexError
|
|
3441
|
+
print("ERROR: Additional residue "+Res+" has no [ atoms ] entries in "+Resitp+". Add its definition to this itp (-res_itp).")
|
|
3442
|
+
sys.exit(1)
|
|
3443
|
+
|
|
3439
3444
|
ResToBead.append(restobead_add)
|
|
3440
3445
|
|
|
3441
3446
|
|
|
@@ -3,11 +3,9 @@ Tests for the per-leaflet protein footprint (-footprint map, docs/footprint-desi
|
|
|
3
3
|
|
|
4
4
|
Run from the repository root with: python -m unittest discover tests
|
|
5
5
|
The synthetic-shape tests call the footprint block directly (well under a second each); the build tests run
|
|
6
|
-
Insane4MemPrO on small synthetic proteins (a few seconds each).
|
|
7
|
-
with MEMPRO_SLOW_TESTS=1.
|
|
6
|
+
Insane4MemPrO on small synthetic proteins (a few seconds each).
|
|
8
7
|
"""
|
|
9
8
|
|
|
10
|
-
import gzip
|
|
11
9
|
import os
|
|
12
10
|
import re
|
|
13
11
|
import shutil
|
|
@@ -21,7 +19,6 @@ import numpy as np
|
|
|
21
19
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
22
20
|
INSANE = os.path.join(ROOT, "mempro", "Insane4MemPrO.py")
|
|
23
21
|
LEAFLETS = os.path.join(ROOT, "mempro", "leaflet_check.py")
|
|
24
|
-
QMCA = os.path.join(ROOT, "tests", "data", "QmcA")
|
|
25
22
|
sys.path.insert(0, os.path.join(ROOT, "mempro"))
|
|
26
23
|
import leaflet_check # noqa: E402
|
|
27
24
|
|
|
@@ -302,27 +299,10 @@ class TestEnclosedBuild(unittest.TestCase):
|
|
|
302
299
|
inside = heads[np.linalg.norm(heads - 5.0, axis=1) < 1.3]
|
|
303
300
|
self.assertGreater(len(inside), 0)
|
|
304
301
|
self.assertLess(len(inside), 10)
|
|
302
|
+
# Lipids start at least LIPID_MIN_SPACING (0.55 nm) apart, but the head bead sits 0.125 nm off the lipid
|
|
303
|
+
# axis in a random direction, plus a kick of up to 0.1 nm in x and y, so heads can come within 0.16 nm
|
|
305
304
|
d = np.linalg.norm(heads[:, None] - heads[None], axis=2) + 10 * np.eye(len(heads))
|
|
306
|
-
self.assertGreater(d.min(), 0.
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
@unittest.skipUnless(os.environ.get("MEMPRO_SLOW_TESTS"), "set MEMPRO_SLOW_TESTS=1 to build QmcA (234k beads)")
|
|
310
|
-
class TestQmcA(unittest.TestCase):
|
|
311
|
-
def test_leaflets_matched(self):
|
|
312
|
-
tmp = tempfile.mkdtemp(prefix="qmca_test_")
|
|
313
|
-
TMP_DIRS.append(tmp)
|
|
314
|
-
with gzip.open(os.path.join(QMCA, "protein-cg_rank_1.pdb.gz"), "rt") as f, open(os.path.join(tmp, "prot.pdb"), "w") as g:
|
|
315
|
-
g.write(f.read())
|
|
316
|
-
cmd = [sys.executable, INSANE, "-f", "prot.pdb", "-x", "32.11502", "-y", "32.262005", "-z", "24.711554",
|
|
317
|
-
"-l", "POPE:8", "-l", "POPG:2", "-sol", "W", "-negi_c0", "CL", "-posi_c0", "NA", "-footprint", "map",
|
|
318
|
-
"-o", "sys.gro", "-p", "topol.top"]
|
|
319
|
-
out = subprocess.run(cmd, cwd=tmp, capture_output=True, text=True)
|
|
320
|
-
self.assertEqual(out.returncode, 0, out.stderr[-3000:])
|
|
321
|
-
(fp_up, _, _), (fp_lo, _, _) = map_leaflets(out.stdout + out.stderr)
|
|
322
|
-
self.assertGreater(fp_lo, 3 * fp_up) # the soluble cap rests on the lower headgroups
|
|
323
|
-
chk = subprocess.run([sys.executable, LEAFLETS, os.path.join(tmp, "sys.gro"), "-insane", INSANE], capture_output=True, text=True)
|
|
324
|
-
diff = float(re.search(r"Differential strain \(upper - lower\): (\S+)%", chk.stdout).group(1))
|
|
325
|
-
self.assertLess(abs(diff), 1.0)
|
|
305
|
+
self.assertGreater(d.min(), 0.15)
|
|
326
306
|
|
|
327
307
|
|
|
328
308
|
if __name__ == "__main__":
|
|
@@ -217,6 +217,64 @@ class TestLipidome(unittest.TestCase):
|
|
|
217
217
|
self.assertEqual(membrane_charge(log), -counts["XXES"])
|
|
218
218
|
self.assertIn("WARNING: The bead names of POPC", log)
|
|
219
219
|
|
|
220
|
+
def test_lipid_itp_bead_names_by_position(self):
|
|
221
|
+
"""Martini 3 lipidome CARD has no ;@INSANE line and renamed beads; the names come from the itp."""
|
|
222
|
+
tmp = tempfile.mkdtemp(prefix="insane_itp_")
|
|
223
|
+
TMP_DIRS.append(tmp)
|
|
224
|
+
itp = os.path.join(tmp, "card.itp")
|
|
225
|
+
beads = ("GLC PO41 GL11 GL21 C1A1 D2A1 C3A1 C4A1 C1B1 C2B1 C3B1 C4B1 "
|
|
226
|
+
"PO42 GL12 GL22 C1A2 D2A2 C3A2 C4A2 C1B2 C2B2 C3B2 C4B2").split()
|
|
227
|
+
with open(itp, "w") as f:
|
|
228
|
+
f.write("[ moleculetype ]\nCARD 1\n[ atoms ]\n")
|
|
229
|
+
for i, b in enumerate(beads):
|
|
230
|
+
f.write("%d C1 1 CARD %s %d %s\n" % (i + 1, b, i + 1, "-1.0" if b.startswith("PO") else "0"))
|
|
231
|
+
log, counts, gro = build(["-x", "10", "-y", "10", "-z", "10", "-l", "POPE:9", "-l", "CARD:1", "-lipid_itp", itp])
|
|
232
|
+
self.assertGreater(counts["CARD"], 0)
|
|
233
|
+
self.assertEqual(residue_beads(gro, "CARD"), beads)
|
|
234
|
+
self.assertNotIn("WARNING: The bead names of CARD", log)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class TestProteinCharge(unittest.TestCase):
|
|
238
|
+
"""A lipoprotein chain: CYST has a neutral N-terminus, so the C-terminal -1 is not cancelled as residue names assume."""
|
|
239
|
+
|
|
240
|
+
def setUp(self):
|
|
241
|
+
self.dir = tempfile.mkdtemp(prefix="insane_charge_")
|
|
242
|
+
TMP_DIRS.append(self.dir)
|
|
243
|
+
resnames = ["CYST"] + ["LEU"] * 19
|
|
244
|
+
with open(os.path.join(self.dir, "prot.pdb"), "w") as f:
|
|
245
|
+
for i, r in enumerate(resnames):
|
|
246
|
+
f.write("ATOM %5d BB %-4sA%4d %8.3f%8.3f%8.3f 1.00 0.00\n" % (i + 1, r, i + 1, 50.0, 50.0, -28.5 + 3 * i))
|
|
247
|
+
f.write("END\n")
|
|
248
|
+
with open(os.path.join(self.dir, "chain.itp"), "w") as f:
|
|
249
|
+
f.write("[ moleculetype ]\nchain 1\n\n[ atoms ]\n")
|
|
250
|
+
for i, r in enumerate(resnames):
|
|
251
|
+
f.write("%d P5 %d %s BB %d %s\n" % (i + 1, i + 1, r, i + 1, "-1" if i == len(resnames) - 1 else "0.0"))
|
|
252
|
+
|
|
253
|
+
def write_top(self, copies):
|
|
254
|
+
top = os.path.join(self.dir, "prot.top")
|
|
255
|
+
with open(top, "w") as f:
|
|
256
|
+
f.write('#include "martini.itp"\n#include "chain.itp"\n\n[ molecules ]\nchain %d\n' % copies)
|
|
257
|
+
return top
|
|
258
|
+
|
|
259
|
+
def ions(self, extra):
|
|
260
|
+
log, counts, _ = build(["-f", os.path.join(self.dir, "prot.pdb"), "-x", "10", "-y", "10", "-z", "10", "-l", "POPE"] + extra)
|
|
261
|
+
return log, counts.get("NA", 0) - counts.get("CL", 0)
|
|
262
|
+
|
|
263
|
+
def test_residue_names_miss_terminus(self):
|
|
264
|
+
log, excess = self.ions([])
|
|
265
|
+
self.assertIn("Charge of protein: 0.0", log)
|
|
266
|
+
self.assertEqual(excess, 0)
|
|
267
|
+
|
|
268
|
+
def test_charge_from_topology(self):
|
|
269
|
+
log, excess = self.ions(["-p_top", self.write_top(1)])
|
|
270
|
+
self.assertIn("Charge of protein: -1.0", log)
|
|
271
|
+
self.assertEqual(excess, 1)
|
|
272
|
+
|
|
273
|
+
def test_mismatched_topology_falls_back(self):
|
|
274
|
+
log, excess = self.ions(["-p_top", self.write_top(2)])
|
|
275
|
+
self.assertIn("do not match the 20 protein beads", log)
|
|
276
|
+
self.assertIn("Charge of protein: 0.0", log)
|
|
277
|
+
|
|
220
278
|
|
|
221
279
|
class TestLeafletCheck(unittest.TestCase):
|
|
222
280
|
def run_check(self, gro):
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for additional residues (-res / -res_itp).
|
|
3
|
+
|
|
4
|
+
Run from the repository root with: python -m unittest discover tests
|
|
5
|
+
The test runs MemPrO on the QmcA CG structure and stops while the additional residues are read (a few seconds).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import gzip
|
|
9
|
+
import os
|
|
10
|
+
import shutil
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
import tempfile
|
|
14
|
+
import unittest
|
|
15
|
+
|
|
16
|
+
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
17
|
+
MEMPRO = os.path.join(ROOT, "mempro", "MemPrO.py")
|
|
18
|
+
QMCA_CG = os.path.join(ROOT, "tests", "data", "QmcA", "QmcA-cg.pdb.gz")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TestAdditionalResidues(unittest.TestCase):
|
|
22
|
+
|
|
23
|
+
def setUp(self):
|
|
24
|
+
self.tmp = tempfile.mkdtemp()
|
|
25
|
+
self.addCleanup(shutil.rmtree, self.tmp, ignore_errors=True)
|
|
26
|
+
self.cg = os.path.join(self.tmp, "QmcA-cg.pdb")
|
|
27
|
+
with gzip.open(QMCA_CG, "rt") as src, open(self.cg, "w") as dst:
|
|
28
|
+
dst.write(src.read())
|
|
29
|
+
|
|
30
|
+
def test_residue_missing_from_itp(self):
|
|
31
|
+
"""A -res residue with no [ atoms ] entries in -res_itp stops with a clear error and a non-zero exit code."""
|
|
32
|
+
itp = os.path.join(self.tmp, "residues.itp")
|
|
33
|
+
with open(itp, "w") as f:
|
|
34
|
+
f.write("[ moleculetype ]\nPOPE 1\n\n[ atoms ]\n1 Q5 1 POPE NH3 1 1.0\n")
|
|
35
|
+
|
|
36
|
+
result = subprocess.run([sys.executable, MEMPRO, "-f", self.cg, "-o", os.path.join(self.tmp, "out"),
|
|
37
|
+
"-res", "CYST", "-res_itp", itp],
|
|
38
|
+
capture_output=True, text=True, timeout=600)
|
|
39
|
+
|
|
40
|
+
self.assertNotEqual(result.returncode, 0)
|
|
41
|
+
self.assertIn("Additional residue CYST has no [ atoms ] entries", result.stdout)
|
|
42
|
+
self.assertNotIn("IndexError", result.stderr)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
if __name__ == "__main__":
|
|
46
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|