diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3r.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3r.pt new file mode 100644 index 0000000000000000000000000000000000000000..31f2b1258d8b64c30fdd6c8d1f8288d13564af1d --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3r.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a251963bc7fc246cc5ea4013cc3c2b5120497007c0547bf231c53dae486e72ac +size 3747 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3r_A.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3r_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..68bf92072f64b2f5f7273e33640594fba05a6878 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3r_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf53b8de9390dce3b4c06bce5212b50f5c2dd34fdfc4fa71606b63e28f882d42 +size 226911 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3r_B.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3r_B.pt new file mode 100644 index 0000000000000000000000000000000000000000..d4b3038a4d400af402fa88c07a1e3197f9b9bcb3 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3r_B.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27146bf21044affd6eae04a3a408e370e42617ddebd4a78f38ec6784ea9ef2aa +size 226911 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3s.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3s.pt new file mode 100644 index 0000000000000000000000000000000000000000..52c4b653c9dd25dd9350522c14d3e0bb226593f2 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3s.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47b904efe4e667be1fd00d266958223515f93a4d9fd5bda267cc87dad726d44e +size 2403 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3s_A.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3s_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..c866edba5a8b644a861345b0ba00fa9b872a640a --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3s_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:36ebe23d5417543a043be2d3ea307636fc4ec17363974346d24b3b7b4295a7ad +size 78623 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3s_B.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3s_B.pt new file mode 100644 index 0000000000000000000000000000000000000000..d3668415e99531c6aa636378569a3b371f7bdf87 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3s_B.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4157e07c6bcca4db942614df21a921b6815c4a6713ebe70f5e07423262fa7d8f +size 78623 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3t.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3t.pt new file mode 100644 index 0000000000000000000000000000000000000000..5ebfb9a0e5ca1041b8b417630ba42899edb423de --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3t.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e190fed8a3a8f98b29143099b96497bf789cf7f0440b2108d9763ed227af04af +size 3815 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3t_A.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3t_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..7a343255d0fa01d6c8fe7d3510f84f9888ace051 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3t_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fcb8402fd3fd17d8f1cf6809bd4bfac774e7c5bb0ee7cba1959eb904ca84b06e +size 95519 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3t_B.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3t_B.pt new file mode 100644 index 0000000000000000000000000000000000000000..384a0b57fe9f91cf1a259b7707b61fd82dc1deef --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3t_B.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:115ce37c1e119aca345802d149cdbbc0bffcf1632ee575a0089823693e4773ce +size 95519 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3t_C.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3t_C.pt new file mode 100644 index 0000000000000000000000000000000000000000..6b4502d73f5f3aeefc538bd5bc53905e52837239 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3t_C.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ad40e1f6008a5dfef8af699a8f5bc0443a9cc2c6def97c4ba71128aec99442a0 +size 95519 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3t_D.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3t_D.pt new file mode 100644 index 0000000000000000000000000000000000000000..b4de61fe6aab0c23d7550d476a4f1ad7ebdbb216 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3t_D.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b8fbb4b832c4d480ba1c98d6cc9ce9c170bd418cccacfce8af86e8712941156f +size 95519 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3t_E.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3t_E.pt new file mode 100644 index 0000000000000000000000000000000000000000..665b2965516a7438440fed7bbeb2184e8c256d98 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3t_E.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:abe4b95285d2ae7a30b15ac335e5892ee8b4bb942d9db0d44ef00eae9b0111e8 +size 95519 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3t_F.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3t_F.pt new file mode 100644 index 0000000000000000000000000000000000000000..944c9b7da3521417ab730f8b4fb77cea9ea1e3f2 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3t_F.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e8f7ed771e2f1566a33253980019a2483a6f9d77022b77a3e941322c0e919028 +size 95519 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3u.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3u.pt new file mode 100644 index 0000000000000000000000000000000000000000..5e5d5ebe5a6744ccd659dfffdfc3ca6393b87759 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3u.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd413df99149c9ad88b79f1b0e35e335c2575213a5f2db56c09ad9874989a55f +size 3815 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3u_A.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3u_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..2f46584a3d041f4d3db28ba1b862ec19ba10ba73 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3u_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e2c195f29114d20ffb6992149bbd4a72534927c05f4d16169b7c8b130f1cd4e0 +size 95519 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3u_B.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3u_B.pt new file mode 100644 index 0000000000000000000000000000000000000000..8dcd6a93c33d403763abca1be1b3e74f854a8fb7 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3u_B.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c4911c440080adde828d3454b7c5aace4879d8bba77e0ad20ae506798029a4c9 +size 95519 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3u_C.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3u_C.pt new file mode 100644 index 0000000000000000000000000000000000000000..24e5a980174532336a6b8223afa19829b1e82e19 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3u_C.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:44356b9ca00606b5c48ad6cfa44a1349737b80d351a8faee44478b6f5a93f659 +size 95519 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3u_D.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3u_D.pt new file mode 100644 index 0000000000000000000000000000000000000000..c18b3b0f0bd20838408111173b6e278b9b47c14e --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3u_D.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15156a5ab8ada4da0c9198bffbadee7417a4566637f02a22ba72f695d8e0671b +size 95519 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3u_E.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3u_E.pt new file mode 100644 index 0000000000000000000000000000000000000000..0142ea2c2fd635b7117bc287213b992a0115d397 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3u_E.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ddb744b5d7df102230d16ce6ba760287d991e86651536a634f9d9e9bcec50d4 +size 95519 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3u_F.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3u_F.pt new file mode 100644 index 0000000000000000000000000000000000000000..6b382e2bf71779176c548165b30ca0fe2bfddf27 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3u_F.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b002b2d4732b42462c93f821afaefa57ab635e31dee686f52aef6b0678caf265 +size 95519 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3v.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3v.pt new file mode 100644 index 0000000000000000000000000000000000000000..307cf6d577e28b2ce52178481e0c0e86e252c901 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3v.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d00ca5ee2c5ad4d7cfc27989caae730e5d849fd276df1a02ddd311cd16307e80 +size 3815 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3v_A.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3v_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..c1260639ccb03a38ea002b2e0a54bebf688cfa91 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3v_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7543a4dcce4b381d9f7c70347f64207f96f0fcaae8b30fb1acb2a7b6f6e1386a +size 95519 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3v_B.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3v_B.pt new file mode 100644 index 0000000000000000000000000000000000000000..e7ba48171bf1c7aa4e07b99ac141dd6dcb766798 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3v_B.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e4e64a13639e5a57cd7a03411959f55bfed5ee5068e342487853e4015fd3f0c +size 95519 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3v_C.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3v_C.pt new file mode 100644 index 0000000000000000000000000000000000000000..a11aeb5f9a29645154c0230a093ca0a92ec5e412 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3v_C.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eb5b28b52f9ccb54b09f1ada853078d81518cc600e3d294798cce288f575e4d5 +size 95519 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3v_D.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3v_D.pt new file mode 100644 index 0000000000000000000000000000000000000000..7beae70afc2575d6ffcae4d58afa8b91645e7597 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3v_D.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ef54441c003d69203d4b5ff76d6dfc433e12dc080efb8b574806221057e525dc +size 95519 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3v_E.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3v_E.pt new file mode 100644 index 0000000000000000000000000000000000000000..02c558fa1162ade9ac14070c9d0131950fa3244b --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3v_E.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8aa30749aaab9abd24446cdc27f3e577960c1a1473573893ece9684e1280863f +size 95519 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3v_F.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3v_F.pt new file mode 100644 index 0000000000000000000000000000000000000000..38988bef69edb73cdd0580e1be71ace82e76764b --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3v_F.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9365b1024280c74cb19444b32ed29ae6efbe36d44084a323d25250e7772330ac +size 95519 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3w.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3w.pt new file mode 100644 index 0000000000000000000000000000000000000000..4e223c09b603ba27ef26c1a64d04896ad53e15d1 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3w.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c84b86d2c46058645c11cc51f8b7fea9ee27b1215ea5c9c11380a18a595724f1 +size 3915 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3w_A.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3w_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..52d8a6e5052f2ce3ce283cae7a20a1fc1c6e095d --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3w_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1166798ac2bd8f388433cbf3a77a086480c1ff16c6355b58e5697999d88ad085 +size 107935 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3x.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3x.pt new file mode 100644 index 0000000000000000000000000000000000000000..955cecc575db5cbfeacf2ee768db24d1975edcde --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3x.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7fd727f0ac0c0bcd4424ec122ebed26912739318d20cb4354b72325dbd768cdb +size 6695 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3x_A.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3x_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..6d18ec1ae64babe5725972b0e456e57f68d8cd17 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3x_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c696452f01196171ec22d6add5a63995435102a6af4cb527df37a5fb96c4f346 +size 60959 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3x_B.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3x_B.pt new file mode 100644 index 0000000000000000000000000000000000000000..67ab6d2028552fcd9a5845ef9f45f0e6794e705d --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3x_B.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ead87c12b61f9442f18d6d584e6c20a254794cdea55de6c25709d257b3cc70a +size 60959 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3x_C.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3x_C.pt new file mode 100644 index 0000000000000000000000000000000000000000..9146233e06a2fa93b755fb78b791116298225964 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3x_C.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2a8c975e44e5774eae288cdf6f908b0bf38c8d923a803d57bef16c8d57ba99a9 +size 60959 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3x_D.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3x_D.pt new file mode 100644 index 0000000000000000000000000000000000000000..26db16c139e8121b2100f232a15c7cb53b1aaefb --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3x_D.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1d27794c3bdf44c5abc50deca5841ed2637170df349e034358ce43b63e7bf19c +size 60959 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3x_E.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3x_E.pt new file mode 100644 index 0000000000000000000000000000000000000000..3539abf370a6e14d182b81e57f888bf305f942fe --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3x_E.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d877d7a25f38a60fb93aa94afdeec6e80911e17e8e2203ea17e160e3d73fd529 +size 60959 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3x_F.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3x_F.pt new file mode 100644 index 0000000000000000000000000000000000000000..704955c85ce6a8582c0aa3e7c9fd2f22768ec795 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3x_F.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d6b8face7bfa911c5cd2d694baece8f2f755560a28eb574b12379078e6d36eb6 +size 60959 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3x_G.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3x_G.pt new file mode 100644 index 0000000000000000000000000000000000000000..db11de0e4b5984463fe91d9d4ab2a94e2e2fc933 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3x_G.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0adf8f05f5c9c12ee999a5d26518df664e716beb88d9045682a862e51a2dd426 +size 60959 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3x_H.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3x_H.pt new file mode 100644 index 0000000000000000000000000000000000000000..a30a6c4ef1fe28522115232269a4a30090324a93 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3x_H.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d5a4499adbb9099d0831a28c9c05ae79d3422503ea7aa76c034aff97a8baecd +size 60959 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3x_I.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3x_I.pt new file mode 100644 index 0000000000000000000000000000000000000000..5421a9258a431b8ebf0feba3a2c2bb9c7a017d9a --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3x_I.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:80584a11d1e5e664ce17dbc74c53e494c21d2a6db3f1b2a3cd13f2e6739a9aeb +size 60959 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3x_J.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3x_J.pt new file mode 100644 index 0000000000000000000000000000000000000000..0683bb84d827e9598e9964d38b27b1d911270c29 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3x_J.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:156a80edef00f736fa9bf2da11a1963c5552012fe3a9a359a108e3a7c54f15c1 +size 60959 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3x_K.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3x_K.pt new file mode 100644 index 0000000000000000000000000000000000000000..48fe2fc9ccef6ca75cbbe6f17b1345eeb9e87a89 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3x_K.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ccfcdf07d391042726415bfb070b46ddc73d2b70b5b415018f17a6090a52d40e +size 60959 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3x_L.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3x_L.pt new file mode 100644 index 0000000000000000000000000000000000000000..19b24967e8921c86f77150df91f6f8b3710a67b1 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3x_L.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a46ba909ed51b1c21e6b29c66fa1c1bdc50f8b78038c0c6abf0dd2699f4d16bd +size 60959 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3x_M.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3x_M.pt new file mode 100644 index 0000000000000000000000000000000000000000..fa2ccbc8097b1be71cc2c961a08a8147d0a62570 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3x_M.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:656bff5c4ae0603d8ed481016f0bccf9f63a2b219dde824437df0f99ce9b5776 +size 60959 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3x_N.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3x_N.pt new file mode 100644 index 0000000000000000000000000000000000000000..b2c3346da66cc6e86904bcf157027ee260015a45 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3x_N.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d22d5f970e55283d6205530384c44663733dfff5e2c6afe5529d9cb119943db9 +size 60959 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3y.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3y.pt new file mode 100644 index 0000000000000000000000000000000000000000..3ea91d16cb51a4637e66902dec332b011c4a2614 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3y.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba494fb3a594159ccbef120030e4850fed1ee238b0093d8a77095aec780b6a23 +size 2919 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3y_A.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3y_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..dd67a8a4d1a62ab15009173401f9ecc47e362561 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3y_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d93d18dd47aea5cc90c783f989129085db02c52269cb1f049ad8e9720e5031d +size 172191 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3y_B.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3y_B.pt new file mode 100644 index 0000000000000000000000000000000000000000..637624a374d62d3fded77177e2ead36ead4fecc0 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3y_B.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f8046480e35d2bc1d596b9bf672e230e0e1c923e565d22a497a2ec53d7a1c92 +size 172191 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3z.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3z.pt new file mode 100644 index 0000000000000000000000000000000000000000..3b2c7d76ef61ad2272cb8cda3c6b93aabb1ed47e --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3z.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6814073e8f01a7523506f4ab7cbb002c3b3436c236dfcd2b4c3d1d556291017e +size 2535 diff --git a/data/pdb_2021aug02_sample/pdb/l3/6l3z_A.pt b/data/pdb_2021aug02_sample/pdb/l3/6l3z_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..b3f496ce2dbd02b0ba54df82a00ebb8d99f706fc --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/6l3z_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:50a9af39fa53db60baf842efc7407695acf70f60a794aacc0b27bc516ea3e383 +size 201887 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l30.pt b/data/pdb_2021aug02_sample/pdb/l3/7l30.pt new file mode 100644 index 0000000000000000000000000000000000000000..5fd8603908e26f184f94d085532c7a7aa060b57c --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l30.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a93100f6298ce45a52ab8e43cc64cf797e7045891652713b142345c2eb86d33d +size 10199 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l30_A.pt b/data/pdb_2021aug02_sample/pdb/l3/7l30_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..f824bac2aa6ca151725fe3b509083965575350b1 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l30_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b10c0d6b97466fd3a580bb1879c1ca881e74ae75d9fa7697889375c7dc622911 +size 168799 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l30_B.pt b/data/pdb_2021aug02_sample/pdb/l3/7l30_B.pt new file mode 100644 index 0000000000000000000000000000000000000000..b04e697dccb5b42878a7daa9f2c543de12d697d9 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l30_B.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0a3bef759efaa300112c4cb8e87a4b7f4c7e13f4a43c24b6ff996b7a2ccd1406 +size 168799 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l30_C.pt b/data/pdb_2021aug02_sample/pdb/l3/7l30_C.pt new file mode 100644 index 0000000000000000000000000000000000000000..338cd25294d70f5d5c2a8844c900b3ab8b6ddbac --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l30_C.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:547b57fdd3acae2e2152302ff6f7194ccb076a33634af4efb4161951a653882d +size 168799 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l30_D.pt b/data/pdb_2021aug02_sample/pdb/l3/7l30_D.pt new file mode 100644 index 0000000000000000000000000000000000000000..3e4e588df0d59271ba9a2cdb8125e759f530f971 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l30_D.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:57ae1bf4dce236ae18d748a4dcf599d6e2ef7299fe5970c85badbe97298b8220 +size 58271 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l30_E.pt b/data/pdb_2021aug02_sample/pdb/l3/7l30_E.pt new file mode 100644 index 0000000000000000000000000000000000000000..0d1c398f8fbf3a1d8d6adcd96d5e2ed78baaf8f5 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l30_E.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a61e8cb44858a70d28f7fe3e3ec4489aa7efbc8dd8fdfce56f9584e0c5eb94db +size 58271 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l30_F.pt b/data/pdb_2021aug02_sample/pdb/l3/7l30_F.pt new file mode 100644 index 0000000000000000000000000000000000000000..2066e1c1d18d482c01a5732efe2ad2b6bf46e4ad --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l30_F.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:73587b145b87ed35197ff01fa32cf9a7f157764e3a92c12cba30ffe8c3eed4ab +size 58271 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l31.pt b/data/pdb_2021aug02_sample/pdb/l3/7l31.pt new file mode 100644 index 0000000000000000000000000000000000000000..b06ef7798a743c49b0c150a1869893a26c411d30 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l31.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d1326818d6369c7d6bc1a1bfce4b8276fb91a9f59114a7832abea69e3c4f0948 +size 4967 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l31_A.pt b/data/pdb_2021aug02_sample/pdb/l3/7l31_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..5aadc4dcb7c1dd90c1548a491ed2fd3aba1426e0 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l31_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:505502105954cd0c9cf246c9737bbddeaba20136e3ce4198d3c4fffecbe0adf0 +size 124063 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l31_B.pt b/data/pdb_2021aug02_sample/pdb/l3/7l31_B.pt new file mode 100644 index 0000000000000000000000000000000000000000..d0f3f9cdbda8def9dd402c0cb507e8c24d9d48e3 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l31_B.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0040968b3fb6ec61521f50a5f42166f2214b786e9a6f6feba55518e7d3eecf9f +size 124063 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l31_C.pt b/data/pdb_2021aug02_sample/pdb/l3/7l31_C.pt new file mode 100644 index 0000000000000000000000000000000000000000..1f19351eb0663c9d1a2290fa0554c606cc787576 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l31_C.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6ddb4f46fbce85754af755a5d5ac8a490dedbbc922b698f6f553317f604479c6 +size 124063 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l31_D.pt b/data/pdb_2021aug02_sample/pdb/l3/7l31_D.pt new file mode 100644 index 0000000000000000000000000000000000000000..da8e69b80e512e64d5b36b76df1f92972eb77081 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l31_D.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e7dda5dad60b1932ec31638a3cb07adeec74f4652b016bdf135a70072a2aa73e +size 124063 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l31_E.pt b/data/pdb_2021aug02_sample/pdb/l3/7l31_E.pt new file mode 100644 index 0000000000000000000000000000000000000000..a801484701e7d4ee842a2233b149b7451555d69a --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l31_E.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3f4c2c5d97dd89fb9c8ef8c51398a6dd68a826f1bd5ff9289a9ac27096f4211e +size 238047 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l32.pt b/data/pdb_2021aug02_sample/pdb/l3/7l32.pt new file mode 100644 index 0000000000000000000000000000000000000000..83ed65146be32700ab78f0d163462fd7ca3a192a --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l32.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c858ae10b67caf20a72195eab9524e05540202320a904a361903e8f52cced39 +size 2719 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l32_A.pt b/data/pdb_2021aug02_sample/pdb/l3/7l32_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..ed8d53ea2a0883b0e37f78bc4dfa5b1e20f9a53a --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l32_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:644321bed7cae3b2a641c13670b3efcc3c0059ba2b9e41ed49c3dd0202847e93 +size 57375 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l32_B.pt b/data/pdb_2021aug02_sample/pdb/l3/7l32_B.pt new file mode 100644 index 0000000000000000000000000000000000000000..d61feba7d05e1379042aa7f2fcef19b434ca325f --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l32_B.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:28dbb86d360059b50775c1cd7326e9e930d4d6c0d5da232da7375bd88b06fcae +size 57375 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l32_C.pt b/data/pdb_2021aug02_sample/pdb/l3/7l32_C.pt new file mode 100644 index 0000000000000000000000000000000000000000..5348076edb467d6212bafaab8f0e8b686efe1d40 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l32_C.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:282c31b0977552bed651761ce3b3dd2f679cf18fbc3512ea68167a1aba985938 +size 57375 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l33.pt b/data/pdb_2021aug02_sample/pdb/l3/7l33.pt new file mode 100644 index 0000000000000000000000000000000000000000..687216dc62f626602b4ad8a3363be196473ac3aa --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l33.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:02a42158ab87b9099b5a593b66b74f98bec83cc1351b814508e5309a8b7d0b89 +size 2591 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l33_A.pt b/data/pdb_2021aug02_sample/pdb/l3/7l33_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..559a800763e419d7ec32481ca4c3802eeea1a984 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l33_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:07faff49ab3a68e160f37cc81c9071d162b4f55f904be6b1cc1e8c7de9e0d6a1 +size 12127 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l33_B.pt b/data/pdb_2021aug02_sample/pdb/l3/7l33_B.pt new file mode 100644 index 0000000000000000000000000000000000000000..4a90edceac5e52b9fa8e712615c484dceb0656a8 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l33_B.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eadba482dd3dd6a457a47288916eb59f0e8f42f13414f71596ff01cedfa25981 +size 12127 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l33_C.pt b/data/pdb_2021aug02_sample/pdb/l3/7l33_C.pt new file mode 100644 index 0000000000000000000000000000000000000000..86f9e79ceccebc9bf2813508b9c3ec674847c3a2 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l33_C.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:28b901b7db89df42363e3db272b96e0fb8f6ff94f24bc00208468686ad8200ea +size 12127 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l34.pt b/data/pdb_2021aug02_sample/pdb/l3/7l34.pt new file mode 100644 index 0000000000000000000000000000000000000000..e1d29928171b7528a0af255626b959a9f45436f5 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l34.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0f022da5649342a13d0d1b74e3b1d75e2c40e1b10251bbae6aa3fc3f4277447d +size 2663 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l34_A.pt b/data/pdb_2021aug02_sample/pdb/l3/7l34_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..52bbaf3aa246495319dd9e5fc338a1a80e183b43 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l34_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e5fe09380a3aa326694d01ab9eab31304e33dfa9038bc6f6122bd23f8958dfa +size 219551 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l35.pt b/data/pdb_2021aug02_sample/pdb/l3/7l35.pt new file mode 100644 index 0000000000000000000000000000000000000000..42bef96744065b2aa8ec24c0973f67eb308186f9 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l35.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:449f3a977fa22f4e8def81f0a07f0ef6e4a9f775a8a8291e8050052216ddd274 +size 2663 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l35_A.pt b/data/pdb_2021aug02_sample/pdb/l3/7l35_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..e35dd36e2613f6b2a53f0a81cc004da3ad3a981e --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l35_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3559081ba888e6f8815a8ebf7ca99fccea65375e9cad10579ba036c07cf834d7 +size 219551 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l36.pt b/data/pdb_2021aug02_sample/pdb/l3/7l36.pt new file mode 100644 index 0000000000000000000000000000000000000000..97879b40f45a8c4d087a660066afc4398872d303 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l36.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a305eeea40a80a2fb077abfc4ba6b3c3bb14dbcda095b74853fcd3d57a0848c4 +size 2599 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l36_A.pt b/data/pdb_2021aug02_sample/pdb/l3/7l36_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..a4c6110e958295b1dfa6167c51d1816edaf63bb7 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l36_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3da777c74d7a660fe12b60cdcc3ffa680fa6d356f29d767658847b58bfe2971b +size 211615 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l37.pt b/data/pdb_2021aug02_sample/pdb/l3/7l37.pt new file mode 100644 index 0000000000000000000000000000000000000000..e401c2ddd4645578df5ce3e23f6bb3fe3175036a --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l37.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fb90bdef0c97dc1bef064c38b5f378a932fae07fae06c9c4a6d4346c082f8b26 +size 1639 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l37_A.pt b/data/pdb_2021aug02_sample/pdb/l3/7l37_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..7b90cb4a275acfa117903b04aebd5326b5af5d36 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l37_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aa0107b668d19aa60ae4b4e07fd49616e26c0b37e00173641affde03e6a4fabe +size 56671 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l38.pt b/data/pdb_2021aug02_sample/pdb/l3/7l38.pt new file mode 100644 index 0000000000000000000000000000000000000000..975910160dc5df35049985d9dc415a5f4e5fd8b0 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l38.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8816754c81ef8017c7d4fef20d9b67f8c98d201e08d51f7f8b483b2724658673 +size 1639 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l38_A.pt b/data/pdb_2021aug02_sample/pdb/l3/7l38_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..9879e713b27543edbe31171951e25c96af40c667 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l38_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d8d3334c0cc52deb1d270529d078efbe407146d4ad7f570ab0c174ff4929f1e +size 56671 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l39.pt b/data/pdb_2021aug02_sample/pdb/l3/7l39.pt new file mode 100644 index 0000000000000000000000000000000000000000..e2ae2b7cad4d8215101e926304970919bd1afbf4 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l39.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3ecc4406644ae3b992b233b7b00128438b12a66998eb240590c9db7f1e63f439 +size 1639 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l39_A.pt b/data/pdb_2021aug02_sample/pdb/l3/7l39_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..68608b28fae47277c622ce39e4f9d4ddd620852e --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l39_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:618d0305fceb379c07ff5c76f3a4f8088a301d3c821c74f15da769ac266ae501 +size 56671 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3a.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3a.pt new file mode 100644 index 0000000000000000000000000000000000000000..439c4ac02bad0e52114796b3c3269044b4f49bfa --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3a.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:649f5cc208116b01d7566467f47bf9f811905de984176fbec036981b9cba664e +size 1639 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3a_A.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3a_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..088308f7fed228d06b525e4d47d7abd876ab9c94 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3a_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd3f8a4e835a167934f7602482396414dd2a28fa2e02028d1e41a0911a60d018 +size 56671 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3b.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3b.pt new file mode 100644 index 0000000000000000000000000000000000000000..2deeb4f5412b8b269eff39e5f0a8f1c311c5f130 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3b.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f8a810da9a04c6585d70ab10201f6a971643ebca9c2f4dd0d880497ad592053 +size 1639 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3b_A.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3b_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..43ac79162dde946ba162e4292682749eb5ce6c45 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3b_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:92fd278945ec0c88df3a35b372289af8942444e2a8c4036ee100d2ac1e82feac +size 56671 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3c.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3c.pt new file mode 100644 index 0000000000000000000000000000000000000000..6f0b7323c04b54d3a6c0945fb6e8fce25b50461e --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3c.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aaff5a127a1218f8c45d7ad240cd6d3a98bbdedceeaa29b2d17fea774c0bd5e2 +size 1639 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3c_A.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3c_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..92d62c37cbdf0b4862b8162ea4be3e9eb6fa365a --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3c_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:35d23a97dec3ee8aed37adbfb66351a3f4f04c731ffe9b4fb5fc0ff1c316f729 +size 56671 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3d.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3d.pt new file mode 100644 index 0000000000000000000000000000000000000000..56ba7274f46be40bc6214cc4f1fbd468adcfad1b --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3d.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4a5ea817c52485fbdb560bd551a9352ad8cdf85e321261faa891096cbb9b760c +size 1639 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3d_A.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3d_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..0ba130d089e7eba6dfaeef1815718abd20c2c11d --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3d_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:430ffe67ff17c0eea181f3ebb61144e6ac45287831edb1f4937bea15b63d8fad +size 56671 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3e.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3e.pt new file mode 100644 index 0000000000000000000000000000000000000000..e26bd1d1f9086579eecf3173d0482f3981e1679b --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3e.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:21b7f9fa8365543f981fe6522afce153ab9c6e3c5180f99d439482c10fb00992 +size 1639 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3e_A.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3e_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..7d8e47a84a7f15fb555270a5d698ca8014ae8235 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3e_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:77e278aedf59ad784fc9146be5c0356576668eeb7ffdd4d407fdf318bbbbd863 +size 56671 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3f.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3f.pt new file mode 100644 index 0000000000000000000000000000000000000000..18e68cfc730328546984792dcad65b3b03ca9781 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3f.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:beb806e42e80ce6e39bfb527136f9c32f6d9f30dbfdb166bbe2a081b898829ea +size 1639 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3f_A.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3f_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..9adcc2541aabd76d6ee53d78e87bebbde2d3b82f --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3f_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:105e17758c003b04bf9e46287c1a21dd1f607c170215aef87bc1c33d3443ab22 +size 56671 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3g.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3g.pt new file mode 100644 index 0000000000000000000000000000000000000000..50ce85ed8cb7f4ebe70a7c9d9a70289a332fef07 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3g.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d7b085da27a32f2cfeed9e8e59025ffd639eb41365e65504e1b1ac6dafd6d034 +size 1639 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3g_A.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3g_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..86e3817253dac321bd8c90515219e5b7fb9e05b2 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3g_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce7b4a9ffd9abde27ef573cab495ce2f89950c1bb22b0e1489453d78b72e7adf +size 56671 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3h.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3h.pt new file mode 100644 index 0000000000000000000000000000000000000000..eef4a936fe19119331d5d7f44dfb0243921cc168 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3h.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:42b7a3cf9456852541c77f236c7cbaa50e45e2c7d1011ac02f316d93af7728f6 +size 1639 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3h_A.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3h_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..67a04f70ef6b7b117ba5a3a2812191932af551ec --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3h_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:efd4076bdcdfc830248a2e9603d6f7ab315f68f1b3c267abcbcb903b88953fa6 +size 56671 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3i.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3i.pt new file mode 100644 index 0000000000000000000000000000000000000000..288329c4be10f092dbf9e9a03ddce594cf13a3a2 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3i.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:477f1eb02decb6fd2ff528850b8daa8f64f4a9944c65673e1863939799419ec8 +size 1639 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3i_A.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3i_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..8af675e96698754a25319f28ea9602ad7ea352ca --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3i_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a8f9c1970e486a69215560840543d2905059deb3f508ea00d7de6b2e6754c5b2 +size 56671 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3j.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3j.pt new file mode 100644 index 0000000000000000000000000000000000000000..5ce49fcf59255664983aa9a6fafe497aef5bb4f4 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3j.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4137d7720101244210f8add7829fa4c8ce7343563c2fe2d9435a44c24a5db06d +size 1639 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3j_A.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3j_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..1dd791514244d1a704937ce51767a364a2337e3a --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3j_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:13668221875f4e08268a2b213a0fe6c14d77a605418abd99beabe186e1a99f83 +size 56671 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3k.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3k.pt new file mode 100644 index 0000000000000000000000000000000000000000..3ffe434c8f472dc574d0bb4d180854f7e123ab61 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3k.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ba08a2cd7aefe6437f0bb7e37de393e17e59d6c9f6736ca388f455d40a4b0ff +size 1639 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3k_A.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3k_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..409f8840040be5454008c113ea78d95989be4b9c --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3k_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3ede08dcca702e2b0a2f27c43cd7e11a2b4eabce0c42166726dcd3bd4cd94d6a +size 56671 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3l.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3l.pt new file mode 100644 index 0000000000000000000000000000000000000000..30bfcc95b23564047b88070be65dfece746feaf0 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3l.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:92d05aca1cdc8aea92a3cf4837692f9afa815d3a53050c6c12230b09bcf60a5b +size 2723 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3l_A.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3l_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..792b4d7bd10395e34a6b19ab397ccf8c807eabce --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3l_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65bfacc4f03d2dfc03f2dc41580001ed3da2d45ebbfe0be1e51e6a8eaa7534a5 +size 49311 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3l_B.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3l_B.pt new file mode 100644 index 0000000000000000000000000000000000000000..c92d90a0d328469204f88f2ad6d34931c3c2fc85 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3l_B.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f684bb9c6c4a17ece315247bda9d6347c1f253716e39711945f5fc760c6e9a3e +size 37407 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3l_C.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3l_C.pt new file mode 100644 index 0000000000000000000000000000000000000000..f4ec55aa0cff3bdf40acb7c1626613d36c84f175 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3l_C.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:21629e39033feab92986f3cc40ff81c3c80fab038b55c79ecd555cbab91c7afb +size 49311 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3l_D.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3l_D.pt new file mode 100644 index 0000000000000000000000000000000000000000..3f103391fe9304403cefecfe6be9a3ecc264783a --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3l_D.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:864a767070d4106d2e087a93fdda760add1b1123b6f9a42a32591dbdb5b4f2a9 +size 37407 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3m.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3m.pt new file mode 100644 index 0000000000000000000000000000000000000000..f77ea8af51b6a468a046441c629ec7e12d25164b --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3m.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6bfcac3c84a604d22a3219464e045a0333192550adce8157d9ad143a76e2ab0f +size 2599 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3m_A.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3m_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..6ee58788031d384ed2a02bf8d73c331143cdb831 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3m_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8e57fbcefae7fca1ddbbe9405cce9ceeefd69befcf88b4b9622a6eca1669ebfe +size 211615 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3n.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3n.pt new file mode 100644 index 0000000000000000000000000000000000000000..2b3f74c49fe978c5407ec2304d9fc53a0eb637c8 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3n.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d4a0c491c71933b9061dd8b9ffb39c6423a002109eaa5280bc25481c4869faa9 +size 7719 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3n_A.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3n_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..157994524f8d548d17e0429af3af52c7f63b6d36 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3n_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b2aedcf4d90ccbe492365837fc3ec8454e81ae1ff4b7f7eeaccd52a84dbd6538 +size 431455 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3n_B.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3n_B.pt new file mode 100644 index 0000000000000000000000000000000000000000..7aa4e5bff0031d8085424c2cfbcd1a82cdd12aa6 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3n_B.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f043214d66cfd281cb7247ba139f5f721653bdf06c26a7559c13f9a2df26b2c0 +size 431455 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3n_C.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3n_C.pt new file mode 100644 index 0000000000000000000000000000000000000000..f7168676c296fcf67572ede46c78c2932e77b233 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3n_C.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3e05b34be50c780e4a97548b3b543568e1843e0f9780675011e18a6eec4d2f39 +size 431455 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3n_D.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3n_D.pt new file mode 100644 index 0000000000000000000000000000000000000000..b1dcf974cf99f586545f7ee39be12654987173e9 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3n_D.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4cd50ef55dd2a2abf1da847cc87e41b032257775e6b90dd349341375cf445e67 +size 78239 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3n_E.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3n_E.pt new file mode 100644 index 0000000000000000000000000000000000000000..cc8972f741c401e38d3507bf74ebb143abc00c9e --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3n_E.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:795a5033e2d8f8bf410a1df7ad6d2fa6b88f3cba7628a5fa9c65a38db97beb68 +size 72863 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3o.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3o.pt new file mode 100644 index 0000000000000000000000000000000000000000..e2a4463d3097ce1e7158391f3826abc5a1a6b673 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3o.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2c67548ce0ed820da4cd8753265954354ef62ebaea962b3723c70b227539e96a +size 4195 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3o_A.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3o_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..16eed42207dd8da41f820de63411777adf9952df --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3o_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:45070a505d650f7e3351dba322a8263f0a83c811a11166bbcf7e79e68b5f53a6 +size 150303 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3o_B.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3o_B.pt new file mode 100644 index 0000000000000000000000000000000000000000..78798a6f749f47c39b6f6f52d124473d0dfcd152 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3o_B.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b316bfa46b0021d808c4fd2bd02889ba7f5dd563396c2d539ddc7e44fc4499d7 +size 150303 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3o_C.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3o_C.pt new file mode 100644 index 0000000000000000000000000000000000000000..50055930a1d04887b457465c2f7eb404f200fb0e --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3o_C.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7b4d6dfc3a8f42c08350c9aa5c5993bdc867216071550e9681a9d3974dea2143 +size 150303 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3o_D.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3o_D.pt new file mode 100644 index 0000000000000000000000000000000000000000..898fd67dddaf402778bfb3dc8aec5a096b002791 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3o_D.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f969aee365bf4600a1a2a4f47c159d0d9728efde188ab663c996225d121bb5ed +size 150303 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3p.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3p.pt new file mode 100644 index 0000000000000000000000000000000000000000..9bb76ad07d49e5fa86e2f62a807195e3ffc4c8d2 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3p.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:89feef530973747f053b3078cc8f965216a3e72b80597f053b09751d75e77e03 +size 2791 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3p_A.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3p_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..5dffb3809e04887bc9a7839941f913dff186887e --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3p_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:91353278aee8bd2941d89a9e63ebefbf3adacb4e34e6009d939bef6b4cba0f4b +size 239327 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3q.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3q.pt new file mode 100644 index 0000000000000000000000000000000000000000..d7d2655b6e7df9e1e322ee633fd8153593506bcf --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3q.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:40f9ebd2735a649925457c2d7b656ce8472537d948fe43ab13a63fc521795210 +size 5023 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3q_A.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3q_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..0dc352e3fdec0b01c7013f941a614ed4dfdcd68e --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3q_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1503ccbeae8e85f9e165e6ab5d53f0e951cd81949633bc3fdbc2eff0f84640c8 +size 239327 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3q_B.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3q_B.pt new file mode 100644 index 0000000000000000000000000000000000000000..5022256104235cd9af3e9dba48e33798bf64f4ca --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3q_B.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:542a4b752fd6ab453ea04e46d8f99d75f568e27a03e5abdf1f9e653fe3ec4359 +size 239327 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3q_C.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3q_C.pt new file mode 100644 index 0000000000000000000000000000000000000000..d772c685c929c6db2a2be11b27b2538ac33e7d4c --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3q_C.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:119f43679b9e14931f77180967c444689e527f275d0506ceea999510d07c9f88 +size 239327 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3r.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3r.pt new file mode 100644 index 0000000000000000000000000000000000000000..ffe07ca948f339e915fd407cdc5c8083f1ba2fdc --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3r.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed0e16ba5e1365375a3fc56b13030440cfac723253576b6d8cd1490a1c24a0a8 +size 1131 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3u.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3u.pt new file mode 100644 index 0000000000000000000000000000000000000000..f93ff120d1e82febbef365bdd9c1683d03b9f656 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3u.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82a6d0012380e1ca16ab2558831c23eeccddf95fc507d806ee3fe2724b4e7558 +size 1639 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3u_A.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3u_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..d87c2089190454b9df3887b7779fd8e4bc3fd7b6 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3u_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d160c2fdd2be87daaf3703d5cf372fe0f69f0a7fe5274aa68183e8f3e6dbbd3c +size 53279 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3v.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3v.pt new file mode 100644 index 0000000000000000000000000000000000000000..6040a1936d1031a4605b55d1cc6b9b536175e720 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3v.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a5fd1ca08a9d161ef45a3261393abfd698194f64ff9b240bf977f2a5ce91ce6a +size 2599 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3v_A.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3v_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..e2fd613d0a3f2c172f9a02934d3527bc78ea8aa5 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3v_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c49b4c570502c5db0da4670b5777282f6015f592326e63eac00b9e8b52b6f03b +size 211615 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3y.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3y.pt new file mode 100644 index 0000000000000000000000000000000000000000..d2d652d2ce2e98fdf6983e4eb1cbc780de63dca4 --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3y.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:749517807f2ffa32bcf7335a41af4ea1e60b7e5ca3d317e0fc6eb21e07d4d72d +size 1639 diff --git a/data/pdb_2021aug02_sample/pdb/l3/7l3y_A.pt b/data/pdb_2021aug02_sample/pdb/l3/7l3y_A.pt new file mode 100644 index 0000000000000000000000000000000000000000..5421dbb4305c2a8f3abd7ab9d1990c2dcb7ae4ec --- /dev/null +++ b/data/pdb_2021aug02_sample/pdb/l3/7l3y_A.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a5f5a0d320a055d1a56a997a4ab51731e38d75e85f4aca4bd8a4a7a1b1b2c33e +size 53279 diff --git a/data/pdb_2021aug02_sample/test_clusters.txt b/data/pdb_2021aug02_sample/test_clusters.txt new file mode 100644 index 0000000000000000000000000000000000000000..b1dc7d399e11e40586e8f01f69bda8b9a4b2a534 --- /dev/null +++ b/data/pdb_2021aug02_sample/test_clusters.txt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65283644818191a5c718d3a3a2688d064fc243857e91a815c18c70bebba9658a +size 8740 diff --git a/data/pdb_2021aug02_sample/valid_clusters.txt b/data/pdb_2021aug02_sample/valid_clusters.txt new file mode 100644 index 0000000000000000000000000000000000000000..253bae229e9f9425f85ee51f904fd0ccd3c65a67 --- /dev/null +++ b/data/pdb_2021aug02_sample/valid_clusters.txt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3251fa289cb61ce156e7ff70aad04f473a636380f958172e435a7de18d13cdb8 +size 8276 diff --git a/model/proteinmpnn/__init__.py b/model/proteinmpnn/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/model/proteinmpnn/model_utils.py b/model/proteinmpnn/model_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..673291f4b17d1c75c9c46072f692fab390378b08 --- /dev/null +++ b/model/proteinmpnn/model_utils.py @@ -0,0 +1,512 @@ +from __future__ import print_function +import json, time, os, sys, glob +import shutil +import numpy as np +import torch +from torch import optim +from torch.utils.data import DataLoader +from torch.utils.data.dataset import random_split, Subset +import torch.utils +import torch.utils.checkpoint + +import copy +import torch.nn as nn +import torch.nn.functional as F +import random +import itertools + + +def featurize(batch, device): + alphabet = 'ACDEFGHIKLMNPQRSTVWYX' + B = len(batch) + lengths = np.array([len(b['seq']) for b in batch], dtype=np.int32) #sum of chain seq lengths + L_max = max([len(b['seq']) for b in batch]) + X = np.zeros([B, L_max, 4, 3]) + residue_idx = -100*np.ones([B, L_max], dtype=np.int32) #residue idx with jumps across chains + chain_M = np.zeros([B, L_max], dtype=np.int32) #1.0 for the bits that need to be predicted, 0.0 for the bits that are given + mask_self = np.ones([B, L_max, L_max], dtype=np.int32) #for interface loss calculation - 0.0 for self interaction, 1.0 for other + chain_encoding_all = np.zeros([B, L_max], dtype=np.int32) #integer encoding for chains 0, 0, 0,...0, 1, 1,..., 1, 2, 2, 2... + S = np.zeros([B, L_max], dtype=np.int32) #sequence AAs integers + init_alphabet = ['A', 'B', 'C', 'D', 'E', 'F', 'G','H', 'I', 'J','K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T','U', 'V','W','X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g','h', 'i', 'j','k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't','u', 'v','w','x', 'y', 'z'] + extra_alphabet = [str(item) for item in list(np.arange(300))] + chain_letters = init_alphabet + extra_alphabet + for i, b in enumerate(batch): + masked_chains = b['masked_list'] + visible_chains = b['visible_list'] + all_chains = masked_chains + visible_chains + visible_temp_dict = {} + masked_temp_dict = {} + for step, letter in enumerate(all_chains): + chain_seq = b[f'seq_chain_{letter}'] + if letter in visible_chains: + visible_temp_dict[letter] = chain_seq + elif letter in masked_chains: + masked_temp_dict[letter] = chain_seq + for km, vm in masked_temp_dict.items(): + for kv, vv in visible_temp_dict.items(): + if vm == vv: + if kv not in masked_chains: + masked_chains.append(kv) + if kv in visible_chains: + visible_chains.remove(kv) + all_chains = masked_chains + visible_chains + random.shuffle(all_chains) #randomly shuffle chain order + num_chains = b['num_of_chains'] + mask_dict = {} + x_chain_list = [] + chain_mask_list = [] + chain_seq_list = [] + chain_encoding_list = [] + c = 1 + l0 = 0 + l1 = 0 + for step, letter in enumerate(all_chains): + if letter in visible_chains: + chain_seq = b[f'seq_chain_{letter}'] + chain_length = len(chain_seq) + chain_coords = b[f'coords_chain_{letter}'] #this is a dictionary + chain_mask = np.zeros(chain_length) #0.0 for visible chains + x_chain = np.stack([chain_coords[c] for c in [f'N_chain_{letter}', f'CA_chain_{letter}', f'C_chain_{letter}', f'O_chain_{letter}']], 1) #[chain_length,4,3] + x_chain_list.append(x_chain) + chain_mask_list.append(chain_mask) + chain_seq_list.append(chain_seq) + chain_encoding_list.append(c*np.ones(np.array(chain_mask).shape[0])) + l1 += chain_length + mask_self[i, l0:l1, l0:l1] = np.zeros([chain_length, chain_length]) + residue_idx[i, l0:l1] = 100*(c-1)+np.arange(l0, l1) + l0 += chain_length + c+=1 + elif letter in masked_chains: + chain_seq = b[f'seq_chain_{letter}'] + chain_length = len(chain_seq) + chain_coords = b[f'coords_chain_{letter}'] #this is a dictionary + chain_mask = np.ones(chain_length) #0.0 for visible chains + x_chain = np.stack([chain_coords[c] for c in [f'N_chain_{letter}', f'CA_chain_{letter}', f'C_chain_{letter}', f'O_chain_{letter}']], 1) #[chain_lenght,4,3] + x_chain_list.append(x_chain) + chain_mask_list.append(chain_mask) + chain_seq_list.append(chain_seq) + chain_encoding_list.append(c*np.ones(np.array(chain_mask).shape[0])) + l1 += chain_length + mask_self[i, l0:l1, l0:l1] = np.zeros([chain_length, chain_length]) + residue_idx[i, l0:l1] = 100*(c-1)+np.arange(l0, l1) + l0 += chain_length + c+=1 + x = np.concatenate(x_chain_list,0) #[L, 4, 3] + all_sequence = "".join(chain_seq_list) + m = np.concatenate(chain_mask_list,0) #[L,], 1.0 for places that need to be predicted + chain_encoding = np.concatenate(chain_encoding_list,0) + + l = len(all_sequence) + x_pad = np.pad(x, [[0,L_max-l], [0,0], [0,0]], 'constant', constant_values=(np.nan, )) + X[i,:,:,:] = x_pad + + m_pad = np.pad(m, [[0,L_max-l]], 'constant', constant_values=(0.0, )) + chain_M[i,:] = m_pad + + chain_encoding_pad = np.pad(chain_encoding, [[0,L_max-l]], 'constant', constant_values=(0.0, )) + chain_encoding_all[i,:] = chain_encoding_pad + + # Convert to labels + indices = np.asarray([alphabet.index(a) for a in all_sequence], dtype=np.int32) + S[i, :l] = indices + + isnan = np.isnan(X) + mask = np.isfinite(np.sum(X,(2,3))).astype(np.float32) + X[isnan] = 0. + + # Conversion + residue_idx = torch.from_numpy(residue_idx).to(dtype=torch.long,device=device) + S = torch.from_numpy(S).to(dtype=torch.long,device=device) + X = torch.from_numpy(X).to(dtype=torch.float32, device=device) + mask = torch.from_numpy(mask).to(dtype=torch.float32, device=device) + mask_self = torch.from_numpy(mask_self).to(dtype=torch.float32, device=device) + chain_M = torch.from_numpy(chain_M).to(dtype=torch.float32, device=device) + chain_encoding_all = torch.from_numpy(chain_encoding_all).to(dtype=torch.long, device=device) + return X, S, mask, lengths, chain_M, residue_idx, mask_self, chain_encoding_all + + +def loss_nll(S, log_probs, mask): + """ Negative log probabilities """ + criterion = torch.nn.NLLLoss(reduction='none') + loss = criterion( + log_probs.contiguous().view(-1, log_probs.size(-1)), S.contiguous().view(-1) + ).view(S.size()) + S_argmaxed = torch.argmax(log_probs,-1) #[B, L] + true_false = (S == S_argmaxed).float() + loss_av = torch.sum(loss * mask) / torch.sum(mask) + return loss, loss_av, true_false + + +def loss_smoothed(S, log_probs, mask, weight=0.1): + """ Negative log probabilities """ + S_onehot = torch.nn.functional.one_hot(S, 21).float() + + # Label smoothing + S_onehot = S_onehot + weight / float(S_onehot.size(-1)) + S_onehot = S_onehot / S_onehot.sum(-1, keepdim=True) + + loss = -(S_onehot * log_probs).sum(-1) + loss_av = torch.sum(loss * mask) / 2000.0 #fixed + return loss, loss_av + + +# The following gather functions +def gather_edges(edges, neighbor_idx): + # Features [B,N,N,C] at Neighbor indices [B,N,K] => Neighbor features [B,N,K,C] + neighbors = neighbor_idx.unsqueeze(-1).expand(-1, -1, -1, edges.size(-1)) + edge_features = torch.gather(edges, 2, neighbors) + return edge_features + +def gather_nodes(nodes, neighbor_idx): + # Features [B,N,C] at Neighbor indices [B,N,K] => [B,N,K,C] + # Flatten and expand indices per batch [B,N,K] => [B,NK] => [B,NK,C] + neighbors_flat = neighbor_idx.view((neighbor_idx.shape[0], -1)) + neighbors_flat = neighbors_flat.unsqueeze(-1).expand(-1, -1, nodes.size(2)) + # Gather and re-pack + neighbor_features = torch.gather(nodes, 1, neighbors_flat) + neighbor_features = neighbor_features.view(list(neighbor_idx.shape)[:3] + [-1]) + return neighbor_features + +def gather_nodes_t(nodes, neighbor_idx): + # Features [B,N,C] at Neighbor index [B,K] => Neighbor features[B,K,C] + idx_flat = neighbor_idx.unsqueeze(-1).expand(-1, -1, nodes.size(2)) + neighbor_features = torch.gather(nodes, 1, idx_flat) + return neighbor_features + +def cat_neighbors_nodes(h_nodes, h_neighbors, E_idx): + h_nodes = gather_nodes(h_nodes, E_idx) + h_nn = torch.cat([h_neighbors, h_nodes], -1) + return h_nn + + +class EncLayer(nn.Module): + def __init__(self, num_hidden, num_in, dropout=0.1, num_heads=None, scale=30): + super(EncLayer, self).__init__() + self.num_hidden = num_hidden + self.num_in = num_in + self.scale = scale + self.dropout1 = nn.Dropout(dropout) + self.dropout2 = nn.Dropout(dropout) + self.dropout3 = nn.Dropout(dropout) + self.norm1 = nn.LayerNorm(num_hidden) + self.norm2 = nn.LayerNorm(num_hidden) + self.norm3 = nn.LayerNorm(num_hidden) + + self.W1 = nn.Linear(num_hidden + num_in, num_hidden, bias=True) + self.W2 = nn.Linear(num_hidden, num_hidden, bias=True) + self.W3 = nn.Linear(num_hidden, num_hidden, bias=True) + self.W11 = nn.Linear(num_hidden + num_in, num_hidden, bias=True) + self.W12 = nn.Linear(num_hidden, num_hidden, bias=True) + self.W13 = nn.Linear(num_hidden, num_hidden, bias=True) + self.act = torch.nn.GELU() + self.dense = PositionWiseFeedForward(num_hidden, num_hidden * 4) + + def forward(self, h_V, h_E, E_idx, mask_V=None, mask_attend=None): + """ Parallel computation of full transformer layer """ + + h_EV = cat_neighbors_nodes(h_V, h_E, E_idx) + h_V_expand = h_V.unsqueeze(-2).expand(-1,-1,h_EV.size(-2),-1) + h_EV = torch.cat([h_V_expand, h_EV], -1) + h_message = self.W3(self.act(self.W2(self.act(self.W1(h_EV))))) + if mask_attend is not None: + h_message = mask_attend.unsqueeze(-1) * h_message + dh = torch.sum(h_message, -2) / self.scale + h_V = self.norm1(h_V + self.dropout1(dh)) + + dh = self.dense(h_V) + h_V = self.norm2(h_V + self.dropout2(dh)) + if mask_V is not None: + mask_V = mask_V.unsqueeze(-1) + h_V = mask_V * h_V + + h_EV = cat_neighbors_nodes(h_V, h_E, E_idx) + h_V_expand = h_V.unsqueeze(-2).expand(-1,-1,h_EV.size(-2),-1) + h_EV = torch.cat([h_V_expand, h_EV], -1) + h_message = self.W13(self.act(self.W12(self.act(self.W11(h_EV))))) + h_E = self.norm3(h_E + self.dropout3(h_message)) + return h_V, h_E + + + +class DecLayer(nn.Module): + def __init__(self, num_hidden, num_in, dropout=0.1, num_heads=None, scale=30): + super(DecLayer, self).__init__() + self.num_hidden = num_hidden + self.num_in = num_in + self.scale = scale + self.dropout1 = nn.Dropout(dropout) + self.dropout2 = nn.Dropout(dropout) + self.norm1 = nn.LayerNorm(num_hidden) + self.norm2 = nn.LayerNorm(num_hidden) + + self.W1 = nn.Linear(num_hidden + num_in, num_hidden, bias=True) + self.W2 = nn.Linear(num_hidden, num_hidden, bias=True) + self.W3 = nn.Linear(num_hidden, num_hidden, bias=True) + self.act = torch.nn.GELU() + self.dense = PositionWiseFeedForward(num_hidden, num_hidden * 4) + + def forward(self, h_V, h_E, mask_V=None, mask_attend=None): + """ Parallel computation of full transformer layer """ + + # Concatenate h_V_i to h_E_ij + h_V_expand = h_V.unsqueeze(-2).expand(-1,-1,h_E.size(-2),-1) + h_EV = torch.cat([h_V_expand, h_E], -1) + + h_message = self.W3(self.act(self.W2(self.act(self.W1(h_EV))))) + if mask_attend is not None: + h_message = mask_attend.unsqueeze(-1) * h_message + dh = torch.sum(h_message, -2) / self.scale + + h_V = self.norm1(h_V + self.dropout1(dh)) + + # Position-wise feedforward + dh = self.dense(h_V) + h_V = self.norm2(h_V + self.dropout2(dh)) + + if mask_V is not None: + mask_V = mask_V.unsqueeze(-1) + h_V = mask_V * h_V + return h_V + + +class PositionWiseFeedForward(nn.Module): + def __init__(self, num_hidden, num_ff): + super(PositionWiseFeedForward, self).__init__() + self.W_in = nn.Linear(num_hidden, num_ff, bias=True) + self.W_out = nn.Linear(num_ff, num_hidden, bias=True) + self.act = torch.nn.GELU() + def forward(self, h_V): + h = self.act(self.W_in(h_V)) + h = self.W_out(h) + return h + +class PositionalEncodings(nn.Module): + def __init__(self, num_embeddings, max_relative_feature=32): + super(PositionalEncodings, self).__init__() + self.num_embeddings = num_embeddings + self.max_relative_feature = max_relative_feature + self.linear = nn.Linear(2*max_relative_feature+1+1, num_embeddings) + + def forward(self, offset, mask): + d = torch.clip(offset + self.max_relative_feature, 0, 2*self.max_relative_feature)*mask + (1-mask)*(2*self.max_relative_feature+1) + d_onehot = torch.nn.functional.one_hot(d, 2*self.max_relative_feature+1+1) + E = self.linear(d_onehot.float()) + return E + + +class ProteinFeatures(nn.Module): + def __init__(self, edge_features, node_features, num_positional_embeddings=16, + num_rbf=16, top_k=30, augment_eps=0., num_chain_embeddings=16): + """ Extract protein features """ + super(ProteinFeatures, self).__init__() + self.edge_features = edge_features + self.node_features = node_features + self.top_k = top_k + self.augment_eps = augment_eps + self.num_rbf = num_rbf + self.num_positional_embeddings = num_positional_embeddings + + self.embeddings = PositionalEncodings(num_positional_embeddings) + node_in, edge_in = 6, num_positional_embeddings + num_rbf*25 + self.edge_embedding = nn.Linear(edge_in, edge_features, bias=False) + self.norm_edges = nn.LayerNorm(edge_features) + + def _dist(self, X, mask, eps=1E-6): + mask_2D = torch.unsqueeze(mask,1) * torch.unsqueeze(mask,2) + dX = torch.unsqueeze(X,1) - torch.unsqueeze(X,2) + D = mask_2D * torch.sqrt(torch.sum(dX**2, 3) + eps) + D_max, _ = torch.max(D, -1, keepdim=True) + D_adjust = D + (1. - mask_2D) * D_max + sampled_top_k = self.top_k + D_neighbors, E_idx = torch.topk(D_adjust, np.minimum(self.top_k, X.shape[1]), dim=-1, largest=False) + return D_neighbors, E_idx + + def _rbf(self, D): + device = D.device + D_min, D_max, D_count = 2., 22., self.num_rbf + D_mu = torch.linspace(D_min, D_max, D_count, device=device) + D_mu = D_mu.view([1,1,1,-1]) + D_sigma = (D_max - D_min) / D_count + D_expand = torch.unsqueeze(D, -1) + RBF = torch.exp(-((D_expand - D_mu) / D_sigma)**2) + return RBF + + def _get_rbf(self, A, B, E_idx): + D_A_B = torch.sqrt(torch.sum((A[:,:,None,:] - B[:,None,:,:])**2,-1) + 1e-6) #[B, L, L] + D_A_B_neighbors = gather_edges(D_A_B[:,:,:,None], E_idx)[:,:,:,0] #[B,L,K] + RBF_A_B = self._rbf(D_A_B_neighbors) + return RBF_A_B + + def forward(self, X, mask, residue_idx, chain_labels): + if self.training and self.augment_eps > 0: + X = X + self.augment_eps * torch.randn_like(X) + + b = X[:,:,1,:] - X[:,:,0,:] + c = X[:,:,2,:] - X[:,:,1,:] + a = torch.cross(b, c, dim=-1) + Cb = -0.58273431*a + 0.56802827*b - 0.54067466*c + X[:,:,1,:] + Ca = X[:,:,1,:] + N = X[:,:,0,:] + C = X[:,:,2,:] + O = X[:,:,3,:] + + D_neighbors, E_idx = self._dist(Ca, mask) + + RBF_all = [] + RBF_all.append(self._rbf(D_neighbors)) #Ca-Ca + RBF_all.append(self._get_rbf(N, N, E_idx)) #N-N + RBF_all.append(self._get_rbf(C, C, E_idx)) #C-C + RBF_all.append(self._get_rbf(O, O, E_idx)) #O-O + RBF_all.append(self._get_rbf(Cb, Cb, E_idx)) #Cb-Cb + RBF_all.append(self._get_rbf(Ca, N, E_idx)) #Ca-N + RBF_all.append(self._get_rbf(Ca, C, E_idx)) #Ca-C + RBF_all.append(self._get_rbf(Ca, O, E_idx)) #Ca-O + RBF_all.append(self._get_rbf(Ca, Cb, E_idx)) #Ca-Cb + RBF_all.append(self._get_rbf(N, C, E_idx)) #N-C + RBF_all.append(self._get_rbf(N, O, E_idx)) #N-O + RBF_all.append(self._get_rbf(N, Cb, E_idx)) #N-Cb + RBF_all.append(self._get_rbf(Cb, C, E_idx)) #Cb-C + RBF_all.append(self._get_rbf(Cb, O, E_idx)) #Cb-O + RBF_all.append(self._get_rbf(O, C, E_idx)) #O-C + RBF_all.append(self._get_rbf(N, Ca, E_idx)) #N-Ca + RBF_all.append(self._get_rbf(C, Ca, E_idx)) #C-Ca + RBF_all.append(self._get_rbf(O, Ca, E_idx)) #O-Ca + RBF_all.append(self._get_rbf(Cb, Ca, E_idx)) #Cb-Ca + RBF_all.append(self._get_rbf(C, N, E_idx)) #C-N + RBF_all.append(self._get_rbf(O, N, E_idx)) #O-N + RBF_all.append(self._get_rbf(Cb, N, E_idx)) #Cb-N + RBF_all.append(self._get_rbf(C, Cb, E_idx)) #C-Cb + RBF_all.append(self._get_rbf(O, Cb, E_idx)) #O-Cb + RBF_all.append(self._get_rbf(C, O, E_idx)) #C-O + RBF_all = torch.cat(tuple(RBF_all), dim=-1) + + offset = residue_idx[:,:,None]-residue_idx[:,None,:] + offset = gather_edges(offset[:,:,:,None], E_idx)[:,:,:,0] #[B, L, K] + + d_chains = ((chain_labels[:, :, None] - chain_labels[:,None,:])==0).long() #find self vs non-self interaction + E_chains = gather_edges(d_chains[:,:,:,None], E_idx)[:,:,:,0] + E_positional = self.embeddings(offset.long(), E_chains) + E = torch.cat((E_positional, RBF_all), -1) + E = self.edge_embedding(E) + E = self.norm_edges(E) + return E, E_idx + + + +class ProteinMPNN(nn.Module): + def __init__(self, num_letters=21, node_features=128, edge_features=128, + hidden_dim=128, num_encoder_layers=3, num_decoder_layers=3, + vocab=21, k_neighbors=32, augment_eps=0.1, dropout=0.1): + super(ProteinMPNN, self).__init__() + + # Hyperparameters + self.node_features = node_features + self.edge_features = edge_features + self.hidden_dim = hidden_dim + + self.features = ProteinFeatures(node_features, edge_features, top_k=k_neighbors, augment_eps=augment_eps) + + self.W_e = nn.Linear(edge_features, hidden_dim, bias=True) + self.W_s = nn.Embedding(vocab, hidden_dim) + + # Encoder layers + self.encoder_layers = nn.ModuleList([ + EncLayer(hidden_dim, hidden_dim*2, dropout=dropout) + for _ in range(num_encoder_layers) + ]) + + # Decoder layers + self.decoder_layers = nn.ModuleList([ + DecLayer(hidden_dim, hidden_dim*3, dropout=dropout) + for _ in range(num_decoder_layers) + ]) + self.W_out = nn.Linear(hidden_dim, num_letters, bias=True) + + for p in self.parameters(): + if p.dim() > 1: + nn.init.xavier_uniform_(p) + + def forward(self, X, S, mask, chain_M, residue_idx, chain_encoding_all): + """ Graph-conditioned sequence model """ + device=X.device + # Prepare node and edge embeddings + E, E_idx = self.features(X, mask, residue_idx, chain_encoding_all) + h_V = torch.zeros((E.shape[0], E.shape[1], E.shape[-1]), device=E.device) + h_E = self.W_e(E) + + # Encoder is unmasked self-attention + mask_attend = gather_nodes(mask.unsqueeze(-1), E_idx).squeeze(-1) + mask_attend = mask.unsqueeze(-1) * mask_attend + for layer in self.encoder_layers: + h_V, h_E = torch.utils.checkpoint.checkpoint(layer, h_V, h_E, E_idx, mask, mask_attend) + + # Concatenate sequence embeddings for autoregressive decoder + h_S = self.W_s(S) + h_ES = cat_neighbors_nodes(h_S, h_E, E_idx) + + # Build encoder embeddings + h_EX_encoder = cat_neighbors_nodes(torch.zeros_like(h_S), h_E, E_idx) + h_EXV_encoder = cat_neighbors_nodes(h_V, h_EX_encoder, E_idx) + + + chain_M = chain_M*mask #update chain_M to include missing regions + decoding_order = torch.argsort((chain_M+0.0001)*(torch.abs(torch.randn(chain_M.shape, device=device)))) #[numbers will be smaller for places where chain_M = 0.0 and higher for places where chain_M = 1.0] + mask_size = E_idx.shape[1] + permutation_matrix_reverse = torch.nn.functional.one_hot(decoding_order, num_classes=mask_size).float() + order_mask_backward = torch.einsum('ij, biq, bjp->bqp',(1-torch.triu(torch.ones(mask_size,mask_size, device=device))), permutation_matrix_reverse, permutation_matrix_reverse) + mask_attend = torch.gather(order_mask_backward, 2, E_idx).unsqueeze(-1) + mask_1D = mask.view([mask.size(0), mask.size(1), 1, 1]) + mask_bw = mask_1D * mask_attend + mask_fw = mask_1D * (1. - mask_attend) + + h_EXV_encoder_fw = mask_fw * h_EXV_encoder + for layer in self.decoder_layers: + h_ESV = cat_neighbors_nodes(h_V, h_ES, E_idx) + h_ESV = mask_bw * h_ESV + h_EXV_encoder_fw + h_V = torch.utils.checkpoint.checkpoint(layer, h_V, h_ESV, mask) + + logits = self.W_out(h_V) + log_probs = F.log_softmax(logits, dim=-1) + return log_probs + + + +class NoamOpt: + "Optim wrapper that implements rate." + def __init__(self, model_size, factor, warmup, optimizer, step): + self.optimizer = optimizer + self._step = step + self.warmup = warmup + self.factor = factor + self.model_size = model_size + self._rate = 0 + + @property + def param_groups(self): + """Return param_groups.""" + return self.optimizer.param_groups + + def step(self): + "Update parameters and rate" + self._step += 1 + rate = self.rate() + for p in self.optimizer.param_groups: + p['lr'] = rate + self._rate = rate + self.optimizer.step() + + def rate(self, step = None): + "Implement `lrate` above" + if step is None: + step = self._step + return self.factor * \ + (self.model_size ** (-0.5) * + min(step ** (-0.5), step * self.warmup ** (-1.5))) + + def zero_grad(self): + self.optimizer.zero_grad() + +def get_std_opt(parameters, d_model, step): + return NoamOpt( + d_model, 2, 4000, torch.optim.Adam(parameters, lr=0, betas=(0.9, 0.98), eps=1e-9), step + ) diff --git a/model/proteinmpnn/protein_mpnn_utils.py b/model/proteinmpnn/protein_mpnn_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..a8fd00821a538d2d82ac4ba0a86d29252bed0293 --- /dev/null +++ b/model/proteinmpnn/protein_mpnn_utils.py @@ -0,0 +1,1383 @@ +from __future__ import print_function +import json, time, os, sys, glob +import shutil +import numpy as np +import torch +from torch import optim +from torch.utils.data import DataLoader +from torch.utils.data.dataset import random_split, Subset + +import copy +import torch.nn as nn +import torch.nn.functional as F +import random +import itertools + +#A number of functions/classes are adopted from: https://github.com/jingraham/neurips19-graph-protein-design + +def parse_fasta(filename,limit=-1, omit=[]): + header = [] + sequence = [] + lines = open(filename, "r") + for line in lines: + line = line.rstrip() + if line[0] == ">": + if len(header) == limit: + break + header.append(line[1:]) + sequence.append([]) + else: + if omit: + line = [item for item in line if item not in omit] + line = ''.join(line) + line = ''.join(line) + sequence[-1].append(line) + lines.close() + sequence = [''.join(seq) for seq in sequence] + return np.array(header), np.array(sequence) + +def _scores(S, log_probs, mask): + """ Negative log probabilities """ + criterion = torch.nn.NLLLoss(reduction='none') + loss = criterion( + log_probs.contiguous().view(-1,log_probs.size(-1)), + S.contiguous().view(-1) + ).view(S.size()) + scores = torch.sum(loss * mask, dim=-1) / torch.sum(mask, dim=-1) + return scores + +def _S_to_seq(S, mask): + alphabet = 'ACDEFGHIKLMNPQRSTVWYX' + seq = ''.join([alphabet[c] for c, m in zip(S.tolist(), mask.tolist()) if m > 0]) + return seq + +def parse_PDB_biounits(x, atoms=['N','CA','C'], chain=None): + ''' + input: x = PDB filename + atoms = atoms to extract (optional) + output: (length, atoms, coords=(x,y,z)), sequence + ''' + + alpha_1 = list("ARNDCQEGHILKMFPSTWYV-") + states = len(alpha_1) + alpha_3 = ['ALA','ARG','ASN','ASP','CYS','GLN','GLU','GLY','HIS','ILE', + 'LEU','LYS','MET','PHE','PRO','SER','THR','TRP','TYR','VAL','GAP'] + + aa_1_N = {a:n for n,a in enumerate(alpha_1)} + aa_3_N = {a:n for n,a in enumerate(alpha_3)} + aa_N_1 = {n:a for n,a in enumerate(alpha_1)} + aa_1_3 = {a:b for a,b in zip(alpha_1,alpha_3)} + aa_3_1 = {b:a for a,b in zip(alpha_1,alpha_3)} + + def AA_to_N(x): + # ["ARND"] -> [[0,1,2,3]] + x = np.array(x); + if x.ndim == 0: x = x[None] + return [[aa_1_N.get(a, states-1) for a in y] for y in x] + + def N_to_AA(x): + # [[0,1,2,3]] -> ["ARND"] + x = np.array(x); + if x.ndim == 1: x = x[None] + return ["".join([aa_N_1.get(a,"-") for a in y]) for y in x] + + xyz,seq,min_resn,max_resn = {},{},1e6,-1e6 + for line in open(x,"rb"): + line = line.decode("utf-8","ignore").rstrip() + + if line[:6] == "HETATM" and line[17:17+3] == "MSE": + line = line.replace("HETATM","ATOM ") + line = line.replace("MSE","MET") + + if line[:4] == "ATOM": + ch = line[21:22] + if ch == chain or chain is None: + atom = line[12:12+4].strip() + resi = line[17:17+3] + resn = line[22:22+5].strip() + x,y,z = [float(line[i:(i+8)]) for i in [30,38,46]] + + if resn[-1].isalpha(): + resa,resn = resn[-1],int(resn[:-1])-1 + else: + resa,resn = "",int(resn)-1 +# resn = int(resn) + if resn < min_resn: + min_resn = resn + if resn > max_resn: + max_resn = resn + if resn not in xyz: + xyz[resn] = {} + if resa not in xyz[resn]: + xyz[resn][resa] = {} + if resn not in seq: + seq[resn] = {} + if resa not in seq[resn]: + seq[resn][resa] = resi + + if atom not in xyz[resn][resa]: + xyz[resn][resa][atom] = np.array([x,y,z]) + + # convert to numpy arrays, fill in missing values + seq_,xyz_ = [],[] + try: + for resn in range(min_resn,max_resn+1): + if resn in seq: + for k in sorted(seq[resn]): seq_.append(aa_3_N.get(seq[resn][k],20)) + else: seq_.append(20) + if resn in xyz: + for k in sorted(xyz[resn]): + for atom in atoms: + if atom in xyz[resn][k]: xyz_.append(xyz[resn][k][atom]) + else: xyz_.append(np.full(3,np.nan)) + else: + for atom in atoms: xyz_.append(np.full(3,np.nan)) + return np.array(xyz_).reshape(-1,len(atoms),3), N_to_AA(np.array(seq_)) + except TypeError: + return 'no_chain', 'no_chain' + +def parse_PDB(path_to_pdb, input_chain_list=None, ca_only=False): + c=0 + pdb_dict_list = [] + init_alphabet = ['A', 'B', 'C', 'D', 'E', 'F', 'G','H', 'I', 'J','K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T','U', 'V','W','X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g','h', 'i', 'j','k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't','u', 'v','w','x', 'y', 'z'] + extra_alphabet = [str(item) for item in list(np.arange(300))] + chain_alphabet = init_alphabet + extra_alphabet + + if input_chain_list: + chain_alphabet = input_chain_list + + + biounit_names = [path_to_pdb] + for biounit in biounit_names: + my_dict = {} + s = 0 + concat_seq = '' + concat_N = [] + concat_CA = [] + concat_C = [] + concat_O = [] + concat_mask = [] + coords_dict = {} + for letter in chain_alphabet: + if ca_only: + sidechain_atoms = ['CA'] + else: + sidechain_atoms = ['N', 'CA', 'C', 'O'] + xyz, seq = parse_PDB_biounits(biounit, atoms=sidechain_atoms, chain=letter) + if type(xyz) != str: + concat_seq += seq[0] + my_dict['seq_chain_'+letter]=seq[0] + coords_dict_chain = {} + if ca_only: + coords_dict_chain['CA_chain_'+letter]=xyz.tolist() + else: + coords_dict_chain['N_chain_' + letter] = xyz[:, 0, :].tolist() + coords_dict_chain['CA_chain_' + letter] = xyz[:, 1, :].tolist() + coords_dict_chain['C_chain_' + letter] = xyz[:, 2, :].tolist() + coords_dict_chain['O_chain_' + letter] = xyz[:, 3, :].tolist() + my_dict['coords_chain_'+letter]=coords_dict_chain + s += 1 + fi = biounit.rfind("/") + my_dict['name']=biounit[(fi+1):-4] + my_dict['num_of_chains'] = s + my_dict['seq'] = concat_seq + if s <= len(chain_alphabet): + pdb_dict_list.append(my_dict) + c+=1 + return pdb_dict_list + + + +def tied_featurize(batch, device, chain_dict, fixed_position_dict=None, omit_AA_dict=None, tied_positions_dict=None, pssm_dict=None, bias_by_res_dict=None, ca_only=False): + """ Pack and pad batch into torch tensors """ + alphabet = 'ACDEFGHIKLMNPQRSTVWYX' + B = len(batch) + lengths = np.array([len(b['seq']) for b in batch], dtype=np.int32) #sum of chain seq lengths + L_max = max([len(b['seq']) for b in batch]) + if ca_only: + X = np.zeros([B, L_max, 1, 3]) + else: + X = np.zeros([B, L_max, 4, 3]) + residue_idx = -100*np.ones([B, L_max], dtype=np.int32) + chain_M = np.zeros([B, L_max], dtype=np.int32) #1.0 for the bits that need to be predicted + pssm_coef_all = np.zeros([B, L_max], dtype=np.float32) #1.0 for the bits that need to be predicted + pssm_bias_all = np.zeros([B, L_max, 21], dtype=np.float32) #1.0 for the bits that need to be predicted + pssm_log_odds_all = 10000.0*np.ones([B, L_max, 21], dtype=np.float32) #1.0 for the bits that need to be predicted + chain_M_pos = np.zeros([B, L_max], dtype=np.int32) #1.0 for the bits that need to be predicted + bias_by_res_all = np.zeros([B, L_max, 21], dtype=np.float32) + chain_encoding_all = np.zeros([B, L_max], dtype=np.int32) #1.0 for the bits that need to be predicted + S = np.zeros([B, L_max], dtype=np.int32) + omit_AA_mask = np.zeros([B, L_max, len(alphabet)], dtype=np.int32) + # Build the batch + letter_list_list = [] + visible_list_list = [] + masked_list_list = [] + masked_chain_length_list_list = [] + tied_pos_list_of_lists_list = [] + for i, b in enumerate(batch): + if chain_dict != None: + masked_chains, visible_chains = chain_dict[b['name']] #masked_chains a list of chain letters to predict [A, D, F] + else: + masked_chains = [item[-1:] for item in list(b) if item[:10]=='seq_chain_'] + visible_chains = [] + masked_chains.sort() #sort masked_chains + visible_chains.sort() #sort visible_chains + all_chains = masked_chains + visible_chains + for i, b in enumerate(batch): + mask_dict = {} + a = 0 + x_chain_list = [] + chain_mask_list = [] + chain_seq_list = [] + chain_encoding_list = [] + c = 1 + letter_list = [] + global_idx_start_list = [0] + visible_list = [] + masked_list = [] + masked_chain_length_list = [] + fixed_position_mask_list = [] + omit_AA_mask_list = [] + pssm_coef_list = [] + pssm_bias_list = [] + pssm_log_odds_list = [] + bias_by_res_list = [] + l0 = 0 + l1 = 0 + for step, letter in enumerate(all_chains): + if letter in visible_chains: + letter_list.append(letter) + visible_list.append(letter) + chain_seq = b[f'seq_chain_{letter}'] + chain_seq = ''.join([a if a!='-' else 'X' for a in chain_seq]) + chain_length = len(chain_seq) + global_idx_start_list.append(global_idx_start_list[-1]+chain_length) + chain_coords = b[f'coords_chain_{letter}'] #this is a dictionary + chain_mask = np.zeros(chain_length) #0.0 for visible chains + if ca_only: + x_chain = np.array(chain_coords[f'CA_chain_{letter}']) #[chain_lenght,1,3] #CA_diff + if len(x_chain.shape) == 2: + x_chain = x_chain[:,None,:] + else: + x_chain = np.stack([chain_coords[c] for c in [f'N_chain_{letter}', f'CA_chain_{letter}', f'C_chain_{letter}', f'O_chain_{letter}']], 1) #[chain_lenght,4,3] + x_chain_list.append(x_chain) + chain_mask_list.append(chain_mask) + chain_seq_list.append(chain_seq) + chain_encoding_list.append(c*np.ones(np.array(chain_mask).shape[0])) + l1 += chain_length + residue_idx[i, l0:l1] = 100*(c-1)+np.arange(l0, l1) + l0 += chain_length + c+=1 + fixed_position_mask = np.ones(chain_length) + fixed_position_mask_list.append(fixed_position_mask) + omit_AA_mask_temp = np.zeros([chain_length, len(alphabet)], np.int32) + omit_AA_mask_list.append(omit_AA_mask_temp) + pssm_coef = np.zeros(chain_length) + pssm_bias = np.zeros([chain_length, 21]) + pssm_log_odds = 10000.0*np.ones([chain_length, 21]) + pssm_coef_list.append(pssm_coef) + pssm_bias_list.append(pssm_bias) + pssm_log_odds_list.append(pssm_log_odds) + bias_by_res_list.append(np.zeros([chain_length, 21])) + if letter in masked_chains: + masked_list.append(letter) + letter_list.append(letter) + chain_seq = b[f'seq_chain_{letter}'] + chain_seq = ''.join([a if a!='-' else 'X' for a in chain_seq]) + chain_length = len(chain_seq) + global_idx_start_list.append(global_idx_start_list[-1]+chain_length) + masked_chain_length_list.append(chain_length) + chain_coords = b[f'coords_chain_{letter}'] #this is a dictionary + chain_mask = np.ones(chain_length) #1.0 for masked + if ca_only: + x_chain = np.array(chain_coords[f'CA_chain_{letter}']) #[chain_lenght,1,3] #CA_diff + if len(x_chain.shape) == 2: + x_chain = x_chain[:,None,:] + else: + x_chain = np.stack([chain_coords[c] for c in [f'N_chain_{letter}', f'CA_chain_{letter}', f'C_chain_{letter}', f'O_chain_{letter}']], 1) #[chain_lenght,4,3] + x_chain_list.append(x_chain) + chain_mask_list.append(chain_mask) + chain_seq_list.append(chain_seq) + chain_encoding_list.append(c*np.ones(np.array(chain_mask).shape[0])) + l1 += chain_length + residue_idx[i, l0:l1] = 100*(c-1)+np.arange(l0, l1) + l0 += chain_length + c+=1 + fixed_position_mask = np.ones(chain_length) + if fixed_position_dict!=None: + fixed_pos_list = fixed_position_dict[b['name']][letter] + if fixed_pos_list: + fixed_position_mask[np.array(fixed_pos_list)-1] = 0.0 + fixed_position_mask_list.append(fixed_position_mask) + omit_AA_mask_temp = np.zeros([chain_length, len(alphabet)], np.int32) + if omit_AA_dict!=None: + for item in omit_AA_dict[b['name']][letter]: + idx_AA = np.array(item[0])-1 + AA_idx = np.array([np.argwhere(np.array(list(alphabet))== AA)[0][0] for AA in item[1]]).repeat(idx_AA.shape[0]) + idx_ = np.array([[a, b] for a in idx_AA for b in AA_idx]) + omit_AA_mask_temp[idx_[:,0], idx_[:,1]] = 1 + omit_AA_mask_list.append(omit_AA_mask_temp) + pssm_coef = np.zeros(chain_length) + pssm_bias = np.zeros([chain_length, 21]) + pssm_log_odds = 10000.0*np.ones([chain_length, 21]) + if pssm_dict: + if pssm_dict[b['name']][letter]: + pssm_coef = pssm_dict[b['name']][letter]['pssm_coef'] + pssm_bias = pssm_dict[b['name']][letter]['pssm_bias'] + pssm_log_odds = pssm_dict[b['name']][letter]['pssm_log_odds'] + pssm_coef_list.append(pssm_coef) + pssm_bias_list.append(pssm_bias) + pssm_log_odds_list.append(pssm_log_odds) + if bias_by_res_dict: + bias_by_res_list.append(bias_by_res_dict[b['name']][letter]) + else: + bias_by_res_list.append(np.zeros([chain_length, 21])) + + + letter_list_np = np.array(letter_list) + tied_pos_list_of_lists = [] + tied_beta = np.ones(L_max) + if tied_positions_dict!=None: + tied_pos_list = tied_positions_dict[b['name']] + if tied_pos_list: + set_chains_tied = set(list(itertools.chain(*[list(item) for item in tied_pos_list]))) + for tied_item in tied_pos_list: + one_list = [] + for k, v in tied_item.items(): + start_idx = global_idx_start_list[np.argwhere(letter_list_np == k)[0][0]] + if isinstance(v[0], list): + for v_count in range(len(v[0])): + one_list.append(start_idx+v[0][v_count]-1)#make 0 to be the first + tied_beta[start_idx+v[0][v_count]-1] = v[1][v_count] + else: + for v_ in v: + one_list.append(start_idx+v_-1)#make 0 to be the first + tied_pos_list_of_lists.append(one_list) + tied_pos_list_of_lists_list.append(tied_pos_list_of_lists) + + + + x = np.concatenate(x_chain_list,0) #[L, 4, 3] + all_sequence = "".join(chain_seq_list) + m = np.concatenate(chain_mask_list,0) #[L,], 1.0 for places that need to be predicted + chain_encoding = np.concatenate(chain_encoding_list,0) + m_pos = np.concatenate(fixed_position_mask_list,0) #[L,], 1.0 for places that need to be predicted + + pssm_coef_ = np.concatenate(pssm_coef_list,0) #[L,], 1.0 for places that need to be predicted + pssm_bias_ = np.concatenate(pssm_bias_list,0) #[L,], 1.0 for places that need to be predicted + pssm_log_odds_ = np.concatenate(pssm_log_odds_list,0) #[L,], 1.0 for places that need to be predicted + + bias_by_res_ = np.concatenate(bias_by_res_list, 0) #[L,21], 0.0 for places where AA frequencies don't need to be tweaked + + l = len(all_sequence) + x_pad = np.pad(x, [[0,L_max-l], [0,0], [0,0]], 'constant', constant_values=(np.nan, )) + X[i,:,:,:] = x_pad + + m_pad = np.pad(m, [[0,L_max-l]], 'constant', constant_values=(0.0, )) + m_pos_pad = np.pad(m_pos, [[0,L_max-l]], 'constant', constant_values=(0.0, )) + omit_AA_mask_pad = np.pad(np.concatenate(omit_AA_mask_list,0), [[0,L_max-l]], 'constant', constant_values=(0.0, )) + chain_M[i,:] = m_pad + chain_M_pos[i,:] = m_pos_pad + omit_AA_mask[i,] = omit_AA_mask_pad + + chain_encoding_pad = np.pad(chain_encoding, [[0,L_max-l]], 'constant', constant_values=(0.0, )) + chain_encoding_all[i,:] = chain_encoding_pad + + pssm_coef_pad = np.pad(pssm_coef_, [[0,L_max-l]], 'constant', constant_values=(0.0, )) + pssm_bias_pad = np.pad(pssm_bias_, [[0,L_max-l], [0,0]], 'constant', constant_values=(0.0, )) + pssm_log_odds_pad = np.pad(pssm_log_odds_, [[0,L_max-l], [0,0]], 'constant', constant_values=(0.0, )) + + pssm_coef_all[i,:] = pssm_coef_pad + pssm_bias_all[i,:] = pssm_bias_pad + pssm_log_odds_all[i,:] = pssm_log_odds_pad + + bias_by_res_pad = np.pad(bias_by_res_, [[0,L_max-l], [0,0]], 'constant', constant_values=(0.0, )) + bias_by_res_all[i,:] = bias_by_res_pad + + # Convert to labels + indices = np.asarray([alphabet.index(a) for a in all_sequence], dtype=np.int32) + S[i, :l] = indices + letter_list_list.append(letter_list) + visible_list_list.append(visible_list) + masked_list_list.append(masked_list) + masked_chain_length_list_list.append(masked_chain_length_list) + + + isnan = np.isnan(X) + mask = np.isfinite(np.sum(X,(2,3))).astype(np.float32) + X[isnan] = 0. + + # Conversion + pssm_coef_all = torch.from_numpy(pssm_coef_all).to(dtype=torch.float32, device=device) + pssm_bias_all = torch.from_numpy(pssm_bias_all).to(dtype=torch.float32, device=device) + pssm_log_odds_all = torch.from_numpy(pssm_log_odds_all).to(dtype=torch.float32, device=device) + + tied_beta = torch.from_numpy(tied_beta).to(dtype=torch.float32, device=device) + + jumps = ((residue_idx[:,1:]-residue_idx[:,:-1])==1).astype(np.float32) + bias_by_res_all = torch.from_numpy(bias_by_res_all).to(dtype=torch.float32, device=device) + phi_mask = np.pad(jumps, [[0,0],[1,0]]) + psi_mask = np.pad(jumps, [[0,0],[0,1]]) + omega_mask = np.pad(jumps, [[0,0],[0,1]]) + dihedral_mask = np.concatenate([phi_mask[:,:,None], psi_mask[:,:,None], omega_mask[:,:,None]], -1) #[B,L,3] + dihedral_mask = torch.from_numpy(dihedral_mask).to(dtype=torch.float32, device=device) + residue_idx = torch.from_numpy(residue_idx).to(dtype=torch.long,device=device) + S = torch.from_numpy(S).to(dtype=torch.long,device=device) + X = torch.from_numpy(X).to(dtype=torch.float32, device=device) + mask = torch.from_numpy(mask).to(dtype=torch.float32, device=device) + chain_M = torch.from_numpy(chain_M).to(dtype=torch.float32, device=device) + chain_M_pos = torch.from_numpy(chain_M_pos).to(dtype=torch.float32, device=device) + omit_AA_mask = torch.from_numpy(omit_AA_mask).to(dtype=torch.float32, device=device) + chain_encoding_all = torch.from_numpy(chain_encoding_all).to(dtype=torch.long, device=device) + if ca_only: + X_out = X[:,:,0] + else: + X_out = X + return X_out, S, mask, lengths, chain_M, chain_encoding_all, letter_list_list, visible_list_list, masked_list_list, masked_chain_length_list_list, chain_M_pos, omit_AA_mask, residue_idx, dihedral_mask, tied_pos_list_of_lists_list, pssm_coef_all, pssm_bias_all, pssm_log_odds_all, bias_by_res_all, tied_beta + + + +def loss_nll(S, log_probs, mask): + """ Negative log probabilities """ + criterion = torch.nn.NLLLoss(reduction='none') + loss = criterion( + log_probs.contiguous().view(-1, log_probs.size(-1)), S.contiguous().view(-1) + ).view(S.size()) + loss_av = torch.sum(loss * mask) / torch.sum(mask) + return loss, loss_av + + +def loss_smoothed(S, log_probs, mask, weight=0.1): + """ Negative log probabilities """ + S_onehot = torch.nn.functional.one_hot(S, 21).float() + + # Label smoothing + S_onehot = S_onehot + weight / float(S_onehot.size(-1)) + S_onehot = S_onehot / S_onehot.sum(-1, keepdim=True) + + loss = -(S_onehot * log_probs).sum(-1) + loss_av = torch.sum(loss * mask) / torch.sum(mask) + return loss, loss_av + +class StructureDataset(): + def __init__(self, jsonl_file, verbose=True, truncate=None, max_length=100, + alphabet='ACDEFGHIKLMNPQRSTVWYX-'): + alphabet_set = set([a for a in alphabet]) + discard_count = { + 'bad_chars': 0, + 'too_long': 0, + 'bad_seq_length': 0 + } + + with open(jsonl_file) as f: + self.data = [] + + lines = f.readlines() + start = time.time() + for i, line in enumerate(lines): + entry = json.loads(line) + seq = entry['seq'] + name = entry['name'] + + # Convert raw coords to np arrays + #for key, val in entry['coords'].items(): + # entry['coords'][key] = np.asarray(val) + + # Check if in alphabet + bad_chars = set([s for s in seq]).difference(alphabet_set) + if len(bad_chars) == 0: + if len(entry['seq']) <= max_length: + if True: + self.data.append(entry) + else: + discard_count['bad_seq_length'] += 1 + else: + discard_count['too_long'] += 1 + else: + if verbose: + print(name, bad_chars, entry['seq']) + discard_count['bad_chars'] += 1 + + # Truncate early + if truncate is not None and len(self.data) == truncate: + return + + if verbose and (i + 1) % 1000 == 0: + elapsed = time.time() - start + print('{} entries ({} loaded) in {:.1f} s'.format(len(self.data), i+1, elapsed)) + if verbose: + print('discarded', discard_count) + def __len__(self): + return len(self.data) + + def __getitem__(self, idx): + return self.data[idx] + + +class StructureDatasetPDB(): + def __init__(self, pdb_dict_list, verbose=True, truncate=None, max_length=100, + alphabet='ACDEFGHIKLMNPQRSTVWYX-'): + alphabet_set = set([a for a in alphabet]) + discard_count = { + 'bad_chars': 0, + 'too_long': 0, + 'bad_seq_length': 0 + } + + self.data = [] + + start = time.time() + for i, entry in enumerate(pdb_dict_list): + seq = entry['seq'] + name = entry['name'] + + bad_chars = set([s for s in seq]).difference(alphabet_set) + if len(bad_chars) == 0: + if len(entry['seq']) <= max_length: + self.data.append(entry) + else: + discard_count['too_long'] += 1 + else: + discard_count['bad_chars'] += 1 + + # Truncate early + if truncate is not None and len(self.data) == truncate: + return + + if verbose and (i + 1) % 1000 == 0: + elapsed = time.time() - start + + #print('Discarded', discard_count) + def __len__(self): + return len(self.data) + + def __getitem__(self, idx): + return self.data[idx] + + + +class StructureLoader(): + def __init__(self, dataset, batch_size=100, shuffle=True, + collate_fn=lambda x:x, drop_last=False): + self.dataset = dataset + self.size = len(dataset) + self.lengths = [len(dataset[i]['seq']) for i in range(self.size)] + self.batch_size = batch_size + sorted_ix = np.argsort(self.lengths) + + # Cluster into batches of similar sizes + clusters, batch = [], [] + batch_max = 0 + for ix in sorted_ix: + size = self.lengths[ix] + if size * (len(batch) + 1) <= self.batch_size: + batch.append(ix) + batch_max = size + else: + clusters.append(batch) + batch, batch_max = [], 0 + if len(batch) > 0: + clusters.append(batch) + self.clusters = clusters + + def __len__(self): + return len(self.clusters) + + def __iter__(self): + np.random.shuffle(self.clusters) + for b_idx in self.clusters: + batch = [self.dataset[i] for i in b_idx] + yield batch + + + +# The following gather functions +def gather_edges(edges, neighbor_idx): + # Features [B,N,N,C] at Neighbor indices [B,N,K] => Neighbor features [B,N,K,C] + neighbors = neighbor_idx.unsqueeze(-1).expand(-1, -1, -1, edges.size(-1)) + edge_features = torch.gather(edges, 2, neighbors) + return edge_features + +def gather_nodes(nodes, neighbor_idx): + # Features [B,N,C] at Neighbor indices [B,N,K] => [B,N,K,C] + # Flatten and expand indices per batch [B,N,K] => [B,NK] => [B,NK,C] + neighbors_flat = neighbor_idx.view((neighbor_idx.shape[0], -1)) + neighbors_flat = neighbors_flat.unsqueeze(-1).expand(-1, -1, nodes.size(2)) + # Gather and re-pack + neighbor_features = torch.gather(nodes, 1, neighbors_flat) + neighbor_features = neighbor_features.view(list(neighbor_idx.shape)[:3] + [-1]) + return neighbor_features + +def gather_nodes_t(nodes, neighbor_idx): + # Features [B,N,C] at Neighbor index [B,K] => Neighbor features[B,K,C] + idx_flat = neighbor_idx.unsqueeze(-1).expand(-1, -1, nodes.size(2)) + neighbor_features = torch.gather(nodes, 1, idx_flat) + return neighbor_features + +def cat_neighbors_nodes(h_nodes, h_neighbors, E_idx): + h_nodes = gather_nodes(h_nodes, E_idx) + h_nn = torch.cat([h_neighbors, h_nodes], -1) + return h_nn + + +class EncLayer(nn.Module): + def __init__(self, num_hidden, num_in, dropout=0.1, num_heads=None, scale=30): + super(EncLayer, self).__init__() + self.num_hidden = num_hidden + self.num_in = num_in + self.scale = scale + self.dropout1 = nn.Dropout(dropout) + self.dropout2 = nn.Dropout(dropout) + self.dropout3 = nn.Dropout(dropout) + self.norm1 = nn.LayerNorm(num_hidden) + self.norm2 = nn.LayerNorm(num_hidden) + self.norm3 = nn.LayerNorm(num_hidden) + + self.W1 = nn.Linear(num_hidden + num_in, num_hidden, bias=True) + self.W2 = nn.Linear(num_hidden, num_hidden, bias=True) + self.W3 = nn.Linear(num_hidden, num_hidden, bias=True) + self.W11 = nn.Linear(num_hidden + num_in, num_hidden, bias=True) + self.W12 = nn.Linear(num_hidden, num_hidden, bias=True) + self.W13 = nn.Linear(num_hidden, num_hidden, bias=True) + self.act = torch.nn.GELU() + self.dense = PositionWiseFeedForward(num_hidden, num_hidden * 4) + + def forward(self, h_V, h_E, E_idx, mask_V=None, mask_attend=None): + """ Parallel computation of full transformer layer """ + + h_EV = cat_neighbors_nodes(h_V, h_E, E_idx) + h_V_expand = h_V.unsqueeze(-2).expand(-1,-1,h_EV.size(-2),-1) + h_EV = torch.cat([h_V_expand, h_EV], -1) + h_message = self.W3(self.act(self.W2(self.act(self.W1(h_EV))))) + if mask_attend is not None: + h_message = mask_attend.unsqueeze(-1) * h_message + dh = torch.sum(h_message, -2) / self.scale + h_V = self.norm1(h_V + self.dropout1(dh)) + + dh = self.dense(h_V) + h_V = self.norm2(h_V + self.dropout2(dh)) + if mask_V is not None: + mask_V = mask_V.unsqueeze(-1) + h_V = mask_V * h_V + + h_EV = cat_neighbors_nodes(h_V, h_E, E_idx) + h_V_expand = h_V.unsqueeze(-2).expand(-1,-1,h_EV.size(-2),-1) + h_EV = torch.cat([h_V_expand, h_EV], -1) + h_message = self.W13(self.act(self.W12(self.act(self.W11(h_EV))))) + h_E = self.norm3(h_E + self.dropout3(h_message)) + return h_V, h_E + + +class DecLayer(nn.Module): + def __init__(self, num_hidden, num_in, dropout=0.1, num_heads=None, scale=30): + super(DecLayer, self).__init__() + self.num_hidden = num_hidden + self.num_in = num_in + self.scale = scale + self.dropout1 = nn.Dropout(dropout) + self.dropout2 = nn.Dropout(dropout) + self.norm1 = nn.LayerNorm(num_hidden) + self.norm2 = nn.LayerNorm(num_hidden) + + self.W1 = nn.Linear(num_hidden + num_in, num_hidden, bias=True) + self.W2 = nn.Linear(num_hidden, num_hidden, bias=True) + self.W3 = nn.Linear(num_hidden, num_hidden, bias=True) + self.act = torch.nn.GELU() + self.dense = PositionWiseFeedForward(num_hidden, num_hidden * 4) + + def forward(self, h_V, h_E, mask_V=None, mask_attend=None): + """ Parallel computation of full transformer layer """ + + # Concatenate h_V_i to h_E_ij + h_V_expand = h_V.unsqueeze(-2).expand(-1,-1,h_E.size(-2),-1) + h_EV = torch.cat([h_V_expand, h_E], -1) + + h_message = self.W3(self.act(self.W2(self.act(self.W1(h_EV))))) + if mask_attend is not None: + h_message = mask_attend.unsqueeze(-1) * h_message + dh = torch.sum(h_message, -2) / self.scale + + h_V = self.norm1(h_V + self.dropout1(dh)) + + # Position-wise feedforward + dh = self.dense(h_V) + h_V = self.norm2(h_V + self.dropout2(dh)) + + if mask_V is not None: + mask_V = mask_V.unsqueeze(-1) + h_V = mask_V * h_V + return h_V + + + +class PositionWiseFeedForward(nn.Module): + def __init__(self, num_hidden, num_ff): + super(PositionWiseFeedForward, self).__init__() + self.W_in = nn.Linear(num_hidden, num_ff, bias=True) + self.W_out = nn.Linear(num_ff, num_hidden, bias=True) + self.act = torch.nn.GELU() + def forward(self, h_V): + h = self.act(self.W_in(h_V)) + h = self.W_out(h) + return h + +class PositionalEncodings(nn.Module): + def __init__(self, num_embeddings, max_relative_feature=32): + super(PositionalEncodings, self).__init__() + self.num_embeddings = num_embeddings + self.max_relative_feature = max_relative_feature + self.linear = nn.Linear(2*max_relative_feature+1+1, num_embeddings) + + def forward(self, offset, mask): + d = torch.clip(offset + self.max_relative_feature, 0, 2*self.max_relative_feature)*mask + (1-mask)*(2*self.max_relative_feature+1) + d_onehot = torch.nn.functional.one_hot(d, 2*self.max_relative_feature+1+1) + E = self.linear(d_onehot.float()) + return E + + + +class CA_ProteinFeatures(nn.Module): + def __init__(self, edge_features, node_features, num_positional_embeddings=16, + num_rbf=16, top_k=30, augment_eps=0., num_chain_embeddings=16): + """ Extract protein features """ + super(CA_ProteinFeatures, self).__init__() + self.edge_features = edge_features + self.node_features = node_features + self.top_k = top_k + self.augment_eps = augment_eps + self.num_rbf = num_rbf + self.num_positional_embeddings = num_positional_embeddings + + # Positional encoding + self.embeddings = PositionalEncodings(num_positional_embeddings) + # Normalization and embedding + node_in, edge_in = 3, num_positional_embeddings + num_rbf*9 + 7 + self.node_embedding = nn.Linear(node_in, node_features, bias=False) #NOT USED + self.edge_embedding = nn.Linear(edge_in, edge_features, bias=False) + self.norm_nodes = nn.LayerNorm(node_features) + self.norm_edges = nn.LayerNorm(edge_features) + + + def _quaternions(self, R): + """ Convert a batch of 3D rotations [R] to quaternions [Q] + R [...,3,3] + Q [...,4] + """ + # Simple Wikipedia version + # en.wikipedia.org/wiki/Rotation_matrix#Quaternion + # For other options see math.stackexchange.com/questions/2074316/calculating-rotation-axis-from-rotation-matrix + diag = torch.diagonal(R, dim1=-2, dim2=-1) + Rxx, Ryy, Rzz = diag.unbind(-1) + magnitudes = 0.5 * torch.sqrt(torch.abs(1 + torch.stack([ + Rxx - Ryy - Rzz, + - Rxx + Ryy - Rzz, + - Rxx - Ryy + Rzz + ], -1))) + _R = lambda i,j: R[:,:,:,i,j] + signs = torch.sign(torch.stack([ + _R(2,1) - _R(1,2), + _R(0,2) - _R(2,0), + _R(1,0) - _R(0,1) + ], -1)) + xyz = signs * magnitudes + # The relu enforces a non-negative trace + w = torch.sqrt(F.relu(1 + diag.sum(-1, keepdim=True))) / 2. + Q = torch.cat((xyz, w), -1) + Q = F.normalize(Q, dim=-1) + return Q + + def _orientations_coarse(self, X, E_idx, eps=1e-6): + dX = X[:,1:,:] - X[:,:-1,:] + dX_norm = torch.norm(dX,dim=-1) + dX_mask = (3.6 0: + Ca = Ca + self.augment_eps * torch.randn_like(Ca) + + D_neighbors, E_idx, mask_neighbors = self._dist(Ca, mask) + + Ca_0 = torch.zeros(Ca.shape, device=Ca.device) + Ca_2 = torch.zeros(Ca.shape, device=Ca.device) + Ca_0[:,1:,:] = Ca[:,:-1,:] + Ca_1 = Ca + Ca_2[:,:-1,:] = Ca[:,1:,:] + + V, O_features = self._orientations_coarse(Ca, E_idx) + + RBF_all = [] + RBF_all.append(self._rbf(D_neighbors)) #Ca_1-Ca_1 + RBF_all.append(self._get_rbf(Ca_0, Ca_0, E_idx)) + RBF_all.append(self._get_rbf(Ca_2, Ca_2, E_idx)) + + RBF_all.append(self._get_rbf(Ca_0, Ca_1, E_idx)) + RBF_all.append(self._get_rbf(Ca_0, Ca_2, E_idx)) + + RBF_all.append(self._get_rbf(Ca_1, Ca_0, E_idx)) + RBF_all.append(self._get_rbf(Ca_1, Ca_2, E_idx)) + + RBF_all.append(self._get_rbf(Ca_2, Ca_0, E_idx)) + RBF_all.append(self._get_rbf(Ca_2, Ca_1, E_idx)) + + + RBF_all = torch.cat(tuple(RBF_all), dim=-1) + + + offset = residue_idx[:,:,None]-residue_idx[:,None,:] + offset = gather_edges(offset[:,:,:,None], E_idx)[:,:,:,0] #[B, L, K] + + d_chains = ((chain_labels[:, :, None] - chain_labels[:,None,:])==0).long() + E_chains = gather_edges(d_chains[:,:,:,None], E_idx)[:,:,:,0] + E_positional = self.embeddings(offset.long(), E_chains) + E = torch.cat((E_positional, RBF_all, O_features), -1) + + + E = self.edge_embedding(E) + E = self.norm_edges(E) + + return E, E_idx + + + + +class ProteinFeatures(nn.Module): + def __init__(self, edge_features, node_features, num_positional_embeddings=16, + num_rbf=16, top_k=30, augment_eps=0., num_chain_embeddings=16): + """ Extract protein features """ + super(ProteinFeatures, self).__init__() + self.edge_features = edge_features + self.node_features = node_features + self.top_k = top_k + self.augment_eps = augment_eps + self.num_rbf = num_rbf + self.num_positional_embeddings = num_positional_embeddings + + self.embeddings = PositionalEncodings(num_positional_embeddings) + node_in, edge_in = 6, num_positional_embeddings + num_rbf*25 + self.edge_embedding = nn.Linear(edge_in, edge_features, bias=False) + self.norm_edges = nn.LayerNorm(edge_features) + + def _dist(self, X, mask, eps=1E-6): + mask_2D = torch.unsqueeze(mask,1) * torch.unsqueeze(mask,2) + dX = torch.unsqueeze(X,1) - torch.unsqueeze(X,2) + D = mask_2D * torch.sqrt(torch.sum(dX**2, 3) + eps) + D_max, _ = torch.max(D, -1, keepdim=True) + D_adjust = D + (1. - mask_2D) * D_max + sampled_top_k = self.top_k + D_neighbors, E_idx = torch.topk(D_adjust, np.minimum(self.top_k, X.shape[1]), dim=-1, largest=False) + return D_neighbors, E_idx + + def _rbf(self, D): + device = D.device + D_min, D_max, D_count = 2., 22., self.num_rbf + D_mu = torch.linspace(D_min, D_max, D_count, device=device) + D_mu = D_mu.view([1,1,1,-1]) + D_sigma = (D_max - D_min) / D_count + D_expand = torch.unsqueeze(D, -1) + RBF = torch.exp(-((D_expand - D_mu) / D_sigma)**2) + return RBF + + def _get_rbf(self, A, B, E_idx): + D_A_B = torch.sqrt(torch.sum((A[:,:,None,:] - B[:,None,:,:])**2,-1) + 1e-6) #[B, L, L] + D_A_B_neighbors = gather_edges(D_A_B[:,:,:,None], E_idx)[:,:,:,0] #[B,L,K] + RBF_A_B = self._rbf(D_A_B_neighbors) + return RBF_A_B + + def forward(self, X, mask, residue_idx, chain_labels): + if self.augment_eps > 0: + X = X + self.augment_eps * torch.randn_like(X) + + b = X[:,:,1,:] - X[:,:,0,:] + c = X[:,:,2,:] - X[:,:,1,:] + a = torch.cross(b, c, dim=-1) + Cb = -0.58273431*a + 0.56802827*b - 0.54067466*c + X[:,:,1,:] + Ca = X[:,:,1,:] + N = X[:,:,0,:] + C = X[:,:,2,:] + O = X[:,:,3,:] + + D_neighbors, E_idx = self._dist(Ca, mask) + + RBF_all = [] + RBF_all.append(self._rbf(D_neighbors)) #Ca-Ca + RBF_all.append(self._get_rbf(N, N, E_idx)) #N-N + RBF_all.append(self._get_rbf(C, C, E_idx)) #C-C + RBF_all.append(self._get_rbf(O, O, E_idx)) #O-O + RBF_all.append(self._get_rbf(Cb, Cb, E_idx)) #Cb-Cb + RBF_all.append(self._get_rbf(Ca, N, E_idx)) #Ca-N + RBF_all.append(self._get_rbf(Ca, C, E_idx)) #Ca-C + RBF_all.append(self._get_rbf(Ca, O, E_idx)) #Ca-O + RBF_all.append(self._get_rbf(Ca, Cb, E_idx)) #Ca-Cb + RBF_all.append(self._get_rbf(N, C, E_idx)) #N-C + RBF_all.append(self._get_rbf(N, O, E_idx)) #N-O + RBF_all.append(self._get_rbf(N, Cb, E_idx)) #N-Cb + RBF_all.append(self._get_rbf(Cb, C, E_idx)) #Cb-C + RBF_all.append(self._get_rbf(Cb, O, E_idx)) #Cb-O + RBF_all.append(self._get_rbf(O, C, E_idx)) #O-C + RBF_all.append(self._get_rbf(N, Ca, E_idx)) #N-Ca + RBF_all.append(self._get_rbf(C, Ca, E_idx)) #C-Ca + RBF_all.append(self._get_rbf(O, Ca, E_idx)) #O-Ca + RBF_all.append(self._get_rbf(Cb, Ca, E_idx)) #Cb-Ca + RBF_all.append(self._get_rbf(C, N, E_idx)) #C-N + RBF_all.append(self._get_rbf(O, N, E_idx)) #O-N + RBF_all.append(self._get_rbf(Cb, N, E_idx)) #Cb-N + RBF_all.append(self._get_rbf(C, Cb, E_idx)) #C-Cb + RBF_all.append(self._get_rbf(O, Cb, E_idx)) #O-Cb + RBF_all.append(self._get_rbf(C, O, E_idx)) #C-O + RBF_all = torch.cat(tuple(RBF_all), dim=-1) + + offset = residue_idx[:,:,None]-residue_idx[:,None,:] + offset = gather_edges(offset[:,:,:,None], E_idx)[:,:,:,0] #[B, L, K] + + d_chains = ((chain_labels[:, :, None] - chain_labels[:,None,:])==0).long() #find self vs non-self interaction + E_chains = gather_edges(d_chains[:,:,:,None], E_idx)[:,:,:,0] + E_positional = self.embeddings(offset.long(), E_chains) + E = torch.cat((E_positional, RBF_all), -1) + E = self.edge_embedding(E) + E = self.norm_edges(E) + return E, E_idx + + + +class ProteinMPNN(nn.Module): + def __init__(self, num_letters, node_features, edge_features, + hidden_dim, num_encoder_layers=3, num_decoder_layers=3, + vocab=21, k_neighbors=64, augment_eps=0.05, dropout=0.1, ca_only=False): + super(ProteinMPNN, self).__init__() + + # Hyperparameters + self.node_features = node_features + self.edge_features = edge_features + self.hidden_dim = hidden_dim + + # Featurization layers + if ca_only: + self.features = CA_ProteinFeatures(node_features, edge_features, top_k=k_neighbors, augment_eps=augment_eps) + self.W_v = nn.Linear(node_features, hidden_dim, bias=True) + else: + self.features = ProteinFeatures(node_features, edge_features, top_k=k_neighbors, augment_eps=augment_eps) + + self.W_e = nn.Linear(edge_features, hidden_dim, bias=True) + self.W_s = nn.Embedding(vocab, hidden_dim) + + # Encoder layers + self.encoder_layers = nn.ModuleList([ + EncLayer(hidden_dim, hidden_dim*2, dropout=dropout) + for _ in range(num_encoder_layers) + ]) + + # Decoder layers + self.decoder_layers = nn.ModuleList([ + DecLayer(hidden_dim, hidden_dim*3, dropout=dropout) + for _ in range(num_decoder_layers) + ]) + self.W_out = nn.Linear(hidden_dim, num_letters, bias=True) + + for p in self.parameters(): + if p.dim() > 1: + nn.init.xavier_uniform_(p) + + def forward(self, X, S, mask, chain_M, residue_idx, chain_encoding_all, randn, use_input_decoding_order=False, decoding_order=None): + """ Graph-conditioned sequence model """ + device=X.device + # Prepare node and edge embeddings + E, E_idx = self.features(X, mask, residue_idx, chain_encoding_all) + h_V = torch.zeros((E.shape[0], E.shape[1], E.shape[-1]), device=E.device) + h_E = self.W_e(E) + + # Encoder is unmasked self-attention + mask_attend = gather_nodes(mask.unsqueeze(-1), E_idx).squeeze(-1) + mask_attend = mask.unsqueeze(-1) * mask_attend + for layer in self.encoder_layers: + h_V, h_E = layer(h_V, h_E, E_idx, mask, mask_attend) + + # Concatenate sequence embeddings for autoregressive decoder + h_S = self.W_s(S) + h_ES = cat_neighbors_nodes(h_S, h_E, E_idx) + + # Build encoder embeddings + h_EX_encoder = cat_neighbors_nodes(torch.zeros_like(h_S), h_E, E_idx) + h_EXV_encoder = cat_neighbors_nodes(h_V, h_EX_encoder, E_idx) + + + chain_M = chain_M*mask #update chain_M to include missing regions + if not use_input_decoding_order: + decoding_order = torch.argsort((chain_M+0.0001)*(torch.abs(randn))) #[numbers will be smaller for places where chain_M = 0.0 and higher for places where chain_M = 1.0] + mask_size = E_idx.shape[1] + permutation_matrix_reverse = torch.nn.functional.one_hot(decoding_order, num_classes=mask_size).float() + order_mask_backward = torch.einsum('ij, biq, bjp->bqp',(1-torch.triu(torch.ones(mask_size,mask_size, device=device))), permutation_matrix_reverse, permutation_matrix_reverse) + mask_attend = torch.gather(order_mask_backward, 2, E_idx).unsqueeze(-1) + mask_1D = mask.view([mask.size(0), mask.size(1), 1, 1]) + mask_bw = mask_1D * mask_attend + mask_fw = mask_1D * (1. - mask_attend) + + h_EXV_encoder_fw = mask_fw * h_EXV_encoder + for layer in self.decoder_layers: + # Masked positions attend to encoder information, unmasked see. + h_ESV = cat_neighbors_nodes(h_V, h_ES, E_idx) + h_ESV = mask_bw * h_ESV + h_EXV_encoder_fw + h_V = layer(h_V, h_ESV, mask) + + logits = self.W_out(h_V) + log_probs = F.log_softmax(logits, dim=-1) + return log_probs + + + + def sample(self, X, randn, S_true, chain_mask, chain_encoding_all, residue_idx, mask=None, temperature=1.0, omit_AAs_np=None, bias_AAs_np=None, chain_M_pos=None, omit_AA_mask=None, pssm_coef=None, pssm_bias=None, pssm_multi=None, pssm_log_odds_flag=None, pssm_log_odds_mask=None, pssm_bias_flag=None, bias_by_res=None): + device = X.device + # Prepare node and edge embeddings + E, E_idx = self.features(X, mask, residue_idx, chain_encoding_all) + h_V = torch.zeros((E.shape[0], E.shape[1], E.shape[-1]), device=device) + h_E = self.W_e(E) + + # Encoder is unmasked self-attention + mask_attend = gather_nodes(mask.unsqueeze(-1), E_idx).squeeze(-1) + mask_attend = mask.unsqueeze(-1) * mask_attend + for layer in self.encoder_layers: + h_V, h_E = layer(h_V, h_E, E_idx, mask, mask_attend) + + # Decoder uses masked self-attention + chain_mask = chain_mask*chain_M_pos*mask #update chain_M to include missing regions + decoding_order = torch.argsort((chain_mask+0.0001)*(torch.abs(randn))) #[numbers will be smaller for places where chain_M = 0.0 and higher for places where chain_M = 1.0] + mask_size = E_idx.shape[1] + permutation_matrix_reverse = torch.nn.functional.one_hot(decoding_order, num_classes=mask_size).float() + order_mask_backward = torch.einsum('ij, biq, bjp->bqp',(1-torch.triu(torch.ones(mask_size,mask_size, device=device))), permutation_matrix_reverse, permutation_matrix_reverse) + mask_attend = torch.gather(order_mask_backward, 2, E_idx).unsqueeze(-1) + mask_1D = mask.view([mask.size(0), mask.size(1), 1, 1]) + mask_bw = mask_1D * mask_attend + mask_fw = mask_1D * (1. - mask_attend) + + N_batch, N_nodes = X.size(0), X.size(1) + log_probs = torch.zeros((N_batch, N_nodes, 21), device=device) + all_probs = torch.zeros((N_batch, N_nodes, 21), device=device, dtype=torch.float32) + h_S = torch.zeros_like(h_V, device=device) + S = torch.zeros((N_batch, N_nodes), dtype=torch.int64, device=device) + h_V_stack = [h_V] + [torch.zeros_like(h_V, device=device) for _ in range(len(self.decoder_layers))] + constant = torch.tensor(omit_AAs_np, device=device) + constant_bias = torch.tensor(bias_AAs_np, device=device) + #chain_mask_combined = chain_mask*chain_M_pos + omit_AA_mask_flag = omit_AA_mask != None + + + h_EX_encoder = cat_neighbors_nodes(torch.zeros_like(h_S), h_E, E_idx) + h_EXV_encoder = cat_neighbors_nodes(h_V, h_EX_encoder, E_idx) + h_EXV_encoder_fw = mask_fw * h_EXV_encoder + for t_ in range(N_nodes): + t = decoding_order[:,t_] #[B] + chain_mask_gathered = torch.gather(chain_mask, 1, t[:,None]) #[B] + mask_gathered = torch.gather(mask, 1, t[:,None]) #[B] + bias_by_res_gathered = torch.gather(bias_by_res, 1, t[:,None,None].repeat(1,1,21))[:,0,:] #[B, 21] + if (mask_gathered==0).all(): #for padded or missing regions only + S_t = torch.gather(S_true, 1, t[:,None]) + else: + # Hidden layers + E_idx_t = torch.gather(E_idx, 1, t[:,None,None].repeat(1,1,E_idx.shape[-1])) + h_E_t = torch.gather(h_E, 1, t[:,None,None,None].repeat(1,1,h_E.shape[-2], h_E.shape[-1])) + h_ES_t = cat_neighbors_nodes(h_S, h_E_t, E_idx_t) + h_EXV_encoder_t = torch.gather(h_EXV_encoder_fw, 1, t[:,None,None,None].repeat(1,1,h_EXV_encoder_fw.shape[-2], h_EXV_encoder_fw.shape[-1])) + mask_t = torch.gather(mask, 1, t[:,None]) + for l, layer in enumerate(self.decoder_layers): + # Updated relational features for future states + h_ESV_decoder_t = cat_neighbors_nodes(h_V_stack[l], h_ES_t, E_idx_t) + h_V_t = torch.gather(h_V_stack[l], 1, t[:,None,None].repeat(1,1,h_V_stack[l].shape[-1])) + h_ESV_t = torch.gather(mask_bw, 1, t[:,None,None,None].repeat(1,1,mask_bw.shape[-2], mask_bw.shape[-1])) * h_ESV_decoder_t + h_EXV_encoder_t + h_V_stack[l+1].scatter_(1, t[:,None,None].repeat(1,1,h_V.shape[-1]), layer(h_V_t, h_ESV_t, mask_V=mask_t)) + # Sampling step + h_V_t = torch.gather(h_V_stack[-1], 1, t[:,None,None].repeat(1,1,h_V_stack[-1].shape[-1]))[:,0] + logits = self.W_out(h_V_t) / temperature + probs = F.softmax(logits-constant[None,:]*1e8+constant_bias[None,:]/temperature+bias_by_res_gathered/temperature, dim=-1) + if pssm_bias_flag: + pssm_coef_gathered = torch.gather(pssm_coef, 1, t[:,None])[:,0] + pssm_bias_gathered = torch.gather(pssm_bias, 1, t[:,None,None].repeat(1,1,pssm_bias.shape[-1]))[:,0] + probs = (1-pssm_multi*pssm_coef_gathered[:,None])*probs + pssm_multi*pssm_coef_gathered[:,None]*pssm_bias_gathered + if pssm_log_odds_flag: + pssm_log_odds_mask_gathered = torch.gather(pssm_log_odds_mask, 1, t[:,None, None].repeat(1,1,pssm_log_odds_mask.shape[-1]))[:,0] #[B, 21] + probs_masked = probs*pssm_log_odds_mask_gathered + probs_masked += probs * 0.001 + probs = probs_masked/torch.sum(probs_masked, dim=-1, keepdim=True) #[B, 21] + if omit_AA_mask_flag: + omit_AA_mask_gathered = torch.gather(omit_AA_mask, 1, t[:,None, None].repeat(1,1,omit_AA_mask.shape[-1]))[:,0] #[B, 21] + probs_masked = probs*(1.0-omit_AA_mask_gathered) + probs = probs_masked/torch.sum(probs_masked, dim=-1, keepdim=True) #[B, 21] + S_t = torch.multinomial(probs, 1) + all_probs.scatter_(1, t[:,None,None].repeat(1,1,21), (chain_mask_gathered[:,:,None,]*probs[:,None,:]).float()) + S_true_gathered = torch.gather(S_true, 1, t[:,None]) + S_t = (S_t*chain_mask_gathered+S_true_gathered*(1.0-chain_mask_gathered)).long() + temp1 = self.W_s(S_t) + h_S.scatter_(1, t[:,None,None].repeat(1,1,temp1.shape[-1]), temp1) + S.scatter_(1, t[:,None], S_t) + output_dict = {"S": S, "probs": all_probs, "decoding_order": decoding_order} + return output_dict + + + def tied_sample(self, X, randn, S_true, chain_mask, chain_encoding_all, residue_idx, mask=None, temperature=1.0, omit_AAs_np=None, bias_AAs_np=None, chain_M_pos=None, omit_AA_mask=None, pssm_coef=None, pssm_bias=None, pssm_multi=None, pssm_log_odds_flag=None, pssm_log_odds_mask=None, pssm_bias_flag=None, tied_pos=None, tied_beta=None, bias_by_res=None): + device = X.device + # Prepare node and edge embeddings + E, E_idx = self.features(X, mask, residue_idx, chain_encoding_all) + h_V = torch.zeros((E.shape[0], E.shape[1], E.shape[-1]), device=device) + h_E = self.W_e(E) + # Encoder is unmasked self-attention + mask_attend = gather_nodes(mask.unsqueeze(-1), E_idx).squeeze(-1) + mask_attend = mask.unsqueeze(-1) * mask_attend + for layer in self.encoder_layers: + h_V, h_E = layer(h_V, h_E, E_idx, mask, mask_attend) + + # Decoder uses masked self-attention + chain_mask = chain_mask*chain_M_pos*mask #update chain_M to include missing regions + decoding_order = torch.argsort((chain_mask+0.0001)*(torch.abs(randn))) #[numbers will be smaller for places where chain_M = 0.0 and higher for places where chain_M = 1.0] + + new_decoding_order = [] + for t_dec in list(decoding_order[0,].cpu().data.numpy()): + if t_dec not in list(itertools.chain(*new_decoding_order)): + list_a = [item for item in tied_pos if t_dec in item] + if list_a: + new_decoding_order.append(list_a[0]) + else: + new_decoding_order.append([t_dec]) + decoding_order = torch.tensor(list(itertools.chain(*new_decoding_order)), device=device)[None,].repeat(X.shape[0],1) + + mask_size = E_idx.shape[1] + permutation_matrix_reverse = torch.nn.functional.one_hot(decoding_order, num_classes=mask_size).float() + order_mask_backward = torch.einsum('ij, biq, bjp->bqp',(1-torch.triu(torch.ones(mask_size,mask_size, device=device))), permutation_matrix_reverse, permutation_matrix_reverse) + mask_attend = torch.gather(order_mask_backward, 2, E_idx).unsqueeze(-1) + mask_1D = mask.view([mask.size(0), mask.size(1), 1, 1]) + mask_bw = mask_1D * mask_attend + mask_fw = mask_1D * (1. - mask_attend) + + N_batch, N_nodes = X.size(0), X.size(1) + log_probs = torch.zeros((N_batch, N_nodes, 21), device=device) + all_probs = torch.zeros((N_batch, N_nodes, 21), device=device, dtype=torch.float32) + h_S = torch.zeros_like(h_V, device=device) + S = torch.zeros((N_batch, N_nodes), dtype=torch.int64, device=device) + h_V_stack = [h_V] + [torch.zeros_like(h_V, device=device) for _ in range(len(self.decoder_layers))] + constant = torch.tensor(omit_AAs_np, device=device) + constant_bias = torch.tensor(bias_AAs_np, device=device) + omit_AA_mask_flag = omit_AA_mask != None + + h_EX_encoder = cat_neighbors_nodes(torch.zeros_like(h_S), h_E, E_idx) + h_EXV_encoder = cat_neighbors_nodes(h_V, h_EX_encoder, E_idx) + h_EXV_encoder_fw = mask_fw * h_EXV_encoder + for t_list in new_decoding_order: + logits = 0.0 + logit_list = [] + done_flag = False + for t in t_list: + if (mask[:,t]==0).all(): + S_t = S_true[:,t] + for t in t_list: + h_S[:,t,:] = self.W_s(S_t) + S[:,t] = S_t + done_flag = True + break + else: + E_idx_t = E_idx[:,t:t+1,:] + h_E_t = h_E[:,t:t+1,:,:] + h_ES_t = cat_neighbors_nodes(h_S, h_E_t, E_idx_t) + h_EXV_encoder_t = h_EXV_encoder_fw[:,t:t+1,:,:] + mask_t = mask[:,t:t+1] + for l, layer in enumerate(self.decoder_layers): + h_ESV_decoder_t = cat_neighbors_nodes(h_V_stack[l], h_ES_t, E_idx_t) + h_V_t = h_V_stack[l][:,t:t+1,:] + h_ESV_t = mask_bw[:,t:t+1,:,:] * h_ESV_decoder_t + h_EXV_encoder_t + h_V_stack[l+1][:,t,:] = layer(h_V_t, h_ESV_t, mask_V=mask_t).squeeze(1) + h_V_t = h_V_stack[-1][:,t,:] + logit_list.append((self.W_out(h_V_t) / temperature)/len(t_list)) + logits += tied_beta[t]*(self.W_out(h_V_t) / temperature)/len(t_list) + if done_flag: + pass + else: + bias_by_res_gathered = bias_by_res[:,t,:] #[B, 21] + probs = F.softmax(logits-constant[None,:]*1e8+constant_bias[None,:]/temperature+bias_by_res_gathered/temperature, dim=-1) + if pssm_bias_flag: + pssm_coef_gathered = pssm_coef[:,t] + pssm_bias_gathered = pssm_bias[:,t] + probs = (1-pssm_multi*pssm_coef_gathered[:,None])*probs + pssm_multi*pssm_coef_gathered[:,None]*pssm_bias_gathered + if pssm_log_odds_flag: + pssm_log_odds_mask_gathered = pssm_log_odds_mask[:,t] + probs_masked = probs*pssm_log_odds_mask_gathered + probs_masked += probs * 0.001 + probs = probs_masked/torch.sum(probs_masked, dim=-1, keepdim=True) #[B, 21] + if omit_AA_mask_flag: + omit_AA_mask_gathered = omit_AA_mask[:,t] + probs_masked = probs*(1.0-omit_AA_mask_gathered) + probs = probs_masked/torch.sum(probs_masked, dim=-1, keepdim=True) #[B, 21] + S_t_repeat = torch.multinomial(probs, 1).squeeze(-1) + S_t_repeat = (chain_mask[:,t]*S_t_repeat + (1-chain_mask[:,t])*S_true[:,t]).long() #hard pick fixed positions + for t in t_list: + h_S[:,t,:] = self.W_s(S_t_repeat) + S[:,t] = S_t_repeat + all_probs[:,t,:] = probs.float() + output_dict = {"S": S, "probs": all_probs, "decoding_order": decoding_order} + return output_dict + + + def conditional_probs(self, X, S, mask, chain_M, residue_idx, chain_encoding_all, randn, backbone_only=False): + """ Graph-conditioned sequence model """ + device=X.device + # Prepare node and edge embeddings + E, E_idx = self.features(X, mask, residue_idx, chain_encoding_all) + h_V_enc = torch.zeros((E.shape[0], E.shape[1], E.shape[-1]), device=E.device) + h_E = self.W_e(E) + + # Encoder is unmasked self-attention + mask_attend = gather_nodes(mask.unsqueeze(-1), E_idx).squeeze(-1) + mask_attend = mask.unsqueeze(-1) * mask_attend + for layer in self.encoder_layers: + h_V_enc, h_E = layer(h_V_enc, h_E, E_idx, mask, mask_attend) + + # Concatenate sequence embeddings for autoregressive decoder + h_S = self.W_s(S) + h_ES = cat_neighbors_nodes(h_S, h_E, E_idx) + + # Build encoder embeddings + h_EX_encoder = cat_neighbors_nodes(torch.zeros_like(h_S), h_E, E_idx) + h_EXV_encoder = cat_neighbors_nodes(h_V_enc, h_EX_encoder, E_idx) + + + chain_M = chain_M*mask #update chain_M to include missing regions + + chain_M_np = chain_M.cpu().numpy() + idx_to_loop = np.argwhere(chain_M_np[0,:]==1)[:,0] + log_conditional_probs = torch.zeros([X.shape[0], chain_M.shape[1], 21], device=device).float() + + for idx in idx_to_loop: + h_V = torch.clone(h_V_enc) + order_mask = torch.zeros(chain_M.shape[1], device=device).float() + if backbone_only: + order_mask = torch.ones(chain_M.shape[1], device=device).float() + order_mask[idx] = 0. + else: + order_mask = torch.zeros(chain_M.shape[1], device=device).float() + order_mask[idx] = 1. + decoding_order = torch.argsort((order_mask[None,]+0.0001)*(torch.abs(randn))) #[numbers will be smaller for places where chain_M = 0.0 and higher for places where chain_M = 1.0] + mask_size = E_idx.shape[1] + permutation_matrix_reverse = torch.nn.functional.one_hot(decoding_order, num_classes=mask_size).float() + order_mask_backward = torch.einsum('ij, biq, bjp->bqp',(1-torch.triu(torch.ones(mask_size,mask_size, device=device))), permutation_matrix_reverse, permutation_matrix_reverse) + mask_attend = torch.gather(order_mask_backward, 2, E_idx).unsqueeze(-1) + mask_1D = mask.view([mask.size(0), mask.size(1), 1, 1]) + mask_bw = mask_1D * mask_attend + mask_fw = mask_1D * (1. - mask_attend) + + h_EXV_encoder_fw = mask_fw * h_EXV_encoder + for layer in self.decoder_layers: + # Masked positions attend to encoder information, unmasked see. + h_ESV = cat_neighbors_nodes(h_V, h_ES, E_idx) + h_ESV = mask_bw * h_ESV + h_EXV_encoder_fw + h_V = layer(h_V, h_ESV, mask) + + logits = self.W_out(h_V) + log_probs = F.log_softmax(logits, dim=-1) + log_conditional_probs[:,idx,:] = log_probs[:,idx,:] + return log_conditional_probs + + + def unconditional_probs(self, X, mask, residue_idx, chain_encoding_all): + """ Graph-conditioned sequence model """ + device=X.device + # Prepare node and edge embeddings + E, E_idx = self.features(X, mask, residue_idx, chain_encoding_all) + h_V = torch.zeros((E.shape[0], E.shape[1], E.shape[-1]), device=E.device) + h_E = self.W_e(E) + + # Encoder is unmasked self-attention + mask_attend = gather_nodes(mask.unsqueeze(-1), E_idx).squeeze(-1) + mask_attend = mask.unsqueeze(-1) * mask_attend + for layer in self.encoder_layers: + h_V, h_E = layer(h_V, h_E, E_idx, mask, mask_attend) + + # Build encoder embeddings + h_EX_encoder = cat_neighbors_nodes(torch.zeros_like(h_V), h_E, E_idx) + h_EXV_encoder = cat_neighbors_nodes(h_V, h_EX_encoder, E_idx) + + order_mask_backward = torch.zeros([X.shape[0], X.shape[1], X.shape[1]], device=device) + mask_attend = torch.gather(order_mask_backward, 2, E_idx).unsqueeze(-1) + mask_1D = mask.view([mask.size(0), mask.size(1), 1, 1]) + mask_bw = mask_1D * mask_attend + mask_fw = mask_1D * (1. - mask_attend) + + h_EXV_encoder_fw = mask_fw * h_EXV_encoder + for layer in self.decoder_layers: + h_V = layer(h_V, h_EXV_encoder_fw, mask) + + logits = self.W_out(h_V) + log_probs = F.log_softmax(logits, dim=-1) + return log_probs + diff --git a/model/proteinmpnn/utils.py b/model/proteinmpnn/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..6700b1499e183a1fbadd4a65023d589a5d26d193 --- /dev/null +++ b/model/proteinmpnn/utils.py @@ -0,0 +1,356 @@ +import torch +from torch.utils.data import DataLoader +import csv +from dateutil import parser +import numpy as np +import time +import random +import os + +class StructureDataset(): + def __init__(self, pdb_dict_list, verbose=True, truncate=None, max_length=100, + alphabet='ACDEFGHIKLMNPQRSTVWYX'): + alphabet_set = set([a for a in alphabet]) + discard_count = { + 'bad_chars': 0, + 'too_long': 0, + 'bad_seq_length': 0 + } + + self.data = [] + + start = time.time() + for i, entry in enumerate(pdb_dict_list): + seq = entry['seq'] + name = entry['name'] + + bad_chars = set([s for s in seq]).difference(alphabet_set) + if len(bad_chars) == 0: + if len(entry['seq']) <= max_length: + self.data.append(entry) + else: + discard_count['too_long'] += 1 + else: + #print(name, bad_chars, entry['seq']) + discard_count['bad_chars'] += 1 + + # Truncate early + if truncate is not None and len(self.data) == truncate: + return + + if verbose and (i + 1) % 1000 == 0: + elapsed = time.time() - start + #print('{} entries ({} loaded) in {:.1f} s'.format(len(self.data), i+1, elapsed)) + + #print('Discarded', discard_count) + def __len__(self): + return len(self.data) + + def __getitem__(self, idx): + return self.data[idx] + + +class StructureLoader(): + def __init__(self, dataset, batch_size=100, shuffle=True, + collate_fn=lambda x:x, drop_last=False): + self.dataset = dataset + self.size = len(dataset) + self.lengths = [len(dataset[i]['seq']) for i in range(self.size)] + self.batch_size = batch_size + sorted_ix = np.argsort(self.lengths) + + # Cluster into batches of similar sizes + clusters, batch = [], [] + batch_max = 0 + for ix in sorted_ix: + size = self.lengths[ix] + if size * (len(batch) + 1) <= self.batch_size: + batch.append(ix) + batch_max = size + else: + clusters.append(batch) + batch, batch_max = [], 0 + if len(batch) > 0: + clusters.append(batch) + self.clusters = clusters + + def __len__(self): + return len(self.clusters) + + def __iter__(self): + np.random.shuffle(self.clusters) + for b_idx in self.clusters: + batch = [self.dataset[i] for i in b_idx] + yield batch + + +def worker_init_fn(worker_id): + np.random.seed() + +class NoamOpt: + "Optim wrapper that implements rate." + def __init__(self, model_size, factor, warmup, optimizer, step): + self.optimizer = optimizer + self._step = step + self.warmup = warmup + self.factor = factor + self.model_size = model_size + self._rate = 0 + + @property + def param_groups(self): + """Return param_groups.""" + return self.optimizer.param_groups + + def step(self): + "Update parameters and rate" + self._step += 1 + rate = self.rate() + for p in self.optimizer.param_groups: + p['lr'] = rate + self._rate = rate + self.optimizer.step() + + def rate(self, step = None): + "Implement `lrate` above" + if step is None: + step = self._step + return self.factor * \ + (self.model_size ** (-0.5) * + min(step ** (-0.5), step * self.warmup ** (-1.5))) + + def zero_grad(self): + self.optimizer.zero_grad() + +def get_std_opt(parameters, d_model, step): + return NoamOpt( + d_model, 2, 4000, torch.optim.Adam(parameters, lr=0, betas=(0.9, 0.98), eps=1e-9), step + ) + + + + +def get_pdbs(data_loader, repeat=1, max_length=10000, num_units=1000000): + init_alphabet = ['A', 'B', 'C', 'D', 'E', 'F', 'G','H', 'I', 'J','K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T','U', 'V','W','X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g','h', 'i', 'j','k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't','u', 'v','w','x', 'y', 'z'] + extra_alphabet = [str(item) for item in list(np.arange(300))] + chain_alphabet = init_alphabet + extra_alphabet + c = 0 + c1 = 0 + pdb_dict_list = [] + t0 = time.time() + for _ in range(repeat): + for step,t in enumerate(data_loader): + t = {k:v[0] for k,v in t.items()} + c1 += 1 + if 'label' in list(t): + my_dict = {} + s = 0 + concat_seq = '' + concat_N = [] + concat_CA = [] + concat_C = [] + concat_O = [] + concat_mask = [] + coords_dict = {} + mask_list = [] + visible_list = [] + if len(list(np.unique(t['idx']))) < 352: + for idx in list(np.unique(t['idx'])): + letter = chain_alphabet[idx] + res = np.argwhere(t['idx']==idx) + initial_sequence= "".join(list(np.array(list(t['seq']))[res][0,])) + if initial_sequence[-6:] == "HHHHHH": + res = res[:,:-6] + if initial_sequence[0:6] == "HHHHHH": + res = res[:,6:] + if initial_sequence[-7:-1] == "HHHHHH": + res = res[:,:-7] + if initial_sequence[-8:-2] == "HHHHHH": + res = res[:,:-8] + if initial_sequence[-9:-3] == "HHHHHH": + res = res[:,:-9] + if initial_sequence[-10:-4] == "HHHHHH": + res = res[:,:-10] + if initial_sequence[1:7] == "HHHHHH": + res = res[:,7:] + if initial_sequence[2:8] == "HHHHHH": + res = res[:,8:] + if initial_sequence[3:9] == "HHHHHH": + res = res[:,9:] + if initial_sequence[4:10] == "HHHHHH": + res = res[:,10:] + if res.shape[1] < 4: + pass + else: + my_dict['seq_chain_'+letter]= "".join(list(np.array(list(t['seq']))[res][0,])) + concat_seq += my_dict['seq_chain_'+letter] + if idx in t['masked']: + mask_list.append(letter) + else: + visible_list.append(letter) + coords_dict_chain = {} + all_atoms = np.array(t['xyz'][res,])[0,] #[L, 14, 3] + coords_dict_chain['N_chain_'+letter]=all_atoms[:,0,:].tolist() + coords_dict_chain['CA_chain_'+letter]=all_atoms[:,1,:].tolist() + coords_dict_chain['C_chain_'+letter]=all_atoms[:,2,:].tolist() + coords_dict_chain['O_chain_'+letter]=all_atoms[:,3,:].tolist() + my_dict['coords_chain_'+letter]=coords_dict_chain + my_dict['name']= t['label'] + my_dict['masked_list']= mask_list + my_dict['visible_list']= visible_list + my_dict['num_of_chains'] = len(mask_list) + len(visible_list) + my_dict['seq'] = concat_seq + if len(concat_seq) <= max_length: + pdb_dict_list.append(my_dict) + if len(pdb_dict_list) >= num_units: + break + return pdb_dict_list + + + +class PDB_dataset(torch.utils.data.Dataset): + def __init__(self, IDs, loader, train_dict, params): + self.IDs = IDs + self.train_dict = train_dict + self.loader = loader + self.params = params + + def __len__(self): + return len(self.IDs) + + def __getitem__(self, index): + ID = self.IDs[index] + sel_idx = np.random.randint(0, len(self.train_dict[ID])) + out = self.loader(self.train_dict[ID][sel_idx], self.params) + return out + + + +def loader_pdb(item,params): + + pdbid,chid = item[0].split('_') + PREFIX = "%s/pdb/%s/%s"%(params['DIR'],pdbid[1:3],pdbid) + + # load metadata + if not os.path.isfile(PREFIX+".pt"): + return {'seq': np.zeros(5)} + meta = torch.load(PREFIX+".pt") + asmb_ids = meta['asmb_ids'] + asmb_chains = meta['asmb_chains'] + chids = np.array(meta['chains']) + + # find candidate assemblies which contain chid chain + asmb_candidates = set([a for a,b in zip(asmb_ids,asmb_chains) + if chid in b.split(',')]) + + # if the chains is missing is missing from all the assemblies + # then return this chain alone + if len(asmb_candidates)<1: + chain = torch.load("%s_%s.pt"%(PREFIX,chid)) + L = len(chain['seq']) + return {'seq' : chain['seq'], + 'xyz' : chain['xyz'], + 'idx' : torch.zeros(L).int(), + 'masked' : torch.Tensor([0]).int(), + 'label' : item[0]} + + # randomly pick one assembly from candidates + asmb_i = random.sample(list(asmb_candidates), 1) + + # indices of selected transforms + idx = np.where(np.array(asmb_ids)==asmb_i)[0] + + # load relevant chains + chains = {c:torch.load("%s_%s.pt"%(PREFIX,c)) + for i in idx for c in asmb_chains[i] + if c in meta['chains']} + + # generate assembly + asmb = {} + for k in idx: + + # pick k-th xform + xform = meta['asmb_xform%d'%k] + u = xform[:,:3,:3] + r = xform[:,:3,3] + + # select chains which k-th xform should be applied to + s1 = set(meta['chains']) + s2 = set(asmb_chains[k].split(',')) + chains_k = s1&s2 + + # transform selected chains + for c in chains_k: + try: + xyz = chains[c]['xyz'] + xyz_ru = torch.einsum('bij,raj->brai', u, xyz) + r[:,None,None,:] + asmb.update({(c,k,i):xyz_i for i,xyz_i in enumerate(xyz_ru)}) + except KeyError: + return {'seq': np.zeros(5)} + + # select chains which share considerable similarity to chid + seqid = meta['tm'][chids==chid][0,:,1] + homo = set([ch_j for seqid_j,ch_j in zip(seqid,chids) + if seqid_j>params['HOMO']]) + # stack all chains in the assembly together + seq,xyz,idx,masked = "",[],[],[] + seq_list = [] + for counter,(k,v) in enumerate(asmb.items()): + seq += chains[k[0]]['seq'] + seq_list.append(chains[k[0]]['seq']) + xyz.append(v) + idx.append(torch.full((v.shape[0],),counter)) + if k[0] in homo: + masked.append(counter) + + return {'seq' : seq, + 'xyz' : torch.cat(xyz,dim=0), + 'idx' : torch.cat(idx,dim=0), + 'masked' : torch.Tensor(masked).int(), + 'label' : item[0]} + + + + +def build_training_clusters(params, debug): + val_ids = set([int(l) for l in open(params['VAL']).readlines()]) + test_ids = set([int(l) for l in open(params['TEST']).readlines()]) + + if debug: + val_ids = [] + test_ids = [] + + # read & clean list.csv + with open(params['LIST'], 'r') as f: + reader = csv.reader(f) + next(reader) + rows = [[r[0],r[3],int(r[4])] for r in reader + if float(r[2])<=params['RESCUT'] and + parser.parse(r[1])<=parser.parse(params['DATCUT'])] + + # compile training and validation sets + train = {} + valid = {} + test = {} + + if debug: + rows = rows[:20] + for r in rows: + if r[2] in val_ids: + if r[2] in valid.keys(): + valid[r[2]].append(r[:2]) + else: + valid[r[2]] = [r[:2]] + elif r[2] in test_ids: + if r[2] in test.keys(): + test[r[2]].append(r[:2]) + else: + test[r[2]] = [r[:2]] + else: + if r[2] in train.keys(): + train[r[2]].append(r[:2]) + else: + train[r[2]] = [r[:2]] + if debug: + valid=train + return train, valid, test diff --git a/scripts/helper_scripts/assign_fixed_chains.py b/scripts/helper_scripts/assign_fixed_chains.py new file mode 100644 index 0000000000000000000000000000000000000000..da36552029030ad3816d80c71ccd8e934dd1f8a2 --- /dev/null +++ b/scripts/helper_scripts/assign_fixed_chains.py @@ -0,0 +1,58 @@ +import argparse +import os +import sys + +_PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__)) +while _PROJECT_ROOT and not os.path.isdir(os.path.join(_PROJECT_ROOT, "model")): + _PARENT = os.path.dirname(_PROJECT_ROOT) + if _PARENT == _PROJECT_ROOT: + break + _PROJECT_ROOT = _PARENT +_MODEL_ROOT = os.path.join(_PROJECT_ROOT, "model") +_ONESCIENCE_ROOT = os.environ.get("ONESCIENCE_ROOT") +for _path in (_MODEL_ROOT, _PROJECT_ROOT): + if os.path.exists(_path) and _path not in sys.path: + sys.path.insert(0, _path) +if _ONESCIENCE_ROOT: + _ONESCIENCE_SRC = os.path.join(_ONESCIENCE_ROOT, "src") + for _path in (_ONESCIENCE_SRC, _ONESCIENCE_ROOT): + if os.path.exists(_path) and _path not in sys.path: + sys.path.insert(0, _path) + +def main(args): + import json + + with open(args.input_path, 'r') as json_file: + json_list = list(json_file) + + global_designed_chain_list = [] + if args.chain_list != '': + global_designed_chain_list = [str(item) for item in args.chain_list.split()] + my_dict = {} + for json_str in json_list: + result = json.loads(json_str) + all_chain_list = [item[-1:] for item in list(result) if item[:9]=='seq_chain'] #['A','B', 'C',...] + if len(global_designed_chain_list) > 0: + designed_chain_list = global_designed_chain_list + else: + #manually specify, e.g. + designed_chain_list = ["A"] + fixed_chain_list = [letter for letter in all_chain_list if letter not in designed_chain_list] #fix/do not redesign these chains + my_dict[result['name']]= (designed_chain_list, fixed_chain_list) + + with open(args.output_path, 'w') as f: + f.write(json.dumps(my_dict) + '\n') + + +if __name__ == "__main__": + argparser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) + argparser.add_argument("--input_path", type=str, help="Path to the parsed PDBs") + argparser.add_argument("--output_path", type=str, help="Path to the output dictionary") + argparser.add_argument("--chain_list", type=str, default='', help="List of the chains that need to be designed") + + args = argparser.parse_args() + main(args) + +# Output looks like this: +# {"5TTA": [["A"], ["B"]], "3LIS": [["A"], ["B"]]} + diff --git a/scripts/helper_scripts/make_bias_AA.py b/scripts/helper_scripts/make_bias_AA.py new file mode 100644 index 0000000000000000000000000000000000000000..b32fb6b69dd4754d2ebef4c3baf5d81b6573d5a2 --- /dev/null +++ b/scripts/helper_scripts/make_bias_AA.py @@ -0,0 +1,27 @@ +import argparse + +def main(args): + + import numpy as np + import json + + bias_list = [float(item) for item in args.bias_list.split()] + AA_list = [str(item) for item in args.AA_list.split()] + + my_dict = dict(zip(AA_list, bias_list)) + + with open(args.output_path, 'w') as f: + f.write(json.dumps(my_dict) + '\n') + + +if __name__ == "__main__": + argparser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) + argparser.add_argument("--output_path", type=str, help="Path to the output dictionary") + argparser.add_argument("--AA_list", type=str, default='', help="List of AAs to be biased") + argparser.add_argument("--bias_list", type=str, default='', help="AA bias strengths") + + args = argparser.parse_args() + main(args) + +#e.g. output +#{"A": -0.01, "G": 0.02} diff --git a/scripts/helper_scripts/make_bias_per_res_dict.py b/scripts/helper_scripts/make_bias_per_res_dict.py new file mode 100644 index 0000000000000000000000000000000000000000..d8c497856bb8c38e0cabb675dc8f8c18cb4aae24 --- /dev/null +++ b/scripts/helper_scripts/make_bias_per_res_dict.py @@ -0,0 +1,72 @@ +import argparse +import os +import sys + +_PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__)) +while _PROJECT_ROOT and not os.path.isdir(os.path.join(_PROJECT_ROOT, "model")): + _PARENT = os.path.dirname(_PROJECT_ROOT) + if _PARENT == _PROJECT_ROOT: + break + _PROJECT_ROOT = _PARENT +_MODEL_ROOT = os.path.join(_PROJECT_ROOT, "model") +_ONESCIENCE_ROOT = os.environ.get("ONESCIENCE_ROOT") +for _path in (_MODEL_ROOT, _PROJECT_ROOT): + if os.path.exists(_path) and _path not in sys.path: + sys.path.insert(0, _path) +if _ONESCIENCE_ROOT: + _ONESCIENCE_SRC = os.path.join(_ONESCIENCE_ROOT, "src") + for _path in (_ONESCIENCE_SRC, _ONESCIENCE_ROOT): + if os.path.exists(_path) and _path not in sys.path: + sys.path.insert(0, _path) + +def main(args): + import glob + import random + import numpy as np + import json + + mpnn_alphabet = 'ACDEFGHIKLMNPQRSTVWYX' + + mpnn_alphabet_dict = {'A': 0,'C': 1,'D': 2,'E': 3,'F': 4,'G': 5,'H': 6,'I': 7,'K': 8,'L': 9,'M': 10,'N': 11,'P': 12,'Q': 13,'R': 14,'S': 15,'T': 16,'V': 17,'W': 18,'Y': 19,'X': 20} + + with open(args.input_path, 'r') as json_file: + json_list = list(json_file) + + my_dict = {} + for json_str in json_list: + result = json.loads(json_str) + all_chain_list = [item[-1:] for item in list(result) if item[:10]=='seq_chain_'] + bias_by_res_dict = {} + for chain in all_chain_list: + chain_length = len(result[f'seq_chain_{chain}']) + bias_per_residue = np.zeros([chain_length, 21]) + + + if chain == 'A': + residues = [0, 1, 2, 3, 4, 5, 11, 12, 13, 14, 15] + amino_acids = [5, 9] #[G, L] + for res in residues: + for aa in amino_acids: + bias_per_residue[res, aa] = 100.5 + + if chain == 'C': + residues = [0, 1, 2, 3, 4, 5, 11, 12, 13, 14, 15] + amino_acids = range(21)[1:] #[G, L] + for res in residues: + for aa in amino_acids: + bias_per_residue[res, aa] = -100.5 + + bias_by_res_dict[chain] = bias_per_residue.tolist() + my_dict[result['name']] = bias_by_res_dict + + with open(args.output_path, 'w') as f: + f.write(json.dumps(my_dict) + '\n') + + +if __name__ == "__main__": + argparser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) + argparser.add_argument("--input_path", type=str, help="Path to the parsed PDBs") + argparser.add_argument("--output_path", type=str, help="Path to the output dictionary") + + args = argparser.parse_args() + main(args) diff --git a/scripts/helper_scripts/make_fixed_positions_dict.py b/scripts/helper_scripts/make_fixed_positions_dict.py new file mode 100644 index 0000000000000000000000000000000000000000..67df7beeea9b45a42b461e4ee14b8857cf5d5a42 --- /dev/null +++ b/scripts/helper_scripts/make_fixed_positions_dict.py @@ -0,0 +1,78 @@ +import argparse +import os +import sys + +_PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__)) +while _PROJECT_ROOT and not os.path.isdir(os.path.join(_PROJECT_ROOT, "model")): + _PARENT = os.path.dirname(_PROJECT_ROOT) + if _PARENT == _PROJECT_ROOT: + break + _PROJECT_ROOT = _PARENT +_MODEL_ROOT = os.path.join(_PROJECT_ROOT, "model") +_ONESCIENCE_ROOT = os.environ.get("ONESCIENCE_ROOT") +for _path in (_MODEL_ROOT, _PROJECT_ROOT): + if os.path.exists(_path) and _path not in sys.path: + sys.path.insert(0, _path) +if _ONESCIENCE_ROOT: + _ONESCIENCE_SRC = os.path.join(_ONESCIENCE_ROOT, "src") + for _path in (_ONESCIENCE_SRC, _ONESCIENCE_ROOT): + if os.path.exists(_path) and _path not in sys.path: + sys.path.insert(0, _path) + +def main(args): + import glob + import random + import numpy as np + import json + import itertools + + with open(args.input_path, 'r') as json_file: + json_list = list(json_file) + + fixed_list = [[int(item) for item in one.split()] for one in args.position_list.split(",")] + global_designed_chain_list = [str(item) for item in args.chain_list.split()] + my_dict = {} + + if not args.specify_non_fixed: + for json_str in json_list: + result = json.loads(json_str) + all_chain_list = [item[-1:] for item in list(result) if item[:9]=='seq_chain'] + fixed_position_dict = {} + for i, chain in enumerate(global_designed_chain_list): + fixed_position_dict[chain] = fixed_list[i] + for chain in all_chain_list: + if chain not in global_designed_chain_list: + fixed_position_dict[chain] = [] + my_dict[result['name']] = fixed_position_dict + else: + for json_str in json_list: + result = json.loads(json_str) + all_chain_list = [item[-1:] for item in list(result) if item[:9]=='seq_chain'] + fixed_position_dict = {} + for chain in all_chain_list: + seq_length = len(result[f'seq_chain_{chain}']) + all_residue_list = (np.arange(seq_length)+1).tolist() + if chain not in global_designed_chain_list: + fixed_position_dict[chain] = all_residue_list + else: + idx = np.argwhere(np.array(global_designed_chain_list) == chain)[0][0] + fixed_position_dict[chain] = list(set(all_residue_list)-set(fixed_list[idx])) + my_dict[result['name']] = fixed_position_dict + + with open(args.output_path, 'w') as f: + f.write(json.dumps(my_dict) + '\n') + + #e.g. output + #{"5TTA": {"A": [1, 2, 3, 7, 8, 9, 22, 25, 33], "B": []}, "3LIS": {"A": [], "B": []}} + +if __name__ == "__main__": + argparser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) + argparser.add_argument("--input_path", type=str, help="Path to the parsed PDBs") + argparser.add_argument("--output_path", type=str, help="Path to the output dictionary") + argparser.add_argument("--chain_list", type=str, default='', help="List of the chains that need to be fixed") + argparser.add_argument("--position_list", type=str, default='', help="Position lists, e.g. 11 12 14 18, 1 2 3 4 for first chain and the second chain") + argparser.add_argument("--specify_non_fixed", action="store_true", default=False, help="Allows specifying just residues that need to be designed (default: false)") + + args = argparser.parse_args() + main(args) + diff --git a/scripts/helper_scripts/make_pos_neg_tied_positions_dict.py b/scripts/helper_scripts/make_pos_neg_tied_positions_dict.py new file mode 100644 index 0000000000000000000000000000000000000000..6cfdf548c2cd06204cc368db7c86189d03e3f52f --- /dev/null +++ b/scripts/helper_scripts/make_pos_neg_tied_positions_dict.py @@ -0,0 +1,92 @@ +import argparse +import os +import sys + +_PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__)) +while _PROJECT_ROOT and not os.path.isdir(os.path.join(_PROJECT_ROOT, "model")): + _PARENT = os.path.dirname(_PROJECT_ROOT) + if _PARENT == _PROJECT_ROOT: + break + _PROJECT_ROOT = _PARENT +_MODEL_ROOT = os.path.join(_PROJECT_ROOT, "model") +_ONESCIENCE_ROOT = os.environ.get("ONESCIENCE_ROOT") +for _path in (_MODEL_ROOT, _PROJECT_ROOT): + if os.path.exists(_path) and _path not in sys.path: + sys.path.insert(0, _path) +if _ONESCIENCE_ROOT: + _ONESCIENCE_SRC = os.path.join(_ONESCIENCE_ROOT, "src") + for _path in (_ONESCIENCE_SRC, _ONESCIENCE_ROOT): + if os.path.exists(_path) and _path not in sys.path: + sys.path.insert(0, _path) + +def main(args): + + import glob + import random + import numpy as np + import json + import itertools + + with open(args.input_path, 'r') as json_file: + json_list = list(json_file) + + homooligomeric_state = args.homooligomer + + if homooligomeric_state == 0: + tied_list = [[int(item) for item in one.split()] for one in args.position_list.split(",")] + global_designed_chain_list = [str(item) for item in args.chain_list.split()] + my_dict = {} + for json_str in json_list: + result = json.loads(json_str) + all_chain_list = sorted([item[-1:] for item in list(result) if item[:9]=='seq_chain']) #A, B, C, ... + tied_positions_list = [] + for i, pos in enumerate(tied_list[0]): + temp_dict = {} + for j, chain in enumerate(global_designed_chain_list): + temp_dict[chain] = [tied_list[j][i]] #needs to be a list + tied_positions_list.append(temp_dict) + my_dict[result['name']] = tied_positions_list + else: + if args.pos_neg_chain_list: + chain_list_input = [[str(item) for item in one.split()] for one in args.pos_neg_chain_list.split(",")] + chain_betas_input = [[float(item) for item in one.split()] for one in args.pos_neg_chain_betas.split(",")] + chain_list_flat = [item for sublist in chain_list_input for item in sublist] + chain_betas_flat = [item for sublist in chain_betas_input for item in sublist] + chain_betas_dict = dict(zip(chain_list_flat, chain_betas_flat)) + my_dict = {} + for json_str in json_list: + result = json.loads(json_str) + all_chain_list = sorted([item[-1:] for item in list(result) if item[:9]=='seq_chain']) #A, B, C, ... + tied_positions_list = [] + chain_length = len(result[f"seq_chain_{all_chain_list[0]}"]) + for chains in chain_list_input: + for i in range(1,chain_length+1): + temp_dict = {} + for j, chain in enumerate(chains): + if args.pos_neg_chain_list and chain in chain_list_flat: + temp_dict[chain] = [[i], [chain_betas_dict[chain]]] + else: + temp_dict[chain] = [[i], [1.0]] #first list is for residue numbers, second list is for weights for the energy, +ive and -ive design + tied_positions_list.append(temp_dict) + my_dict[result['name']] = tied_positions_list + + with open(args.output_path, 'w') as f: + f.write(json.dumps(my_dict) + '\n') + +if __name__ == "__main__": + argparser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) + argparser.add_argument("--input_path", type=str, help="Path to the parsed PDBs") + argparser.add_argument("--output_path", type=str, help="Path to the output dictionary") + argparser.add_argument("--chain_list", type=str, default='', help="List of the chains that need to be fixed") + argparser.add_argument("--position_list", type=str, default='', help="Position lists, e.g. 11 12 14 18, 1 2 3 4 for first chain and the second chain") + argparser.add_argument("--homooligomer", type=int, default=0, help="If 0 do not use, if 1 then design homooligomer") + argparser.add_argument("--pos_neg_chain_list", type=str, default='', help="Chain lists to be tied together") + argparser.add_argument("--pos_neg_chain_betas", type=str, default='', help="Chain beta list for the chain lists provided; 1.0 for the positive design, -0.1 or -0.5 for negative, 0.0 means do not use that chain info") + + args = argparser.parse_args() + main(args) + + +#e.g. output +#{"5TTA": [], "3LIS": [{"A": [1], "B": [1]}, {"A": [2], "B": [2]}, {"A": [3], "B": [3]}, {"A": [4], "B": [4]}, {"A": [5], "B": [5]}, {"A": [6], "B": [6]}, {"A": [7], "B": [7]}, {"A": [8], "B": [8]}, {"A": [9], "B": [9]}, {"A": [10], "B": [10]}, {"A": [11], "B": [11]}, {"A": [12], "B": [12]}, {"A": [13], "B": [13]}, {"A": [14], "B": [14]}, {"A": [15], "B": [15]}, {"A": [16], "B": [16]}, {"A": [17], "B": [17]}, {"A": [18], "B": [18]}, {"A": [19], "B": [19]}, {"A": [20], "B": [20]}, {"A": [21], "B": [21]}, {"A": [22], "B": [22]}, {"A": [23], "B": [23]}, {"A": [24], "B": [24]}, {"A": [25], "B": [25]}, {"A": [26], "B": [26]}, {"A": [27], "B": [27]}, {"A": [28], "B": [28]}, {"A": [29], "B": [29]}, {"A": [30], "B": [30]}, {"A": [31], "B": [31]}, {"A": [32], "B": [32]}, {"A": [33], "B": [33]}, {"A": [34], "B": [34]}, {"A": [35], "B": [35]}, {"A": [36], "B": [36]}, {"A": [37], "B": [37]}, {"A": [38], "B": [38]}, {"A": [39], "B": [39]}, {"A": [40], "B": [40]}, {"A": [41], "B": [41]}, {"A": [42], "B": [42]}, {"A": [43], "B": [43]}, {"A": [44], "B": [44]}, {"A": [45], "B": [45]}, {"A": [46], "B": [46]}, {"A": [47], "B": [47]}, {"A": [48], "B": [48]}, {"A": [49], "B": [49]}, {"A": [50], "B": [50]}, {"A": [51], "B": [51]}, {"A": [52], "B": [52]}, {"A": [53], "B": [53]}, {"A": [54], "B": [54]}, {"A": [55], "B": [55]}, {"A": [56], "B": [56]}, {"A": [57], "B": [57]}, {"A": [58], "B": [58]}, {"A": [59], "B": [59]}, {"A": [60], "B": [60]}, {"A": [61], "B": [61]}, {"A": [62], "B": [62]}, {"A": [63], "B": [63]}, {"A": [64], "B": [64]}, {"A": [65], "B": [65]}, {"A": [66], "B": [66]}, {"A": [67], "B": [67]}, {"A": [68], "B": [68]}, {"A": [69], "B": [69]}, {"A": [70], "B": [70]}, {"A": [71], "B": [71]}, {"A": [72], "B": [72]}, {"A": [73], "B": [73]}, {"A": [74], "B": [74]}, {"A": [75], "B": [75]}, {"A": [76], "B": [76]}, {"A": [77], "B": [77]}, {"A": [78], "B": [78]}, {"A": [79], "B": [79]}, {"A": [80], "B": [80]}, {"A": [81], "B": [81]}, {"A": [82], "B": [82]}, {"A": [83], "B": [83]}, {"A": [84], "B": [84]}, {"A": [85], "B": [85]}, {"A": [86], "B": [86]}, {"A": [87], "B": [87]}, {"A": [88], "B": [88]}, {"A": [89], "B": [89]}, {"A": [90], "B": [90]}, {"A": [91], "B": [91]}, {"A": [92], "B": [92]}, {"A": [93], "B": [93]}, {"A": [94], "B": [94]}, {"A": [95], "B": [95]}, {"A": [96], "B": [96]}]} + diff --git a/scripts/helper_scripts/make_pssm_input_dict.py b/scripts/helper_scripts/make_pssm_input_dict.py new file mode 100644 index 0000000000000000000000000000000000000000..f2d3465255971800df42f2bf5bc098ac28ea02c6 --- /dev/null +++ b/scripts/helper_scripts/make_pssm_input_dict.py @@ -0,0 +1,55 @@ +import argparse +import os +import sys + +_PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__)) +while _PROJECT_ROOT and not os.path.isdir(os.path.join(_PROJECT_ROOT, "model")): + _PARENT = os.path.dirname(_PROJECT_ROOT) + if _PARENT == _PROJECT_ROOT: + break + _PROJECT_ROOT = _PARENT +_MODEL_ROOT = os.path.join(_PROJECT_ROOT, "model") +_ONESCIENCE_ROOT = os.environ.get("ONESCIENCE_ROOT") +for _path in (_MODEL_ROOT, _PROJECT_ROOT): + if os.path.exists(_path) and _path not in sys.path: + sys.path.insert(0, _path) +if _ONESCIENCE_ROOT: + _ONESCIENCE_SRC = os.path.join(_ONESCIENCE_ROOT, "src") + for _path in (_ONESCIENCE_SRC, _ONESCIENCE_ROOT): + if os.path.exists(_path) and _path not in sys.path: + sys.path.insert(0, _path) + +def main(args): + import json + import numpy as np + with open(args.jsonl_input_path, 'r') as json_file: + json_list = list(json_file) + + my_dict = {} + for json_str in json_list: + result = json.loads(json_str) + all_chain_list = [item[-1:] for item in list(result) if item[:9]=='seq_chain'] + path_to_PSSM = args.PSSM_input_path+"/"+result['name'] + ".npz" + print(path_to_PSSM) + pssm_input = np.load(path_to_PSSM) + pssm_dict = {} + for chain in all_chain_list: + pssm_dict[chain] = {} + pssm_dict[chain]['pssm_coef'] = pssm_input[chain+'_coef'].tolist() #[L] per position coefficient to trust PSSM; 0.0 - do not use it; 1.0 - just use PSSM only + pssm_dict[chain]['pssm_bias'] = pssm_input[chain+'_bias'].tolist() #[L,21] probability (sums up to 1.0 over alphabet of size 21) from PSSM + pssm_dict[chain]['pssm_log_odds'] = pssm_input[chain+'_odds'].tolist() #[L,21] log_odds ratios coming from PSSM; optional/not needed + my_dict[result['name']] = pssm_dict + + #Write output to: + with open(args.output_path, 'w') as f: + f.write(json.dumps(my_dict) + '\n') + +if __name__ == "__main__": + argparser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + argparser.add_argument("--PSSM_input_path", type=str, help="Path to PSSMs saved as npz files.") + argparser.add_argument("--jsonl_input_path", type=str, help="Path where to load .jsonl dictionary of parsed pdbs.") + argparser.add_argument("--output_path", type=str, help="Path where to save .jsonl dictionary with PSSM bias.") + + args = argparser.parse_args() + main(args) diff --git a/scripts/helper_scripts/make_tied_positions_dict.py b/scripts/helper_scripts/make_tied_positions_dict.py new file mode 100644 index 0000000000000000000000000000000000000000..25fb6f66af22cbd1fe8a13cdee4d760d550ea936 --- /dev/null +++ b/scripts/helper_scripts/make_tied_positions_dict.py @@ -0,0 +1,80 @@ +import argparse +import os +import sys + +_PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__)) +while _PROJECT_ROOT and not os.path.isdir(os.path.join(_PROJECT_ROOT, "model")): + _PARENT = os.path.dirname(_PROJECT_ROOT) + if _PARENT == _PROJECT_ROOT: + break + _PROJECT_ROOT = _PARENT +_MODEL_ROOT = os.path.join(_PROJECT_ROOT, "model") +_ONESCIENCE_ROOT = os.environ.get("ONESCIENCE_ROOT") +for _path in (_MODEL_ROOT, _PROJECT_ROOT): + if os.path.exists(_path) and _path not in sys.path: + sys.path.insert(0, _path) +if _ONESCIENCE_ROOT: + _ONESCIENCE_SRC = os.path.join(_ONESCIENCE_ROOT, "src") + for _path in (_ONESCIENCE_SRC, _ONESCIENCE_ROOT): + if os.path.exists(_path) and _path not in sys.path: + sys.path.insert(0, _path) + +def main(args): + + import glob + import random + import numpy as np + import json + import itertools + + with open(args.input_path, 'r') as json_file: + json_list = list(json_file) + + homooligomeric_state = args.homooligomer + + if homooligomeric_state == 0: + tied_list = [[int(item) for item in one.split()] for one in args.position_list.split(",")] + global_designed_chain_list = [str(item) for item in args.chain_list.split()] + my_dict = {} + for json_str in json_list: + result = json.loads(json_str) + all_chain_list = sorted([item[-1:] for item in list(result) if item[:9]=='seq_chain']) #A, B, C, ... + tied_positions_list = [] + for i, pos in enumerate(tied_list[0]): + temp_dict = {} + for j, chain in enumerate(global_designed_chain_list): + temp_dict[chain] = [tied_list[j][i]] #needs to be a list + tied_positions_list.append(temp_dict) + my_dict[result['name']] = tied_positions_list + else: + my_dict = {} + for json_str in json_list: + result = json.loads(json_str) + all_chain_list = sorted([item[-1:] for item in list(result) if item[:9]=='seq_chain']) #A, B, C, ... + tied_positions_list = [] + chain_length = len(result[f"seq_chain_{all_chain_list[0]}"]) + for i in range(1,chain_length+1): + temp_dict = {} + for j, chain in enumerate(all_chain_list): + temp_dict[chain] = [i] #needs to be a list + tied_positions_list.append(temp_dict) + my_dict[result['name']] = tied_positions_list + + with open(args.output_path, 'w') as f: + f.write(json.dumps(my_dict) + '\n') + +if __name__ == "__main__": + argparser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) + argparser.add_argument("--input_path", type=str, help="Path to the parsed PDBs") + argparser.add_argument("--output_path", type=str, help="Path to the output dictionary") + argparser.add_argument("--chain_list", type=str, default='', help="List of the chains that need to be fixed") + argparser.add_argument("--position_list", type=str, default='', help="Position lists, e.g. 11 12 14 18, 1 2 3 4 for first chain and the second chain") + argparser.add_argument("--homooligomer", type=int, default=0, help="If 0 do not use, if 1 then design homooligomer") + + args = argparser.parse_args() + main(args) + + +#e.g. output +#{"5TTA": [], "3LIS": [{"A": [1], "B": [1]}, {"A": [2], "B": [2]}, {"A": [3], "B": [3]}, {"A": [4], "B": [4]}, {"A": [5], "B": [5]}, {"A": [6], "B": [6]}, {"A": [7], "B": [7]}, {"A": [8], "B": [8]}, {"A": [9], "B": [9]}, {"A": [10], "B": [10]}, {"A": [11], "B": [11]}, {"A": [12], "B": [12]}, {"A": [13], "B": [13]}, {"A": [14], "B": [14]}, {"A": [15], "B": [15]}, {"A": [16], "B": [16]}, {"A": [17], "B": [17]}, {"A": [18], "B": [18]}, {"A": [19], "B": [19]}, {"A": [20], "B": [20]}, {"A": [21], "B": [21]}, {"A": [22], "B": [22]}, {"A": [23], "B": [23]}, {"A": [24], "B": [24]}, {"A": [25], "B": [25]}, {"A": [26], "B": [26]}, {"A": [27], "B": [27]}, {"A": [28], "B": [28]}, {"A": [29], "B": [29]}, {"A": [30], "B": [30]}, {"A": [31], "B": [31]}, {"A": [32], "B": [32]}, {"A": [33], "B": [33]}, {"A": [34], "B": [34]}, {"A": [35], "B": [35]}, {"A": [36], "B": [36]}, {"A": [37], "B": [37]}, {"A": [38], "B": [38]}, {"A": [39], "B": [39]}, {"A": [40], "B": [40]}, {"A": [41], "B": [41]}, {"A": [42], "B": [42]}, {"A": [43], "B": [43]}, {"A": [44], "B": [44]}, {"A": [45], "B": [45]}, {"A": [46], "B": [46]}, {"A": [47], "B": [47]}, {"A": [48], "B": [48]}, {"A": [49], "B": [49]}, {"A": [50], "B": [50]}, {"A": [51], "B": [51]}, {"A": [52], "B": [52]}, {"A": [53], "B": [53]}, {"A": [54], "B": [54]}, {"A": [55], "B": [55]}, {"A": [56], "B": [56]}, {"A": [57], "B": [57]}, {"A": [58], "B": [58]}, {"A": [59], "B": [59]}, {"A": [60], "B": [60]}, {"A": [61], "B": [61]}, {"A": [62], "B": [62]}, {"A": [63], "B": [63]}, {"A": [64], "B": [64]}, {"A": [65], "B": [65]}, {"A": [66], "B": [66]}, {"A": [67], "B": [67]}, {"A": [68], "B": [68]}, {"A": [69], "B": [69]}, {"A": [70], "B": [70]}, {"A": [71], "B": [71]}, {"A": [72], "B": [72]}, {"A": [73], "B": [73]}, {"A": [74], "B": [74]}, {"A": [75], "B": [75]}, {"A": [76], "B": [76]}, {"A": [77], "B": [77]}, {"A": [78], "B": [78]}, {"A": [79], "B": [79]}, {"A": [80], "B": [80]}, {"A": [81], "B": [81]}, {"A": [82], "B": [82]}, {"A": [83], "B": [83]}, {"A": [84], "B": [84]}, {"A": [85], "B": [85]}, {"A": [86], "B": [86]}, {"A": [87], "B": [87]}, {"A": [88], "B": [88]}, {"A": [89], "B": [89]}, {"A": [90], "B": [90]}, {"A": [91], "B": [91]}, {"A": [92], "B": [92]}, {"A": [93], "B": [93]}, {"A": [94], "B": [94]}, {"A": [95], "B": [95]}, {"A": [96], "B": [96]}]} + diff --git a/scripts/helper_scripts/other_tools/make_omit_AA.py b/scripts/helper_scripts/other_tools/make_omit_AA.py new file mode 100644 index 0000000000000000000000000000000000000000..353a74bcf0ec1c0238a32b17a001d15591d18243 --- /dev/null +++ b/scripts/helper_scripts/other_tools/make_omit_AA.py @@ -0,0 +1,39 @@ +import glob +import random +import numpy as np +import json +import itertools + +#MODIFY this path +with open('/home/justas/projects/lab_github/mpnn/data/pdbs.jsonl', 'r') as json_file: + json_list = list(json_file) + +my_dict = {} +for json_str in json_list: + result = json.loads(json_str) + all_chain_list = [item[-1:] for item in list(result) if item[:9]=='seq_chain'] + fixed_position_dict = {} + print(result['name']) + if result['name'] == '5TTA': + for chain in all_chain_list: + if chain == 'A': + fixed_position_dict[chain] = [ + [[int(item) for item in list(itertools.chain(list(np.arange(1,4)), list(np.arange(7,10)), [22, 25, 33]))], 'GPL'], + [[int(item) for item in list(itertools.chain([40, 41, 42, 43]))], 'WC'], + [[int(item) for item in list(itertools.chain(list(np.arange(50,150))))], 'ACEFGHIKLMNRSTVWYX'], + [[int(item) for item in list(itertools.chain(list(np.arange(160,200))))], 'FGHIKLPQDMNRSTVWYX']] + else: + fixed_position_dict[chain] = [] + else: + for chain in all_chain_list: + fixed_position_dict[chain] = [] + my_dict[result['name']] = fixed_position_dict + +#MODIFY this path +with open('/home/justas/projects/lab_github/mpnn/data/omit_AA.jsonl', 'w') as f: + f.write(json.dumps(my_dict) + '\n') + + +print('Finished') +#e.g. output +#{"5TTA": {"A": [[[1, 2, 3, 7, 8, 9, 22, 25, 33], "GPL"], [[40, 41, 42, 43], "WC"], [[50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149], "ACEFGHIKLMNRSTVWYX"], [[160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199], "FGHIKLPQDMNRSTVWYX"]], "B": []}, "3LIS": {"A": [], "B": []}} diff --git a/scripts/helper_scripts/other_tools/make_pssm_dict.py b/scripts/helper_scripts/other_tools/make_pssm_dict.py new file mode 100644 index 0000000000000000000000000000000000000000..c6cf83df6febb2ac9e12da3e127dbc9a7ea08d7f --- /dev/null +++ b/scripts/helper_scripts/other_tools/make_pssm_dict.py @@ -0,0 +1,64 @@ +import pandas as pd +import numpy as np + +import glob +import random +import numpy as np +import json + + +def softmax(x, T): + return np.exp(x/T)/np.sum(np.exp(x/T), -1, keepdims=True) + +def parse_pssm(path): + data = pd.read_csv(path, skiprows=2) + floats_list_list = [] + for i in range(data.values.shape[0]): + str1 = data.values[i][0][4:] + floats_list = [] + for item in str1.split(): + floats_list.append(float(item)) + floats_list_list.append(floats_list) + np_lines = np.array(floats_list_list) + return np_lines + +np_lines = parse_pssm('/home/swang523/RLcage/capsid/monomersfordesign/8-16-21/pssm_rainity_final_8-16-21_int/build_0.2089_0.98_0.4653_19_2.00_0.005745.pssm') + +mpnn_alphabet = 'ACDEFGHIKLMNPQRSTVWYX' +input_alphabet = 'ARNDCQEGHILKMFPSTWYV' + +permutation_matrix = np.zeros([20,21]) +for i in range(20): + letter1 = input_alphabet[i] + for j in range(21): + letter2 = mpnn_alphabet[j] + if letter1 == letter2: + permutation_matrix[i,j]=1. + +pssm_log_odds = np_lines[:,:20] @ permutation_matrix +pssm_probs = np_lines[:,20:40] @ permutation_matrix + +X_mask = np.concatenate([np.zeros([1,20]), np.ones([1,1])], -1) + +def softmax(x, T): + return np.exp(x/T)/np.sum(np.exp(x/T), -1, keepdims=True) + +#Load parsed PDBs: +with open('/home/justas/projects/cages/parsed/test.jsonl', 'r') as json_file: + json_list = list(json_file) + +my_dict = {} +for json_str in json_list: + result = json.loads(json_str) + all_chain_list = [item[-1:] for item in list(result) if item[:9]=='seq_chain'] + pssm_dict = {} + for chain in all_chain_list: + pssm_dict[chain] = {} + pssm_dict[chain]['pssm_coef'] = (np.ones(len(result['seq_chain_A']))).tolist() #a number between 0.0 and 1.0 specifying how much attention put to PSSM, can be adjusted later as a flag + pssm_dict[chain]['pssm_bias'] = (softmax(pssm_log_odds-X_mask*1e8, 1.0)).tolist() #PSSM like, [length, 21] such that sum over the last dimension adds up to 1.0 + pssm_dict[chain]['pssm_log_odds'] = (pssm_log_odds).tolist() + my_dict[result['name']] = pssm_dict + +#Write output to: +with open('/home/justas/projects/lab_github/mpnn/data/pssm_dict.jsonl', 'w') as f: + f.write(json.dumps(my_dict) + '\n') diff --git a/scripts/helper_scripts/parse_multiple_chains.py b/scripts/helper_scripts/parse_multiple_chains.py new file mode 100644 index 0000000000000000000000000000000000000000..ac156bab6e5cf360ca4cb2433becb3e9b4335c51 --- /dev/null +++ b/scripts/helper_scripts/parse_multiple_chains.py @@ -0,0 +1,182 @@ +import argparse +import os +import sys + +_PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__)) +while _PROJECT_ROOT and not os.path.isdir(os.path.join(_PROJECT_ROOT, "model")): + _PARENT = os.path.dirname(_PROJECT_ROOT) + if _PARENT == _PROJECT_ROOT: + break + _PROJECT_ROOT = _PARENT +_MODEL_ROOT = os.path.join(_PROJECT_ROOT, "model") +_ONESCIENCE_ROOT = os.environ.get("ONESCIENCE_ROOT") +for _path in (_MODEL_ROOT, _PROJECT_ROOT): + if os.path.exists(_path) and _path not in sys.path: + sys.path.insert(0, _path) +if _ONESCIENCE_ROOT: + _ONESCIENCE_SRC = os.path.join(_ONESCIENCE_ROOT, "src") + for _path in (_ONESCIENCE_SRC, _ONESCIENCE_ROOT): + if os.path.exists(_path) and _path not in sys.path: + sys.path.insert(0, _path) + +def main(args): + + import numpy as np + import os, time, gzip, json + import glob + + folder_with_pdbs_path = args.input_path + save_path = args.output_path + ca_only = args.ca_only + + alpha_1 = list("ARNDCQEGHILKMFPSTWYV-") + states = len(alpha_1) + alpha_3 = ['ALA','ARG','ASN','ASP','CYS','GLN','GLU','GLY','HIS','ILE', + 'LEU','LYS','MET','PHE','PRO','SER','THR','TRP','TYR','VAL','GAP'] + + aa_1_N = {a:n for n,a in enumerate(alpha_1)} + aa_3_N = {a:n for n,a in enumerate(alpha_3)} + aa_N_1 = {n:a for n,a in enumerate(alpha_1)} + aa_1_3 = {a:b for a,b in zip(alpha_1,alpha_3)} + aa_3_1 = {b:a for a,b in zip(alpha_1,alpha_3)} + + def AA_to_N(x): + # ["ARND"] -> [[0,1,2,3]] + x = np.array(x); + if x.ndim == 0: x = x[None] + return [[aa_1_N.get(a, states-1) for a in y] for y in x] + + def N_to_AA(x): + # [[0,1,2,3]] -> ["ARND"] + x = np.array(x); + if x.ndim == 1: x = x[None] + return ["".join([aa_N_1.get(a,"-") for a in y]) for y in x] + + + def parse_PDB_biounits(x, atoms=['N','CA','C'], chain=None): + ''' + input: x = PDB filename + atoms = atoms to extract (optional) + output: (length, atoms, coords=(x,y,z)), sequence + ''' + xyz,seq,min_resn,max_resn = {},{},1e6,-1e6 + for line in open(x,"rb"): + line = line.decode("utf-8","ignore").rstrip() + + if line[:6] == "HETATM" and line[17:17+3] == "MSE": + line = line.replace("HETATM","ATOM ") + line = line.replace("MSE","MET") + + if line[:4] == "ATOM": + ch = line[21:22] + if ch == chain or chain is None: + atom = line[12:12+4].strip() + resi = line[17:17+3] + resn = line[22:22+5].strip() + x,y,z = [float(line[i:(i+8)]) for i in [30,38,46]] + + if resn[-1].isalpha(): + resa,resn = resn[-1],int(resn[:-1])-1 + else: + resa,resn = "",int(resn)-1 + # resn = int(resn) + if resn < min_resn: + min_resn = resn + if resn > max_resn: + max_resn = resn + if resn not in xyz: + xyz[resn] = {} + if resa not in xyz[resn]: + xyz[resn][resa] = {} + if resn not in seq: + seq[resn] = {} + if resa not in seq[resn]: + seq[resn][resa] = resi + + if atom not in xyz[resn][resa]: + xyz[resn][resa][atom] = np.array([x,y,z]) + + # convert to numpy arrays, fill in missing values + seq_,xyz_ = [],[] + try: + for resn in range(min_resn,max_resn+1): + if resn in seq: + for k in sorted(seq[resn]): seq_.append(aa_3_N.get(seq[resn][k],20)) + else: seq_.append(20) + if resn in xyz: + for k in sorted(xyz[resn]): + for atom in atoms: + if atom in xyz[resn][k]: xyz_.append(xyz[resn][k][atom]) + else: xyz_.append(np.full(3,np.nan)) + else: + for atom in atoms: xyz_.append(np.full(3,np.nan)) + return np.array(xyz_).reshape(-1,len(atoms),3), N_to_AA(np.array(seq_)) + except TypeError: + return 'no_chain', 'no_chain' + + + + pdb_dict_list = [] + c = 0 + + if folder_with_pdbs_path[-1]!='/': + folder_with_pdbs_path = folder_with_pdbs_path+'/' + + + init_alphabet = ['A', 'B', 'C', 'D', 'E', 'F', 'G','H', 'I', 'J','K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T','U', 'V','W','X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g','h', 'i', 'j','k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't','u', 'v','w','x', 'y', 'z'] + extra_alphabet = [str(item) for item in list(np.arange(300))] + chain_alphabet = init_alphabet + extra_alphabet + + biounit_names = glob.glob(folder_with_pdbs_path+'*.pdb') + for biounit in biounit_names: + my_dict = {} + s = 0 + concat_seq = '' + concat_N = [] + concat_CA = [] + concat_C = [] + concat_O = [] + concat_mask = [] + coords_dict = {} + for letter in chain_alphabet: + if ca_only: + sidechain_atoms = ['CA'] + else: + sidechain_atoms = ['N', 'CA', 'C', 'O'] + xyz, seq = parse_PDB_biounits(biounit, atoms=sidechain_atoms, chain=letter) + if type(xyz) != str: + concat_seq += seq[0] + my_dict['seq_chain_'+letter]=seq[0] + coords_dict_chain = {} + if ca_only: + coords_dict_chain['CA_chain_'+letter]=xyz.tolist() + else: + coords_dict_chain['N_chain_' + letter] = xyz[:, 0, :].tolist() + coords_dict_chain['CA_chain_' + letter] = xyz[:, 1, :].tolist() + coords_dict_chain['C_chain_' + letter] = xyz[:, 2, :].tolist() + coords_dict_chain['O_chain_' + letter] = xyz[:, 3, :].tolist() + my_dict['coords_chain_'+letter]=coords_dict_chain + s += 1 + fi = biounit.rfind("/") + my_dict['name']=biounit[(fi+1):-4] + my_dict['num_of_chains'] = s + my_dict['seq'] = concat_seq + if s < len(chain_alphabet): + pdb_dict_list.append(my_dict) + c+=1 + + + with open(save_path, 'w') as f: + for entry in pdb_dict_list: + f.write(json.dumps(entry) + '\n') + + +if __name__ == "__main__": + argparser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + argparser.add_argument("--input_path", type=str, help="Path to a folder with pdb files, e.g. /home/my_pdbs/") + argparser.add_argument("--output_path", type=str, help="Path where to save .jsonl dictionary of parsed pdbs") + argparser.add_argument("--ca_only", action="store_true", default=False, help="parse a backbone-only structure (default: false)") + + args = argparser.parse_args() + main(args) diff --git a/scripts/helper_scripts/parse_multiple_chains.sh b/scripts/helper_scripts/parse_multiple_chains.sh new file mode 100644 index 0000000000000000000000000000000000000000..c2a8a108b05cc9f81c636b3b48bf58ddb4ce1a02 --- /dev/null +++ b/scripts/helper_scripts/parse_multiple_chains.sh @@ -0,0 +1,7 @@ +#!/bin/bash +#SBATCH --mem=32g +#SBATCH -c 2 +#SBATCH --output=parse_multiple_chains.out + +source activate mlfold +python parse_multiple_chains.py --input_path='../PDB_complexes/pdbs/' --output_path='../PDB_complexes/parsed_pdbs.jsonl' diff --git a/scripts/infer_examples/submit_example_1.sh b/scripts/infer_examples/submit_example_1.sh new file mode 100644 index 0000000000000000000000000000000000000000..1ed473e7841923cf6661087a01772f6bd4c8c41e --- /dev/null +++ b/scripts/infer_examples/submit_example_1.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +ONESCIENCE_ROOT="${ONESCIENCE_ROOT:-$(cd "${PROJECT_ROOT}/.." && pwd)}" +export PYTHONPATH="${PROJECT_ROOT}/model:${ONESCIENCE_ROOT}/src:${PYTHONPATH:-}" + +folder_with_pdbs="${PROJECT_ROOT}/data/inputs/PDB_monomers/pdbs/" + +output_dir="${PROJECT_ROOT}/outputs/example_1_outputs" +if [ ! -d "$output_dir" ] +then + mkdir -p "$output_dir" +fi + +path_for_parsed_chains=$output_dir"/parsed_pdbs.jsonl" + +python "${PROJECT_ROOT}/scripts/helper_scripts/parse_multiple_chains.py" --input_path="$folder_with_pdbs" --output_path="$path_for_parsed_chains" + +python "${PROJECT_ROOT}/scripts/inference.py" \ + --jsonl_path "$path_for_parsed_chains" \ + --out_folder "$output_dir" \ + --num_seq_per_target 2 \ + --sampling_temp "0.1" \ + --seed 37 \ + --batch_size 1 \ + --path_to_model_weights "${PROJECT_ROOT}/weight/vanilla_model_weights" diff --git a/scripts/infer_examples/submit_example_2.sh b/scripts/infer_examples/submit_example_2.sh new file mode 100644 index 0000000000000000000000000000000000000000..643c4529fdf9f01528da2c15ef777ba2c94ed403 --- /dev/null +++ b/scripts/infer_examples/submit_example_2.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +ONESCIENCE_ROOT="${ONESCIENCE_ROOT:-$(cd "${PROJECT_ROOT}/.." && pwd)}" +export PYTHONPATH="${PROJECT_ROOT}/model:${ONESCIENCE_ROOT}/src:${PYTHONPATH:-}" + +folder_with_pdbs="${PROJECT_ROOT}/data/inputs/PDB_complexes/pdbs/" + +output_dir="${PROJECT_ROOT}/outputs/example_2_outputs" +if [ ! -d "$output_dir" ] +then + mkdir -p "$output_dir" +fi + +path_for_parsed_chains=$output_dir"/parsed_pdbs.jsonl" +path_for_assigned_chains=$output_dir"/assigned_pdbs.jsonl" +chains_to_design="A B" + +python "${PROJECT_ROOT}/scripts/helper_scripts/parse_multiple_chains.py" --input_path="$folder_with_pdbs" --output_path="$path_for_parsed_chains" + +python "${PROJECT_ROOT}/scripts/helper_scripts/assign_fixed_chains.py" --input_path="$path_for_parsed_chains" --output_path="$path_for_assigned_chains" --chain_list "$chains_to_design" + +python "${PROJECT_ROOT}/scripts/inference.py" \ + --jsonl_path "$path_for_parsed_chains" \ + --chain_id_jsonl "$path_for_assigned_chains" \ + --out_folder "$output_dir" \ + --num_seq_per_target 2 \ + --sampling_temp "0.1" \ + --seed 37 \ + --batch_size 1 \ + --path_to_model_weights "${PROJECT_ROOT}/weight/vanilla_model_weights" diff --git a/scripts/infer_examples/submit_example_3.sh b/scripts/infer_examples/submit_example_3.sh new file mode 100644 index 0000000000000000000000000000000000000000..8b84a8fc5f54a1ee2683ded1b4f5c229951bcd4a --- /dev/null +++ b/scripts/infer_examples/submit_example_3.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +ONESCIENCE_ROOT="${ONESCIENCE_ROOT:-$(cd "${PROJECT_ROOT}/.." && pwd)}" +export PYTHONPATH="${PROJECT_ROOT}/model:${ONESCIENCE_ROOT}/src:${PYTHONPATH:-}" + +path_to_PDB="${PROJECT_ROOT}/data/inputs/PDB_complexes/pdbs/3HTN.pdb" + +output_dir="${PROJECT_ROOT}/outputs/example_3_outputs" +if [ ! -d "$output_dir" ] +then + mkdir -p "$output_dir" +fi + +chains_to_design="A B" + +python "${PROJECT_ROOT}/scripts/inference.py" \ + --pdb_path "$path_to_PDB" \ + --pdb_path_chains "$chains_to_design" \ + --out_folder "$output_dir" \ + --num_seq_per_target 2 \ + --sampling_temp "0.1" \ + --seed 37 \ + --batch_size 1 \ + --path_to_model_weights "${PROJECT_ROOT}/weight/vanilla_model_weights" diff --git a/scripts/infer_examples/submit_example_3_score_only.sh b/scripts/infer_examples/submit_example_3_score_only.sh new file mode 100644 index 0000000000000000000000000000000000000000..84701e97855b946c4bf26dbe8a16007d53a352c0 --- /dev/null +++ b/scripts/infer_examples/submit_example_3_score_only.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +ONESCIENCE_ROOT="${ONESCIENCE_ROOT:-$(cd "${PROJECT_ROOT}/.." && pwd)}" +export PYTHONPATH="${PROJECT_ROOT}/model:${ONESCIENCE_ROOT}/src:${PYTHONPATH:-}" + +path_to_PDB="${PROJECT_ROOT}/data/inputs/PDB_complexes/pdbs/3HTN.pdb" + +output_dir="${PROJECT_ROOT}/outputs/example_3_score_only_outputs" +if [ ! -d "$output_dir" ] +then + mkdir -p "$output_dir" +fi + +chains_to_design="A B" + +python "${PROJECT_ROOT}/scripts/inference.py" \ + --pdb_path "$path_to_PDB" \ + --pdb_path_chains "$chains_to_design" \ + --out_folder "$output_dir" \ + --num_seq_per_target 10 \ + --sampling_temp "0.1" \ + --score_only 1 \ + --seed 37 \ + --batch_size 1 \ + --path_to_model_weights "${PROJECT_ROOT}/weight/vanilla_model_weights" diff --git a/scripts/infer_examples/submit_example_3_score_only_from_fasta.sh b/scripts/infer_examples/submit_example_3_score_only_from_fasta.sh new file mode 100644 index 0000000000000000000000000000000000000000..2f5d30f3c2ac6a7e3ab124cb19c6c95f6fef6c10 --- /dev/null +++ b/scripts/infer_examples/submit_example_3_score_only_from_fasta.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +ONESCIENCE_ROOT="${ONESCIENCE_ROOT:-$(cd "${PROJECT_ROOT}/.." && pwd)}" +export PYTHONPATH="${PROJECT_ROOT}/model:${ONESCIENCE_ROOT}/src:${PYTHONPATH:-}" + +path_to_PDB="${PROJECT_ROOT}/data/inputs/PDB_complexes/pdbs/3HTN.pdb" +path_to_fasta="${PROJECT_ROOT}/outputs/example_3_outputs/seqs/3HTN.fa" + +output_dir="${PROJECT_ROOT}/outputs/example_3_score_only_from_fasta_outputs" +if [ ! -d "$output_dir" ] +then + mkdir -p "$output_dir" +fi + +chains_to_design="A B" + +python "${PROJECT_ROOT}/scripts/inference.py" \ + --path_to_fasta "$path_to_fasta" \ + --pdb_path "$path_to_PDB" \ + --pdb_path_chains "$chains_to_design" \ + --out_folder "$output_dir" \ + --num_seq_per_target 5 \ + --sampling_temp "0.1" \ + --score_only 1 \ + --seed 13 \ + --batch_size 1 \ + --path_to_model_weights "${PROJECT_ROOT}/weight/vanilla_model_weights" diff --git a/scripts/infer_examples/submit_example_4.sh b/scripts/infer_examples/submit_example_4.sh new file mode 100644 index 0000000000000000000000000000000000000000..b265248a738b4a5cf0cec76d198c8b3671abb837 --- /dev/null +++ b/scripts/infer_examples/submit_example_4.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +ONESCIENCE_ROOT="${ONESCIENCE_ROOT:-$(cd "${PROJECT_ROOT}/.." && pwd)}" +export PYTHONPATH="${PROJECT_ROOT}/model:${ONESCIENCE_ROOT}/src:${PYTHONPATH:-}" + +folder_with_pdbs="${PROJECT_ROOT}/data/inputs/PDB_complexes/pdbs/" + +output_dir="${PROJECT_ROOT}/outputs/example_4_outputs" +if [ ! -d "$output_dir" ] +then + mkdir -p "$output_dir" +fi + + +path_for_parsed_chains=$output_dir"/parsed_pdbs.jsonl" +path_for_assigned_chains=$output_dir"/assigned_pdbs.jsonl" +path_for_fixed_positions=$output_dir"/fixed_pdbs.jsonl" +chains_to_design="A C" +#The first amino acid in the chain corresponds to 1 and not PDB residues index for now. +fixed_positions="1 2 3 4 5 6 7 8 23 25, 10 11 12 13 14 15 16 17 18 19 20 40" #fixing/not designing residues 1 2 3...25 in chain A and residues 10 11 12...40 in chain C + +python "${PROJECT_ROOT}/scripts/helper_scripts/parse_multiple_chains.py" --input_path="$folder_with_pdbs" --output_path="$path_for_parsed_chains" + +python "${PROJECT_ROOT}/scripts/helper_scripts/assign_fixed_chains.py" --input_path="$path_for_parsed_chains" --output_path="$path_for_assigned_chains" --chain_list "$chains_to_design" + +python "${PROJECT_ROOT}/scripts/helper_scripts/make_fixed_positions_dict.py" --input_path="$path_for_parsed_chains" --output_path="$path_for_fixed_positions" --chain_list "$chains_to_design" --position_list "$fixed_positions" + +python "${PROJECT_ROOT}/scripts/inference.py" \ + --jsonl_path "$path_for_parsed_chains" \ + --chain_id_jsonl "$path_for_assigned_chains" \ + --fixed_positions_jsonl "$path_for_fixed_positions" \ + --out_folder "$output_dir" \ + --num_seq_per_target 2 \ + --sampling_temp "0.1" \ + --seed 37 \ + --batch_size 1 \ + --path_to_model_weights "${PROJECT_ROOT}/weight/vanilla_model_weights" diff --git a/scripts/infer_examples/submit_example_4_non_fixed.sh b/scripts/infer_examples/submit_example_4_non_fixed.sh new file mode 100644 index 0000000000000000000000000000000000000000..5435c9a51d3988e43b7cc7cb1bfcc8d2e1d6326c --- /dev/null +++ b/scripts/infer_examples/submit_example_4_non_fixed.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +ONESCIENCE_ROOT="${ONESCIENCE_ROOT:-$(cd "${PROJECT_ROOT}/.." && pwd)}" +export PYTHONPATH="${PROJECT_ROOT}/model:${ONESCIENCE_ROOT}/src:${PYTHONPATH:-}" + +folder_with_pdbs="${PROJECT_ROOT}/data/inputs/PDB_complexes/pdbs/" + +output_dir="${PROJECT_ROOT}/outputs/example_4_non_fixed_outputs" +if [ ! -d "$output_dir" ] +then + mkdir -p "$output_dir" +fi + + +path_for_parsed_chains=$output_dir"/parsed_pdbs.jsonl" +path_for_assigned_chains=$output_dir"/assigned_pdbs.jsonl" +path_for_fixed_positions=$output_dir"/fixed_pdbs.jsonl" +chains_to_design="A C" +#The first amino acid in the chain corresponds to 1 and not PDB residues index for now. +design_only_positions="1 2 3 4 5 6 7 8 9 10, 3 4 5 6 7 8" #design only these residues; use flag --specify_non_fixed + +python "${PROJECT_ROOT}/scripts/helper_scripts/parse_multiple_chains.py" --input_path="$folder_with_pdbs" --output_path="$path_for_parsed_chains" + +python "${PROJECT_ROOT}/scripts/helper_scripts/assign_fixed_chains.py" --input_path="$path_for_parsed_chains" --output_path="$path_for_assigned_chains" --chain_list "$chains_to_design" + +python "${PROJECT_ROOT}/scripts/helper_scripts/make_fixed_positions_dict.py" --input_path="$path_for_parsed_chains" --output_path="$path_for_fixed_positions" --chain_list "$chains_to_design" --position_list "$design_only_positions" --specify_non_fixed + +python "${PROJECT_ROOT}/scripts/inference.py" \ + --jsonl_path "$path_for_parsed_chains" \ + --chain_id_jsonl "$path_for_assigned_chains" \ + --fixed_positions_jsonl "$path_for_fixed_positions" \ + --out_folder "$output_dir" \ + --num_seq_per_target 2 \ + --sampling_temp "0.1" \ + --seed 37 \ + --batch_size 1 \ + --path_to_model_weights "${PROJECT_ROOT}/weight/vanilla_model_weights" diff --git a/scripts/infer_examples/submit_example_5.sh b/scripts/infer_examples/submit_example_5.sh new file mode 100644 index 0000000000000000000000000000000000000000..78d5e20e853800a953c03b23763a3c45819aa382 --- /dev/null +++ b/scripts/infer_examples/submit_example_5.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +ONESCIENCE_ROOT="${ONESCIENCE_ROOT:-$(cd "${PROJECT_ROOT}/.." && pwd)}" +export PYTHONPATH="${PROJECT_ROOT}/model:${ONESCIENCE_ROOT}/src:${PYTHONPATH:-}" + +folder_with_pdbs="${PROJECT_ROOT}/data/inputs/PDB_complexes/pdbs/" + +output_dir="${PROJECT_ROOT}/outputs/example_5_outputs" +if [ ! -d "$output_dir" ] +then + mkdir -p "$output_dir" +fi + + +path_for_parsed_chains=$output_dir"/parsed_pdbs.jsonl" +path_for_assigned_chains=$output_dir"/assigned_pdbs.jsonl" +path_for_fixed_positions=$output_dir"/fixed_pdbs.jsonl" +path_for_tied_positions=$output_dir"/tied_pdbs.jsonl" +chains_to_design="A C" +fixed_positions="9 10 11 12 13 14 15 16 17 18 19 20 21 22 23, 10 11 18 19 20 22" +tied_positions="1 2 3 4 5 6 7 8, 1 2 3 4 5 6 7 8" #two list must match in length; residue 1 in chain A and C will be sampled togther; + +python "${PROJECT_ROOT}/scripts/helper_scripts/parse_multiple_chains.py" --input_path="$folder_with_pdbs" --output_path="$path_for_parsed_chains" + +python "${PROJECT_ROOT}/scripts/helper_scripts/assign_fixed_chains.py" --input_path="$path_for_parsed_chains" --output_path="$path_for_assigned_chains" --chain_list "$chains_to_design" + +python "${PROJECT_ROOT}/scripts/helper_scripts/make_fixed_positions_dict.py" --input_path="$path_for_parsed_chains" --output_path="$path_for_fixed_positions" --chain_list "$chains_to_design" --position_list "$fixed_positions" + +python "${PROJECT_ROOT}/scripts/helper_scripts/make_tied_positions_dict.py" --input_path="$path_for_parsed_chains" --output_path="$path_for_tied_positions" --chain_list "$chains_to_design" --position_list "$tied_positions" + +python "${PROJECT_ROOT}/scripts/inference.py" \ + --jsonl_path "$path_for_parsed_chains" \ + --chain_id_jsonl "$path_for_assigned_chains" \ + --fixed_positions_jsonl "$path_for_fixed_positions" \ + --tied_positions_jsonl "$path_for_tied_positions" \ + --out_folder "$output_dir" \ + --num_seq_per_target 2 \ + --sampling_temp "0.1" \ + --seed 37 \ + --batch_size 1 \ + --path_to_model_weights "${PROJECT_ROOT}/weight/vanilla_model_weights" diff --git a/scripts/infer_examples/submit_example_6.sh b/scripts/infer_examples/submit_example_6.sh new file mode 100644 index 0000000000000000000000000000000000000000..93ed5311fd09dfb790466f367dd9777a40183b00 --- /dev/null +++ b/scripts/infer_examples/submit_example_6.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +ONESCIENCE_ROOT="${ONESCIENCE_ROOT:-$(cd "${PROJECT_ROOT}/.." && pwd)}" +export PYTHONPATH="${PROJECT_ROOT}/model:${ONESCIENCE_ROOT}/src:${PYTHONPATH:-}" + +folder_with_pdbs="${PROJECT_ROOT}/data/inputs/PDB_homooligomers/pdbs/" + +output_dir="${PROJECT_ROOT}/outputs/example_6_outputs" +if [ ! -d "$output_dir" ] +then + mkdir -p "$output_dir" +fi + + +path_for_parsed_chains=$output_dir"/parsed_pdbs.jsonl" +path_for_tied_positions=$output_dir"/tied_pdbs.jsonl" +path_for_designed_sequences=$output_dir"/temp_0.1" + +python "${PROJECT_ROOT}/scripts/helper_scripts/parse_multiple_chains.py" --input_path="$folder_with_pdbs" --output_path="$path_for_parsed_chains" + +python "${PROJECT_ROOT}/scripts/helper_scripts/make_tied_positions_dict.py" --input_path="$path_for_parsed_chains" --output_path="$path_for_tied_positions" --homooligomer 1 + +python "${PROJECT_ROOT}/scripts/inference.py" \ + --jsonl_path "$path_for_parsed_chains" \ + --tied_positions_jsonl "$path_for_tied_positions" \ + --out_folder "$output_dir" \ + --num_seq_per_target 2 \ + --sampling_temp "0.2" \ + --seed 37 \ + --batch_size 1 \ + --path_to_model_weights "${PROJECT_ROOT}/weight/vanilla_model_weights" diff --git a/scripts/infer_examples/submit_example_7.sh b/scripts/infer_examples/submit_example_7.sh new file mode 100644 index 0000000000000000000000000000000000000000..dcfd66df2ae7504cbe5e2b5a06e2f07354fe49ee --- /dev/null +++ b/scripts/infer_examples/submit_example_7.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +ONESCIENCE_ROOT="${ONESCIENCE_ROOT:-$(cd "${PROJECT_ROOT}/.." && pwd)}" +export PYTHONPATH="${PROJECT_ROOT}/model:${ONESCIENCE_ROOT}/src:${PYTHONPATH:-}" + +folder_with_pdbs="${PROJECT_ROOT}/data/inputs/PDB_monomers/pdbs/" + +output_dir="${PROJECT_ROOT}/outputs/example_7_outputs" +if [ ! -d "$output_dir" ] +then + mkdir -p "$output_dir" +fi + +path_for_parsed_chains=$output_dir"/parsed_pdbs.jsonl" + +python "${PROJECT_ROOT}/scripts/helper_scripts/parse_multiple_chains.py" --input_path="$folder_with_pdbs" --output_path="$path_for_parsed_chains" + +python "${PROJECT_ROOT}/scripts/inference.py" \ + --jsonl_path "$path_for_parsed_chains" \ + --out_folder "$output_dir" \ + --num_seq_per_target 1 \ + --sampling_temp "0.1" \ + --unconditional_probs_only 1 \ + --seed 37 \ + --batch_size 1 \ + --path_to_model_weights "${PROJECT_ROOT}/weight/vanilla_model_weights" diff --git a/scripts/infer_examples/submit_example_8.sh b/scripts/infer_examples/submit_example_8.sh new file mode 100644 index 0000000000000000000000000000000000000000..80954f15796a38fc88cffacbeba28ad1a284bbd5 --- /dev/null +++ b/scripts/infer_examples/submit_example_8.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +ONESCIENCE_ROOT="${ONESCIENCE_ROOT:-$(cd "${PROJECT_ROOT}/.." && pwd)}" +export PYTHONPATH="${PROJECT_ROOT}/model:${ONESCIENCE_ROOT}/src:${PYTHONPATH:-}" + +folder_with_pdbs="${PROJECT_ROOT}/data/inputs/PDB_monomers/pdbs/" + +output_dir="${PROJECT_ROOT}/outputs/example_8_outputs" +if [ ! -d "$output_dir" ] +then + mkdir -p "$output_dir" +fi + +path_for_bias=$output_dir"/bias_pdbs.jsonl" +#Adding global polar amino acid bias (Doug Tischer) +AA_list="D E H K N Q R S T W Y" +bias_list="1.39 1.39 1.39 1.39 1.39 1.39 1.39 1.39 1.39 1.39 1.39" +python "${PROJECT_ROOT}/scripts/helper_scripts/make_bias_AA.py" --output_path="$path_for_bias" --AA_list="$AA_list" --bias_list="$bias_list" + +path_for_parsed_chains=$output_dir"/parsed_pdbs.jsonl" +python "${PROJECT_ROOT}/scripts/helper_scripts/parse_multiple_chains.py" --input_path="$folder_with_pdbs" --output_path="$path_for_parsed_chains" + +python "${PROJECT_ROOT}/scripts/inference.py" \ + --jsonl_path "$path_for_parsed_chains" \ + --out_folder "$output_dir" \ + --bias_AA_jsonl "$path_for_bias" \ + --num_seq_per_target 2 \ + --sampling_temp "0.1" \ + --seed 37 \ + --batch_size 1 \ + --path_to_model_weights "${PROJECT_ROOT}/weight/vanilla_model_weights" diff --git a/scripts/infer_examples/submit_example_pssm.sh b/scripts/infer_examples/submit_example_pssm.sh new file mode 100644 index 0000000000000000000000000000000000000000..a0088ffc2fb56cb9f597df2b14a954d9b1a8806d --- /dev/null +++ b/scripts/infer_examples/submit_example_pssm.sh @@ -0,0 +1,49 @@ +#!/bin/bash + + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +ONESCIENCE_ROOT="${ONESCIENCE_ROOT:-$(cd "${PROJECT_ROOT}/.." && pwd)}" +export PYTHONPATH="${PROJECT_ROOT}/model:${ONESCIENCE_ROOT}/src:${PYTHONPATH:-}" + + +#new_probabilities_using_PSSM = (1-pssm_multi*pssm_coef_gathered[:,None])*probs + pssm_multi*pssm_coef_gathered[:,None]*pssm_bias_gathered +#probs - predictions from MPNN +#pssm_bias_gathered - input PSSM bias (needs to be a probability distribution) +#pssm_multi - a number between 0.0 (no bias) and 1.0 (no MPNN) inputed via flag --pssm_multi; this is a global number equally applied to all the residues +#pssm_coef_gathered - a number between 0.0 (no bias) and 1.0 (no MPNN) inputed via helper_scripts/make_pssm_input_dict.py can be adjusted per residue level; i.e only apply PSSM bias to specific residues; or chains + + + +pssm_input_path="${PROJECT_ROOT}/data/inputs/PSSM_inputs" +folder_with_pdbs="${PROJECT_ROOT}/data/inputs/PDB_complexes/pdbs/" + +output_dir="${PROJECT_ROOT}/outputs/example_pssm_outputs" +if [ ! -d "$output_dir" ] +then + mkdir -p "$output_dir" +fi + +path_for_parsed_chains=$output_dir"/parsed_pdbs.jsonl" +path_for_assigned_chains=$output_dir"/assigned_pdbs.jsonl" +pssm=$output_dir"/pssm.jsonl" +chains_to_design="A B" + +python "${PROJECT_ROOT}/scripts/helper_scripts/parse_multiple_chains.py" --input_path="$folder_with_pdbs" --output_path="$path_for_parsed_chains" + +python "${PROJECT_ROOT}/scripts/helper_scripts/assign_fixed_chains.py" --input_path="$path_for_parsed_chains" --output_path="$path_for_assigned_chains" --chain_list "$chains_to_design" + +python "${PROJECT_ROOT}/scripts/helper_scripts/make_pssm_input_dict.py" --jsonl_input_path="$path_for_parsed_chains" --PSSM_input_path="$pssm_input_path" --output_path="$pssm" + +python "${PROJECT_ROOT}/scripts/inference.py" \ + --jsonl_path "$path_for_parsed_chains" \ + --chain_id_jsonl "$path_for_assigned_chains" \ + --out_folder "$output_dir" \ + --num_seq_per_target 2 \ + --sampling_temp "0.1" \ + --seed 37 \ + --batch_size 1 \ + --pssm_jsonl "$pssm" \ + --pssm_multi 0.3 \ + --pssm_bias_flag 1 \ + --path_to_model_weights "${PROJECT_ROOT}/weight/vanilla_model_weights" diff --git a/scripts/inference.py b/scripts/inference.py new file mode 100644 index 0000000000000000000000000000000000000000..064b9e359d2074d310fa03136762005b60a77a10 --- /dev/null +++ b/scripts/inference.py @@ -0,0 +1,494 @@ +import argparse +import os +import sys + +_PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__)) +while _PROJECT_ROOT and not os.path.isdir(os.path.join(_PROJECT_ROOT, "model")): + _PARENT = os.path.dirname(_PROJECT_ROOT) + if _PARENT == _PROJECT_ROOT: + break + _PROJECT_ROOT = _PARENT +_MODEL_ROOT = os.path.join(_PROJECT_ROOT, "model") +_ONESCIENCE_ROOT = os.environ.get("ONESCIENCE_ROOT") +for _path in (_MODEL_ROOT, _PROJECT_ROOT): + if os.path.exists(_path) and _path not in sys.path: + sys.path.insert(0, _path) +if _ONESCIENCE_ROOT: + _ONESCIENCE_SRC = os.path.join(_ONESCIENCE_ROOT, "src") + for _path in (_ONESCIENCE_SRC, _ONESCIENCE_ROOT): + if os.path.exists(_path) and _path not in sys.path: + sys.path.insert(0, _path) +import os.path + + +def _resolve_model_folder_path(args): + if args.path_to_model_weights: + return os.path.abspath(os.path.normpath(args.path_to_model_weights)) + if args.ca_only and args.use_soluble_model: + raise ValueError("CA-SolubleMPNN is not available yet") + if args.ca_only: + variant = "ca_model_weights" + elif args.use_soluble_model: + variant = "soluble_model_weights" + else: + variant = "vanilla_model_weights" + return os.path.join(_PROJECT_ROOT, "weight", variant) + + +def main(args): + + import json, time, os, sys, glob + import shutil + import warnings + import numpy as np + import torch + from torch import optim + from torch.utils.data import DataLoader + from torch.utils.data.dataset import random_split, Subset + import copy + import torch.nn as nn + import torch.nn.functional as F + import random + import os.path + import subprocess + + from proteinmpnn.protein_mpnn_utils import loss_nll, loss_smoothed, gather_edges, gather_nodes, gather_nodes_t, cat_neighbors_nodes, _scores, _S_to_seq, tied_featurize, parse_PDB, parse_fasta + from proteinmpnn.protein_mpnn_utils import StructureDataset, StructureDatasetPDB, ProteinMPNN + + if args.seed: + seed=args.seed + else: + seed=int(np.random.randint(0, high=999, size=1, dtype=int)[0]) + + torch.manual_seed(seed) + random.seed(seed) + np.random.seed(seed) + + hidden_dim = 128 + num_layers = 3 + + + try: + model_folder_path = _resolve_model_folder_path(args) + except ValueError as exc: + print(f"WARNING: {exc}") + sys.exit(1) + if not args.path_to_model_weights: + if args.ca_only: + print("Using CA-ProteinMPNN!") + elif args.use_soluble_model: + print("Using ProteinMPNN trained on soluble proteins only!") + + checkpoint_path = os.path.join(model_folder_path, f'{args.model_name}.pt') + folder_for_outputs = args.out_folder + + NUM_BATCHES = args.num_seq_per_target//args.batch_size + BATCH_COPIES = args.batch_size + temperatures = [float(item) for item in args.sampling_temp.split()] + omit_AAs_list = args.omit_AAs + alphabet = 'ACDEFGHIKLMNPQRSTVWYX' + alphabet_dict = dict(zip(alphabet, range(21))) + print_all = args.suppress_print == 0 + omit_AAs_np = np.array([AA in omit_AAs_list for AA in alphabet]).astype(np.float32) + device = torch.device("cuda:0" if (torch.cuda.is_available()) else "cpu") + if os.path.isfile(args.chain_id_jsonl): + with open(args.chain_id_jsonl, 'r') as json_file: + json_list = list(json_file) + for json_str in json_list: + chain_id_dict = json.loads(json_str) + else: + chain_id_dict = None + if print_all: + print(40*'-') + print('chain_id_jsonl is NOT loaded') + + if os.path.isfile(args.fixed_positions_jsonl): + with open(args.fixed_positions_jsonl, 'r') as json_file: + json_list = list(json_file) + for json_str in json_list: + fixed_positions_dict = json.loads(json_str) + else: + if print_all: + print(40*'-') + print('fixed_positions_jsonl is NOT loaded') + fixed_positions_dict = None + + + if os.path.isfile(args.pssm_jsonl): + with open(args.pssm_jsonl, 'r') as json_file: + json_list = list(json_file) + pssm_dict = {} + for json_str in json_list: + pssm_dict.update(json.loads(json_str)) + else: + if print_all: + print(40*'-') + print('pssm_jsonl is NOT loaded') + pssm_dict = None + + + if os.path.isfile(args.omit_AA_jsonl): + with open(args.omit_AA_jsonl, 'r') as json_file: + json_list = list(json_file) + for json_str in json_list: + omit_AA_dict = json.loads(json_str) + else: + if print_all: + print(40*'-') + print('omit_AA_jsonl is NOT loaded') + omit_AA_dict = None + + + if os.path.isfile(args.bias_AA_jsonl): + with open(args.bias_AA_jsonl, 'r') as json_file: + json_list = list(json_file) + for json_str in json_list: + bias_AA_dict = json.loads(json_str) + else: + if print_all: + print(40*'-') + print('bias_AA_jsonl is NOT loaded') + bias_AA_dict = None + + + if os.path.isfile(args.tied_positions_jsonl): + with open(args.tied_positions_jsonl, 'r') as json_file: + json_list = list(json_file) + for json_str in json_list: + tied_positions_dict = json.loads(json_str) + else: + if print_all: + print(40*'-') + print('tied_positions_jsonl is NOT loaded') + tied_positions_dict = None + + + if os.path.isfile(args.bias_by_res_jsonl): + with open(args.bias_by_res_jsonl, 'r') as json_file: + json_list = list(json_file) + + for json_str in json_list: + bias_by_res_dict = json.loads(json_str) + if print_all: + print('bias by residue dictionary is loaded') + else: + if print_all: + print(40*'-') + print('bias by residue dictionary is not loaded, or not provided') + bias_by_res_dict = None + + + if print_all: + print(40*'-') + bias_AAs_np = np.zeros(len(alphabet)) + if bias_AA_dict: + for n, AA in enumerate(alphabet): + if AA in list(bias_AA_dict.keys()): + bias_AAs_np[n] = bias_AA_dict[AA] + + if args.pdb_path: + pdb_dict_list = parse_PDB(args.pdb_path, ca_only=args.ca_only) + dataset_valid = StructureDatasetPDB(pdb_dict_list, truncate=None, max_length=args.max_length) + all_chain_list = [item[-1:] for item in list(pdb_dict_list[0]) if item[:9]=='seq_chain'] #['A','B', 'C',...] + if args.pdb_path_chains: + designed_chain_list = [str(item) for item in args.pdb_path_chains.split()] + else: + designed_chain_list = all_chain_list + fixed_chain_list = [letter for letter in all_chain_list if letter not in designed_chain_list] + chain_id_dict = {} + chain_id_dict[pdb_dict_list[0]['name']]= (designed_chain_list, fixed_chain_list) + else: + dataset_valid = StructureDataset(args.jsonl_path, truncate=None, max_length=args.max_length, verbose=print_all) + + checkpoint = torch.load(checkpoint_path, map_location=device) + noise_level_print = checkpoint['noise_level'] + model = ProteinMPNN(ca_only=args.ca_only, num_letters=21, node_features=hidden_dim, edge_features=hidden_dim, hidden_dim=hidden_dim, num_encoder_layers=num_layers, num_decoder_layers=num_layers, augment_eps=args.backbone_noise, k_neighbors=checkpoint['num_edges']) + model.to(device) + model.load_state_dict(checkpoint['model_state_dict']) + model.eval() + + if print_all: + print(40*'-') + print('Number of edges:', checkpoint['num_edges']) + print(f'Training noise level: {noise_level_print}A') + + # Build paths for experiment + base_folder = folder_for_outputs + if base_folder[-1] != '/': + base_folder = base_folder + '/' + if not os.path.exists(base_folder): + os.makedirs(base_folder) + + if not os.path.exists(base_folder + 'seqs'): + os.makedirs(base_folder + 'seqs') + + if args.save_score: + if not os.path.exists(base_folder + 'scores'): + os.makedirs(base_folder + 'scores') + + if args.score_only: + if not os.path.exists(base_folder + 'score_only'): + os.makedirs(base_folder + 'score_only') + + + if args.conditional_probs_only: + if not os.path.exists(base_folder + 'conditional_probs_only'): + os.makedirs(base_folder + 'conditional_probs_only') + + if args.unconditional_probs_only: + if not os.path.exists(base_folder + 'unconditional_probs_only'): + os.makedirs(base_folder + 'unconditional_probs_only') + + if args.save_probs: + if not os.path.exists(base_folder + 'probs'): + os.makedirs(base_folder + 'probs') + + # Timing + start_time = time.time() + total_residues = 0 + protein_list = [] + total_step = 0 + # Validation epoch + with torch.no_grad(): + test_sum, test_weights = 0., 0. + for ix, protein in enumerate(dataset_valid): + score_list = [] + global_score_list = [] + all_probs_list = [] + all_log_probs_list = [] + S_sample_list = [] + batch_clones = [copy.deepcopy(protein) for i in range(BATCH_COPIES)] + X, S, mask, lengths, chain_M, chain_encoding_all, chain_list_list, visible_list_list, masked_list_list, masked_chain_length_list_list, chain_M_pos, omit_AA_mask, residue_idx, dihedral_mask, tied_pos_list_of_lists_list, pssm_coef, pssm_bias, pssm_log_odds_all, bias_by_res_all, tied_beta = tied_featurize(batch_clones, device, chain_id_dict, fixed_positions_dict, omit_AA_dict, tied_positions_dict, pssm_dict, bias_by_res_dict, ca_only=args.ca_only) + pssm_log_odds_mask = (pssm_log_odds_all > args.pssm_threshold).float() #1.0 for true, 0.0 for false + name_ = batch_clones[0]['name'] + if args.score_only: + loop_c = 0 + if args.path_to_fasta: + fasta_names, fasta_seqs = parse_fasta(args.path_to_fasta, omit=["/"]) + loop_c = len(fasta_seqs) + for fc in range(1+loop_c): + if fc == 0: + structure_sequence_score_file = base_folder + '/score_only/' + batch_clones[0]['name'] + f'_pdb' + else: + structure_sequence_score_file = base_folder + '/score_only/' + batch_clones[0]['name'] + f'_fasta_{fc}' + native_score_list = [] + global_native_score_list = [] + if fc > 0: + input_seq_length = len(fasta_seqs[fc-1]) + S_input = torch.tensor([alphabet_dict[AA] for AA in fasta_seqs[fc-1]], device=device)[None,:].repeat(X.shape[0], 1) + S[:,:input_seq_length] = S_input #assumes that S and S_input are alphabetically sorted for masked_chains + for j in range(NUM_BATCHES): + randn_1 = torch.randn(chain_M.shape, device=X.device) + log_probs = model(X, S, mask, chain_M*chain_M_pos, residue_idx, chain_encoding_all, randn_1) + mask_for_loss = mask*chain_M*chain_M_pos + scores = _scores(S, log_probs, mask_for_loss) + native_score = scores.cpu().data.numpy() + native_score_list.append(native_score) + global_scores = _scores(S, log_probs, mask) + global_native_score = global_scores.cpu().data.numpy() + global_native_score_list.append(global_native_score) + native_score = np.concatenate(native_score_list, 0) + global_native_score = np.concatenate(global_native_score_list, 0) + ns_mean = native_score.mean() + ns_mean_print = np.format_float_positional(np.float32(ns_mean), unique=False, precision=4) + ns_std = native_score.std() + ns_std_print = np.format_float_positional(np.float32(ns_std), unique=False, precision=4) + + global_ns_mean = global_native_score.mean() + global_ns_mean_print = np.format_float_positional(np.float32(global_ns_mean), unique=False, precision=4) + global_ns_std = global_native_score.std() + global_ns_std_print = np.format_float_positional(np.float32(global_ns_std), unique=False, precision=4) + + ns_sample_size = native_score.shape[0] + seq_str = _S_to_seq(S[0,], chain_M[0,]) + np.savez(structure_sequence_score_file, score=native_score, global_score=global_native_score, S=S[0,].cpu().numpy(), seq_str=seq_str) + if print_all: + if fc == 0: + print(f'Score for {name_} from PDB, mean: {ns_mean_print}, std: {ns_std_print}, sample size: {ns_sample_size}, global score, mean: {global_ns_mean_print}, std: {global_ns_std_print}, sample size: {ns_sample_size}') + else: + print(f'Score for {name_}_{fc} from FASTA, mean: {ns_mean_print}, std: {ns_std_print}, sample size: {ns_sample_size}, global score, mean: {global_ns_mean_print}, std: {global_ns_std_print}, sample size: {ns_sample_size}') + elif args.conditional_probs_only: + if print_all: + print(f'Calculating conditional probabilities for {name_}') + conditional_probs_only_file = base_folder + '/conditional_probs_only/' + batch_clones[0]['name'] + log_conditional_probs_list = [] + for j in range(NUM_BATCHES): + randn_1 = torch.randn(chain_M.shape, device=X.device) + log_conditional_probs = model.conditional_probs(X, S, mask, chain_M*chain_M_pos, residue_idx, chain_encoding_all, randn_1, args.conditional_probs_only_backbone) + log_conditional_probs_list.append(log_conditional_probs.cpu().numpy()) + concat_log_p = np.concatenate(log_conditional_probs_list, 0) #[B, L, 21] + mask_out = (chain_M*chain_M_pos*mask)[0,].cpu().numpy() + np.savez(conditional_probs_only_file, log_p=concat_log_p, S=S[0,].cpu().numpy(), mask=mask[0,].cpu().numpy(), design_mask=mask_out) + elif args.unconditional_probs_only: + if print_all: + print(f'Calculating sequence unconditional probabilities for {name_}') + unconditional_probs_only_file = base_folder + '/unconditional_probs_only/' + batch_clones[0]['name'] + log_unconditional_probs_list = [] + for j in range(NUM_BATCHES): + log_unconditional_probs = model.unconditional_probs(X, mask, residue_idx, chain_encoding_all) + log_unconditional_probs_list.append(log_unconditional_probs.cpu().numpy()) + concat_log_p = np.concatenate(log_unconditional_probs_list, 0) #[B, L, 21] + mask_out = (chain_M*chain_M_pos*mask)[0,].cpu().numpy() + np.savez(unconditional_probs_only_file, log_p=concat_log_p, S=S[0,].cpu().numpy(), mask=mask[0,].cpu().numpy(), design_mask=mask_out) + else: + randn_1 = torch.randn(chain_M.shape, device=X.device) + log_probs = model(X, S, mask, chain_M*chain_M_pos, residue_idx, chain_encoding_all, randn_1) + mask_for_loss = mask*chain_M*chain_M_pos + scores = _scores(S, log_probs, mask_for_loss) #score only the redesigned part + native_score = scores.cpu().data.numpy() + global_scores = _scores(S, log_probs, mask) #score the whole structure-sequence + global_native_score = global_scores.cpu().data.numpy() + # Generate some sequences + ali_file = base_folder + '/seqs/' + batch_clones[0]['name'] + '.fa' + score_file = base_folder + '/scores/' + batch_clones[0]['name'] + '.npz' + probs_file = base_folder + '/probs/' + batch_clones[0]['name'] + '.npz' + if print_all: + print(f'Generating sequences for: {name_}') + t0 = time.time() + with open(ali_file, 'w') as f: + for temp in temperatures: + for j in range(NUM_BATCHES): + randn_2 = torch.randn(chain_M.shape, device=X.device) + if tied_positions_dict == None: + sample_dict = model.sample(X, randn_2, S, chain_M, chain_encoding_all, residue_idx, mask=mask, temperature=temp, omit_AAs_np=omit_AAs_np, bias_AAs_np=bias_AAs_np, chain_M_pos=chain_M_pos, omit_AA_mask=omit_AA_mask, pssm_coef=pssm_coef, pssm_bias=pssm_bias, pssm_multi=args.pssm_multi, pssm_log_odds_flag=bool(args.pssm_log_odds_flag), pssm_log_odds_mask=pssm_log_odds_mask, pssm_bias_flag=bool(args.pssm_bias_flag), bias_by_res=bias_by_res_all) + S_sample = sample_dict["S"] + else: + sample_dict = model.tied_sample(X, randn_2, S, chain_M, chain_encoding_all, residue_idx, mask=mask, temperature=temp, omit_AAs_np=omit_AAs_np, bias_AAs_np=bias_AAs_np, chain_M_pos=chain_M_pos, omit_AA_mask=omit_AA_mask, pssm_coef=pssm_coef, pssm_bias=pssm_bias, pssm_multi=args.pssm_multi, pssm_log_odds_flag=bool(args.pssm_log_odds_flag), pssm_log_odds_mask=pssm_log_odds_mask, pssm_bias_flag=bool(args.pssm_bias_flag), tied_pos=tied_pos_list_of_lists_list[0], tied_beta=tied_beta, bias_by_res=bias_by_res_all) + # Compute scores + S_sample = sample_dict["S"] + log_probs = model(X, S_sample, mask, chain_M*chain_M_pos, residue_idx, chain_encoding_all, randn_2, use_input_decoding_order=True, decoding_order=sample_dict["decoding_order"]) + mask_for_loss = mask*chain_M*chain_M_pos + scores = _scores(S_sample, log_probs, mask_for_loss) + scores = scores.cpu().data.numpy() + + global_scores = _scores(S_sample, log_probs, mask) #score the whole structure-sequence + global_scores = global_scores.cpu().data.numpy() + + all_probs_list.append(sample_dict["probs"].cpu().data.numpy()) + all_log_probs_list.append(log_probs.cpu().data.numpy()) + S_sample_list.append(S_sample.cpu().data.numpy()) + for b_ix in range(BATCH_COPIES): + masked_chain_length_list = masked_chain_length_list_list[b_ix] + masked_list = masked_list_list[b_ix] + seq_recovery_rate = torch.sum(torch.sum(torch.nn.functional.one_hot(S[b_ix], 21)*torch.nn.functional.one_hot(S_sample[b_ix], 21),axis=-1)*mask_for_loss[b_ix])/torch.sum(mask_for_loss[b_ix]) + seq = _S_to_seq(S_sample[b_ix], chain_M[b_ix]) + score = scores[b_ix] + score_list.append(score) + global_score = global_scores[b_ix] + global_score_list.append(global_score) + native_seq = _S_to_seq(S[b_ix], chain_M[b_ix]) + if b_ix == 0 and j==0 and temp==temperatures[0]: + start = 0 + end = 0 + list_of_AAs = [] + for mask_l in masked_chain_length_list: + end += mask_l + list_of_AAs.append(native_seq[start:end]) + start = end + native_seq = "".join(list(np.array(list_of_AAs)[np.argsort(masked_list)])) + l0 = 0 + for mc_length in list(np.array(masked_chain_length_list)[np.argsort(masked_list)])[:-1]: + l0 += mc_length + native_seq = native_seq[:l0] + '/' + native_seq[l0:] + l0 += 1 + sorted_masked_chain_letters = np.argsort(masked_list_list[0]) + print_masked_chains = [masked_list_list[0][i] for i in sorted_masked_chain_letters] + sorted_visible_chain_letters = np.argsort(visible_list_list[0]) + print_visible_chains = [visible_list_list[0][i] for i in sorted_visible_chain_letters] + native_score_print = np.format_float_positional(np.float32(native_score.mean()), unique=False, precision=4) + global_native_score_print = np.format_float_positional(np.float32(global_native_score.mean()), unique=False, precision=4) + script_dir = os.path.dirname(os.path.realpath(__file__)) + try: + commit_str = subprocess.check_output(f'git --git-dir {script_dir}/.git rev-parse HEAD', shell=True, stderr=subprocess.DEVNULL).decode().strip() + except subprocess.CalledProcessError: + commit_str = 'unknown' + if args.ca_only: + print_model_name = 'CA_model_name' + else: + print_model_name = 'model_name' + f.write('>{}, score={}, global_score={}, fixed_chains={}, designed_chains={}, {}={}, git_hash={}, seed={}\n{}\n'.format(name_, native_score_print, global_native_score_print, print_visible_chains, print_masked_chains, print_model_name, args.model_name, commit_str, seed, native_seq)) #write the native sequence + start = 0 + end = 0 + list_of_AAs = [] + for mask_l in masked_chain_length_list: + end += mask_l + list_of_AAs.append(seq[start:end]) + start = end + + seq = "".join(list(np.array(list_of_AAs)[np.argsort(masked_list)])) + l0 = 0 + for mc_length in list(np.array(masked_chain_length_list)[np.argsort(masked_list)])[:-1]: + l0 += mc_length + seq = seq[:l0] + '/' + seq[l0:] + l0 += 1 + score_print = np.format_float_positional(np.float32(score), unique=False, precision=4) + global_score_print = np.format_float_positional(np.float32(global_score), unique=False, precision=4) + seq_rec_print = np.format_float_positional(np.float32(seq_recovery_rate.detach().cpu().numpy()), unique=False, precision=4) + sample_number = j*BATCH_COPIES+b_ix+1 + f.write('>T={}, sample={}, score={}, global_score={}, seq_recovery={}\n{}\n'.format(temp,sample_number,score_print,global_score_print,seq_rec_print,seq)) #write generated sequence + if args.save_score: + np.savez(score_file, score=np.array(score_list, np.float32), global_score=np.array(global_score_list, np.float32)) + if args.save_probs: + all_probs_concat = np.concatenate(all_probs_list) + all_log_probs_concat = np.concatenate(all_log_probs_list) + S_sample_concat = np.concatenate(S_sample_list) + np.savez(probs_file, probs=np.array(all_probs_concat, np.float32), log_probs=np.array(all_log_probs_concat, np.float32), S=np.array(S_sample_concat, np.int32), mask=mask_for_loss.cpu().data.numpy(), chain_order=chain_list_list) + t1 = time.time() + dt = round(float(t1-t0), 4) + num_seqs = len(temperatures)*NUM_BATCHES*BATCH_COPIES + total_length = X.shape[1] + if print_all: + print(f'{num_seqs} sequences of length {total_length} generated in {dt} seconds') + +if __name__ == "__main__": + argparser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + argparser.add_argument("--suppress_print", type=int, default=0, help="0 for False, 1 for True") + + + argparser.add_argument("--ca_only", action="store_true", default=False, help="Parse CA-only structures and use CA-only models (default: false)") + argparser.add_argument("--path_to_model_weights", type=str, default="", help="Path to model weights folder;") + argparser.add_argument("--model_name", type=str, default="v_48_020", help="ProteinMPNN model name: v_48_002, v_48_010, v_48_020, v_48_030; v_48_010=version with 48 edges 0.10A noise") + argparser.add_argument("--use_soluble_model", action="store_true", default=False, help="Flag to load ProteinMPNN weights trained on soluble proteins only.") + + + argparser.add_argument("--seed", type=int, default=0, help="If set to 0 then a random seed will be picked;") + + argparser.add_argument("--save_score", type=int, default=0, help="0 for False, 1 for True; save score=-log_prob to npy files") + argparser.add_argument("--save_probs", type=int, default=0, help="0 for False, 1 for True; save MPNN predicted probabilites per position") + + argparser.add_argument("--score_only", type=int, default=0, help="0 for False, 1 for True; score input backbone-sequence pairs") + argparser.add_argument("--path_to_fasta", type=str, default="", help="score provided input sequence in a fasta format; e.g. GGGGGG/PPPPS/WWW for chains A, B, C sorted alphabetically and separated by /") + + + argparser.add_argument("--conditional_probs_only", type=int, default=0, help="0 for False, 1 for True; output conditional probabilities p(s_i given the rest of the sequence and backbone)") + argparser.add_argument("--conditional_probs_only_backbone", type=int, default=0, help="0 for False, 1 for True; if true output conditional probabilities p(s_i given backbone)") + argparser.add_argument("--unconditional_probs_only", type=int, default=0, help="0 for False, 1 for True; output unconditional probabilities p(s_i given backbone) in one forward pass") + + argparser.add_argument("--backbone_noise", type=float, default=0.00, help="Standard deviation of Gaussian noise to add to backbone atoms") + argparser.add_argument("--num_seq_per_target", type=int, default=1, help="Number of sequences to generate per target") + argparser.add_argument("--batch_size", type=int, default=1, help="Batch size; can set higher for titan, quadro GPUs, reduce this if running out of GPU memory") + argparser.add_argument("--max_length", type=int, default=200000, help="Max sequence length") + argparser.add_argument("--sampling_temp", type=str, default="0.1", help="A string of temperatures, 0.2 0.25 0.5. Sampling temperature for amino acids. Suggested values 0.1, 0.15, 0.2, 0.25, 0.3. Higher values will lead to more diversity.") + + argparser.add_argument("--out_folder", type=str, help="Path to a folder to output sequences, e.g. /home/out/") + argparser.add_argument("--pdb_path", type=str, default='', help="Path to a single PDB to be designed") + argparser.add_argument("--pdb_path_chains", type=str, default='', help="Define which chains need to be designed for a single PDB ") + argparser.add_argument("--jsonl_path", type=str, help="Path to a folder with parsed pdb into jsonl") + argparser.add_argument("--chain_id_jsonl",type=str, default='', help="Path to a dictionary specifying which chains need to be designed and which ones are fixed, if not specied all chains will be designed.") + argparser.add_argument("--fixed_positions_jsonl", type=str, default='', help="Path to a dictionary with fixed positions") + argparser.add_argument("--omit_AAs", type=list, default='X', help="Specify which amino acids should be omitted in the generated sequence, e.g. 'AC' would omit alanine and cystine.") + argparser.add_argument("--bias_AA_jsonl", type=str, default='', help="Path to a dictionary which specifies AA composion bias if neededi, e.g. {A: -1.1, F: 0.7} would make A less likely and F more likely.") + + argparser.add_argument("--bias_by_res_jsonl", default='', help="Path to dictionary with per position bias.") + argparser.add_argument("--omit_AA_jsonl", type=str, default='', help="Path to a dictionary which specifies which amino acids need to be omited from design at specific chain indices") + argparser.add_argument("--pssm_jsonl", type=str, default='', help="Path to a dictionary with pssm") + argparser.add_argument("--pssm_multi", type=float, default=0.0, help="A value between [0.0, 1.0], 0.0 means do not use pssm, 1.0 ignore MPNN predictions") + argparser.add_argument("--pssm_threshold", type=float, default=0.0, help="A value between -inf + inf to restric per position AAs") + argparser.add_argument("--pssm_log_odds_flag", type=int, default=0, help="0 for False, 1 for True") + argparser.add_argument("--pssm_bias_flag", type=int, default=0, help="0 for False, 1 for True") + + argparser.add_argument("--tied_positions_jsonl", type=str, default='', help="Path to a dictionary with tied positions") + + args = argparser.parse_args() + main(args) diff --git a/scripts/test_inference.sh b/scripts/test_inference.sh new file mode 100644 index 0000000000000000000000000000000000000000..81fd4e04fa736cf21bb4354ead68b2a9a22669ca --- /dev/null +++ b/scripts/test_inference.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +ONESCIENCE_ROOT="${ONESCIENCE_ROOT:-$(cd "${PROJECT_ROOT}/.." && pwd)}" +export PYTHONPATH="${PROJECT_ROOT}/model:${ONESCIENCE_ROOT}/src:${PYTHONPATH:-}" + +path_to_PDB="${PROJECT_ROOT}/data/inputs/PDB_monomers/pdbs/5L33.pdb" +output_dir="${PROJECT_ROOT}/outputs/test_inference" + +if [ ! -d $output_dir ] +then + mkdir -p $output_dir +fi + +chains_to_design="A" + +python "${PROJECT_ROOT}/scripts/inference.py" \ + --pdb_path $path_to_PDB \ + --pdb_path_chains "$chains_to_design" \ + --out_folder $output_dir \ + --num_seq_per_target 2 \ + --sampling_temp "0.1" \ + --seed 37 \ + --batch_size 1 \ + --path_to_model_weights "${PROJECT_ROOT}/weight/vanilla_model_weights" diff --git a/scripts/test_train.sh b/scripts/test_train.sh new file mode 100644 index 0000000000000000000000000000000000000000..2b3895aceee86b0958bb986d0c9b6c4fed7542fb --- /dev/null +++ b/scripts/test_train.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +ONESCIENCE_ROOT="${ONESCIENCE_ROOT:-$(cd "${PROJECT_ROOT}/.." && pwd)}" +export PYTHONPATH="${PROJECT_ROOT}/model:${ONESCIENCE_ROOT}/src:${PYTHONPATH:-}" + +PATH_FOR_TRAINING_DATA="${PROJECT_ROOT}/data/pdb_2021aug02_sample" + +python "${PROJECT_ROOT}/scripts/training.py" \ + --path_for_outputs "${PROJECT_ROOT}/outputs/train/exp_020" \ + --path_for_training_data "$PATH_FOR_TRAINING_DATA" \ + --num_examples_per_epoch 1000 \ + --save_model_every_n_epochs 50 diff --git a/scripts/train/LICENSE.txt b/scripts/train/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..553804ee1b65bdebb4f971b1ba226b15e1e2623b --- /dev/null +++ b/scripts/train/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Justas Dauparas + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/scripts/train/colab_training_example.ipynb b/scripts/train/colab_training_example.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..38a4b572860b9d9eafc7efd9567a5b5573414ecf --- /dev/null +++ b/scripts/train/colab_training_example.ipynb @@ -0,0 +1,1154 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "view-in-github" + }, + "source": [ + "\"Open" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "lPQopPm1WWrG", + "outputId": "9546d08c-9248-445e-81b0-b3ba82f4a801" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "--2022-10-26 03:43:33-- https://files.ipd.uw.edu/pub/training_sets/pdb_2021aug02_sample.tar.gz\n", + "Resolving files.ipd.uw.edu (files.ipd.uw.edu)... 198.48.92.75, 128.95.160.134, 128.95.160.135, ...\n", + "Connecting to files.ipd.uw.edu (files.ipd.uw.edu)|198.48.92.75|:443... connected.\n", + "HTTP request sent, awaiting response... 200 OK\n", + "Length: 49690915 (47M) [application/octet-stream]\n", + "Saving to: ‘pdb_2021aug02_sample.tar.gz’\n", + "\n", + "pdb_2021aug02_sampl 100%[===================>] 47.39M 13.5MB/s in 3.7s \n", + "\n", + "2022-10-26 03:43:38 (12.9 MB/s) - ‘pdb_2021aug02_sample.tar.gz’ saved [49690915/49690915]\n", + "\n" + ] + } + ], + "source": [ + "!wget https://files.ipd.uw.edu/pub/training_sets/pdb_2021aug02_sample.tar.gz" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "21to7lSNWemb", + "outputId": "7a51e458-a4e4-4b79-d26b-81570e855d62" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "./pdb_2021aug02_sample/\n", + "./pdb_2021aug02_sample/README\n", + "./pdb_2021aug02_sample/list.csv\n", + "./pdb_2021aug02_sample/pdb/\n", + "./pdb_2021aug02_sample/pdb/l3/\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3g_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3f.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3r_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3o_G.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3b_E.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3t_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3y_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_DB.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l36_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3y_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l36.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l35_G.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l33_F.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3n.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3x.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3r_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3g_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3e.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l39_E.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3f_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l31_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3k_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l35.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l37_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_CB.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_E.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3x_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3u_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_S.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3m.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l38.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3v.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3s_L.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3v_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_I.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3x_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3n_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3s_G.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3e_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l32_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3t_F.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l35_I.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3d_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3u.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3k.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l33_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3g_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3i_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3q_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l33.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3o_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3z_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3t.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3o_H.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l35_H.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l38_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3b_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l38_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l32.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3i_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3g_E.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l33_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3j.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3v_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3c_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3a.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l32_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l39.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3h_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l34_H.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l32_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3c_J.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3s_F.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3c_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3x_E.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_H.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l39_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l31_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3r.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l39_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3l.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_R.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3u_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3z.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l34.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3k_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3t_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3b_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3a_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3o_F.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3g.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3r_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3q.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l30_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3y.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l33_G.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3o.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l35_F.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l37.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3a_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l36.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_CA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l37_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l34_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3x.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3m_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_F.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3n.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3x_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_P.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l39_F.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l37_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3f.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3p.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3m_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3f_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l35.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3l_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l36_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l35_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3m.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l33_E.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3r_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3s.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3g_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3r_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3e.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3b_F.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3o_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_DA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3y_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l36_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3h.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3q_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3l_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3o_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l30.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l38_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3t_E.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3o_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3b_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l35_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l38.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l35_J.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_JA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3q_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3v.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l34_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3x_G.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_J.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3c_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3m_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3n_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3p_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3s_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_MA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3p_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3u.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3c.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l34_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l31_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l32_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3h_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3s_E.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3c_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_K.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3x_F.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l39_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3v_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_FA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3c_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3b.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_VA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l32_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3h_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l38_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_AA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l31.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_QA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3g_F.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3i_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3i.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l33_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3i_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3w.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3a.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l33_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l35_K.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l39.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3b_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3w_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3t_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3j_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_OA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3z.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3l.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l33_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l30_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l35_E.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l34.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3t_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3o_E.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3b_G.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3d.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3g_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3r.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l30_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_Q.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3u_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3o.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_G.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l37.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3k_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_HA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l31_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3f_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3k_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_XA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3u_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3q.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3g.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3l_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l36_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l35_O.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3s.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3e.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3m.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3r_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3g_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3l_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l35.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l38_F.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l36_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l37_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3f.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3p.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3s_J.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3m_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l37_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_Y.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3m_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3x_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_O.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3f_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3s_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3u.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l39_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3n_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3f_G.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3p_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3p_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3k_E.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l33.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l34_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3k.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_U.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3u_E.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3n_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3r_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3q_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3v.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3z_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3t_E.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3b_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3o_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l35_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l38.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l30.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l35_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3h.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3d_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3q_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l39.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l38_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3b_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3w_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3t_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3w.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3i_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3a.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l33_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3d_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l33_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3i.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3w_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3b_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l31.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l32_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l31_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3f_F.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3h_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3b.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l39_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3t.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3u_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3v_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_T.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3j.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l32.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l32_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3h_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3k_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3s_K.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3u_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3q.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3g.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l31_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3k_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l31_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3f_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_N.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3u_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3x_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_BB.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_X.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3j_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3r.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l35_N.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_EB.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3t_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3a_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l38_G.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l34.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3a_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3t_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3g_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3j_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3z.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3l.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l33_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l37_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_Z.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3m_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_L.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3x_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3f_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3s_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l31_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_IA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3f_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3e.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3x_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3s_I.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3n.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_NA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3x.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3r_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3g_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3y_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l36.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3l_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l38_E.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3t_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l36_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l35_L.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3p.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3f.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l33.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l35_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_PA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l33_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3d_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3q_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l33_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3c.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3q_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3d_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3u.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3t_F.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3z_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3o_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3k_F.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l30.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l34_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_V.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3u_F.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3v_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3h.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3n_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3v.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_GA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l38.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_WA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3e_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3f_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3h_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3p_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3i.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3v_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_W.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l34_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l39_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l31.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_LA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3k_G.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3h_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3f_E.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l39.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l39_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3a.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3w.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3q_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3j.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l33_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l35_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l32.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3b_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l38_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3b_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3o_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3t_G.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3t.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_KA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3b.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3q_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l33_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l38_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3a_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3l_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l37.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3y.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3o.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3g.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3q.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3l_H.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l35_M.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_EA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3t_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3a_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l31_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3s_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3u_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_M.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_BA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3c_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3m_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3s_H.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3r.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3d.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3k_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3s_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_MA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3n_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l34_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l31_E.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3h.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3p_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3e_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3v.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_H.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3n_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l38.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l34_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3d_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3b_H.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3q_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l33.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l30_E.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l35_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3o_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3k.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l35_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3c.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_JA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3u.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3t_E.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3o_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3q_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l35_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l36_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3n.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3x.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3o_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3r_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l36.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3i_E.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3g_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_DA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3r_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3p.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_TA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3f.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3o_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3l_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_CA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3m.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3h_E.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3s_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l35.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_SA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l37_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3x_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3n_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l37_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l34_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3x_I.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_R.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3u_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l34.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3h_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l31_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3l.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_S.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3x_H.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_E.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l32_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l31_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_HA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l34_E.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3c_G.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l37.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l30_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3j_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3a_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3o_E.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_OA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3y.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3t_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3o.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l35_E.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3g.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3q.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l38_L.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l33_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l30_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3g_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3j_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3i_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3j.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3b_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l38_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_AA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l33_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l30_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l32.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l33_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3i_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3t_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3t.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3b.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3e_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3h_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3i.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l32_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l31_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l31.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l39.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3v_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3a.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3h_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_I.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l33_H.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3z_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3t_F.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l38.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3d_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3q_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l30.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l30_F.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l35_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3o_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3h.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3z_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_K.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3u.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3c.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3v_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l33.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3n_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l34_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3k.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l32_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l31_F.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3e_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3u_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3x_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l34_G.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l31_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3x_J.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_G.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3f_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_Q.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3n.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3h_F.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3f_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3s_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l36.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l37_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3j_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3i_F.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3r_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3s.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3e.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3y_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l36_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l35_G.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l35_Q.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3y_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3m.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3o_G.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3r_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3g_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l35.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3d.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3r.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3j_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l34.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3z.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3o_F.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3a_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3t_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l35_P.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l35_F.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_P.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_F.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3x_K.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l31_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l34_F.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3c_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3s_H.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l37_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l37.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3s_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3o.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l31_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3y.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l39_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3v_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3b.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3t.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_J.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l32_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3j.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l39_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3c_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3n_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l32.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3i_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l39.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3b_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3w.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3a.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l38_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3i.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3a_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3b_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l38_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l35_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_AB.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l31.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3q_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3o_H.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3k.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3s_G.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3h_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3e_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l33.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3n_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l34_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_GA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l32_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_T.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3e_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l35_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_PA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3l_G.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3o_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3z_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3h.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l30.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l38.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3i_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l38_K.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l33_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l35_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3v.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3o_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3t_H.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3g_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l30_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l35.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_NA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3t_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3m.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3y_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3s.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l36_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3e.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3t_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3g_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l36.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3x_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3x.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3n.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l31_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3f_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3e_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3k_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3s_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_X.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3f.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_N.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3f_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3p.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3x_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3v_F.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_IA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3k_H.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l37_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_BA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3k_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3e_E.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l31_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3o.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3y.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_RA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l37.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3q.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_O.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3g.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_Y.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3z.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3a_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3l.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l35_F.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l34.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l30_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_EA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3t_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3a_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_UA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3d.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3r.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l31.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3o_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3l_F.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l35_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l38_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_KA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3b_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3o_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3w.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l38_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3a.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l33_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l38_J.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3i_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l39.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_LA.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l39_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l32.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3c_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3h_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3s_F.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3j.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3x_N.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_U.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l32_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l39_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l34_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3v_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3c_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3n_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3q_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l38_H.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l35_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3c.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3u.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3o_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l35_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3o_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3z_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l33.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3n_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l34_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l31_E.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_W.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3e_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3x_L.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3h.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3s_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3p_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3e_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3n_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l30.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l37_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l34_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3s_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3s.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_M.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3v_E.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l37_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3m_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3n_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l35.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3x_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3s_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3p.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3f.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l36_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3g_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3r_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3q_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l36.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l35_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l36_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3y_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3n.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3a_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3l_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3x.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l30_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3g_C.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3t_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3g.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3q.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3y.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3a_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3o.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l35_E.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l37.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l30_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3j_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3v_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3u_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3k_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_L.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3r.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3c_Z.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3d.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l31_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3k_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3l.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l31_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l32_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3z.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l34.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3n_E.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3f_H.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3x_M.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3p_V.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l31_D.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l32_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l39_B.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3v_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l37_E.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l31.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3v_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l3h_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/3l3s_E.pt\n", + "./pdb_2021aug02_sample/pdb/l3/7l3i.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l32_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/2l3w_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3t.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3b.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l38_I.pt\n", + "./pdb_2021aug02_sample/pdb/l3/4l33_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3i_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l32.pt\n", + "./pdb_2021aug02_sample/pdb/l3/5l3i_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/6l3w_A.pt\n", + "./pdb_2021aug02_sample/pdb/l3/1l3l_E.pt\n", + "./pdb_2021aug02_sample/test_clusters.txt\n", + "./pdb_2021aug02_sample/valid_clusters.txt\n" + ] + } + ], + "source": [ + "!tar xvf \"pdb_2021aug02_sample.tar.gz\"" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "id": "Ul9pmaQzXAg3" + }, + "outputs": [], + "source": [ + "import sys" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "id": "o-03W_vgWguT" + }, + "outputs": [], + "source": [ + "sys.path.append(\"/content/ProteinMPNN/training\")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "id": "6-xXsyM-W_7k" + }, + "outputs": [], + "source": [ + "from training import main as run_training" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "id": "mks1E1FTXGXL" + }, + "outputs": [], + "source": [ + "# argparser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)\n", + "\n", + "# argparser.add_argument(\"--path_for_training_data\", type=str, default=\"my_path/pdb_2021aug02\", help=\"path for loading training data\") \n", + "# argparser.add_argument(\"--path_for_outputs\", type=str, default=\"./test\", help=\"path for logs and model weights\")\n", + "# argparser.add_argument(\"--previous_checkpoint\", type=str, default=\"\", help=\"path for previous model weights, e.g. file.pt\")\n", + "# argparser.add_argument(\"--num_epochs\", type=int, default=200, help=\"number of epochs to train for\")\n", + "# argparser.add_argument(\"--save_model_every_n_epochs\", type=int, default=10, help=\"save model weights every n epochs\")\n", + "# argparser.add_argument(\"--reload_data_every_n_epochs\", type=int, default=2, help=\"reload training data every n epochs\")\n", + "# argparser.add_argument(\"--num_examples_per_epoch\", type=int, default=1000000, help=\"number of training example to load for one epoch\")\n", + "# argparser.add_argument(\"--batch_size\", type=int, default=10000, help=\"number of tokens for one batch\")\n", + "# argparser.add_argument(\"--max_protein_length\", type=int, default=10000, help=\"maximum length of the protein complext\")\n", + "# argparser.add_argument(\"--hidden_dim\", type=int, default=128, help=\"hidden model dimension\")\n", + "# argparser.add_argument(\"--num_encoder_layers\", type=int, default=3, help=\"number of encoder layers\") \n", + "# argparser.add_argument(\"--num_decoder_layers\", type=int, default=3, help=\"number of decoder layers\")\n", + "# argparser.add_argument(\"--num_neighbors\", type=int, default=48, help=\"number of neighbors for the sparse graph\") \n", + "# argparser.add_argument(\"--dropout\", type=float, default=0.1, help=\"dropout level; 0.0 means no dropout\")\n", + "# argparser.add_argument(\"--backbone_noise\", type=float, default=0.2, help=\"amount of noise added to backbone during training\") \n", + "# argparser.add_argument(\"--rescut\", type=float, default=3.5, help=\"PDB resolution cutoff\")\n", + "# argparser.add_argument(\"--debug\", type=bool, default=False, help=\"minimal data loading for debugging\")\n", + "# argparser.add_argument(\"--gradient_norm\", type=float, default=-1.0, help=\"clip gradient norm, set to negative to omit clipping\")\n", + "# argparser.add_argument(\"--mixed_precision\", type=bool, default=True, help=\"train with mixed precision\")\n", + "\n", + "# args = argparser.parse_args() \n", + "# main(args)\n", + "\n", + "class MyArgs(object):\n", + " def __init__(self):\n", + " self.path_for_training_data = \"/content/pdb_2021aug02_sample\"\n", + " self.path_for_outputs = \"/content/test\"\n", + " self.previous_checkpoint = \"\"\n", + " self.num_epochs = 50\n", + " self.save_model_every_n_epochs = 5\n", + " self.reload_data_every_n_epochs = 4\n", + " self.num_examples_per_epoch = 200\n", + " self.batch_size = 2000\n", + " self.max_protein_length = 2000\n", + " self.hidden_dim = 128\n", + " self.num_encoder_layers = 3\n", + " self.num_decoder_layers = 3\n", + " self.num_neighbors = 32\n", + " self.dropout = 0.1\n", + " self.backbone_noise = 0.1\n", + " self.rescut = 3.5\n", + " self.debug = False\n", + " self.gradient_norm = -1.0 #no norm\n", + " self.mixed_precision= True \n", + "\n", + "args = MyArgs()" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "id": "6csB6zUrbcAH" + }, + "outputs": [], + "source": [ + "#epoch - number of times data is trained on\n", + "#step - number of optimizer steps\n", + "#time - time in seconds for one epoch to finish\n", + "#train - training perplexity = exp(average categorical cross entropy)\n", + "#valid - validation perplexity\n", + "#train_acc - training accuracy\n", + "#valid_acc - validation accuracy" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "68wFaZ0IXHzj", + "outputId": "52c84f9e-599b-4ca4-b46f-25170828a310" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/usr/local/lib/python3.7/dist-packages/torch/utils/data/dataloader.py:566: UserWarning: This DataLoader will create 4 worker processes in total. Our suggested max number of worker in current system is 2, which is smaller than what this DataLoader is going to create. Please be aware that excessive worker creation might get DataLoader running slow or even freeze, lower the worker number to avoid potential slowness/freeze if necessary.\n", + " cpuset_checked))\n", + "/usr/local/lib/python3.7/dist-packages/torch/utils/checkpoint.py:25: UserWarning: None of the inputs have requires_grad=True. Gradients will be None\n", + " warnings.warn(\"None of the inputs have requires_grad=True. Gradients will be None\")\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "epoch: 1, step: 4, time: 1.3, train: 32.478, valid: 26.652, train_acc: 0.057, valid_acc: 0.060\n", + "epoch: 2, step: 8, time: 1.4, train: 31.076, valid: 24.790, train_acc: 0.056, valid_acc: 0.067\n", + "epoch: 3, step: 12, time: 0.9, train: 28.561, valid: 22.354, train_acc: 0.058, valid_acc: 0.075\n", + "epoch: 4, step: 16, time: 0.9, train: 25.958, valid: 20.493, train_acc: 0.063, valid_acc: 0.090\n", + "epoch: 5, step: 22, time: 1.6, train: 23.583, valid: 19.557, train_acc: 0.065, valid_acc: 0.114\n", + "epoch: 6, step: 28, time: 1.5, train: 21.809, valid: 19.653, train_acc: 0.069, valid_acc: 0.104\n", + "epoch: 7, step: 34, time: 1.5, train: 20.483, valid: 19.440, train_acc: 0.074, valid_acc: 0.105\n", + "epoch: 8, step: 40, time: 1.2, train: 19.474, valid: 19.000, train_acc: 0.097, valid_acc: 0.122\n", + "epoch: 9, step: 46, time: 2.0, train: 19.268, valid: 17.082, train_acc: 0.093, valid_acc: 0.116\n", + "epoch: 10, step: 52, time: 2.3, train: 18.658, valid: 16.812, train_acc: 0.097, valid_acc: 0.109\n", + "epoch: 11, step: 58, time: 2.3, train: 18.158, valid: 16.962, train_acc: 0.105, valid_acc: 0.120\n", + "epoch: 12, step: 64, time: 1.7, train: 17.705, valid: 16.780, train_acc: 0.109, valid_acc: 0.120\n", + "epoch: 13, step: 69, time: 32.2, train: 18.135, valid: 16.984, train_acc: 0.105, valid_acc: 0.116\n", + "epoch: 14, step: 74, time: 1.9, train: 18.008, valid: 17.287, train_acc: 0.106, valid_acc: 0.120\n", + "epoch: 15, step: 79, time: 2.0, train: 17.833, valid: 17.172, train_acc: 0.118, valid_acc: 0.111\n", + "epoch: 16, step: 84, time: 1.7, train: 17.451, valid: 16.884, train_acc: 0.119, valid_acc: 0.115\n", + "epoch: 17, step: 90, time: 3.0, train: 18.145, valid: 17.919, train_acc: 0.111, valid_acc: 0.129\n", + "epoch: 18, step: 96, time: 2.8, train: 17.651, valid: 17.883, train_acc: 0.103, valid_acc: 0.103\n", + "epoch: 19, step: 102, time: 2.0, train: 17.608, valid: 17.774, train_acc: 0.104, valid_acc: 0.126\n", + "epoch: 20, step: 108, time: 2.3, train: 17.374, valid: 18.082, train_acc: 0.114, valid_acc: 0.132\n", + "epoch: 21, step: 114, time: 31.8, train: 17.079, valid: 18.081, train_acc: 0.123, valid_acc: 0.117\n", + "epoch: 22, step: 120, time: 2.5, train: 16.938, valid: 18.005, train_acc: 0.130, valid_acc: 0.133\n", + "epoch: 23, step: 126, time: 1.6, train: 16.719, valid: 18.174, train_acc: 0.123, valid_acc: 0.116\n", + "epoch: 24, step: 132, time: 1.2, train: 16.580, valid: 17.844, train_acc: 0.129, valid_acc: 0.136\n", + "epoch: 25, step: 139, time: 2.6, train: 16.797, valid: 15.777, train_acc: 0.118, valid_acc: 0.125\n", + "epoch: 26, step: 146, time: 2.7, train: 16.433, valid: 15.730, train_acc: 0.125, valid_acc: 0.134\n", + "epoch: 27, step: 153, time: 1.8, train: 16.127, valid: 15.673, train_acc: 0.127, valid_acc: 0.135\n", + "epoch: 28, step: 160, time: 1.8, train: 15.798, valid: 15.235, train_acc: 0.124, valid_acc: 0.148\n", + "epoch: 29, step: 166, time: 31.4, train: 16.003, valid: 15.344, train_acc: 0.139, valid_acc: 0.146\n", + "epoch: 30, step: 172, time: 2.7, train: 15.639, valid: 15.524, train_acc: 0.146, valid_acc: 0.133\n", + "epoch: 31, step: 178, time: 1.9, train: 15.546, valid: 14.927, train_acc: 0.142, valid_acc: 0.153\n", + "epoch: 32, step: 184, time: 1.7, train: 15.215, valid: 15.001, train_acc: 0.155, valid_acc: 0.155\n", + "epoch: 33, step: 190, time: 4.9, train: 15.418, valid: 15.358, train_acc: 0.144, valid_acc: 0.137\n", + "epoch: 34, step: 196, time: 3.2, train: 15.255, valid: 14.912, train_acc: 0.144, valid_acc: 0.152\n", + "epoch: 35, step: 202, time: 1.9, train: 14.925, valid: 14.894, train_acc: 0.153, valid_acc: 0.156\n", + "epoch: 36, step: 208, time: 2.0, train: 14.736, valid: 14.729, train_acc: 0.154, valid_acc: 0.168\n", + "epoch: 37, step: 214, time: 32.7, train: 14.467, valid: 17.068, train_acc: 0.152, valid_acc: 0.140\n", + "epoch: 38, step: 220, time: 2.8, train: 14.410, valid: 16.987, train_acc: 0.158, valid_acc: 0.122\n", + "epoch: 39, step: 226, time: 2.0, train: 14.101, valid: 16.935, train_acc: 0.162, valid_acc: 0.123\n", + "epoch: 40, step: 232, time: 2.4, train: 14.034, valid: 17.452, train_acc: 0.168, valid_acc: 0.131\n", + "epoch: 41, step: 237, time: 4.0, train: 15.268, valid: 16.151, train_acc: 0.158, valid_acc: 0.147\n", + "epoch: 42, step: 242, time: 2.2, train: 15.403, valid: 16.473, train_acc: 0.147, valid_acc: 0.148\n", + "epoch: 43, step: 247, time: 1.6, train: 15.008, valid: 17.016, train_acc: 0.161, valid_acc: 0.129\n", + "epoch: 44, step: 252, time: 2.0, train: 14.492, valid: 16.040, train_acc: 0.168, valid_acc: 0.152\n", + "epoch: 45, step: 258, time: 32.4, train: 14.046, valid: 14.339, train_acc: 0.172, valid_acc: 0.161\n", + "epoch: 46, step: 264, time: 2.4, train: 13.564, valid: 13.959, train_acc: 0.179, valid_acc: 0.156\n", + "epoch: 47, step: 270, time: 1.8, train: 13.514, valid: 14.189, train_acc: 0.181, valid_acc: 0.151\n", + "epoch: 48, step: 276, time: 1.5, train: 13.257, valid: 13.961, train_acc: 0.180, valid_acc: 0.160\n", + "epoch: 49, step: 281, time: 4.1, train: 13.741, valid: 16.552, train_acc: 0.176, valid_acc: 0.154\n", + "epoch: 50, step: 286, time: 2.2, train: 13.157, valid: 17.389, train_acc: 0.180, valid_acc: 0.130\n" + ] + } + ], + "source": [ + "run_training(args)" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "authorship_tag": "ABX9TyNOXLieRc+7DSEUOVtmG9NS", + "collapsed_sections": [], + "include_colab_link": true, + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/scripts/train/parse_cif_noX.py b/scripts/train/parse_cif_noX.py new file mode 100644 index 0000000000000000000000000000000000000000..1c4b3c5fcc4e72a1786e3773d4051d0d7069dc58 --- /dev/null +++ b/scripts/train/parse_cif_noX.py @@ -0,0 +1,488 @@ +import pdbx +from pdbx.reader.PdbxReader import PdbxReader +from pdbx.reader.PdbxContainers import DataCategory +import gzip +import numpy as np +import torch +import os,sys +import glob +import re +from scipy.spatial import KDTree +from itertools import combinations,permutations +import tempfile +import subprocess + +RES_NAMES = [ + 'ALA','ARG','ASN','ASP','CYS', + 'GLN','GLU','GLY','HIS','ILE', + 'LEU','LYS','MET','PHE','PRO', + 'SER','THR','TRP','TYR','VAL' +] + +RES_NAMES_1 = 'ARNDCQEGHILKMFPSTWYV' + +to1letter = {aaa:a for a,aaa in zip(RES_NAMES_1,RES_NAMES)} +to3letter = {a:aaa for a,aaa in zip(RES_NAMES_1,RES_NAMES)} + +ATOM_NAMES = [ + ("N", "CA", "C", "O", "CB"), # ala + ("N", "CA", "C", "O", "CB", "CG", "CD", "NE", "CZ", "NH1", "NH2"), # arg + ("N", "CA", "C", "O", "CB", "CG", "OD1", "ND2"), # asn + ("N", "CA", "C", "O", "CB", "CG", "OD1", "OD2"), # asp + ("N", "CA", "C", "O", "CB", "SG"), # cys + ("N", "CA", "C", "O", "CB", "CG", "CD", "OE1", "NE2"), # gln + ("N", "CA", "C", "O", "CB", "CG", "CD", "OE1", "OE2"), # glu + ("N", "CA", "C", "O"), # gly + ("N", "CA", "C", "O", "CB", "CG", "ND1", "CD2", "CE1", "NE2"), # his + ("N", "CA", "C", "O", "CB", "CG1", "CG2", "CD1"), # ile + ("N", "CA", "C", "O", "CB", "CG", "CD1", "CD2"), # leu + ("N", "CA", "C", "O", "CB", "CG", "CD", "CE", "NZ"), # lys + ("N", "CA", "C", "O", "CB", "CG", "SD", "CE"), # met + ("N", "CA", "C", "O", "CB", "CG", "CD1", "CD2", "CE1", "CE2", "CZ"), # phe + ("N", "CA", "C", "O", "CB", "CG", "CD"), # pro + ("N", "CA", "C", "O", "CB", "OG"), # ser + ("N", "CA", "C", "O", "CB", "OG1", "CG2"), # thr + ("N", "CA", "C", "O", "CB", "CG", "CD1", "CD2", "CE2", "CE3", "NE1", "CZ2", "CZ3", "CH2"), # trp + ("N", "CA", "C", "O", "CB", "CG", "CD1", "CD2", "CE1", "CE2", "CZ", "OH"), # tyr + ("N", "CA", "C", "O", "CB", "CG1", "CG2") # val +] + +idx2ra = {(RES_NAMES_1[i],j):(RES_NAMES[i],a) for i in range(20) for j,a in enumerate(ATOM_NAMES[i])} + +aa2idx = {(r,a):i for r,atoms in zip(RES_NAMES,ATOM_NAMES) + for i,a in enumerate(atoms)} +aa2idx.update({(r,'OXT'):3 for r in RES_NAMES}) + + +def writepdb(f, xyz, seq, bfac=None): + + #f = open(filename,"w") + f.seek(0) + + ctr = 1 + seq = str(seq) + L = len(seq) + + if bfac is None: + bfac = np.zeros((L)) + + idx = [] + for i in range(L): + for j,xyz_ij in enumerate(xyz[i]): + key = (seq[i],j) + if key not in idx2ra.keys(): + continue + if np.isnan(xyz_ij).sum()>0: + continue + r,a = idx2ra[key] + f.write ("%-6s%5s %4s %3s %s%4d %8.3f%8.3f%8.3f%6.2f%6.2f\n"%( + "ATOM", ctr, a, r, + "A", i+1, xyz_ij[0], xyz_ij[1], xyz_ij[2], + 1.0, bfac[i,j] ) ) + if a == 'CA': + idx.append(i) + ctr += 1 + + #f.close() + f.flush() + + return np.array(idx) + + +def TMalign(chainA, chainB): + + # temp files to save the two input protein chains + # and TMalign transformation + fA = tempfile.NamedTemporaryFile(mode='w+t', dir='/dev/shm') + fB = tempfile.NamedTemporaryFile(mode='w+t', dir='/dev/shm') + mtx = tempfile.NamedTemporaryFile(mode='w+t', dir='/dev/shm') + + # create temp PDB files keep track of residue indices which were saved + idxA = writepdb(fA, chainA['xyz'], chainA['seq'], bfac=chainA['bfac']) + idxB = writepdb(fB, chainB['xyz'], chainB['seq'], bfac=chainB['bfac']) + + # run TMalign + tm = subprocess.Popen('/home/aivan/prog/TMalign %s %s -m %s'%(fA.name, fB.name, mtx.name), + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + encoding='utf-8') + stdout,stderr = tm.communicate() + lines = stdout.split('\n') + + # if TMalign failed + if len(stderr) > 0: + return None,None + + # parse transformation + mtx.seek(0) + tu = np.fromstring(''.join(l[2:] for l in mtx.readlines()[2:5]), + dtype=float, sep=' ').reshape((3,4)) + t = tu[:,0] + u = tu[:,1:] + + # parse rmsd, sequence identity, and two TM-scores + rmsd = float(lines[16].split()[4][:-1]) + seqid = float(lines[16].split()[-1]) + tm1 = float(lines[17].split()[1]) + tm2 = float(lines[18].split()[1]) + + # parse alignment + seq1 = lines[-5] + seq2 = lines[-3] + + ss1 = np.array(list(seq1.strip()))!='-' + ss2 = np.array(list(seq2.strip()))!='-' + #print(ss1) + #print(ss2) + mask = np.logical_and(ss1, ss2) + + alnAB = np.stack((idxA[(np.cumsum(ss1)-1)[mask]], + idxB[(np.cumsum(ss2)-1)[mask]])) + + alnBA = np.stack((alnAB[1],alnAB[0])) + + # clean up + fA.close() + fB.close() + mtx.close() + + resAB = {'rmsd':rmsd, 'seqid':seqid, 'tm':tm1, 'aln':alnAB, 't':t, 'u':u} + resBA = {'rmsd':rmsd, 'seqid':seqid, 'tm':tm2, 'aln':alnBA, 't':-u.T@t, 'u':u.T} + + return resAB,resBA + + +def get_tm_pairs(chains): + """run TM-align for all pairs of chains""" + + tm_pairs = {} + for A,B in combinations(chains.keys(),r=2): + resAB,resBA = TMalign(chains[A],chains[B]) + #if resAB is None: + # continue + tm_pairs.update({(A,B):resAB}) + tm_pairs.update({(B,A):resBA}) + + # add self-alignments + for A in chains.keys(): + L = chains[A]['xyz'].shape[0] + aln = np.arange(L)[chains[A]['mask'][:,1]] + aln = np.stack((aln,aln)) + tm_pairs.update({(A,A):{'rmsd':0.0, 'seqid':1.0, 'tm':1.0, 'aln':aln}}) + + return tm_pairs + + + +def parseOperationExpression(expression) : + + expression = expression.strip('() ') + operations = [] + for e in expression.split(','): + e = e.strip() + pos = e.find('-') + if pos>0: + start = int(e[0:pos]) + stop = int(e[pos+1:]) + operations.extend([str(i) for i in range(start,stop+1)]) + else: + operations.append(e) + + return operations + + +def parseAssemblies(data,chids): + + xforms = {'asmb_chains' : None, + 'asmb_details' : None, + 'asmb_method' : None, + 'asmb_ids' : None} + + assembly_data = data.getObj("pdbx_struct_assembly") + assembly_gen = data.getObj("pdbx_struct_assembly_gen") + oper_list = data.getObj("pdbx_struct_oper_list") + + if (assembly_data is None) or (assembly_gen is None) or (oper_list is None): + return xforms + + # save all basic transformations in a dictionary + opers = {} + for k in range(oper_list.getRowCount()): + key = oper_list.getValue("id", k) + val = np.eye(4) + for i in range(3): + val[i,3] = float(oper_list.getValue("vector[%d]"%(i+1), k)) + for j in range(3): + val[i,j] = float(oper_list.getValue("matrix[%d][%d]"%(i+1,j+1), k)) + opers.update({key:val}) + + + chains,details,method,ids = [],[],[],[] + + for index in range(assembly_gen.getRowCount()): + + # Retrieve the assembly_id attribute value for this assembly + assemblyId = assembly_gen.getValue("assembly_id", index) + ids.append(assemblyId) + + # Retrieve the operation expression for this assembly from the oper_expression attribute + oper_expression = assembly_gen.getValue("oper_expression", index) + + oper_list = [parseOperationExpression(expression) + for expression in re.split('\(|\)', oper_expression) if expression] + + # chain IDs which the transform should be applied to + chains.append(assembly_gen.getValue("asym_id_list", index)) + + index_asmb = min(index,assembly_data.getRowCount()-1) + details.append(assembly_data.getValue("details", index_asmb)) + method.append(assembly_data.getValue("method_details", index_asmb)) + + # + if len(oper_list)==1: + xform = np.stack([opers[o] for o in oper_list[0]]) + elif len(oper_list)==2: + xform = np.stack([opers[o1]@opers[o2] + for o1 in oper_list[0] + for o2 in oper_list[1]]) + + else: + print('Error in processing assembly') + return xforms + + xforms.update({'asmb_xform%d'%(index):xform}) + + xforms['asmb_chains'] = chains + xforms['asmb_details'] = details + xforms['asmb_method'] = method + xforms['asmb_ids'] = ids + + return xforms + + +def parse_mmcif(filename): + + #print(filename) + + chains = {} # 'chain_id' -> chain_strucure + + # read a gzipped .cif file + data = [] + with gzip.open(filename,'rt') as cif: + reader = PdbxReader(cif) + reader.read(data) + data = data[0] + + # + # get sequences + # + + # map chain entity to chain ID + entity_poly = data.getObj('entity_poly') + if entity_poly is None: + return {},{} + + pdbx_poly_seq_scheme = data.getObj('pdbx_poly_seq_scheme') + pdb2asym = dict({ + (r[pdbx_poly_seq_scheme.getIndex('pdb_strand_id')], + r[pdbx_poly_seq_scheme.getIndex('asym_id')]) + for r in data.getObj('pdbx_poly_seq_scheme').getRowList() + }) + + chs2num = {pdb2asym[ch]:r[entity_poly.getIndex('entity_id')] + for r in entity_poly.getRowList() + for ch in r[entity_poly.getIndex('pdbx_strand_id')].split(',') + if r[entity_poly.getIndex('type')]=='polypeptide(L)'} + + # get canonical sequences for polypeptide chains + num2seq = {r[entity_poly.getIndex('entity_id')]:r[entity_poly.getIndex('pdbx_seq_one_letter_code_can')].replace('\n','') + for r in entity_poly.getRowList() + if r[entity_poly.getIndex('type')]=='polypeptide(L)'} + + # map chain entity to amino acid sequence + #entity_poly_seq = data.getObj('entity_poly_seq') + #num2seq = dict.fromkeys(set(chs2num.values()), "") + #for row in entity_poly_seq.getRowList(): + # num = row[entity_poly_seq.getIndex('entity_id')] + # res = row[entity_poly_seq.getIndex('mon_id')] + # if num not in num2seq.keys(): + # continue + # num2seq[num] += (to1letter[res] if res in to1letter.keys() else 'X') + + # modified residues + pdbx_struct_mod_residue = data.getObj('pdbx_struct_mod_residue') + if pdbx_struct_mod_residue is None: + modres = {} + else: + modres = dict({(r[pdbx_struct_mod_residue.getIndex('label_comp_id')], + r[pdbx_struct_mod_residue.getIndex('parent_comp_id')]) + for r in pdbx_struct_mod_residue.getRowList()}) + for k,v in modres.items(): + print("# non-standard residue: %s %s"%(k,v)) + + # initialize dict of chains + for c,n in chs2num.items(): + seq = num2seq[n] + L = len(seq) + chains.update({c : {'seq' : seq, + 'xyz' : np.full((L,14,3),np.nan,dtype=np.float32), + 'mask' : np.zeros((L,14),dtype=bool), + 'bfac' : np.full((L,14),np.nan,dtype=np.float32), + 'occ' : np.zeros((L,14),dtype=np.float32) }}) + + + # + # populate structures + # + + # get indices of fields of interest + atom_site = data.getObj('atom_site') + i = {k:atom_site.getIndex(val) for k,val in [('atm', 'label_atom_id'), # atom name + ('atype', 'type_symbol'), # atom chemical type + ('res', 'label_comp_id'), # residue name (3-letter) + #('chid', 'auth_asym_id'), # chain ID + ('chid', 'label_asym_id'), # chain ID + ('num', 'label_seq_id'), # sequence number + ('alt', 'label_alt_id'), # alternative location ID + ('x', 'Cartn_x'), # xyz coords + ('y', 'Cartn_y'), + ('z', 'Cartn_z'), + ('occ', 'occupancy'), # occupancy + ('bfac', 'B_iso_or_equiv'), # B-factors + ('model', 'pdbx_PDB_model_num') # model number (for multi-model PDBs, e.g. NMR) + ]} + + for a in atom_site.getRowList(): + + # skip HETATM + #if a[0] != 'ATOM': + # continue + + # skip hydrogens + if a[i['atype']] == 'H': + continue + + # skip if not a polypeptide + if a[i['chid']] not in chains.keys(): + continue + + # parse atom + atm, res, chid, num, alt, x, y, z, occ, Bfac, model = \ + (t(a[i[k]]) for k,t in (('atm',str), ('res',str), ('chid',str), + ('num',int), ('alt',str), + ('x',float), ('y',float), ('z',float), + ('occ',float), ('bfac',float), ('model',int))) + + + #print(atm, res, chid, num, alt, x, y, z, occ, Bfac, model) + c = chains[chid] + + # remap residue to canonical + a = c['seq'][num-1] + if a in to3letter.keys(): + res = to3letter[a] + else: + if res in modres.keys() and modres[res] in to1letter.keys(): + res = modres[res] + c['seq'] = c['seq'][:num-1] + to1letter[res] + c['seq'][num:] + else: + res = 'GLY' + + # skip if not a standard residue/atom + if (res,atm) not in aa2idx.keys(): + continue + + # skip everything except model #1 + if model > 1: + continue + + # populate chians using max occup atoms + idx = (num-1, aa2idx[(res,atm)]) + if occ > c['occ'][idx]: + c['xyz'][idx] = [x,y,z] + c['mask'][idx] = True + c['occ'][idx] = occ + c['bfac'][idx] = Bfac + + # + # metadata + # + #if data.getObj('reflns') is not None: + # res = data.getObj('reflns').getValue('d_resolution_high',0) + res = None + if data.getObj('refine') is not None: + try: + res = float(data.getObj('refine').getValue('ls_d_res_high',0)) + except: + res = None + + if (data.getObj('em_3d_reconstruction') is not None) and (res is None): + try: + res = float(data.getObj('em_3d_reconstruction').getValue('resolution',0)) + except: + res = None + + chids = list(chains.keys()) + seq = [] + for ch in chids: + mask = chains[ch]['mask'][:,:3].sum(1)==3 + ref_seq = chains[ch]['seq'] + atom_seq = ''.join([a if m else '-' for a,m in zip(ref_seq,mask)]) + seq.append([ref_seq,atom_seq]) + + metadata = { + 'method' : data.getObj('exptl').getValue('method',0).replace(' ','_'), + 'date' : data.getObj('pdbx_database_status').getValue('recvd_initial_deposition_date',0), + 'resolution' : res, + 'chains' : chids, + 'seq' : seq, + 'id' : data.getObj('entry').getValue('id',0) + } + + + # + # assemblies + # + + asmbs = parseAssemblies(data,chains) + metadata.update(asmbs) + + return chains, metadata + + +IN = sys.argv[1] +OUT = sys.argv[2] + +chains,metadata = parse_mmcif(IN) +ID = metadata['id'] + +tm_pairs = get_tm_pairs(chains) +if 'chains' in metadata.keys() and len(metadata['chains'])>0: + chids = metadata['chains'] + tm = [] + for a in chids: + tm_a = [] + for b in chids: + tm_ab = tm_pairs[(a,b)] + if tm_ab is None: + tm_a.append([0.0,0.0,999.9]) + else: + tm_a.append([tm_ab[k] for k in ['tm','seqid','rmsd']]) + tm.append(tm_a) + metadata.update({'tm':tm}) + +for k,v in chains.items(): + nres = (v['mask'][:,:3].sum(1)==3).sum() + print(">%s_%s %s %s %s %d %d\n%s"%(ID,k,metadata['date'],metadata['method'], + metadata['resolution'],len(v['seq']),nres,v['seq'])) + + torch.save({kc:torch.Tensor(vc) if kc!='seq' else str(vc) + for kc,vc in v.items()}, f"{OUT}_{k}.pt") + +meta_pt = {} +for k,v in metadata.items(): + if "asmb_xform" in k or k=="tm": + v = torch.Tensor(v) + meta_pt.update({k:v}) +torch.save(meta_pt, f"{OUT}.pt") diff --git a/scripts/train/plot_training_results.ipynb b/scripts/train/plot_training_results.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..e15f3219708a9dd7657f8be725a177d1cc1ba0a7 --- /dev/null +++ b/scripts/train/plot_training_results.ipynb @@ -0,0 +1,193 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "2e419712", + "metadata": {}, + "outputs": [], + "source": [ + "import re\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "plt.rcParams['font.size'] = 20 \n", + "import json\n", + "import random" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "6057b30a", + "metadata": {}, + "outputs": [], + "source": [ + "def running_mean(x, N):\n", + " cumsum = np.cumsum(np.insert(x, 0, 0)) \n", + " return (cumsum[N:] - cumsum[:-N]) / float(N)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "26f98407", + "metadata": {}, + "outputs": [], + "source": [ + "def get_results(txt_file, number=15):\n", + " # Using readlines()\n", + " file1 = open(txt_file, 'r')\n", + " Lines = file1.readlines()\n", + " results = []\n", + " for line in Lines:\n", + " if line[0] == 'e':\n", + " result_1 = [_.start() for _ in re.finditer(':', line)] \n", + " result_2 = [_.start() for _ in re.finditer(',', line)] + [-3]\n", + " bla = []\n", + " for i in range(number):\n", + " if i == number-1:\n", + " bla.append(float(line[result_1[i]+2:]))\n", + " else:\n", + " bla.append(float(line[result_1[i]+2:result_2[i]]))\n", + " \n", + " \n", + " results.append(bla)\n", + "\n", + " results = np.array(results)\n", + " return results" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "8270b259", + "metadata": {}, + "outputs": [], + "source": [ + "exp_020 = get_results('./exp_020/log.txt', 7)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "bd573c81", + "metadata": {}, + "outputs": [], + "source": [ + "l=1 #average over l epochs" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "14bf1f4a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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FosFf//9t0JV1V3nzp3h4eGBPXv2ID8/v9r25ngNBoMBgwcPRlFREf76669q23/99VcAzocI1OTMmTMYMWIEDh06hPfeew933nlnjftu374du3btQmRkJG6//XanHz169EBOTo7TafpqYjKZnE4lmZiYCAAOc5fbVgadOHFitf2d/UE3cOBAAMDvv//u0INcmx49emDdunUICAjAhAkTsHLlyhr33blzp9P3RNX6fXx8EBcXh7Nnz9r/cKps7dq1AKzDXhrK29sbXbt2xf79+5GVldXg4zSU7bXaXntlJpPJ/r6tz2v09/fH2LFj8eGHH2LGjBnIysrC+vXrAVR8fZ19P7QEDOMKKDNZMP7tDej/wmqYLfXvNSEiclWxsbE4evQozp49a2+TUmLBggU4cOCAgpXVjU6nww033IDc3Fw8//zzDtv++ecffPLJJ81Sxz333AMAeOKJJ1BSUmJv37ZtG1asWIGQkJBqQfXcuXM4dOhQteEtJ0+exPDhw3H8+HF89NFHta6gaZtH/MEHH8TixYudfrz++usO+9bV/PnzHYaXZGVl2c/zrbfeam+3LTRVNez9/vvvTsfs9+3bF4MHD8bu3bvxyiuvVNuemZnpcB4ru+SSS7B+/XqEhYVh8uTJWLFihdP9cnNz8eyzzzq0bd++HZ9//jn8/PwcxtbfdtttkFLi0UcfdRjekZGRgeeee86+z8WYPXs2ysrKcNtttzkdDpKdnd1kvea2udaXLVtmH69vs3DhQpw4cQKXX355rdeGrF271ul/j2z/VbCtzNuvXz8MGzYM3377LT766COnx9q7d2+N/41wdRymogCdRoVzucVIzy9FSnYxooMufhloIiJX8PDDD+Puu+9G7969MXHiRGi1WmzYsAEHDhzANddcgx9//FHpEmv18ssvY82aNfjPf/6DLVu2YPDgwTh37hy+/PJLjB07FitXrqzXRYINMWXKFHz77bf4+uuv0bt3b1xzzTXIzMzEihUrYDab8eGHH9qH1NjMnz8fS5curTa0IyEhAcnJyejbty+Sk5OxYMECh4vfAOs48NjYWBQUFGDZsmXQarWYPn16jfWNGjUK7du3x8aNG7F//3507dq11tcUERGB0tJSdOvWDddeey2MRiO+/vprnDt3DrNmzcLw4cPt+86aNQtLlizB5MmTMWnSJERGRmLfvn347bff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Fi1ahLS0NMyaNavGx65atQrJycmYPn06wsPDsXTpUpSVlTXGS6RWpn379oiLi1O6jEbBMO4CHv9uL7ovWIW/j2UoXQoRtVK23vGaeirNZjOWLFkCIQTuuOMOAMDKlStx0003oWPHjvDy8oKXlxf69u2LN954AxaLpU7Pe6Hxv8eOHcPkyZMREBAALy8vDB48GD///HONx1q7di1mzpyJLl26wNfXFx4eHujWrRueeeYZlJSUOOwbGxuLZ555BgAwcuRI+7AJW48tcOEx419++SWGDx8OPz8/eHh4oHv37njppZdQWlpabd/Y2FjExsaisLAQjz76KKKjo6HX6xEfH49XXnkFUjZtR8zx48exatUqxMbG4t5773XY9swzz8DLywuffvopCgtrn9WroKAAn376Kby8vLBgwQKHbffddx9iYmLw+++/19g7bnt/3XnnnZg2bRoyMjIuasjToUOHMH78eAQGBsLLywtDhw7FqlWrqu2Xm5uLV199FaNGjUKbNm2g0+kQEhKCa6+9Fps2bbrg8W+77Tb7cJzQ0FAMGzYM7777bp3qe/XVV6FSqTBkyBBkZWVV2/7zzz9j8ODB8PLyQkBAACZNmoSjR486Pda5c+dw7733IjY21l7/ddddhx07dlTbt/K49LVr1yIhIQE+Pj7w9fXF1VdfjYMHDzp9jqKiIrz00kvo1asXvLy84O3tjUGDBmHZsmV1er02DX3P1+f7ytmY8bKyMrzxxhvo06cPAgIC4OnpidjYWIwbNw6rV6+udoxDhw5hxowZaNu2LXQ6HcLCwnDjjTc2//ApKWWr/ejbt6+sbO3atVIJ87/dI2Me+0n+768kRZ6/uSh1flsTnuOGOXDgQJ33zcvLa8JKlJOWliZ1Op308/OThYWF1bb/+OOPEoAcPXq0va1Tp07ykksukTfddJN87LHH5N133y07duwoAcibbrqp2jGmT58uAcgTJ07Y206cOCEByOnTp9vb8vLy5JEjR2RQUJAEIK+66io5f/58OXnyZKnRaOQ111wjAcglS5Y4HH/MmDEyJiZGTp06Vc6ZM0fed999snfv3hKATEhIkCaTyb7v//3f/8kRI0bYn/vpp5+2f1yoXimlnD9/vgQgg4OD5d133y3nzJkju3btKgHIESNGyNLSUof9Y2JiZGRkpBwyZIhs166dnDlzppw1a5aMjIyUAOSCBQsu8JW5eB9++KEEIGfOnGlvq/w+vuKKKyQAuXr16lqP9ccff0gA8oorrnC6febMmRKAXLx4cbVt58+fl1qtVnbs2FFKKeXevXslADlq1Kh6vR7be2b48OHS399fDhs2TM6bN09Onz5dGgwGqVKp5PLlyx0es2nTJqnVauVll10mZ86cKR977DE5depU6eXlJTUajfz111+rPc9PP/0kPTw8pEqlkmPHjpXz5s2Td999txw0aJCMjY112Nf2tbcxm83yrrvukgDkddddJ4uLi+3blixZIgHIa665Rmo0Gjl58mQ5f/58edVVV0kAMjAwUB46dMjh+ElJSfb3y6hRo+S8efPktGnTpE6nkzqdTv74448O+9ueY+LEifbvmTlz5sixY8dKADIkJESmp6c7PCY7O9v+/dKnTx953333yVmzZsm4uDgJQD7++ON1/ho15D1f3++r6OhoGRMT49A2depUCUB269ZNPvDAA/Kxxx6TN998s2zXrp185JFHHPb99ddfpYeHh9RoNHLChAny0UcflVOnTpV6vV76+vrKHTt21Om11vX3B4DtsoY8qnggVvLDVcL4//5KkjGP/STnf7tHkedvLgyKTY/nuGFq+mEa89hPbvVxsa6//nqnIVdKKa+99loJQH711Vf2tmPHjlXbz2w2y1tuuUUCkJs3b3bYVp8wPnr0aAlALly40OEYK1eulACc1nn8+HFpsViq1fTEE09IANUC2tNPPy0B1Ph946zejRs3SgCybdu28ty5c/Z2o9Eo//Wvf0kA8oUXXnA4TkxMjP2PiqKiInt7amqq9PPzk35+frKsrMxpDY1hzpw5EoD873//a2+rHMbvvfdeCUC+8847tR7rrbfekgDkfffd53T7q6++KgHIuXPnVtv20ksvSQDyxRdftLf17dtXCiHk0aNH6/x6bO8ZAHLOnDkO27Zt2yY1Go309/eXubm59vacnJxq4VNKKU+fPi0jIiJk586dHdrT09Olr6+v1Gq1MjEx0enjKqscxouLi+V1111nP09ms9lhX1tQBlAtRC9cuNDpHyi2P5ief/55h/YNGzZItVotAwMDZX5+frXnUKvV1f7ImjdvngQgX3nlFYd22/u9antxcbEcM2aMFELIXbt2VTsXztT3Pd+Q76uqYTwnJ0cKIWTfvn0d/vC2ycjIsN/OysqS/v7+Migo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l9/OkFLOKb/9HoCxADIApAB4SghR9ZCJUsrEJiq3WdhW4UxKK4CUEk5eIxERERG5OZcI4wB6AZhepa19+QcAnARgC+Ptyj8HA3jqAsdMbKTaFBHopYO/pxY5RUak5ZcizNegdElERERE1MhcIoxLKRcAWFDHfROashZXIYRAjzb+yCkqQ16xkWGciIiIqAVyiTBOzn1y2wClSyAiIiKiJuR2F3ASEREREbUUDOMuzmyRSM8vVboMIiIiImoCDOMuLCWnGF2e+g3XvvW30qUQERERURNgGHdhYT56SAmcyy1BQWmLWVyUiIiIiMoxjLswjVqFdsHWxX9OpHMlTiIiIqKWhmHcxcWF2lbizFe4EiIiIiJqbAzjLi4uxLoS5/E09owTERERtTQM4y4uPrQ8jKcXKFwJERERETU2hnEXZ+sZP5bGME5ERETU0nAFThcXF+KN16/viQ6hPkqXQkRERESNjGHcxXno1LiuTxulyyAiIiKiJsBhKkRERERECmHPuBvYcTILv+w9j74xARjbPULpcoiIiIiokbBn3A0cOJeP//19AmsPpSldChERERE1IoZxNxAXYl34h9MbEhEREbUsDONuoGKu8UJIKRWuhoiIiIgaC8O4Gwjx1sPHoEFusREZBWVKl0NEREREjYRh3A0IIbgSJxEREVELxDDuJmwrcTKMExEREbUcnNrQTXSP8sOxtAJ46/klIyIiImopmOzcxPTBsZg+OFbpMoiIiIioEXGYChERERGRQhjG3YjZInEysxBlJovSpRARERFRI2AYdyP/evNvjHg1EUdS85UuhYiIiIgaAcO4G4kO9ADAGVWIiIiIWgqGcTdSeSVOIiIiInJ/DONuxD7XeBp7xomIiIhaAoZxN8KFf4iIiIhaFoZxN9I+xAsAkJRRCLNFKlwNEREREV0shnE34mPQIsxXjzKTBSnZxUqXQ0REREQXiStwupnXJveCv6cW4X4GpUshIiIioovEMO5mhnYIVroEIiIiImokHKZCRERERKQQ9oy7mfO5JXhv3XGoVQJP/quL0uUQERER0UVgz7gb+nhjMr7deUbpMoiIiIjoIjGMu5kwXz289RpkFxmRVVimdDlEREREdBEYxt2MEAJx5fONH+NKnERERERujWHcDXElTiIiIqKWgWHcDcWFlodx9owTERERuTWGcTfEnnEiIiKiloFh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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.figure(figsize=(12,8))\n", + "\n", + "plt.plot(running_mean(exp_020[:,1],l), running_mean(exp_020[:,3],l), '--', linewidth=2.0, c='C0')\n", + "plt.plot(running_mean(exp_020[:,1],l), running_mean(exp_020[:,4],l), linewidth=4.0, c='C0')\n", + "\n", + "plt.xlabel('Optimizer steps')\n", + "plt.ylabel('Perplexity')\n", + "plt.grid(True)\n", + "plt.legend(['Training - 0.2A backbone noise','Validation - 0.0A backbone noise'] )" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "00acbe24", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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ueGGG8jNzeX1119n6tSpvPXWW5xzzjkNquuCCy4gOzubiRMncskll+B0Ovnf//7HP/7xD1577TUWLlzIhAkT6j2+KpmvSuznzp3bJNco7cuDDz7InXfeSffu3YMdSshRYt/GrdqdB8Cw7olEOPUFjYg0r/vuu69W2QsvvEB+fj433XRTs3ehGDRoUJPU06tXryapJxS89tprvPnmm0ycOJFPP/2UqKgoAK699lomT57M1VdfzYwZMxq06ubNN9/MZZddRrdu3fxlhYWFPPHEE9x9993MnTuXtWvX1nlsXl4e//rXvzjuuOMYPnw4b731Funp6YwaNappLlTaja5dux7TIk5tmTK9Nq5DTDgXjO7BacenBjsUEZEAVX2Ty8vLeeCBBxg4cCCRkZH+/sf5+fk8/PDDzJgxgx49ehAREUFycjJnn302y5Ytq7POuvrY33fffRhjWLx4MW+++Sbjxo0jJiaGjh07ctFFF7Fnz556Y6tu8eLFGGO47777WLVqFT/4wQ9ISkoiJiaGadOm1dutZd++fcyZM4eUlBSio6MZOXIkL774YkB9zelvf/sbAL/97W/9ST3ACSecwI9//GOys7N58803G1TXHXfcEZDUVy+Pjo7mu+++Iycnp85jX375ZUpLS5k9e7b/3/hYuuPk5+dzww030L17d6KiohgyZAiPP/441tae1vmFF17gggsuoG/fvkRHR5OQkMCkSZN4+eWX660/NzeXu+++m6FDhxITE0NiYiIjRozgzjvvpLi4+IjxffbZZyQmJtKtWzdWrVpVa/uGDRs499xz6dixI7GxsUyePJmPP/64zrpcLhcPPfQQw4YNIyYmhoSEBKZMmcIbb7xRa9/q/fgzMjK46KKL6Ny5M1FRUYwdO5b//Oc/9cY8f/58TjrpJJKSkoiKimLw4MH89re/xeVyHfF6q1Tv//73v/+dYcOGERUVRWpqKnPnziU/P7/O41asWMEFF1xASkoKkZGR9O7dm+uuu459+/Yd9hzV/fvf/2bmzJl07dqVyMhIunXrxrRp03jqqadq1ZGbm8tdd93F4MGDiY6OpkePHsycObPef4NWw1qrnyb4GTNmjK1u0aJFVpqX7nHz0v09OuvWrWvwvgUFBc0YSejo3bu3Bez27dsDyqdNm2YB+8Mf/tCmpqba2bNn29tvv93+6U9/stZau2zZMhseHm5nzpxp586da++44w578cUX29jYWOt0Ou0HH3xQ61yAnTZtmv91QUGBvffeey1gZ82aZSMjI+2sWbPsbbfdZqdMmWIBO2jQIFtWVlZnbNUtWrTIAvYHP/iBjY6OtjNmzLC33nqrnTVrlnU4HDYqKspu2LAh4JjMzEz/9U+dOtXeeeeddvbs2TYmJsaee+65FrD33nvv0d/cIygtLbVhYWE2JibGVlRU1Nr+6quvWsBeeumlR32OgoIC63a7bXx8vAXswYMH69xv+PDh1uFw2F27dtmKigrbpUsXm5CQYIuKihp1vt69e9uuXbvasWPH2v79+9tbbrnF3nDDDbZr164WsNddd12tY6KiouyYMWPsFVdcYe+880579dVX2+7du1vA3nPPPbX237Ztm//fbcyYMfaWW26xN910kz3zzDNtREREwHu5rvfKyy+/bCMiIuygQYNsRkaGv3z79u3+90JSUpKdMmWKvfPOO+0VV1xho6KirMPhsK+99lpAXS6Xy06ePNn/Xr3tttvsddddZ1NSUixg77rrroD9q84xffp0m5ycbE888UR700032csvv9xGRkZah8NhP/vss1rXPGfOHAvYHj162CuvvNLecsstduLEif666nr/1OWKK67w/74lJCTYSy65xN5yyy121KhRFrAnnXRSrWPee+89GxERYcPDw+3FF19s77zzTnvKKadYwHbr1s1u27atznNU/3f4+9//bgHbpUsXe/XVV9u77rrLXnXVVfaEE06wY8eODTg+IyPDpqWlWcBOmTLF3nTTTXb27Nm2a9eu1hhjn3nmmQZda3No6P8hwLe2jnw06AlxW/lRYt/ydI+bl+7v0VFiX9uREvthw4bZ7OzsWsfl5eXVWb5r1y7btWtXO2jQoFrbDpfYx8fH2zVr1gTsf/HFF1vAvv7663XGVl1VYg/YefPmBWx7+umnLWB/9rOfBZRfeeWVFrC33357QPmqVatsREREsyf23333nQXs0KFD69z+zTffWMCOGzfuqM9RUFBgX3vtNQvY8ePH17nPsmXLLGBPPfVUf9mtt95qAfvcc8816nxV76dJkyYFfCDLycmxffv2tYD9/PPPA47ZsmVLrXpcLpedMWOGdTqddvfu3QHbJkyYYAH7+9//vtZx2dnZtrS01P+65nvloYcessYYO3nyZJuTkxNwbFXSDdjbbrstYNs333xjnU6nTUpKsvn5+f7y3//+9xawZ5xxRkByXf1D45IlS+o8x3333Rdwjg8//NBfV3Xz5s2zgD3vvPNsSUlJwLaq359HH3201r2oS1XS3bNnT7tjxw5/eUVFhf/D9FdffeUvLywstB07drQOh8N+8cUXAXU99NBDFrCnnHJKneeo/jdl9OjRNiIiwmZmZtaKqebfkWnTplljjJ0/f76/rKCgwB48eNCOGDHCRkVF2f379zfoepuaEvsQ+QnFxH5/fqn9YO1em5lfeuSdW6FQuMdtme7v0TnSH+Xed/yn3p9Xlh/6T/CV5TsOu291P3j8i3r3u/Ot1f791uzKO2yda3blNe3NqLrmIyT2CxYsaHSdP//5zy0QkDhYe/jE/u67765Vz2effWYBe+utt9YZW3VVif2kSZNq1VNeXm6dTqet/n+By+Wy0dHRNjExsc4PcT/96U+bPbFfsmRJvTFba+2mTZssYAcMGHDU51izZo1NTk62TqfTLl26tM59qj7gvPrqq/6ytWvXHtWHiqr3U80k0NpDCers2bMbVNdbb71lAfviiy/6y7799lsL2JEjR1q3233EOqreK263215//fUWsOeff35A8l+lKumu7z1RlbC+8MIL/rL+/ftbY4xdv359rf2fe+45C9g5c+bUOkfv3r1tZWVlrWN69eplO3XqFFA2cuRI63Q66/y2pbKy0nbq1MmecMIJh70PNa/h2WefrbXt+eeft4B94okn/GUvv/yyBezFF19ca/+Kigp/y3r13/X6EvuYmBibm5t72PhWrVplAXvhhRcGlFf9eyxYsMAC9sknn2zQ9Ta1Y03sNXi2Dft8Uza3v7mGM4Z24W+Xjgl2OCIidRo3bly925YsWcJjjz3GsmXLyMrKory8PGD7nj17GjzQdezYsbXKevbsCcDBgwcbHG9d9YSHh5OamhpQz8aNGyktLWXs2LF1DkydPHkyzz33XIPOmZGRUefUoM3dP/9IsrKy/DPlPPnkk3XOiFNQUMDrr79OUlIS5513nr986NChjBkzhq+//po1a9YwfPjwBp/X6XQyceLEWuVV4yvS09MDynfu3Mkf/vAHPv30U3bu3ElpaWnA9urjLJYvXw7AaaedhsPR8KGIF1xwAQsWLODnP/85jz766GGPHT16dJ3vienTp/Piiy+Snp7OFVdcQWFhIVu2bKFbt251DgyfMWNGndcL3hmnwsLCapX37NkzYIxKSUkJq1evpnPnzjz66KN1xhsZGcn69evrvZ66NPT3beXKlcCha6nO6XQydepUMjIySE9PP+zv+iWXXMKtt97KkCFDuOiii5g2bRqTJk0iOTk5YL+qa8/Pzw/4/XG5XERGRpKdnQ3Q6OsNFUrs2zCtOCsSejIe+oH/eWFhYb0zkfzkxF785MSGJaz/+fmUBu03rEdiwPlDRZcuXeosf+edd7jwwguJiorilFNOoV+/fsTGxuJwOFi8eDGff/55owb11ZxqE7yJA3jneT+Weqrqql5P1SDB1NS6Jy+or7wuGRkZ3H///bXKj5TYJyYmBsRSU1V5fdd0OFlZWcyYMYPNmzfz2GOPcd1119W53yuvvEJxcTHXXHNNwOBd8A6CXLFiBc888wx//etfG3zuzp0715m0Vr2Xql/vtm3bGDduHAcPHmTKlCmceuqpJCYmEhYWRkZGBi+++GLA+ygvLw+g0VMpfvHFFzidTs4666wjfiCo79++ZvxHeg9VzQxTFXN1h3ufejwe/+uDBw9irSU7O7vO99jRaujvW9U11jfLzeGusbpbbrmFzp0789RTT/H444/z6KOPYoxh2rRpPPzww/4PGlWDuxcuXMjChQvrra+oqOiw5wtVSuzbMK04KyKtQX2rdv76178mIiKCb7/9lsGDBwdsu+aaa/j8889bIryjlpDgXTskMzOzzu31lddl+vTp3v6zjdSvXz/CwsLYtm0blZWV/sSqyubNmwEYMGBAo+rdt28fM2fOZMOGDfz5z3/mxhtvrHffZ599FoC///3v/P3vf69zn1deeYWHH36Y6OjoBp3/wIEDuN3uWsn9/v37gUMfaAAeeeQRcnJymDdvXq0VX+fPn8+LL74YUFaVkNY1W9LhLFq0iJNPPpmzzz6bt956izPPPLPefev7t68Zf9VjVlZWnftXzRhT/Xobq+rYUaNG+VvPW1LV+auuvabGXOPll1/O5ZdfTl5eHkuXLuWdd97h+eef57TTTmPDhg0kJyf763nssccC3reHa2hpTTTdZRtV5KpkY2YhTodhaPej/4UXEQmWLVu2MGTIkFpJvcfj4X//+1+Qomq4QYMGER0dzZo1aygsLKy1vSWuISoqiokTJ1JSUsKXX35Za/sHH3wA1N0Noj67d+9m2rRpbNiwgaeffpqrr7663n2//fZb0tPT6datG1dddVWdP8OHDycvL6/OqRvrU1lZWef0oosXLwYImBu/akXcCy64oNb+dX04HD9+PAAfffRRQMv2kQwfPpzPP/+cDh06cN5557FgwYJ69125cmWd74ma8cfHx9OvXz/27t3r/xBW3aJFiwBv156jFRcXx/HHH8/3339Pbm7uUddztKquteraq6usrPS/bxtzjUlJSZx55pk8++yzzJ49m9zcXL744gvg0L9vXb8PbYES+zZqza48rIXjuyUQFV7760oRkVCXlpbG5s2b2bt3r7/MWst9993HunXrghhZw0RERPDjH/+Y/Px8fvvb3wZsW716NS+99FKLxPGzn/0MgHvuuYeysjJ/+TfffMPrr79OcnJyraR33759bNiwoVYXnh07djB16lS2bt3K888/f8SVY6vmqf/FL37Bc889V+fPI488ErBvQ911110BXWhyc3P993nOnDn+8qpF0Womjh999FGdYxzGjBnDxIkTWbVqFX/4wx9qbc/JyQm4j9UNHjyYL774gtTUVGbNmsXrr79e5375+fk88MADAWXffvstr7zyComJiQFjEa688kqstfzyl78M6MJy4MAB/u///s+/z7G45ZZbKC8v58orr6yzy8vBgwebrTW/ai7/+fPn+8c3VHn00UfZvn07J5988hHH0ixatKjOb7Wqvu2oWpF67NixTJkyhbfffpvnn3++zrrWrl1b77ckoU5dcdqolf5uOOpfLyKt080338y1117LqFGjuOCCCwgPD2fJkiWsW7eOs846i/feey/YIR7RQw89xGeffcYf//hHvvrqKyZOnMi+fft44403OPPMM1mwYEGjBmgejYsuuoi3336bN998k1GjRnHWWWeRk5PD66+/jtvt5tlnn/V3G6py11138eKLL9bqvjJ9+nQyMjIYM2YMGRkZ3Hffff5Bh1Vmz55NWloaRUVFzJ8/n/DwcK644op645sxYwZ9+/Zl6dKlfP/99xx//PFHvKauXbvicrkYOnQoZ599NhUVFbz55pvs27eP6667jqlTp/r3ve6665g3bx6zZs3iwgsvpFu3bnz33Xd8+OGH/OhHP6oz+X755ZeZPn06v/rVr3jrrbf8XaE2b97Mxx9/zIYNG+pdRbl///58+eWXzJgxg0suuQSXy8Xll18esM/UqVN57rnn+Oqrr5g0aRL79u3j9ddfx+Px8Pe//z3g3+O2227jP//5D++++y4jRozgzDPPpKSkhH/9619kZWVx++23M3ny5CPes8O58sorWbFiBU899RT9+vXjtNNOo1evXuTm5rJ9+3a++OIL5syZw9NPP31M56lLXFwczz//PLNmzWLatGnMmjWLXr16sWLFCj7++GO6dOlSbxeu6s477zzi4uIYP348aWlpWGv58ssv+eabbxgzZgwnn3yyf99XX32VGTNmcNVVV/H4449z4oknEhMTQ1ZWFmvWrOG7775j2bJlpKSkNPn1Njcl9m1UdqELh1H/ehFpva655hoiIyN59NFHefHFF4mOjmbKlCnMmzePt956q1Uk9qmpqSxdupRf/epXvP/++3z11VcMHDiQp556itjYWBYsWFArqW5qxhjmz5/PxIkTef7553niiSeIiopi6tSp3HPPPXXOLlOfqpU+V6xYwYoVK+rcZ/r06aSlpTF//nyKioo477zzDjtQ2BjDVVddxd13380zzzzDY489dsQ4IiIi+OSTT/jVr37Fa6+9xoEDB+jbty933nknP//5zwP2HT58OIsWLeKee+7hv//9L5WVlYwYMYK3336bpKSkOhP7Pn36sHLlSv74xz+yYMEC/vrXvxIVFUVaWhq33nrrERO+3r1788UXXzBz5kzmzJmDy+UK6LLUp08fnn76ae68806efvppXC4Xo0eP5je/+Q2nnXZarWt99913efbZZ3n11Vd54okncDqdjBgxgkcffZSLL774iPerIZ588knOOOMMnn76aT755BPy8vLo2LEjvXr14pe//CWXXnppk5ynLueccw5Llizh97//PR999BH5+fl06dKFa6+9ll//+td1rnZc00MPPcRHH33EypUref/994mKiqJ379784Q9/4Gc/+xnh4eH+fXv06MGKFSt44okneOutt3jllVdwu9106dKFIUOG8POf/5xhw4Y12/U2J3M0g3GktrFjx9pvv/3W/3rx4sW1ljVvaUWuSsKMITqibXbFCYV73Jbp/h6d9evX1+oTXp+2MlgrlIXyPb777rv5/e9/z4cfflgrmWtNQvketxW6x80vVO5xQ/8PMcassNbWmlNUfezbsLhIZ5tN6kVEWovqYwSqrF27lscff5yOHTsybdq0IEQlIm2RuuK0QWUVbiKdjnqnkBMRkZYzduxY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J5zAZZddFrp07NiR1157jdzc3LjP65133ilXB/j22kSuFbhr07dv33Kd+pKSElasWBF6ngAvv/xy3PUBGD16NPPnz2fLli2ceeaZMT8AQXi7LC4uLq1PpP6R9YoVKzh48GC5MkuXLgXc0KSq+sEPfkCDBg1KO741LRW/V927d2fUqFG8+uqrHHHEEaxYsaL0Tn3k+tbW+QZZx76Oe8feOGuMSWORITd33303CxYsoEOHDlxwwQVl8vTq1Qso/4f43XffrfZbZL/zne8wdOhQcnNzmT17dpltixYtCu0QxarPhg0buOmmm0KPE3mYLvqhycpceumlANx+++1s3769NL24uJgbb7yRkpISLrvssrj3VxN69+7NGWecQV5eHvfff3+ZbVOmTGHPnj2MGTOG5s2bl9n2ySef8Mknn5RJa9GiBWPGjGHPnj3lvqmZPXs2eXl5/PjHPy7z4qDIcIcHHniARx55JHS58sorSx+wjdeuXbu47bbbyqStXr2ap556itatW5dps7169WLt2rVs2bKlNE1VmTp1augDkmPHjqVVq1Y8+OCDLF++vNz2TZs2xazXiBEjeO655/jqq68YNGgQH374YWi+JUuWsHjx4jJps2fPZv369WRnZ9OzZ0/APag7dOhQ8vLymDlzZpn8b731FvPmzaNt27blfkcT0alTJ0aNGsXq1auZPn166Ifd9evXJ/TBKxHJ+L3avn0777//frn0PXv2UFRURMOGDWncuDHgvpls06YN06ZNK/MQcURJSUnoh4yUCZsqx5a6M93laTOWaM+bFusHmwtq5HjpxKZjTC2Lb9XYPPZllZSUaK9evUqn5AvO/6yqunnzZm3Xrp02aNBAL7jgAp00aZJecMEF2qhRI73ooovKTWGpWvV57M8666xK57EvKirSI444onSu/UmTJunPfvYzbdGiRWl9oucZV3XXvUGDBtq5c2e94YYbdPr06Tp9+vQK66uqOmnSJAW0U6dOevXVV+uvfvUrPeaYYxTQU089NXS+7Z49e4bGurIpN5Nl3bp12qlTJwV02LBhesMNN2h2drYC2qdPnzJTAUZErn9Qfn5+6fsKInOqDxs2rDQm0dOgLl26VAE99thjK6xfbm6uioh27dpVDxw4UGleEpzH/qGHHiqt3y9+8QudMGGCnnDCCdq0adPS9hS8BosXLy7d39lnn62T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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.figure(figsize=(12,8))\n", + "k = 5\n", + "\n", + "plt.plot(running_mean(exp_020[:,1],l), running_mean(exp_020[:,5],l), '--', linewidth=2.0, c='C0')\n", + "plt.plot(running_mean(exp_020[:,1],l), running_mean(exp_020[:,6],l), linewidth=4.0, c='C0')\n", + "\n", + "plt.xlabel('Optimizer steps')\n", + "plt.ylabel('Accuracy')\n", + "plt.grid(True)\n", + "plt.legend(['Training - 0.2A backbone noise','Validation - 0.0A backbone noise'] )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b8d291e8", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/scripts/training.py b/scripts/training.py new file mode 100644 index 0000000000000000000000000000000000000000..01189ed17249804a9d1df0cf5eacd4a50915af73 --- /dev/null +++ b/scripts/training.py @@ -0,0 +1,270 @@ +import argparse +import os +import sys + +_PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__)) +while _PROJECT_ROOT and not os.path.isdir(os.path.join(_PROJECT_ROOT, "model")): + _PARENT = os.path.dirname(_PROJECT_ROOT) + if _PARENT == _PROJECT_ROOT: + break + _PROJECT_ROOT = _PARENT +_MODEL_ROOT = os.path.join(_PROJECT_ROOT, "model") +_ONESCIENCE_ROOT = os.environ.get("ONESCIENCE_ROOT") +for _path in (_MODEL_ROOT, _PROJECT_ROOT): + if os.path.exists(_path) and _path not in sys.path: + sys.path.insert(0, _path) +if _ONESCIENCE_ROOT: + _ONESCIENCE_SRC = os.path.join(_ONESCIENCE_ROOT, "src") + for _path in (_ONESCIENCE_SRC, _ONESCIENCE_ROOT): + if os.path.exists(_path) and _path not in sys.path: + sys.path.insert(0, _path) +import os.path + +def main(args): + import json, time, os, sys, glob + import shutil + import warnings + import numpy as np + import torch + from torch import optim + from torch.utils.data import DataLoader + import queue + import copy + import torch.nn as nn + import torch.nn.functional as F + import random + import os.path + import subprocess + from concurrent.futures import ProcessPoolExecutor + from proteinmpnn.utils import worker_init_fn, get_pdbs, loader_pdb, build_training_clusters, PDB_dataset, StructureDataset, StructureLoader + from proteinmpnn.model_utils import featurize, loss_smoothed, loss_nll, get_std_opt, ProteinMPNN + + scaler = torch.cuda.amp.GradScaler() + + device = torch.device("cuda:0" if (torch.cuda.is_available()) else "cpu") + + base_folder = time.strftime(args.path_for_outputs, time.localtime()) + + if base_folder[-1] != '/': + base_folder += '/' + if not os.path.exists(base_folder): + os.makedirs(base_folder) + subfolders = ['model_weights'] + for subfolder in subfolders: + if not os.path.exists(base_folder + subfolder): + os.makedirs(base_folder + subfolder) + + PATH = args.previous_checkpoint + + logfile = base_folder + 'log.txt' + if not PATH: + with open(logfile, 'w') as f: + f.write('Epoch\tTrain\tValidation\n') + + data_path = args.path_for_training_data + params = { + "LIST" : f"{data_path}/list.csv", + "VAL" : f"{data_path}/valid_clusters.txt", + "TEST" : f"{data_path}/test_clusters.txt", + "DIR" : f"{data_path}", + "DATCUT" : "2030-Jan-01", + "RESCUT" : args.rescut, #resolution cutoff for PDBs + "HOMO" : 0.70 #min seq.id. to detect homo chains + } + + + LOAD_PARAM = {'batch_size': 1, + 'shuffle': True, + 'pin_memory':False, + 'num_workers': 4} + + + if args.debug: + args.num_examples_per_epoch = 50 + args.max_protein_length = 1000 + args.batch_size = 1000 + + train, valid, test = build_training_clusters(params, args.debug) + + train_set = PDB_dataset(list(train.keys()), loader_pdb, train, params) + train_loader = torch.utils.data.DataLoader(train_set, worker_init_fn=worker_init_fn, **LOAD_PARAM) + valid_set = PDB_dataset(list(valid.keys()), loader_pdb, valid, params) + valid_loader = torch.utils.data.DataLoader(valid_set, worker_init_fn=worker_init_fn, **LOAD_PARAM) + + + model = ProteinMPNN(node_features=args.hidden_dim, + edge_features=args.hidden_dim, + hidden_dim=args.hidden_dim, + num_encoder_layers=args.num_encoder_layers, + num_decoder_layers=args.num_encoder_layers, + k_neighbors=args.num_neighbors, + dropout=args.dropout, + augment_eps=args.backbone_noise) + model.to(device) + + + if PATH: + checkpoint = torch.load(PATH) + total_step = checkpoint['step'] #write total_step from the checkpoint + epoch = checkpoint['epoch'] #write epoch from the checkpoint + model.load_state_dict(checkpoint['model_state_dict']) + else: + total_step = 0 + epoch = 0 + + optimizer = get_std_opt(model.parameters(), args.hidden_dim, total_step) + + + if PATH: + optimizer.optimizer.load_state_dict(checkpoint['optimizer_state_dict']) + + + with ProcessPoolExecutor(max_workers=12) as executor: + q = queue.Queue(maxsize=3) + p = queue.Queue(maxsize=3) + for i in range(3): + q.put_nowait(executor.submit(get_pdbs, train_loader, 1, args.max_protein_length, args.num_examples_per_epoch)) + p.put_nowait(executor.submit(get_pdbs, valid_loader, 1, args.max_protein_length, args.num_examples_per_epoch)) + pdb_dict_train = q.get().result() + pdb_dict_valid = p.get().result() + + dataset_train = StructureDataset(pdb_dict_train, truncate=None, max_length=args.max_protein_length) + dataset_valid = StructureDataset(pdb_dict_valid, truncate=None, max_length=args.max_protein_length) + + loader_train = StructureLoader(dataset_train, batch_size=args.batch_size) + loader_valid = StructureLoader(dataset_valid, batch_size=args.batch_size) + + reload_c = 0 + for e in range(args.num_epochs): + t0 = time.time() + e = epoch + e + model.train() + train_sum, train_weights = 0., 0. + train_acc = 0. + if e % args.reload_data_every_n_epochs == 0: + if reload_c != 0: + pdb_dict_train = q.get().result() + dataset_train = StructureDataset(pdb_dict_train, truncate=None, max_length=args.max_protein_length) + loader_train = StructureLoader(dataset_train, batch_size=args.batch_size) + pdb_dict_valid = p.get().result() + dataset_valid = StructureDataset(pdb_dict_valid, truncate=None, max_length=args.max_protein_length) + loader_valid = StructureLoader(dataset_valid, batch_size=args.batch_size) + q.put_nowait(executor.submit(get_pdbs, train_loader, 1, args.max_protein_length, args.num_examples_per_epoch)) + p.put_nowait(executor.submit(get_pdbs, valid_loader, 1, args.max_protein_length, args.num_examples_per_epoch)) + reload_c += 1 + for _, batch in enumerate(loader_train): + start_batch = time.time() + X, S, mask, lengths, chain_M, residue_idx, mask_self, chain_encoding_all = featurize(batch, device) + elapsed_featurize = time.time() - start_batch + optimizer.zero_grad() + mask_for_loss = mask*chain_M + + if args.mixed_precision: + with torch.cuda.amp.autocast(): + log_probs = model(X, S, mask, chain_M, residue_idx, chain_encoding_all) + _, loss_av_smoothed = loss_smoothed(S, log_probs, mask_for_loss) + + scaler.scale(loss_av_smoothed).backward() + + if args.gradient_norm > 0.0: + total_norm = torch.nn.utils.clip_grad_norm_(model.parameters(), args.gradient_norm) + + scaler.step(optimizer) + scaler.update() + else: + log_probs = model(X, S, mask, chain_M, residue_idx, chain_encoding_all) + _, loss_av_smoothed = loss_smoothed(S, log_probs, mask_for_loss) + loss_av_smoothed.backward() + + if args.gradient_norm > 0.0: + total_norm = torch.nn.utils.clip_grad_norm_(model.parameters(), args.gradient_norm) + + optimizer.step() + + loss, loss_av, true_false = loss_nll(S, log_probs, mask_for_loss) + + train_sum += torch.sum(loss * mask_for_loss).cpu().data.numpy() + train_acc += torch.sum(true_false * mask_for_loss).cpu().data.numpy() + train_weights += torch.sum(mask_for_loss).cpu().data.numpy() + + total_step += 1 + + model.eval() + with torch.no_grad(): + validation_sum, validation_weights = 0., 0. + validation_acc = 0. + for _, batch in enumerate(loader_valid): + X, S, mask, lengths, chain_M, residue_idx, mask_self, chain_encoding_all = featurize(batch, device) + log_probs = model(X, S, mask, chain_M, residue_idx, chain_encoding_all) + mask_for_loss = mask*chain_M + loss, loss_av, true_false = loss_nll(S, log_probs, mask_for_loss) + + validation_sum += torch.sum(loss * mask_for_loss).cpu().data.numpy() + validation_acc += torch.sum(true_false * mask_for_loss).cpu().data.numpy() + validation_weights += torch.sum(mask_for_loss).cpu().data.numpy() + + train_loss = train_sum / train_weights + train_accuracy = train_acc / train_weights + train_perplexity = np.exp(train_loss) + validation_loss = validation_sum / validation_weights + validation_accuracy = validation_acc / validation_weights + validation_perplexity = np.exp(validation_loss) + + train_perplexity_ = np.format_float_positional(np.float32(train_perplexity), unique=False, precision=3) + validation_perplexity_ = np.format_float_positional(np.float32(validation_perplexity), unique=False, precision=3) + train_accuracy_ = np.format_float_positional(np.float32(train_accuracy), unique=False, precision=3) + validation_accuracy_ = np.format_float_positional(np.float32(validation_accuracy), unique=False, precision=3) + + t1 = time.time() + dt = np.format_float_positional(np.float32(t1-t0), unique=False, precision=1) + with open(logfile, 'a') as f: + f.write(f'epoch: {e+1}, step: {total_step}, time: {dt}, train: {train_perplexity_}, valid: {validation_perplexity_}, train_acc: {train_accuracy_}, valid_acc: {validation_accuracy_}\n') + print(f'epoch: {e+1}, step: {total_step}, time: {dt}, train: {train_perplexity_}, valid: {validation_perplexity_}, train_acc: {train_accuracy_}, valid_acc: {validation_accuracy_}') + + checkpoint_filename_last = base_folder+'model_weights/epoch_last.pt'.format(e+1, total_step) + torch.save({ + 'epoch': e+1, + 'step': total_step, + 'num_edges' : args.num_neighbors, + 'noise_level': args.backbone_noise, + 'model_state_dict': model.state_dict(), + 'optimizer_state_dict': optimizer.optimizer.state_dict(), + }, checkpoint_filename_last) + + if (e+1) % args.save_model_every_n_epochs == 0: + checkpoint_filename = base_folder+'model_weights/epoch{}_step{}.pt'.format(e+1, total_step) + torch.save({ + 'epoch': e+1, + 'step': total_step, + 'num_edges' : args.num_neighbors, + 'noise_level': args.backbone_noise, + 'model_state_dict': model.state_dict(), + 'optimizer_state_dict': optimizer.optimizer.state_dict(), + }, checkpoint_filename) + + +if __name__ == "__main__": + argparser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + argparser.add_argument("--path_for_training_data", type=str, default="my_path/pdb_2021aug02", help="path for loading training data") + argparser.add_argument("--path_for_outputs", type=str, default="./exp_020", help="path for logs and model weights") + argparser.add_argument("--previous_checkpoint", type=str, default="", help="path for previous model weights, e.g. file.pt") + argparser.add_argument("--num_epochs", type=int, default=200, help="number of epochs to train for") + argparser.add_argument("--save_model_every_n_epochs", type=int, default=10, help="save model weights every n epochs") + argparser.add_argument("--reload_data_every_n_epochs", type=int, default=2, help="reload training data every n epochs") + argparser.add_argument("--num_examples_per_epoch", type=int, default=1000000, help="number of training example to load for one epoch") + argparser.add_argument("--batch_size", type=int, default=10000, help="number of tokens for one batch") + argparser.add_argument("--max_protein_length", type=int, default=10000, help="maximum length of the protein complext") + argparser.add_argument("--hidden_dim", type=int, default=128, help="hidden model dimension") + argparser.add_argument("--num_encoder_layers", type=int, default=3, help="number of encoder layers") + argparser.add_argument("--num_decoder_layers", type=int, default=3, help="number of decoder layers") + argparser.add_argument("--num_neighbors", type=int, default=48, help="number of neighbors for the sparse graph") + argparser.add_argument("--dropout", type=float, default=0.1, help="dropout level; 0.0 means no dropout") + argparser.add_argument("--backbone_noise", type=float, default=0.2, help="amount of noise added to backbone during training") + argparser.add_argument("--rescut", type=float, default=3.5, help="PDB resolution cutoff") + argparser.add_argument("--debug", type=bool, default=False, help="minimal data loading for debugging") + argparser.add_argument("--gradient_norm", type=float, default=-1.0, help="clip gradient norm, set to negative to omit clipping") + argparser.add_argument("--mixed_precision", type=bool, default=True, help="train with mixed precision") + + args = argparser.parse_args() + main(args) diff --git a/weight/README.md b/weight/README.md new file mode 100644 index 0000000000000000000000000000000000000000..06d6c8c657d42fae169125ae0b1b805174c1d7e2 --- /dev/null +++ b/weight/README.md @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7edea501e2e0a1dc01871d3224129786e90fcfac563892d62ade89db15285c20 +size 267 diff --git a/weight/ca_model_weights/v_48_002.pt b/weight/ca_model_weights/v_48_002.pt new file mode 100644 index 0000000000000000000000000000000000000000..a7e5820ed4b5293b0f7967c88b18aeca96d4a23f --- /dev/null +++ b/weight/ca_model_weights/v_48_002.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ec038b44a987d7c8351b6ed887c82a2370d54e45e55a6bdaf508a729cef0340e +size 6624011 diff --git a/weight/ca_model_weights/v_48_010.pt b/weight/ca_model_weights/v_48_010.pt new file mode 100644 index 0000000000000000000000000000000000000000..809e27eea1dc02e5cc94bb5d9285144d2bb8eaed --- /dev/null +++ b/weight/ca_model_weights/v_48_010.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cdb50498d45578d20b271fa7817b8cd8bfde3875ad69dbd3f5e4b5dd3e588301 +size 6624011 diff --git a/weight/ca_model_weights/v_48_020.pt b/weight/ca_model_weights/v_48_020.pt new file mode 100644 index 0000000000000000000000000000000000000000..8e05a785ad84ea2a4a26cd1fd83b2b3be993d2a6 --- /dev/null +++ b/weight/ca_model_weights/v_48_020.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f28f40170e21858c5ff31ef50b6e63414ff76dc331b19f85aa8586a12031744a +size 6624011 diff --git a/weight/soluble_model_weights/excluded_PDBs.csv b/weight/soluble_model_weights/excluded_PDBs.csv new file mode 100644 index 0000000000000000000000000000000000000000..37ce8d933890e7662a8ffd270f6847a1ece550ed --- /dev/null +++ b/weight/soluble_model_weights/excluded_PDBs.csv @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6fd459eed9852cc12ca94cc33654b9a163f88d692a2caf7f9a428c937dc89b3b +size 179958 diff --git a/weight/soluble_model_weights/v_48_002.pt b/weight/soluble_model_weights/v_48_002.pt new file mode 100644 index 0000000000000000000000000000000000000000..1b8acfdc10e14aaabd8ac32abf469b47ec177cd2 --- /dev/null +++ b/weight/soluble_model_weights/v_48_002.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0877f840978fe770be6fcec025784d8f50c438571db3260c05e41aa207a7c448 +size 6681301 diff --git a/weight/soluble_model_weights/v_48_010.pt b/weight/soluble_model_weights/v_48_010.pt new file mode 100644 index 0000000000000000000000000000000000000000..17046258ee561c4260db62c75c618b889b14dc2c --- /dev/null +++ b/weight/soluble_model_weights/v_48_010.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:79562f7444f72c84595a1c96010713864865a616f4f3967633493041e169fa6e +size 6681301 diff --git a/weight/soluble_model_weights/v_48_020.pt b/weight/soluble_model_weights/v_48_020.pt new file mode 100644 index 0000000000000000000000000000000000000000..67a02002ad0cb9d9cd00957013d2b638f943ca0b --- /dev/null +++ b/weight/soluble_model_weights/v_48_020.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7af52d090172c230c7f0e9d21e02203f6b3a38b16db58d3c7a3960e0a9a6e31a +size 6681301 diff --git a/weight/soluble_model_weights/v_48_030.pt b/weight/soluble_model_weights/v_48_030.pt new file mode 100644 index 0000000000000000000000000000000000000000..102d4a7c533a9cc21a9254706bdb749482a486fd --- /dev/null +++ b/weight/soluble_model_weights/v_48_030.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1dd63f1e9fc68a133cc9ef859edf43b489e5ac581cb5624e0b9ec848ff062421 +size 6681301 diff --git a/weight/vanilla_model_weights/v_48_002.pt b/weight/vanilla_model_weights/v_48_002.pt new file mode 100644 index 0000000000000000000000000000000000000000..aaf73d9bbacd4c9c1c3c58afcbd1c5340b169443 --- /dev/null +++ b/weight/vanilla_model_weights/v_48_002.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:925f2ca1007bf9b02e0e7f420ff00eb91f50fcc2722f64b42e644ae95adaa131 +size 6681301 diff --git a/weight/vanilla_model_weights/v_48_010.pt b/weight/vanilla_model_weights/v_48_010.pt new file mode 100644 index 0000000000000000000000000000000000000000..ece7a865b78c19b62fd3c2df549a28cc2ab982b0 --- /dev/null +++ b/weight/vanilla_model_weights/v_48_010.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:db866fae956a28661f926053d630610c55e9fc4bc03922f2aeeb98a37435ccce +size 6681301 diff --git a/weight/vanilla_model_weights/v_48_020.pt b/weight/vanilla_model_weights/v_48_020.pt new file mode 100644 index 0000000000000000000000000000000000000000..9f217ffe4c9707fdd589112acd813a57b102c6b7 --- /dev/null +++ b/weight/vanilla_model_weights/v_48_020.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c9cb4a671d79604111231f8dbfc7c590e06f1197453b7a6854ac6661a642f5bd +size 6681301 diff --git a/weight/vanilla_model_weights/v_48_030.pt b/weight/vanilla_model_weights/v_48_030.pt new file mode 100644 index 0000000000000000000000000000000000000000..7142bd5b5d1e66b1103877fbaca0edd0a111c6ff --- /dev/null +++ b/weight/vanilla_model_weights/v_48_030.pt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c34b7bfb38418ea30989fda3314f4781ac4e3920f9825731cf555f1fed44ac66 +size 6681301