diff --git a/.gitattributes b/.gitattributes index a6344aac8c09253b3b630fb776ae94478aa0275b..a926c946b82bb1eed40e004b1f08e1de328813a3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,35 +1,3 @@ -*.7z filter=lfs diff=lfs merge=lfs -text -*.arrow filter=lfs diff=lfs merge=lfs -text -*.bin filter=lfs diff=lfs merge=lfs -text -*.bz2 filter=lfs diff=lfs merge=lfs -text -*.ckpt filter=lfs diff=lfs merge=lfs -text -*.ftz filter=lfs diff=lfs merge=lfs -text -*.gz filter=lfs diff=lfs merge=lfs -text -*.h5 filter=lfs diff=lfs merge=lfs -text -*.joblib filter=lfs diff=lfs merge=lfs -text -*.lfs.* filter=lfs diff=lfs merge=lfs -text -*.mlmodel filter=lfs diff=lfs merge=lfs -text -*.model filter=lfs diff=lfs merge=lfs -text -*.msgpack filter=lfs diff=lfs merge=lfs -text -*.npy filter=lfs diff=lfs merge=lfs -text -*.npz filter=lfs diff=lfs merge=lfs -text -*.onnx filter=lfs diff=lfs merge=lfs -text -*.ot filter=lfs diff=lfs merge=lfs -text -*.parquet filter=lfs diff=lfs merge=lfs -text -*.pb filter=lfs diff=lfs merge=lfs -text -*.pickle filter=lfs diff=lfs merge=lfs -text -*.pkl filter=lfs diff=lfs merge=lfs -text -*.pt filter=lfs diff=lfs merge=lfs -text -*.pth filter=lfs diff=lfs merge=lfs -text -*.rar filter=lfs diff=lfs merge=lfs -text -*.safetensors filter=lfs diff=lfs merge=lfs -text -saved_model/**/* filter=lfs diff=lfs merge=lfs -text -*.tar.* filter=lfs diff=lfs merge=lfs -text -*.tar filter=lfs diff=lfs merge=lfs -text -*.tflite filter=lfs diff=lfs merge=lfs -text -*.tgz filter=lfs diff=lfs merge=lfs -text -*.wasm filter=lfs diff=lfs merge=lfs -text -*.xz filter=lfs diff=lfs merge=lfs -text -*.zip filter=lfs diff=lfs merge=lfs -text -*.zst filter=lfs diff=lfs merge=lfs -text -*tfevents* filter=lfs diff=lfs merge=lfs -text +weight/**/*.npz filter=lfs diff=lfs merge=lfs -text +weight/**/*.tar filter=lfs diff=lfs merge=lfs -text +scripts/paper_resources/extras/top1_benchmark_1_2_pdbs.zip filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..f33f05c6569a0e1a9349fb79e435c32b43554e95 --- /dev/null +++ b/.gitignore @@ -0,0 +1,45 @@ +# Prerequisites +*.d + +# Python caches +__pycache__/ +*.py[cod] + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# Other +.DS_Store +model/CombinatorialAssembler/.vscode/* +*/.idea/* +scripts/paper_resources/figures/output + +# Runtime outputs +output/ diff --git a/.ms_upload_cache b/.ms_upload_cache new file mode 100644 index 0000000000000000000000000000000000000000..1ad9e3d573d8dc0c25a74b96c2a2474885f792f1 --- /dev/null +++ b/.ms_upload_cache @@ -0,0 +1 @@ +{"version": 3, "repo_id": "OneScience/CombFold", "files": {".gitignore|1787795579.0|477": {"hash": "79fe2df87fdfa764102ce75153ce260598b7958ef9f2cb8f9da2ba7db9b79104", "size": 477, "status": "c"}, "conf/config.json|1787795221.0|272": {"hash": "b1eb917a4b127436c1477b0d72cc2c8570548d16ee460309f5defebe92507536", "size": 272, "status": "c"}, "model/CombinatorialAssembler/AF2trans/AF2trans.cc|1785404936.0|4915": {"hash": "01b00782b600c60022c53ccecf8619285fc71f7d31e09ea1386b83a39fc29331", "size": 4915, "status": "c"}, ".gitattributes|1787795481.0|104": {"hash": "afb36dd7bd41d9dea1da8c9b1140f3231fcda3c5c2105df14396f271dc968a1b", "size": 104, "status": "c"}, "configuration.json|1787795222.0|386": {"hash": "66b1e8a9ad8538718120d2d58dd0f996d35c44210115171e31aeb5e889229b50", "size": 386, "status": "c"}, "model/CombinatorialAssembler/BB.cc|1785404936.0|7052": {"hash": "28e9ae9bf638f6c760473b4e3c924cf0097d718d3f9f0602b30b91b722a84eca", "size": 7052, "status": "c"}, "LICENSE|1785404936.0|11558": {"hash": "1eb85fc97224598dad1852b5d6483bbcf0aa8608790dcc657a5a2a761ae9c8c6", "size": 11558, "status": "c"}, "README.md|1787799250.0|10797": {"hash": "ea97d92e93d0f5bed4746d58c72adc25d45ddea4f6a9d9eeb7824da7a0b41579", "size": 10797, "status": "c"}, "model/CombinatorialAssembler/BB.h|1785404936.0|3642": {"hash": "15472c251ac3508668890a80007d087dfff569462ae82dd4c840d5569a005401", "size": 3642, "status": "c"}, "model/CombinatorialAssembler/BBGrid.cc|1785404936.0|2228": {"hash": "3e81da02c2db357e3dbc50e5837494c496ce2939353ae2556871c91c67036b5b", "size": 2228, "status": "c"}, "model/CombinatorialAssembler/BestKContainer.h|1785404936.0|1416": {"hash": "bfd1b7059a40f89ca00f8ccb7c152319143af0cab3a3767cc619b4182a917906", "size": 1416, "status": "c"}, "model/CombinatorialAssembler/BBContainer.cc|1785404936.0|5297": {"hash": "7ffbf29d0e7afc40edf0d096cfc170f9ea948734992c3c51cf2ebf412b9086ab", "size": 5297, "status": "c"}, "model/CombinatorialAssembler/BestK.cc|1785404936.0|2452": {"hash": "5243ac22205eb9a5b9ff56d71741ca9685c37a16727057fbe4f85b745a72ed4b", "size": 2452, "status": "c"}, "model/CombinatorialAssembler/BestK.h|1785404936.0|1865": {"hash": "de607f197d313cc9566409d416e346b5c025e14736f8aa96c0db63693a499d47", "size": 1865, "status": "c"}, "model/CombinatorialAssembler/BBGrid.h|1785404936.0|521": {"hash": "a978ac48c215dd77910fe2d7bc87e63651d26d46ad5085267b1a228d3d7f4461", "size": 521, "status": "c"}, "model/CombinatorialAssembler/BBContainer.h|1785404936.0|1226": {"hash": "f31bf57c1c9e1c46e8c4b9dec175ed755072fd28cde644c9f6abf3207feaceba", "size": 1226, "status": "c"}, "model/CombinatorialAssembler/BitId.h|1785404936.0|1160": {"hash": "a275633352df81c05ffa9b57c4aec15f65048917d9c77995dcf42d358c7364f3", "size": 1160, "status": "c"}, "model/CombinatorialAssembler/ComplexDistanceConstraint.cc|1785404936.0|9692": {"hash": "9cacc0d10c532e4602c1e2c11166d6a9f2017d9506bf38f39b00a9e987afea07", "size": 9692, "status": "c"}, "model/CombinatorialAssembler/DOCK.conf|1785404936.0|441": {"hash": "bafa54cbfd67ba9dfab039890e4582836718797fa48464bb6d3863f50c674600", "size": 441, "status": "c"}, "model/CombinatorialAssembler/HierarchicalFold.h|1785404936.0|3680": {"hash": "c4f92601d9ede3f2c70ebbf4d92ee900cca6161d8567cf731a57bfc4d2ee15ef", "size": 3680, "status": "c"}, "model/CombinatorialAssembler/ComplexDistanceConstraint.h|1785404936.0|2349": {"hash": "4227d984a46096694832aa582d2809999a4a90425baabcbe071627ab18afb48f", "size": 2349, "status": "c"}, "model/CombinatorialAssembler/HierarchicalFold.cc|1785404936.0|34499": {"hash": "26c0b622d3028025ff362e99dbc45868a7314d200874a7bc4478dc7e2f1a9057", "size": 34499, "status": "c"}, "model/CombinatorialAssembler/FoldStep.h|1785404936.0|1192": {"hash": "7c18a2f6b08defbfa35d49615ee533387fc4e5557f11234add6d002e23b57748", "size": 1192, "status": "c"}, "model/CombinatorialAssembler/MainCombDock.cc|1785404936.0|5743": {"hash": "0f377d48e0405d172a56bcde0321877449d943f7ef030fcbfd4f9ad714a36814", "size": 5743, "status": "c"}, "model/CombinatorialAssembler/Makefile|1785404936.0|1714": {"hash": "61eb4ec6e5b000787f704bc5f0426e7073d31f2af2fe66ca78762f989e072182", "size": 1714, "status": "c"}, "model/CombinatorialAssembler/SuperBB.cc|1785404936.0|12116": {"hash": "68f970a26c6bb121b6faff32ba8224b7880269f55650665b9015c3047aea7262", "size": 12116, "status": "c"}, "model/CombinatorialAssembler/TransformationAndScore.cc|1785404936.0|411": {"hash": "c625cb13d70a7fd49c4d80539d1816025e0f3f759cb587866cc2e9c275cf8c0a", "size": 411, "status": "c"}, "model/CombinatorialAssembler/SuperBB.h|1785404936.0|2994": {"hash": "e72e752b8a3561ededfcbabf9d27c045e3944df7ea77a6e2026dba93c5ba9669", "size": 2994, "status": "c"}, "model/CombinatorialAssembler/chem_params.txt|1785404936.0|10712": {"hash": "e380e22ddb17f94bd0a62f8848039e973585fb95e98b47541cdca8daaeaf3e6e", "size": 10712, "status": "c"}, "model/CombinatorialAssembler/TransformationAndScore.h|1785404936.0|1865": {"hash": "39ab9de2b36d7d408c359d6f7b9d4e83c58b48107bf625e097cc0793f68c7d4b", "size": 1865, "status": "c"}, "model/CombinatorialAssembler/libs_DockingLib/ChemAtom.h|1785404936.0|3955": {"hash": "e2772818037fcef65a46f5964478dfef9c42fcd1ffa9ccaa7143ad16f176cb62", "size": 3955, "status": "c"}, "model/CombinatorialAssembler/libs_DockingLib/ChemAtom.cc|1785404936.0|2039": {"hash": "99a852c6fde6d4fd1371c895dfc39e70de731c0daf507a8fa1c880e7c1545c50", "size": 2039, "status": "c"}, "model/CombinatorialAssembler/libs_DockingLib/ChemLib.cc|1785404936.0|3956": {"hash": "c024cbca027d55c8d9067999dc89ced567d82af7d471bce87831dc79ba3b601c", "size": 3956, "status": "c"}, "model/CombinatorialAssembler/libs_DockingLib/ChemLib.h|1785404936.0|2921": {"hash": "a62d90ed08d343914390d9134f2fbc0a3ddb399f79bc0d75d87afe68072c49df", "size": 2921, "status": "c"}, "model/CombinatorialAssembler/libs_DockingLib/ChemMolecule.h|1785404936.0|4074": {"hash": "8a79db5835a90b6441906455df40dacfe0c93e997dd332ae4825392b46ed925d", "size": 4074, "status": "c"}, "model/CombinatorialAssembler/libs_DockingLib/ChemMolecule.cc|1785404936.0|10581": {"hash": "855f8cc68274309d29cd4c7893c3b7265f7df8e1b1f5037d73e5e381df45711a", "size": 10581, "status": "c"}, "model/CombinatorialAssembler/libs_DockingLib/Common.h|1785404936.0|599": {"hash": "deae8fe2ed93eb8fa57bb5acaad3a29a906bad423992e0822c8f76c3c7f0b852", "size": 599, "status": "c"}, "model/CombinatorialAssembler/libs_DockingLib/Common.cc|1785404936.0|1643": {"hash": "aa7b4371b28fd74571814a4cd59c32f5800503829e731ad05223820da862821d", "size": 1643, "status": "c"}, "model/CombinatorialAssembler/libs_DockingLib/CrossLink.h|1785404936.0|2355": {"hash": "b7c71e0bd9564f53e03b0d95d95de8c1b15033986b2fc8c8e9532a17f741da6f", "size": 2355, "status": "c"}, "model/CombinatorialAssembler/libs_DockingLib/CrossLink.cc|1785404936.0|4415": {"hash": "4234e0d5533031e4d4b15576ccd08c2661212f59e7d1156e1930362a92870ba1", "size": 4415, "status": "c"}, "model/CombinatorialAssembler/libs_DockingLib/DistanceRestraint.cc|1785404936.0|1303": {"hash": "01aa5d0d74797b28cbbed0fdede7ef4305e058aa5fd621a4c5b9ff8fdce798b7", "size": 1303, "status": "c"}, "model/CombinatorialAssembler/libs_DockingLib/DistanceRestraint.h|1785404936.0|6678": {"hash": "df9318e1c187154559b94276ce116db05776238214af5d73f1c69152318ea54b", "size": 6678, "status": "c"}, "model/CombinatorialAssembler/libs_DockingLib/DotSphere.h|1785404936.0|328": {"hash": "a50848a6a17fb3afa0dba1b6f68a82699eaa6da5195f6b191c8ffb26ab35189e", "size": 328, "status": "c"}, "model/CombinatorialAssembler/libs_DockingLib/DotSphere.cc|1785404936.0|598": {"hash": "d534a919e1bb579b6447f63a408f6a9854cf261533870829ca2101b48326e4cd", "size": 598, "status": "c"}, "model/CombinatorialAssembler/libs_DockingLib/GeomScore.cc|1785404936.0|14695": {"hash": "3a3c410d73a11a6352c7e32ab919ee2b23718ee6decf6ecbf9dc933fa5f7a402", "size": 14695, "status": "c"}, "model/CombinatorialAssembler/libs_DockingLib/EnergyAtom.h|1785404936.0|478": {"hash": "69579f8cdbbbd5ceefa1110f3c1f1f5a7879c1beb93fd1389cda7f7c9f50ded2", "size": 478, "status": "c"}, "model/CombinatorialAssembler/libs_DockingLib/GeomScore.h|1785404936.0|4103": {"hash": "220161b194949ba5c89f889c4bfa02a83cb6ed4f61b434375df07065efab8788", "size": 4103, "status": "c"}, "model/CombinatorialAssembler/libs_DockingLib/MolIntersection.h|1785404936.0|4287": {"hash": "5bc18c8d8c5102a7da5374c9aca5ac1ed4028be84b053a14a7fffaecac433f36", "size": 4287, "status": "c"}, "model/CombinatorialAssembler/libs_DockingLib/ResidueIndexedMolecule.h|1785404936.0|6482": {"hash": "5edb3ef5057e3e2568cf174f84c6e84cda4ffb3047c11d0e2302e9b9471931d9", "size": 6482, "status": "c"}, "model/CombinatorialAssembler/libs_DockingLib/MultiResolution.h|1785404936.0|9437": {"hash": "956734373c0816a3fcf5394bfceeb3d4c194e0d65b6d536270e18dc442d023d6", "size": 9437, "status": "c"}, "model/CombinatorialAssembler/libs_DockingLib/SASurface.cc|1785404936.0|2793": {"hash": "f9d6de546150da78b9d0a387147fb032d139526edab744677b3a9982d20a8b84", "size": 2793, "status": "c"}, "model/CombinatorialAssembler/libs_DockingLib/prGrid.h|1785404936.0|12048": {"hash": "fdafb00a5c2852b5111c192bbe04390338ede7fd4a0aa504615ea244306b6c0c", "size": 12048, "status": "c"}, "model/CombinatorialAssembler/libs_DockingLib/SASurface.h|1785404936.0|768": {"hash": "21cdf9e890d5efd339332b7d8f7c1ae76feb64d419b87e848e632d92ddfff1ce", "size": 768, "status": "c"}, "model/CombinatorialAssembler/libs_DockingLib/connolly_surface.cc|1785404936.0|39444": {"hash": "f7be0bb0d48cf1b58af73d1d18014ab92dce3896895d3ab8155aced8b80798bb", "size": 39444, "status": "c"}, "model/CombinatorialAssembler/libs_DockingLib/prGrid.cc|1785404936.0|7203": {"hash": "1b3d635257b6c8b2218ca6600084056af192d0230a396c330221d693ee336dae", "size": 7203, "status": "c"}, "model/CombinatorialAssembler/libs_DockingLib/connolly_surface.h|1785404936.0|617": {"hash": "9ffd1083c7efae2ad2da438eb66d121ef44e750fa2b84c7111c4357ea2527479", "size": 617, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/AminoAcid.h|1785404936.0|9244": {"hash": "a0f68756fdac61fd07dd60f8d1434b36f0a94f3940798be7d2b429407278407b", "size": 9244, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/Atom.cc|1785404936.0|15625": {"hash": "50ab0a6336e1f9568664aa84d87e6aaf39e9f2aff6aefc2cea45068329bd8f54", "size": 15625, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/Atom.h|1785404936.0|11440": {"hash": "d8d30e15e9e0e7a8b803c43a91d69b6a4938758b5cc99dd5facdd9be825cfb29", "size": 11440, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/CIF.cc|1785404936.0|4630": {"hash": "f04e64d5a4441b2d2e95733119ce80684e2f142b3ba93c92663c0f49267e5882", "size": 4630, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/CIF.h|1785404936.0|5519": {"hash": "de3d716d6040f14a1010bfb6eced26533287cf78d19f5df53510eec27080349e", "size": 5519, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/DebugStream.cc|1785404936.0|2984": {"hash": "7f3bc633dce9ba8153612ecfa63f82ea02dc6654f12dc57e07badd92f690e409", "size": 2984, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/DebugStream.h|1785404936.0|7509": {"hash": "e1e565a9293a8087068a84a350d5be869b6fa3396ea2c820c9dbcf04f15aba8a", "size": 7509, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/GeomHash.h|1785404936.0|16509": {"hash": "eb86e2be59aa2548a748d42f19d881298cc5055127ec458e0563b3bbd99d3439", "size": 16509, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/HashLink.h|1785404936.0|22422": {"hash": "4e49a735c530f1dac390ec9af805b6c27e2378d75dd7a1d3fb00972d13b9af52", "size": 22422, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/Logger.cc|1785404936.0|1262": {"hash": "2a3ea75ca4a2d5fb0297c238d612c8cd629088da28a7ed19e40189196e11252c", "size": 1262, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/HashResult.h|1785404936.0|12685": {"hash": "f76829e14e95f2fae0630e8fc3740c41756e31cef09da1bde2be8ee93ee004e5", "size": 12685, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/Interface.cc|1785404936.0|6599": {"hash": "c8ae817355c4d69fc562cec1dc14125ce56ab67769443b39142bf1fa3d3097a8", "size": 6599, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/Interface.h|1785404936.0|5941": {"hash": "c016c641ff4cf36f4657135c41963e82ea55cb81d8bcc4fc965ab55d836fa316", "size": 5941, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/Logger.h|1785404936.0|4399": {"hash": "495818c4fb7ab28c5d4be3657f21fbb1acabceae70ffe32a054d3f046ec07201", "size": 4399, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/Matrix3.cc|1785404936.0|5161": {"hash": "bf0b39b49b76d8371c9296965e68e287225001a36e06fb896d95f331e800d6c9", "size": 5161, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/Match.cc|1785404936.0|5275": {"hash": "8d4ceb4241994f18df8128e772bc9b9e7cde7f309f942a4a195046f8fa8c213c", "size": 5275, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/Match.h|1785404936.0|23112": {"hash": "65a25fecdabaf3017b73299bbcce1c61fa5796af31f0f894eafb82fc83a4c3a5", "size": 23112, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/Matrix3.h|1785404936.0|11377": {"hash": "49d1e4f4781391d7800b0a8e654f8b9029d7eb8f669b2a251ef753601bec54d0", "size": 11377, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/MoleculeGrid.h|1785404936.0|9421": {"hash": "2232d1bfdf185c4bad24524e448919e87d5eb1930395555e80dbfd4bc38b9609", "size": 9421, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/Molecule.h|1785404936.0|19691": {"hash": "4c5ad089d37a47d5b5244cf34d08d7f055d5cae503d83926a0e5ed115190dd2e", "size": 19691, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/MoleculeGrid.cc|1785404936.0|9050": {"hash": "da0b198503bbde4a4a903934a78820f4bdd6d64060b5563960f7d22990806876", "size": 9050, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/PDB.h|1785404936.0|14746": {"hash": "7181f60d2fa21a9f9a48c5aff0c2df610f67013d36c1a58f6e6027ee2ea83f05", "size": 14746, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/PDB.cc|1785404936.0|7253": {"hash": "44a129413c9fbcbfcf64a3e4146ecca8c172c93658991639b866b32a05475cca", "size": 7253, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/Parameters.h|1785404936.0|9370": {"hash": "1a2d9036e035936dbecea92a3389efa1f80ee80fe1e555793a3a45e346e3a909", "size": 9370, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/Parameters.cc|1785404936.0|9041": {"hash": "3eb32595137c559b92a6205033bf79b8e48cb83dabe91ba0dbae6553cd6fd508", "size": 9041, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/Particle.cc|1785404936.0|1067": {"hash": "5e08a8f71522cdd7ca99f2b9c9574b5e309430abee017e73712c7ddc36bd49a0", "size": 1067, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/Particle.h|1785404936.0|3416": {"hash": "5c43f835fb6196795e233e689565de38875db69c05f0502c712a5145ba1b5b01", "size": 3416, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/RigidTrans3.cc|1785404936.0|1098": {"hash": "e997abfdfc045ecc4f9064d15c4725ca60a8ebb3a7747eb1b7adcbd6d8546cdd", "size": 1098, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/RigidTrans3.h|1785404936.0|8030": {"hash": "3afc08665f85b8c2acc66c4a6a50e56396165d92ea3dc000ddf233829f2bca06", "size": 8030, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/Rotation3.cc|1785404936.0|8931": {"hash": "3ce036fb21103c65b2775682687fa240eea51549f9c71e6f3059524625170394", "size": 8931, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/Rotation3.h|1785404936.0|6246": {"hash": "d95db5838043ec6990389d656548f5b5fc26582e6ecd5a80dff08057383f67e1", "size": 6246, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/Shou.cc|1785404936.0|2561": {"hash": "e2501e3b44bb92f2a5bf442aaa9afc86e30a16741166f4ec715fb7c6eb248c84", "size": 2561, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/Shou.h|1785404936.0|2910": {"hash": "d1eb8909fdadc62c22f3ae2b95210e976f9ce3ebdd54640010b178078c10566d", "size": 2910, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/Surface.cc|1785404936.0|4681": {"hash": "40f9be6f52bda357399826b685d9bd0f86446ea804c69f72ff85b17e09e12a75", "size": 4681, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/Timer.cc|1785404936.0|1656": {"hash": "4005e43031b7c354da51be57f64a929f5455a12166083785841128db33ed9f2c", "size": 1656, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/Surface.h|1785404936.0|2017": {"hash": "78b1d38aa344a8cbb7b01b711c66c43ab79999ae956453e74c96667093ef888c", "size": 2017, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/SurfacePoint.h|1785404936.0|4842": {"hash": "d9b104c739bed9c3a39b15c6f0266965253d5c9117da00bf99ab2c3d0496a0be", "size": 4842, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/SurfacePoint.cc|1785404936.0|5032": {"hash": "ae9e11ca05157595ab2828525aca67bab06bb7d694d3e59102a19921ed7355b0", "size": 5032, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/Vector3.cc|1785404936.0|67": {"hash": "fa039a00ee76a228ecf337cdceb73da8718b8a333cd3514f0ca248f2453efda2", "size": 67, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/Timer.h|1785404936.0|3552": {"hash": "a2403cde838f4461b68c867c1698e2e46ad3359bb30de6de78510e2f18c65cab", "size": 3552, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/Vector3.h|1785404936.0|8989": {"hash": "76f866d3e6f40309a29d23f90463c1b456ac830f58b08432d09eb9da0b8f7c35", "size": 8989, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/macros.h|1785404936.0|1534": {"hash": "8b85bfd1f59fc54675709725cffaf06fb3a73e8e99795b44fc09990278338526", "size": 1534, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/numerics.h|1785404936.0|527": {"hash": "3a27aa7327a92d3fe649c59ed144d16eb2f944003229e4e749fa13f10bc3940b", "size": 527, "status": "c"}, "model/README.md|1787795226.0|451": {"hash": "c0934e5ec35e2c013f57be172f73d2070d38d45753032fdb4d4cf0ac11a03286", "size": 451, "status": "c"}, "model/CombinatorialAssembler/libs_gamb/numerics.cc|1785404936.0|5104": {"hash": "1e80f501c92aa02481aac78c5fb47f6006cb09ea6e9965581d70208329ae7d5b", "size": 5104, "status": "c"}, "requirements.txt|1787797232.0|1226": {"hash": "5275f02a8e47fd8f151d362443701511eaa982b582e99e82c9d8615875d0d692", "size": 1226, "status": "c"}, "scripts/automatic_pipeline/configurable.py|1787795219.0|6248": {"hash": "bad03a08e43407a3c8a96aadd8f9755a2ed33a280af3d4da0f097b58313cd694", "size": 6248, "status": "c"}, "scripts/CombFold_Crosslinks.ipynb|1787795530.0|87704": {"hash": "522805d37591b75a41d4c0dda24ff45e78b74b7d02529ae038caeaa1ec935923", "size": 87704, "status": "c"}, "scripts/CombFold.ipynb|1787795529.0|83256": {"hash": "49ff2b96194cf745c98fb24ccbe2c2d5bd47c39e91ee8c4286b67559164328d9", "size": 83256, "status": "c"}, "scripts/automatic_pipeline/libs/get_alphafold_jobs.py|1785404936.0|6219": {"hash": "6b8decb7e9742465c90fa66c91bffc8ebca50671bf479b6a08ee47bcf83e51a9", "size": 6219, "status": "c"}, "scripts/automatic_pipeline/libs/prepare_complex.py|1785404936.0|6358": {"hash": "d8f4d4bb50b5379467f0cb5f00af466b403b2298012a822ffea323cf0f4580df", "size": 6358, "status": "c"}, "scripts/automatic_pipeline/libs/run_assembly.py|1785404936.0|9795": {"hash": "de4bb55efc32624d27f5304cab566fee6897085de701d0a3067b9a26fe345fe1", "size": 9795, "status": "c"}, "scripts/automatic_pipeline/libs/run_alphafold_jobs.py|1785404936.0|1085": {"hash": "5a39812d99710dde43ec2c5fb61b61975087755af59ce126fcfda32bd83a9246", "size": 1085, "status": "c"}, "scripts/automatic_pipeline/libs/parse_alphafold_jobs.py|1785404936.0|8140": {"hash": "fee0c9fd59d004caa6c3d7576eab8bb70471f0b051c76c60bf4ec55bc08905d7", "size": 8140, "status": "c"}, "scripts/automatic_pipeline/libs/utils_classes.py|1785404936.0|3969": {"hash": "67d44305646e3871e40786419eba6c4a6419240055b4e2571a30714e7c70e14b", "size": 3969, "status": "c"}, "scripts/automatic_pipeline/run_on_job.py|1785404936.0|4329": {"hash": "2b5443c174c6c9c5b10ec0134ffe15856c0704d70c67809d4706ba410fdbeef1", "size": 4329, "status": "c"}, "scripts/automatic_pipeline/libs/utils_pdb.py|1785404936.0|6607": {"hash": "388fe9147914b45f30c78da8011addcd0d71dd18be280649fbf3d021e916e649", "size": 6607, "status": "c"}, "scripts/example/example_xlinks/crosslinks.txt|1785404936.0|316": {"hash": "501fabd831244bd592f8179cd155edb826bc93e5955bdd3dfbfe64d95a0c7c2d", "size": 316, "status": "c"}, "scripts/example/example_xlinks/pdbs/AFM_2_A_rank_002.pdb|1785404936.0|494132": {"hash": "928b643dd795978dce3ac4fb75ceae2c27a77674454aa71f112a62c1910330f8", "size": 494132, "status": "c"}, "scripts/example/example_xlinks/pdbs/AFM_2_A_rank_001.pdb|1785404936.0|494132": {"hash": "f464c2bd640bf0de1375856bf9046c2002941ea925a3d206f5976b26fae08f25", "size": 494132, "status": "c"}, "scripts/example/example_xlinks/pdbs/AFM_2_C_rank_001.pdb|1785404936.0|870512": {"hash": "1f9496e7f6db143bec394743f7dd2a382b44a0d86df0b26a59cc79e28286a841", "size": 870512, "status": "c"}, "scripts/example/example_xlinks/pdbs/AFM_2_T_rank_001.pdb|1785404936.0|557026": {"hash": "f7e5dda3c64e00c8a6af45b5a6f67cad3d9f79055a4c0a29d1176c852b4616ed", "size": 557026, "status": "c"}, "scripts/example/example_xlinks/pdbs/AFM_2_b_rank_001.pdb|1785404936.0|356536": {"hash": "95640758894c24f710a5180944ca1f4db0c8e84ef3a86ab0edf315ab5e2226ef", "size": 356536, "status": "c"}, "scripts/example/example_xlinks/pdbs/AFM_2_T_rank_002.pdb|1785404936.0|557026": {"hash": "ec8fbdccaa8fa0b18d0ace929e63535dfca2f850d37cecb566d64deab5b1402d", "size": 557026, "status": "c"}, "scripts/example/example_xlinks/pdbs/AFM_2_C_rank_002.pdb|1785404936.0|870512": {"hash": "4598afe3c6fe0df5e163ff92a8604f0e2ed8195cdd638dbbfe6c77f0e47b5011", "size": 870512, "status": "c"}, "scripts/example/example_xlinks/pdbs/AFM_2_b_rank_002.pdb|1785404936.0|356536": {"hash": "0857d7336d7a60be25b54ce07c3bbe507af4af8dacc6e82d6a4202e0f1d09580", "size": 356536, "status": "c"}, "scripts/example/example_xlinks/pdbs/AFM_A_C_rank_001.pdb|1785404936.0|745298": {"hash": "2e0e39b04df4a1f455e6fc34c89e766d5112c5d846f176c6f370f5e6a2077a04", "size": 745298, "status": "c"}, "scripts/example/example_xlinks/pdbs/AFM_A_b_rank_001.pdb|1785404936.0|231322": {"hash": "016a3f611709e2d4731c667c290eea119535855f27122652c6551b7559c251e1", "size": 231322, "status": "c"}, "scripts/example/example_xlinks/pdbs/AFM_A_b_rank_002.pdb|1785404936.0|231322": {"hash": "958ab20e774d72c9fee31131cbb837160da2af91fae84a58c5f087076ddab4aa", "size": 231322, "status": "c"}, "scripts/example/example_xlinks/pdbs/AFM_A_C_rank_002.pdb|1785404936.0|745298": {"hash": "48da90800e408e2bd6340b8f88c0a88afa74254ef7b78656dd2f9044d852dd41", "size": 745298, "status": "c"}, "scripts/example/example_xlinks/pdbs/AFM_A_T_rank_001.pdb|1785404936.0|431812": {"hash": "e37fcbb08a3299730bed2bb354bd2a9c18288ba1d778d27e6aafee59fa921786", "size": 431812, "status": "c"}, "scripts/example/example_xlinks/pdbs/AFM_A_T_rank_002.pdb|1785404936.0|431812": {"hash": "eed841b2fb417d74b3ebebaeecd33d14fda4fe6ccf46de511d88ecfa273a83a9", "size": 431812, "status": "c"}, "scripts/example/example_xlinks/pdbs/AFM_C_T_rank_001.pdb|1785404936.0|808192": {"hash": "a6035e1a8a23d8dc49c8a314c26b838b417a75a807b80509f6976a2099b6d12f", "size": 808192, "status": "c"}, "scripts/example/example_xlinks/pdbs/AFM_C_T_rank_002.pdb|1785404936.0|808192": {"hash": "0b70f9f57eef0d261ed1f942b5e096ed5c09c277accad44b1a0cdebd145933f5", "size": 808192, "status": "c"}, "scripts/example/example_xlinks/subunits.json|1785404936.0|2654": {"hash": "3bd8d02064ca41aa245aac75f85dbf565a4b9a71f57e8e670c088de01c3aec34", "size": 2654, "status": "c"}, "scripts/example/example_xlinks/pdbs/AFM_T_b_rank_001.pdb|1785404936.0|294216": {"hash": "43f3d6463d1a84e39bff97c3b51758b6538df35c4bbf946da99f6cb7bf9001f1", "size": 294216, "status": "c"}, "scripts/example/example_xlinks/pdbs/AFM_T_b_rank_002.pdb|1785404936.0|294216": {"hash": "ee1c98ccd9104bf07cc2926cf0ca48e6502a3c94b4b1f16bbe8f1988c34f1bbf", "size": 294216, "status": "c"}, "scripts/example/example_xlinks/pdbs/AFM_C_b_rank_001.pdb|1785404936.0|607702": {"hash": "680235ded1c071aac63c965950b56957ceed282b04f0cb826683887d4dca3990", "size": 607702, "status": "c"}, "scripts/example/example_xlinks/pdbs/AFM_C_b_rank_002.pdb|1785404936.0|607702": {"hash": "c94c9fa1551ebe3a18d6811caaabf406dcf0d0855488a1b04f9bdbb5b86f3a39", "size": 607702, "status": "c"}, "scripts/example/pdbs/AFM_A0_A0_A0_unrelaxed_rank_1_model_3.pdb|1785404936.0|968256": {"hash": "4f0eeee05d105b6a1e492c7611f9984b386248f22a75872966688ed0d3dcef13", "size": 968256, "status": "c"}, "scripts/example/pdbs/AFM_A0_A0_A0_unrelaxed_rank_2_model_1.pdb|1785404936.0|968256": {"hash": "a09a04acfc10e191b79175b0046256db2d3e730babbf2d7c6d565c454babca8c", "size": 968256, "status": "c"}, "scripts/example/pdbs/AFM_A0_A0_unrelaxed_rank_1_model_1.pdb|1785404936.0|645586": {"hash": "c470fd17aa53063c1fbad947b5cd2f43d864ca3a8250477ff7cd640cd32b5969", "size": 645586, "status": "c"}, "scripts/example/pdbs/AFM_A0_A0_unrelaxed_rank_2_model_4.pdb|1785404936.0|645586": {"hash": "5402896c70a8fab799645a0ec63b81880863745851348ab66714f5e544a567f2", "size": 645586, "status": "c"}, "scripts/example/pdbs/AFM_A0_G0_unrelaxed_rank_1_model_2.pdb|1785404936.0|737426": {"hash": "c881427e15b125d0905bb2ffa4476df686cce7e425911be1c7b3155390bdba89", "size": 737426, "status": "c"}, "scripts/example/pdbs/AFM_A0_A0_G0_unrelaxed_rank_2_model_3.pdb|1785404936.0|1060096": {"hash": "ecf6fbcdc015a300de15e30b02eb9d13041e22ce30b1f55ec53db73261f19f22", "size": 1060096, "status": "c"}, "scripts/example/pdbs/AFM_A0_A0_G0_unrelaxed_rank_1_model_1.pdb|1785404936.0|1060096": {"hash": "7d7f157325cc66d1db6573c138d4cc2bd6ee6e3fe39e671da6dc9562ef2fe75c", "size": 1060096, "status": "c"}, "scripts/example/pdbs/AFM_A0_G0_unrelaxed_rank_2_model_3.pdb|1785404936.0|737426": {"hash": "9bbbdd7e8b3aa2e8848fd55b1542528b1dbb4ed6ff5abc124fc02b9a493c34b8", "size": 737426, "status": "c"}, "scripts/example/pdbs/AFM_G0_G0_unrelaxed_rank_1_model_4.pdb|1785404936.0|829266": {"hash": "2995afff7485536781cb2a0bca06fb5c17a2a8a0dc587c2b6d57ec47fa98d3e6", "size": 829266, "status": "c"}, "scripts/example/expected_assembled/output_clustered_0.pdb|1785404936.0|4423100": {"hash": "ca9d08cfd8f72e20445aa8416f0ae61fb2c507c3117d131c3cc04d838b7d21f9", "size": 4423100, "status": "c"}, "scripts/example/subunits.json|1785404936.0|1536": {"hash": "282496d8fad0d518c093baaad89e3c4f8134923a8fe6225995ee49336e0baf78", "size": 1536, "status": "c"}, "scripts/inference.py|1787795417.0|2503": {"hash": "201fd4f253f3512d2731456c914fa9278903f1f9262fde8e439dddaa6e4b182d", "size": 2503, "status": "c"}, "scripts/libs/prepare_complex.py|1785404936.0|6358": {"hash": "d8f4d4bb50b5379467f0cb5f00af466b403b2298012a822ffea323cf0f4580df", "size": 6358, "status": "c"}, "scripts/libs/utils_classes.py|1785404936.0|2933": {"hash": "9a98729996d1b7f94668ef8d595f49460f3b6808ee5c2e08d40d9afdede3b7b8", "size": 2933, "status": "c"}, "scripts/libs/utils_pdb.py|1785404936.0|6588": {"hash": "840d0ca5eaf5de1cb1cd46d66c4c265169b1879bb336e4d3745a718530ff3796", "size": 6588, "status": "c"}, "scripts/paper_resources/analysis_scripts/build_crosslinks_benchmark.py|1785404936.0|9142": {"hash": "edb23fe44cff1aaeff477611d1327a9bf0956ba35b2f5c948b20d30452074882", "size": 9142, "status": "c"}, "scripts/paper_resources/analysis_scripts/calc_all_dockqs.py|1785404936.0|6071": {"hash": "8d8edf19fdbc9b2a4c4abfe9a041fb9441d3d5b5ada4f258097020b248095163", "size": 6071, "status": "c"}, "scripts/paper_resources/analysis_scripts/calc_ics_benchmark.py|1785404936.0|10120": {"hash": "ab1465248d9be97b746023016c1f9b9474dedf36d6a2e549d76a2838c161c07b", "size": 10120, "status": "c"}, "scripts/example/pdbs/AFM_G0_G0_unrelaxed_rank_2_model_5.pdb|1785404936.0|829266": {"hash": "e3f1020242213cd162ceab8d12b7993b1ca5580c560dae55c6e4d2ba1748f351", "size": 829266, "status": "c"}, "scripts/paper_resources/analysis_scripts/relax_pdbs.py|1785404936.0|3631": {"hash": "00e372b54e0a6e323b41dbb5fa51e1236e4a607ffc1be22397bd4586d83b692e", "size": 3631, "status": "c"}, "scripts/paper_resources/analysis_scripts/get_all_pae_scores.py|1785404936.0|2136": {"hash": "fe8712e772727f081db4a7cffd5e2cc371f6fa57ab12f80eb1d7eb386ef55287", "size": 2136, "status": "c"}, "scripts/paper_resources/analysis_scripts/rmsd_pairs_scores.py|1785404936.0|4435": {"hash": "2c745b46d5fc952239c504c76e9c296ead68e0be340391ea5ff6b0880372cca9", "size": 4435, "status": "c"}, "scripts/paper_resources/datasets/benchmark1.csv|1785404936.0|210": {"hash": "728de21a160e7730e7dad6610b9c33bc96dbae70303c972a389f85e66f52294b", "size": 210, "status": "c"}, "scripts/paper_resources/analysis_scripts/run_haddock.py|1785404936.0|3313": {"hash": "b110a148dd3030aad5aadcd2354319d2146eb6105d2cd871e0f1ae446e1e07e4", "size": 3313, "status": "c"}, "scripts/paper_resources/datasets/benchmark2.csv|1785404936.0|150": {"hash": "56e45668a1a6c2544fc8d3dd041f1001e2554a17357f56781ac8f0bb78c7dc32", "size": 150, "status": "c"}, "scripts/paper_resources/datasets/benchmark2_simulated_xlinks/7ARC_xlinks.txt|1785404936.0|2883": {"hash": "5fd0988c625fc32430fc8fd98a1f6fb67add05d3e34a9e77cd7485d936a55cf6", "size": 2883, "status": "c"}, "scripts/paper_resources/datasets/benchmark2_simulated_xlinks/7OBA_xlinks.txt|1785404936.0|1980": {"hash": "612053ad0bfaf31b70ace29cf02af604c6df544389156728e1e2db8de0cef631", "size": 1980, "status": "c"}, "scripts/paper_resources/datasets/benchmark2_simulated_xlinks/7E8T_xlinks.txt|1785404936.0|1920": {"hash": "e31b6ae6e3973ee053b118077f7f57e1a4f6be23818e37a43e73faac394045db", "size": 1920, "status": "c"}, "scripts/paper_resources/datasets/benchmark2_simulated_xlinks/7OZN_xlinks.txt|1785404936.0|2120": {"hash": "fee7311d9732a22473c63d454b24ebdc43ecab0822fefdee5dd562a7e5e8c1a4", "size": 2120, "status": "c"}, "scripts/paper_resources/datasets/benchmark2_simulated_xlinks/7P2Y_xlinks.txt|1785404936.0|974": {"hash": "80094e99332a290dd496c32efb9d2ab2e5696c6303f4c0632014ba9c1f309930", "size": 974, "status": "c"}, "scripts/paper_resources/datasets/benchmark2_simulated_xlinks/7PKN_xlinks.txt|1785404936.0|1710": {"hash": "70fbd3edef53f9d95fc227577846c13fd56340cd93320147adc161b77caf9ce0", "size": 1710, "status": "c"}, "scripts/paper_resources/datasets/benchmark2_simulated_xlinks/7QRU_xlinks.txt|1785404936.0|336": {"hash": "cffa10a0ed695cc5956ca2fdd8f8117728e5d4b2f33c5f45880aa730bd5c4ca3", "size": 336, "status": "c"}, "scripts/paper_resources/datasets/benchmark2_simulated_xlinks/7QVE_xlinks.txt|1785404936.0|2023": {"hash": "5f0a19ae379f8fa4b2968c01587c0ee31650c7b1e429c4e1ec6e8d7afc517bd5", "size": 2023, "status": "c"}, "scripts/paper_resources/datasets/benchmark2_simulated_xlinks/7T2R_xlinks.txt|1785404936.0|753": {"hash": "3642fcc9af73871e14ccb7f55f0fabf98f2dee5e9689df1d35090cdd0fd07903", "size": 753, "status": "c"}, "scripts/paper_resources/datasets/benchmark2_simulated_xlinks/7T3B_xlinks.txt|1785404936.0|994": {"hash": "e7e2acffd805ca1abae2dfa2351163031fe28d49ce2685b4c16923b5154d46f9", "size": 994, "status": "c"}, "scripts/paper_resources/datasets/benchmark2_simulated_xlinks/7WFF_xlinks.txt|1785404936.0|1732": {"hash": "bd2a14b3f7373c0114545589a74c54640a92aa0eadd9e030eb40ad8716f9e87d", "size": 1732, "status": "c"}, "scripts/paper_resources/datasets/benchmark2_simulated_xlinks/7USE_xlinks.txt|1785404936.0|756": {"hash": "609ccb1d6e12a8f871cb8a82143b47dc7a259c204fb06a7de2e7627761706b9d", "size": 756, "status": "c"}, "scripts/paper_resources/datasets/benchmark2_simulated_xlinks/7UIC_xlinks.txt|1785404936.0|842": {"hash": "a9d417b6aabbd087ddf7662f2e635d1349c74e30686757ba5663cfb1dfbe77ee", "size": 842, "status": "c"}, "scripts/paper_resources/datasets/benchmark2_simulated_xlinks/7XHO_xlinks.txt|1785404936.0|3031": {"hash": "e0417617e3c3c05cc89366e3db0841bf93a929e24922d7e4d8841fbbbc04f4d7", "size": 3031, "status": "c"}, "scripts/paper_resources/datasets/benchmark2_simulated_xlinks/7ZKQ_xlinks.txt|1785404936.0|316": {"hash": "501fabd831244bd592f8179cd155edb826bc93e5955bdd3dfbfe64d95a0c7c2d", "size": 316, "status": "c"}, "scripts/paper_resources/datasets/benchmark2_simulated_xlinks/8A3T_xlinks.txt|1785404936.0|4773": {"hash": "6dcd6fc6e88043763485698caaa4ab7e3521ee86ced70a60741948b2de1f3ceb", "size": 4773, "status": "c"}, "scripts/paper_resources/datasets/benchmark2_simulated_xlinks/8A5O_xlinks.txt|1785404936.0|730": {"hash": "75465198187afdb3b1f688e836fb0a14200c3062607850769ab94458a0b0d918", "size": 730, "status": "c"}, "scripts/paper_resources/datasets/benchmark2_simulated_xlinks/8ADL_xlinks.txt|1785404936.0|2423": {"hash": "c68797d27a31afdc513a8a92f699cff24e42d44d6fbd133d172a520f2beeb26c", "size": 2423, "status": "c"}, "scripts/paper_resources/datasets/benchmark2_simulated_xlinks/8ADN_xlinks.txt|1785404936.0|4428": {"hash": "1acfca9aa300aab5a265d8e41483d80a7c5469773e0cf6721e1c47d3f3294ce4", "size": 4428, "status": "c"}, "scripts/paper_resources/datasets/benchmark2_simulated_xlinks/8BEL_xlinks.txt|1785404936.0|336": {"hash": "3d43cbce5a5798866c662326e5e7ec2fb36122a4cd3341df51b9e1d4dd56b777", "size": 336, "status": "c"}, "scripts/paper_resources/datasets/benchmark2_simulated_xlinks/8CTE_xlinks.txt|1785404936.0|942": {"hash": "cc79270cbb18af14e42c7a62e6bea1d20ee85e19a3c7741fc15acefc77bf81a2", "size": 942, "status": "c"}, "scripts/paper_resources/datasets/benchmark2_simulated_xlinks/8E9G_xlinks.txt|1785404936.0|532": {"hash": "71378589014f893e307d813984ec925328b6f553244b054f33fb449251079154", "size": 532, "status": "c"}, "scripts/paper_resources/datasets/benchmark2_simulated_xlinks/8FEE_xlinks.txt|1785404936.0|629": {"hash": "e4daec0d341b87534d24ad24822666f4cba1e5f671943fb398c1b82bb3676529", "size": 629, "status": "c"}, "scripts/paper_resources/datasets/benchmark2_simulated_xlinks/8F5O_xlinks.txt|1785404936.0|850": {"hash": "adabd2c3c5e3a19b6291960d3dc64044a93b60c729dcad0797423778ea5c42f5", "size": 850, "status": "c"}, "scripts/paper_resources/datasets/benchmark2_simulated_xlinks/8HIL_xlinks.txt|1785404936.0|1492": {"hash": "a587a0852d0e5484157a3f4fc2eb57cab62fce814dcfe90b38f44de31fcac814", "size": 1492, "status": "c"}, "scripts/paper_resources/datasets/benchmark3.csv|1785404936.0|1050": {"hash": "a39fe86de8d76b01aaba4515c12b4c666025d18259882c18709ce1f65fa747f6", "size": 1050, "status": "c"}, "scripts/paper_resources/datasets/casp15_subunits_by_iupred/H1111.json|1785404936.0|1842": {"hash": "8770077200f507df5b52d3e72fc6463adbe07836461198e8514b611bde805111", "size": 1842, "status": "c"}, "scripts/paper_resources/datasets/casp15_subunits_by_iupred/H1114.json|1785404936.0|1729": {"hash": "0646924d57f579de9d916b85d75c3e73d3ae6baa26262bf8492730f3ad363827", "size": 1729, "status": "c"}, "scripts/paper_resources/datasets/casp15_subunits_by_iupred/H1137.json|1785404936.0|4376": {"hash": "b7896e9e3cba67b5832e77ceab7c0511bf754780c9d938143f1840316dcec1b9", "size": 4376, "status": "c"}, "scripts/paper_resources/datasets/casp15_subunits_by_iupred/T1115.json|1785404936.0|614": {"hash": "9b739275f18ea8f6c14e5acb08da172abffdebe3ccb5a764c673bf91683dd2c2", "size": 614, "status": "c"}, "scripts/paper_resources/datasets/casp15_subunits_by_iupred/T1165.json|1785404936.0|3695": {"hash": "12c43a6c4c818f887cc127505656886f4ad3a76dad3009697d8fa79b429efc66", "size": 3695, "status": "c"}, "scripts/paper_resources/datasets/casp15_subunits_by_iupred/T1169.json|1785404936.0|3607": {"hash": "7782ff01ad332b69d77147a0a23f9b9f4a877cd33b4420a456099d0a8d14f930", "size": 3607, "status": "c"}, "scripts/paper_resources/datasets/casp15_subunits_by_iupred/T1192.json|1785404936.0|444": {"hash": "229abbae2a68e006843e029d7db557e6ed3496e33a5c4572a8ecd5a420be66c9", "size": 444, "status": "c"}, "scripts/paper_resources/datasets/complex_portal/10090CPX3198.json|1785404936.0|1858": {"hash": "c262d54554646248aabdec98d2ad7bba1d7331ac3cc1747a3178171d4eb38813", "size": 1858, "status": "c"}, "scripts/paper_resources/datasets/complex_portal/10090CPX3197.json|1785404936.0|1852": {"hash": "b0f898c644d70b2f447d585c7ffe987e0184216db0882d64bcb1f99544b35fd1", "size": 1852, "status": "c"}, "scripts/paper_resources/datasets/complex_portal/10090CPX3266.json|1785404936.0|6333": {"hash": "2f7bdc078cf7cd42ea37e24ca553532506eab172149149ea6d6b98cccd4449cd", "size": 6333, "status": "c"}, "scripts/paper_resources/datasets/complex_portal/10090CPX3963.json|1785404936.0|2415": {"hash": "e843372b884a06f3a99bdbe0971ee39245e01a56587de7b2aac40a86b1e2a7aa", "size": 2415, "status": "c"}, "scripts/paper_resources/datasets/complex_portal/10090CPX3267.json|1785404936.0|6370": {"hash": "228ada0c9f1db2ba391518e1c3678aeb782321106d8289c844585355bc0f8cc1", "size": 6370, "status": "c"}, "scripts/paper_resources/datasets/complex_portal/10090CPX444.json|1785404936.0|3267": {"hash": "2d460305f7e33efa7875dd93418ecec40c779b652bfc5901f94f59a9f39bc11f", "size": 3267, "status": "c"}, "scripts/paper_resources/datasets/complex_portal/10090CPX980.json|1785404936.0|6825": {"hash": "f1e6fe0331562fe793ae8b05c6e5625e416038ec405029be7f7bfafc8adeb0af", "size": 6825, "status": "c"}, "scripts/paper_resources/datasets/complex_portal/10090CPX986.json|1785404936.0|7102": {"hash": "fac21cfe6cf3b5eb748d8f422e60276a3c5016e0594b00face1a0c270587231c", "size": 7102, "status": "c"}, "scripts/paper_resources/datasets/complex_portal/559292CPX124.json|1785404936.0|4773": {"hash": "6f4339a49e78db92b2b215d502e2dda53cf86ab8246ec553ebd1c1fc7cb8031a", "size": 4773, "status": "c"}, "scripts/paper_resources/datasets/complex_portal/559292CPX1039.json|1785404936.0|4738": {"hash": "5df8bd3f33d9e73a970c653d6135da791e213b4ae483fea8042d5e7446f87019", "size": 4738, "status": "c"}, "scripts/paper_resources/datasets/complex_portal/559292CPX1275.json|1785404936.0|3381": {"hash": "7f472e862f384e08aa8970a1426268eeda782f4eec9656f4039e2205dc2fa92c", "size": 3381, "status": "c"}, "scripts/paper_resources/datasets/complex_portal/559292CPX1882.json|1785404936.0|3844": {"hash": "64a9eda01a4672634bb59c3d0b388befd59a591a37d1551bb000cf0e122eecdc", "size": 3844, "status": "c"}, "scripts/paper_resources/datasets/complex_portal/559292CPX1901.json|1785404936.0|3340": {"hash": "3d6e1c6b73fc96a988dda2042184ef46503c0b1bdebda916fd83415bf7e6f8be", "size": 3340, "status": "c"}, "scripts/paper_resources/datasets/complex_portal/559292CPX1616.json|1785404936.0|2417": {"hash": "8dfa4cf648f13f0717e55f85ed02c09698cbf8cc54c90485343dc2090dbc2bb5", "size": 2417, "status": "c"}, "scripts/paper_resources/datasets/complex_portal/559292CPX1869.json|1785404936.0|6774": {"hash": "aa1cdf85b4c8ec2313ef9edeb635716b9e398c5fbff1d20b60885bd3bac93518", "size": 6774, "status": "c"}, "scripts/paper_resources/datasets/complex_portal/9606CPX1949.json|1785404936.0|4858": {"hash": "c00e12a7c0d83196e87b3153c197b337ce56059473fc4d2a19ce847ceb8e94ae", "size": 4858, "status": "c"}, "scripts/paper_resources/datasets/complex_portal/9606CPX3072.json|1785404936.0|1855": {"hash": "613edef9b02d6ef6e995ec5f8e9582e8e433e923ebdb4dc942f7eadf4de6d0a5", "size": 1855, "status": "c"}, "scripts/paper_resources/datasets/complex_portal/9606CPX3263.json|1785404936.0|6361": {"hash": "acf933d1df3b901b6328e7c2165f71e6720bd4eb1b8795b75db793deaced9fa2", "size": 6361, "status": "c"}, "scripts/paper_resources/datasets/complex_portal/9606CPX3073.json|1785404936.0|1849": {"hash": "f1844d8355d44c7ce04c918711984c8ad02c652d1125e5e449eb5c49e0400543", "size": 1849, "status": "c"}, "scripts/paper_resources/datasets/complex_portal/9606CPX3232.json|1785404936.0|6323": {"hash": "f1a3ba241593632084348c547d530e7101a0b6fa7325db0ab7f21894aa9ee8db", "size": 6323, "status": "c"}, "scripts/paper_resources/datasets/complex_portal/9606CPX3905.json|1785404936.0|2410": {"hash": "d70eab3f8ab17a48a0d77a38fdbf41466cb25d62a48b40a2e7648448af8cd3a4", "size": 2410, "status": "c"}, "scripts/paper_resources/datasets/complex_portal/9606CPX7142.json|1785404936.0|2814": {"hash": "4ca742168ad8a68ec249ac65de4f589513c6bfb42eca65f8936883ae158b78f1", "size": 2814, "status": "c"}, "scripts/paper_resources/datasets/complex_portal/9606CPX434.json|1785404936.0|3269": {"hash": "c78296590fb93ebd05baf92a19f62442209b232829d01716778b25ea341bdbd8", "size": 3269, "status": "c"}, "scripts/paper_resources/datasets/complex_portal/9606CPX6199.json|1785404936.0|7573": {"hash": "65f1684025010172ae61842f8c95f906b8c108c2fe5c7be5e656f109d9627390", "size": 7573, "status": "c"}, "scripts/paper_resources/datasets/complex_portal/9606CPX6213.json|1785404936.0|7006": {"hash": "b9576ce4e2889d81d728a98645d92c1f647a10b344628c4ced7603ff8e9efc84", "size": 7006, "status": "c"}, "scripts/paper_resources/datasets/complex_portal/9606CPX6212.json|1785404936.0|6304": {"hash": "6bc1d5eba3c2ef216867d61556fe5a8eca235ad7758aba96b8adf9fad826cdcb", "size": 6304, "status": "c"}, "scripts/paper_resources/datasets/complex_portal/9606CPX979.json|1785404936.0|6863": {"hash": "38b361fa72cb899ab0f9ca6efddbbee6122346e15a173c464f2652c8c1cc86b7", "size": 6863, "status": "c"}, "scripts/paper_resources/extras/benchmark2_extra_coverage.csv|1785404936.0|849": {"hash": "bce6ebd238ed6b51c08cc6644942fd0deb3503c35528427f041e6b75362f2434", "size": 849, "status": "c"}, "scripts/paper_resources/datasets/complex_portal/9606CPX985.json|1785404936.0|7105": {"hash": "1841c98c7eb5f5ea92a9be262db4ac071c9fda5a457eee0d7b312a596db3692b", "size": 7105, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/AFMv2_results.json|1785404936.0|16078": {"hash": "d58e4275e473dcd4058118fbf2c7bbf469481955fc07866a1edf49a22c7aa488", "size": 16078, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/combfold_results.json|1785404936.0|36661": {"hash": "6561e563d5b7a7bf803978e6bbf16d56441a4e261a202a3f58de5161a11b10fb", "size": 36661, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/6F0K.json|1785404936.0|3736": {"hash": "06738259eec8c9a122400df70582ee52e9e2de19b03f83e1e6bfdca8b01f9974", "size": 3736, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/6K15.json|1785404936.0|9386": {"hash": "edd55c06392aa1e50a97c95eaf0c53ece514f310cf6e6ab5ca5e0bfb45ecce25", "size": 9386, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/6I3M.json|1785404936.0|4954": {"hash": "0999b29391ae4b8ab801137608b6b1e8b5f02a07a2cbc175b4c9ced52d136822", "size": 4954, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/6QLF.json|1785404936.0|3678": {"hash": "a13a70cebd4fac6acfcb134381f68d55d418f73035f8172a702ac547898f1c92", "size": 3678, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pairs_validation.json|1785404936.0|1692661": {"hash": "54cf953f2607cff88d7b0ab0e68f0e0521ab808fdd083927c19fe16c035815b1", "size": 1692661, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/6RDB.json|1785404936.0|3213": {"hash": "ad630916d4a55accd6e8e0ccc8c640d6c5820da05685af66cbf8c66f53690677", "size": 3213, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/6RD7.json|1785404936.0|3298": {"hash": "15569fe6aa6ffccb667beed521d6fd923f1108377cd63cae717e071a6ac30fe9", "size": 3298, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/6RIC.json|1785404936.0|5637": {"hash": "8796f4ebeab55da1b9c97f6306ae7aaac054092ef15928de2dd142fd29f5f9a2", "size": 5637, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/6SWY.json|1785404936.0|3980": {"hash": "6c9ef75e4b7ae4e86a168c9f6ad0183dfeb2ed1bd32913f4d2bd3881aa5cbbf8", "size": 3980, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/6SZ9.json|1785404936.0|2593": {"hash": "9c2856cad86c640aec0d448b30a584d17b77ab47fdb82dfc3cee3c6844d3abc5", "size": 2593, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/6TDX.json|1785404936.0|1636": {"hash": "99dd5edac5926d57da0f59b9ff46defdbc8743b6bac16b7f258eba2dfe31244b", "size": 1636, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/6TMI.json|1785404936.0|2756": {"hash": "c75fc7bc09991e77c72ad4ccacd32007e972815ede035488dd4789c5caaeae32", "size": 2756, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/6VQC.json|1785404936.0|4394": {"hash": "dbe6ce82f1160b4d915f11baedc87b78960ba072f5c7bfeca9f590077be8e478", "size": 4394, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/6YBD.json|1785404936.0|9561": {"hash": "e427ae087f0b9ef853093d4e8068a6c15488fc8f100bf978a0c4ea0b5f92b6a9", "size": 9561, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/6YY0.json|1785404936.0|3605": {"hash": "db3ef72671de4b61f6d2e517495d1ca8d32a101d7a67a3eb7d6fe5e75b2ead73", "size": 3605, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/6Z16.json|1785404936.0|2987": {"hash": "329b1ddc05d4f28659c1a5b8e47edfc3d00656c7a2f8a2211872426324026dca", "size": 2987, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/6ZBB.json|1785404936.0|2588": {"hash": "f032fedd0e7f40713a50e32dca1f62adfcea521513e78729ac8baf99d3cb4490", "size": 2588, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/7A6H.json|1785404936.0|8619": {"hash": "a664d1352a2676546e14c14f2354b7454f38336d59dc9e41f1753c4e5296036c", "size": 8619, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/7AQW.json|1785404936.0|4054": {"hash": "885b3c3dd1e5895b6b3d9ed79d42ed4f175b379cb3031bb9a7b818a10094fd01", "size": 4054, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/7ARC.json|1785404936.0|6175": {"hash": "2d3fa82782c287708e66df5ecae8b0d072db2d0ba34069be1db745fcd94527a1", "size": 6175, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/7BKD.json|1785404936.0|3271": {"hash": "24d2beb7ca9684d6daa32c74c631421cd39c765e980669d3762f41edfd03407a", "size": 3271, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/7CGP.json|1785404936.0|2101": {"hash": "0ad8369e8a1d85c25d5dbdda78bef41e50cffa0d409bc2ec2188982c373d1d61", "size": 2101, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/7CK6.json|1785404936.0|1438": {"hash": "594e42166dba12f783fd04c54236a73ee39d1356c494692ac07f5177c8e3979d", "size": 1438, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/7D3U.json|1785404936.0|2836": {"hash": "787d6139d2ed9cc4e1ce7b7d45c1bb2824a71e1b5494538556cca7d60f5adcd3", "size": 2836, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/7EGF.json|1785404936.0|6202": {"hash": "fefb578853b1ff797ba89284f00d4fea3de9c08f19d51fe2ae15272316453b62", "size": 6202, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/7JG5.json|1785404936.0|4337": {"hash": "2c89a9cb3ec008b8973cfa8ab96e8490279b1468ecd6e8721c632b099eca92ba", "size": 4337, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/7K01.json|1785404936.0|4622": {"hash": "7e40c26260481ddbba48bc01d0a25a023aa4deda5f4111fb5e9e3e36630ba7bf", "size": 4622, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/7K36.json|1785404936.0|3419": {"hash": "5e3c765e12b6f42d1df92af731df4048f0a86e9316ed05d9f23eca6e2c4629a3", "size": 3419, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/7LIV.json|1785404936.0|3108": {"hash": "1365f373ccd44d5dfc1221daf23523e1c4363bb373166eb6fd42d94e60cc9b63", "size": 3108, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/7NYR.json|1785404936.0|6688": {"hash": "44047aca81a9d278b8bf27570dc53a6e441b7966aa2c470e000326d3e246b9ea", "size": 6688, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/7PKN.json|1785404936.0|5118": {"hash": "e23c09a4c0d48aad6e9768386c29e8ebe9ae54f5aecc885c2b76542e5ac2ca5c", "size": 5118, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/7RJA.json|1785404936.0|3483": {"hash": "620465dc01a8c27ef1977ee9a86ee8c68b5484d67ab84d8fea7948cbe7d41271", "size": 3483, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/7T2R.json|1785404936.0|2857": {"hash": "56e5b18958f7605efb76b575dbfad3d2a472ba995aa60f551d622fe9c1b2dfa8", "size": 2857, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/7USE.json|1785404936.0|4290": {"hash": "83245a6a70d4dee696ad78cb98779176f56d33fbc5d37dfcc4c64bf4321d5e03", "size": 4290, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/7WFF.json|1785404936.0|7393": {"hash": "663c34c1b6146966b25298d8fc1cfd5cabdd3c6ba1d1ffea850d98db3cbb50e2", "size": 7393, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/pdb_infos/7Z15.json|1785404936.0|2405": {"hash": "56ce87e4f051c5e083445c296209f4f945b4d81211cec5ca39ad38c866334bd0", "size": 2405, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/runtimes/duration_simple.json|1785404936.0|5541": {"hash": "48589b04068e157a620169d1e6046a1ac3711269f1280927f3781773c9289018", "size": 5541, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/runtimes/duration_combfold.json|1785404936.0|220614": {"hash": "067fe1dd96f36b1d269bf3a6d8d46211128a3a60e8361c0a0d19d50b7df6ed3d", "size": 220614, "status": "c"}, "scripts/paper_resources/figures/data/benchmark1/runtimes/runtime_summary.csv|1785404936.0|3312": {"hash": "403f4e5027bc13258569fd5f6b33bbfabefbd656d699f3d573eceac4126697ce", "size": 3312, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/AFMv3_results.json|1785404936.0|7752": {"hash": "34315c13a97eef97c9b6c29a5b607005932cc97b3dd8f27e211d493d67e38cfb", "size": 7752, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/clashscores.csv|1785404936.0|398": {"hash": "2111b0c36715a97ebe68a064543e674b898d186944caa2c0321aa39da48b5ff3", "size": 398, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/alphalink_results.json|1785404936.0|76": {"hash": "caf5e2df54eaadd0a197f47b209490e8d7945f524f1cae1c6b879f0952b84b6b", "size": 76, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/combfold_results.json|1785404936.0|67256": {"hash": "98f67806ae23b2356c971c96a1a4db10c8b238f2a7f7518aa749dc07a6e4c37c", "size": 67256, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/combfold_xlinks_results.json|1785404936.0|51803": {"hash": "c66fdc642fbdc9a1f208baf08d46a509c6614b9a064f4c8e44423d94278a9405", "size": 51803, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/haddock_results.json|1785404936.0|5638": {"hash": "321dbd4bc0095a59c51135fc1e654476e03f6fa8059a90cb4733ffe6e6cd802a", "size": 5638, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/iptm_validate.json|1785404936.0|190983": {"hash": "10b4af787406f20d55e82232a7a89ed275227c3c8300d88f7138bde43e31ddad", "size": 190983, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/pae_data/top1_used_pae_scores.json|1785404936.0|2653": {"hash": "7699ca10c708eefebefe1c4692c45204d439d7504911794049e074119fdea48e", "size": 2653, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/pae_data/all_pae_scores.json|1785404936.0|562657": {"hash": "a29c96611c2fcc49b11b9bb511f74752ca933ef83f8c55b7b63beebe785c3a1f", "size": 562657, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/pae_data/used_pae_scores.json|1785404936.0|70981": {"hash": "b7fe4ecf164e1cf360439cf89872eefaf3a1daa9c8cc7afd05399cea3ca80aa9", "size": 70981, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/pdb_infos/7ARC.json|1785404936.0|5872": {"hash": "6850919d5c6bc1b9599fd5d574c6aa58743f9f936e56b221e892d92f3cc94c6d", "size": 5872, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/pairs_validation.json|1785404936.0|686404": {"hash": "b09db2dba78393b4d7f5143cc8eaac7a6581682fade6ed84739b6ab0d5d77710", "size": 686404, "status": "c"}, "scripts/paper_resources/extras/top1_benchmark_1_2_pdbs.zip|1785404936.0|36678137": {"hash": "97cd405c075a7aecb869b6915ae97e499a384605e35218897260107caab53740", "size": 36678137, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/pdb_infos/7E8T.json|1785404936.0|5957": {"hash": "af86623ce30bd932b6ba1cfc301a7f8dbcf8d46032b187065a24089b60b06268", "size": 5957, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/pdb_infos/7OBA.json|1785404936.0|7673": {"hash": "3f759d9567826c9ee6480336881902b48e8c8eb3a89f1fe9ba466743363ec3ce", "size": 7673, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/pdb_infos/7OZN.json|1785404936.0|6308": {"hash": "2d989a437761256d15e997532ec68a32042befafc91300b3f79c5037eb03618d", "size": 6308, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/pdb_infos/7P2Y.json|1785404936.0|3243": {"hash": "5eb28a93d6001eba6e6e800db66f9ffc3d27bb402c60c4b378a7fa3476e2652c", "size": 3243, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/pdb_infos/7PKN.json|1785404936.0|4909": {"hash": "f4d122ee0318c3e9a22440adac9b13cf9a1d0f8835e28a52128cdaf59448ba69", "size": 4909, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/pdb_infos/7QRU.json|1785404936.0|2791": {"hash": "03c947a334a1f4561062330a42ea5bdeba9fabefcdb6f9d5112afcdcdbf2ee9b", "size": 2791, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/pdb_infos/7QVE.json|1785404936.0|5247": {"hash": "c8f77bcd3de08a4294b74099c174d8f0c0373e27d28051c7b4f6e913369de393", "size": 5247, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/pdb_infos/7T2R.json|1785404936.0|2762": {"hash": "2bd004e859ea547848b94ccfe38b2a9cbaff7a65af426a9e0e38e1f1d83a9946", "size": 2762, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/pdb_infos/7T3B.json|1785404936.0|5067": {"hash": "c1e44751223d8f7b70f95c98fce642c057a4cdf51aba5edbd87808340d50e841", "size": 5067, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/pdb_infos/7UIC.json|1785404936.0|5820": {"hash": "a274cfa46d9f408b650750f4513b63bdba0df0751dafad8bc1b837edbe545d07", "size": 5820, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/pdb_infos/7USE.json|1785404936.0|4156": {"hash": "ab5be4fcbb26183acbfe0f1ebb887ba341360bb3442edad678df1375a3d113ef", "size": 4156, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/pdb_infos/7WFF.json|1785404936.0|7070": {"hash": "f7a705ee883ebe7be015cd7317c422a5a1b8be8ab78096d541cddd91ab66f85c", "size": 7070, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/pdb_infos/7XHO.json|1785404936.0|7332": {"hash": "f50a0b9ab290d793dce06f4de1f31bbcf9daad802573ff1398861ef5a9829e56", "size": 7332, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/pdb_infos/7ZKQ.json|1785404936.0|2654": {"hash": "3bd8d02064ca41aa245aac75f85dbf565a4b9a71f57e8e670c088de01c3aec34", "size": 2654, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/pdb_infos/8A3T.json|1785404936.0|11571": {"hash": "820560eb21264956e888674364cd52b3c59b1b4732a618d2cd630e4f3330a7e7", "size": 11571, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/pdb_infos/8A5O.json|1785404936.0|3080": {"hash": "c9267d48da48e6ea34b93dc8237c7390794d7cf86bfca7eaaf0fd39f97f6ecd9", "size": 3080, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/pdb_infos/8ADL.json|1785404936.0|8783": {"hash": "2a2dce910fe6a2a163a62ce230c3e032bd718eb77db1d01d0e623e66a5024a57", "size": 8783, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/pdb_infos/8ADN.json|1785404936.0|5332": {"hash": "fa76076338dfb6ee7cc3b27b761588b6a35d4464472fb98ac49fa31c4db8ef35", "size": 5332, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/pdb_infos/8BEL.json|1785404936.0|2169": {"hash": "c982c57e5531671d28b0a6332cf39afc54157c79acf9bd8855e11c606d4b6948", "size": 2169, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/pdb_infos/8CTE.json|1785404936.0|5655": {"hash": "56dd23b172e70659556622f7950498d1c782ddedad19952e24090c465a41d151", "size": 5655, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/pdb_infos/8FEE.json|1785404936.0|5034": {"hash": "853f90679cbb7f1c45b96fe569b591ec299781594e1420ef1a8fba7426fc6486", "size": 5034, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/pdb_infos/8E9G.json|1785404936.0|7021": {"hash": "7997f7bd596b8dabc594d1b8dd2065158887a3fa043ff21b29bf69d40a2c6f2e", "size": 7021, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/pdb_infos/8F5O.json|1785404936.0|8546": {"hash": "5196469d6e45219f904a3e6c96a3f651462e6b2372373d4fd288c3064bc73a48", "size": 8546, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/pdb_infos/8HIL.json|1785404936.0|5718": {"hash": "96536d9a50fe3103aefdff8a7ef468ecdd3252d9bd701434cd6eb6f218f1d039", "size": 5718, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/prodigy_results.json|1785404936.0|12086": {"hash": "0ffffe1641131eab0eb900807db9836fd4e79923a61c579da1b110ad96253702", "size": 12086, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/rmsd_validate.json|1785404936.0|1221394": {"hash": "00098a10975e0c807d0009ac7241c9f85581f1a6be7a0e466deee9a4a00b209e", "size": 1221394, "status": "c"}, "scripts/paper_resources/figures/data/benchmark2/rosetta_results.json|1785404936.0|2513": {"hash": "f9e58833676da1958ccf82377d70a908c64b1de456a4e5965a4669d79125d188", "size": 2513, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/AFM_v2_all_trimer_mmscores.csv|1785404936.0|4401": {"hash": "1c393c8594aaa63ee35e27cb4b2cd2f128f80fac3a1befe053dae288549e5435", "size": 4401, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/FD_all_trimer_mmscores.csv|1785404936.0|4524": {"hash": "6eeadd19aa144fc75428c44aaa6759d930e0e9568faf733989226211c82af50b", "size": 4524, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/combfold_results.json|1785404936.0|252822": {"hash": "d63c52bdea87e9c050a57be3e306f33f3330b5c45e2b6223972c1104488af76c", "size": 252822, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1A8R.json|1785404936.0|471": {"hash": "63715881b917e0fe9977b94c5bccec5c174c5616b7653abee152a85b37027dcb", "size": 471, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1AVO.json|1785404936.0|624": {"hash": "5f52585450ce96c9ceec843c13091f4e51a8f55d4ef2ec81bdd388e090da8a63", "size": 624, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1BCF.json|1785404936.0|576": {"hash": "32c5748b657509cfb1802f16d31e8801121595548764472c0855d7b8173c8a4a", "size": 576, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1BHC.json|1785404936.0|307": {"hash": "ac16fa0d1534a0e193b7214aeadd3a47a44f9ef2f2619bc8adc7108bfd3bffc2", "size": 307, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1D0I.json|1785404936.0|430": {"hash": "f2cd5bc944f1941324231509edceb5d5007177a64e47b4cb85430f753ac78aae", "size": 430, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1DPS.json|1785404936.0|441": {"hash": "0896554e3ef4a6b7f79de061b174279ebf089546ab21ffbb1bc6ccc1e812309b", "size": 441, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1DW9.json|1785404936.0|406": {"hash": "6592ba2fc953db353a7b84f94ab7326b6f29fd4037ca929a9cd8c72b1020ed6c", "size": 406, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1F1H.json|1785404936.0|742": {"hash": "8d6fd10f61125d4ebf6c8442126b1bc6b2acb60e1cc8ec137798c41d24156432", "size": 742, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1G0U.json|1785404936.0|5370": {"hash": "405152da10a9fd87510fc2b7b32c64ad4c174c7ac75b7965aaf1962c8652d51b", "size": 5370, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1G63.json|1785404936.0|455": {"hash": "f5d50173682292a969091fd87f675bf3a16c9cab655f41aa6a75d202ebca561a", "size": 455, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pairs_validation.json|1785404936.0|18321249": {"hash": "0a4ab340b8e0a0ce11f116f4a46f1c38826fc8ed0c7b75456d57250328d6dc57", "size": 18321249, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1GH0.json|1785404936.0|735": {"hash": "559aee150b34e1dfc2626a95fcda781ae48f894d9d67080be5e4cd7bde7a9ad7", "size": 735, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1H2I.json|1785404936.0|471": {"hash": "a04156e3d1e137c59cee849c2c910323a165365ee38c463cae10780abd5de9dc", "size": 471, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1HKX.json|1785404936.0|445": {"hash": "11f62435b9e23da7eb211babaf9196a64a0d398db5733e2f5143438138c82ea4", "size": 445, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1I50.json|1785404936.0|5709": {"hash": "c18b26704eb478e2f3e687ec036c4d2fb5b39f0d6a3847cca2ee5b90b3f80040", "size": 5709, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1IJG.json|1785404936.0|583": {"hash": "e2950844d1c5d028ab89fb8789ac507a52df1bb9af617d054d682dc4c903d50b", "size": 583, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1IRU.json|1785404936.0|5344": {"hash": "7329e03801db231183b2bbf22e14b86ec654f72c394a940d5e1605180501e30a", "size": 5344, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1IS7.json|1785404936.0|810": {"hash": "d270822b59d93e5ffd629789fc0de26aa9bd376fff8ff1d59a23b76dec1c668e", "size": 810, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1IWA.json|1785404936.0|1080": {"hash": "c19323ebc0b2974d7afcf814e7d52f63d7d4d6bf830795d37c4b709a7c2804d7", "size": 1080, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1JYM.json|1785404936.0|433": {"hash": "8c93e1cef5fbcc91c0d182841071d3cff1b92fc386536904aa464095fe15333a", "size": 433, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1KB9.json|1785404936.0|3709": {"hash": "ef7731df348aefdd5d5680eba9b57a68e66fa23e73e9692551283352a48ec3cc", "size": 3709, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1KP8.json|1785404936.0|845": {"hash": "ae509cbb9261d6f5c75c4c47a05f13a85b51886eb003d0055820c8958dc7b2ee", "size": 845, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1KYO.json|1785404936.0|4092": {"hash": "f40a719b8f6a81863f79545a4f928ee35498526c4a1973f8a2a562737b568058", "size": 4092, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1L0L.json|1785404936.0|3693": {"hash": "b28ef072fab1aefd4f7feaa26e598369e1824e7baa6996a8b9cee9da7e2884ed", "size": 3693, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1L6W.json|1785404936.0|470": {"hash": "de4f8185bc52955e82d4a88a0c3c5feb996fe672aee4ed9f973d3b2cd82083ca", "size": 470, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1M3U.json|1785404936.0|514": {"hash": "cc1594a0e3f5c554138f1938154e2d44acfab34a4c96f5b9f319b7f56ccf090e", "size": 514, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1M5Q.json|1785404936.0|428": {"hash": "c768094334afd77c59b2095600e275d1288c3ae9114b7cc1fd48b5e7411af9d1", "size": 428, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1MFR.json|1785404936.0|594": {"hash": "c553226a62a8044381604a6696154ade2ff7c8e8aec77ba9100a1d991ab114a3", "size": 594, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1N8J.json|1785404936.0|436": {"hash": "32920c5c693398c41b318b689042398184bd5e04a867db8172e44d72a103a796", "size": 436, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1NGK.json|1785404936.0|402": {"hash": "a0805b4ab67b040835986f745752f46b563c62de2e43efa122e47fb4ee793ae6", "size": 402, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1NLX.json|1785404936.0|409": {"hash": "f337da48539ac8758766a6da65207ae5b2a6a22e8df4c383fb9edbc5e2bd27bf", "size": 409, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1PCQ.json|1785404936.0|1129": {"hash": "8fbc6431d8bf58501271906c06862c73f08b75f51f0a370a5020b6fda1907105", "size": 1129, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1Q5Q.json|1785404936.0|919": {"hash": "23fb6de28eaddba4b134191a317ebbaaca5918847eb4e44f1136a6c2b8b4f5aa", "size": 919, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1Q95.json|1785404936.0|864": {"hash": "64055eb8a72e8797ecc542a89d1c4e394ed2d64a244578e9c94ad413505785a0", "size": 864, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1QAW.json|1785404936.0|335": {"hash": "77547178948939ba2fef8c70b26f21c3b480879c86bd80426b6b2f2efc84d372", "size": 335, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1RBL.json|1785404936.0|1025": {"hash": "72cd4c984259baa28583ab9a54fde165fa82bf8390068af0b975aac732910538", "size": 1025, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1RVV.json|1785404936.0|644": {"hash": "fd6c83cf90356276a8b71fa1d5ab9172e7fe5e7424f3cd230767889b3b0edf29", "size": 644, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1S3Q.json|1785404936.0|591": {"hash": "d69965d7d61ad53ac8aba15ce072f779d5014033f7052ee5acd7414da93e329c", "size": 591, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1TH7.json|1785404936.0|378": {"hash": "c95e71e790647e0fc638470323a2cabf0cce480e342d78e938451ae7635ee986", "size": 378, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1TR0.json|1785404936.0|382": {"hash": "466567fc79e99c779bec0e633b5fc368d503a69c244de5f9f61e222a004e5cac", "size": 382, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1TYF.json|1785404936.0|491": {"hash": "f4e801ce60345ff19249d0791ee31762f0ff03d59928f5494604e52fce338662", "size": 491, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1VDM.json|1785404936.0|427": {"hash": "680e5b7697e214f8220630b3dd7f70bc67b7dd1c5cdb3366e3e006bec0d24c6c", "size": 427, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1VF7.json|1785404936.0|655": {"hash": "77b8d64bfe0166ff648e80627c48eef473e6d986194f42a8c9e8474f4eb54f50", "size": 655, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1X0P.json|1785404936.0|393": {"hash": "38ef80abc19574bc84dc34199ed8ab51e3777ed7205ed0f8ad9e8e85e22836bd", "size": 393, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1X0R.json|1785404936.0|499": {"hash": "2bd8a4e8c5bff58bc72137a4e48d0a5fbc9b4bbbbfee3579cad3dd8f501b1dda", "size": 499, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1X9F.json|1785404936.0|1244": {"hash": "9007d9708166d45f5a19758f3310a1d951a5cdbc8ac0ad44a29f26af3fb54e41", "size": 1244, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1YKK.json|1785404936.0|983": {"hash": "6466e11d71a79df8602e46e9438d968a6a81bcac822c843b28f0674b53f21fe7", "size": 983, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/1Z6O.json|1785404936.0|948": {"hash": "c58455e153b634c261a2635baec26d9b78b94d5258f9ba84336618f63af0c33e", "size": 948, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/2A3W.json|1785404936.0|454": {"hash": "1c92e3c6558e0877bfff20315ca89801ad1a84ead1ee377fcd0a23960faecbe8", "size": 454, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/2BL2.json|1785404936.0|406": {"hash": "8c3bd3afe72f882109496d7b90b0dd7199cd7d6002f40c3c4515aca3fa0c5937", "size": 406, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/2BX9.json|1785404936.0|326": {"hash": "3538f60cac4ed3290d2b7f5811c3ff32566eddf34437d0202e9bd00014d05a35", "size": 326, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/2C41.json|1785404936.0|432": {"hash": "a2a53a0ef675c8715080716e5b8c1d9a1d6c0c39bd6b0b06d35e966c73768af0", "size": 432, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/2D3A.json|1785404936.0|606": {"hash": "a9ef8b6fdd0f2d5a54b4f263dabbd5eaef51c962ce4a5df87a6d8a5fb0bf565e", "size": 606, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/2DD4.json|1785404936.0|1054": {"hash": "8a58aa30190192007151af202f6fcc9a2dfa268becbf1a1fa2c2fa55a39e9622", "size": 1054, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/2DJW.json|1785404936.0|341": {"hash": "126df72221b6a152d9a8c14ed3013a5a01a588edecc3682856dd17f2c1719005", "size": 341, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/2E6G.json|1785404936.0|518": {"hash": "f1dbe30b317ba3092952f0d64cfd8873b3762322ca51a35458340555f04a2016", "size": 518, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/2EWC.json|1785404936.0|400": {"hash": "c45e46f39519cc3d2ffb8ecd799ce80662339d8a40d64c879f8006683e8569c3", "size": 400, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/2FKD.json|1785404936.0|408": {"hash": "6cfeb1da7763ba315d4e7e0811a0cea5efbbe7850536ea46da756f9a7c546350", "size": 408, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/2G9T.json|1785404936.0|426": {"hash": "3ab52d639eac28a876726d6edc0abfd82c05170018d16d32290cbed4f68c2f6b", "size": 426, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/2GRE.json|1785404936.0|623": {"hash": "9bb70cbcbe1163a220d0019f63837d073f1fbbdd5a8948c1800700f6d1ee4366", "size": 623, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/2NV2.json|1785404936.0|1043": {"hash": "e10650f14ae54083e33b4a0bf06833cabedb477ed564235aae105e16236a2e3b", "size": 1043, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/2OBX.json|1785404936.0|407": {"hash": "ac1572798df752851573fbe50b15a298e1fc4fcfbdd42bef45cf25cf69a87034", "size": 407, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/2P1B.json|1785404936.0|372": {"hash": "96ec98ca020258481c21fcfa6384c855465a94235192f5fad98cd2eb91994342", "size": 372, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/2QJG.json|1785404936.0|523": {"hash": "a31aca229433b6aa50c63eb0aa78aadd463016817a0fe88afe138e51859539e8", "size": 523, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/2V18.json|1785404936.0|341": {"hash": "6632380045529717a80946e97246b6d5aa9d9955da88a6b8af9710042e1b568f", "size": 341, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/2V5H.json|1785404936.0|834": {"hash": "bd3deb3031cc74f79abf97ade8a631acb3babb10193e86902ee5ad2df7db2d78", "size": 834, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/2V7Q.json|1785404936.0|2409": {"hash": "50ea654cb0329114eb261f6cb737d32e30107f0e14a3507d69c393712b58ac8d", "size": 2409, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/2VYC.json|1785404936.0|1005": {"hash": "f1e8a7085a4bab2c3c1b1bf842e77aa439900b01ab30193f637f210473dcf684", "size": 1005, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/2VYR.json|1785404936.0|655": {"hash": "a01d5d02b8262feefe2caa8af0edaf1d5f3f116cf195b51e4256050d7f1ee68b", "size": 655, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/2WCV.json|1785404936.0|390": {"hash": "981a9fe76f426a75b98e00a98251aba028b399da2ad596b7c94672a270034227", "size": 390, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/2WYR.json|1785404936.0|606": {"hash": "ce19c81083b9f0ce3a6c5d8a7f0b8cc4b94bec5e50884ac6e0b5403e9e2670dc", "size": 606, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/2X2V.json|1785404936.0|354": {"hash": "0a8f7559214ee4fd5eeab0b4c45af6fc7e4632ac2597edc684a6be8a69db60a2", "size": 354, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/2YMD.json|1785404936.0|462": {"hash": "873b415f7d332858acd558235e976ec49d77d02cabaf464c05a39253349325a2", "size": 462, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/3A2C.json|1785404936.0|598": {"hash": "85b996fc036ab64ea8000c557633e4573667ec158dd25844b8082bbcf70cdd08", "size": 598, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/3BFJ.json|1785404936.0|637": {"hash": "95cbfc76bf3883944397fbf89bbf06a818f16f19199f74dffb9e83a44e2ab279", "size": 637, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/3BJQ.json|1785404936.0|566": {"hash": "303a52a566deca003e38ce0e61403e7d1320af77500aaec62cd3b1d19f83343f", "size": 566, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/3DTD.json|1785404936.0|449": {"hash": "ab1a2e1b88bf86e412ab0983b77756b546b49e2c1b68589c83c8ecab8d7a084b", "size": 449, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/3FP9.json|1785404936.0|427": {"hash": "67d84907800434348ff10811da1d6f329b0baa92192375620cbe274a0bd8d94c", "size": 427, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/3HHW.json|1785404936.0|1004": {"hash": "9281911f2b4a539f9b12aeb6329f1fd73bfdc0ef73f6ca7b9f618ae22ae85583", "size": 1004, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/3JVO.json|1785404936.0|394": {"hash": "4918b4d9705634f7b2f5f8f78d5216e2114f0e8804f2d8395dab90f6ec5dc014", "size": 394, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/3K6C.json|1785404936.0|344": {"hash": "86c18cb49fff2c4dbcb4c42b6fcf4046e8ec7c38e7366db4585c97be7bc19e61", "size": 344, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/3KIF.json|1785404936.0|356": {"hash": "abd1018dc91f8599e986f7a9df4572dd7ffc7231c3b6f20d6723800c8907c5ad", "size": 356, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/3LAY.json|1785404936.0|425": {"hash": "d82d1ae04605bedfa9fc13dc2c2289d9a31b340760aced10d4186827e987d310", "size": 425, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/3M0W.json|1785404936.0|350": {"hash": "f489f328546ec6c84bf405dbdfd84125d651ae1687cd9c5f98e5a7d94e47d8d1", "size": 350, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/3M1A.json|1785404936.0|531": {"hash": "65c722f8d7f211570ba3dee192f94e3bd83f69554e6ef04941dc80feaa4b53ea", "size": 531, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/3MQ7.json|1785404936.0|395": {"hash": "702adc5fcf564f79487c2676c85c462f44847f4a48dab8b105798bd2b27161b1", "size": 395, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/3N99.json|1785404936.0|540": {"hash": "95850b7b130321215a4b0192be1fafc512f4557c4803cb17bfaa67aca33a62ae", "size": 540, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/3OJ5.json|1785404936.0|607": {"hash": "06d1330694e6ab352c8c829e49abf9aeff2e5e1d7d92d476a008c2f4c60612e3", "size": 607, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/3ONR.json|1785404936.0|345": {"hash": "cfa90a5fd9e8166642461e02e63c6ba54082fb74db4793bdb24c616bda970267", "size": 345, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/3OOQ.json|1785404936.0|646": {"hash": "0866aefdda8fe31f30ccafd1ffd08c98054b8f91cc82dc61070e64270f4085b0", "size": 646, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/3R90.json|1785404936.0|462": {"hash": "67c9fed3d947a65c2997e125176555bca825dcd76a38e1a91b6120bbf78eaac7", "size": 462, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/3TOS.json|1785404936.0|507": {"hash": "c91b579fa5b0d5b8dd47cffd0e10635f4138151c83cfb4f139d8440b6803eaa2", "size": 507, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/3TXQ.json|1785404936.0|348": {"hash": "9252ce811570b622b1b0cf128537f4d231040323322212ac24d322f116c30735", "size": 348, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/3U6X.json|1785404936.0|670": {"hash": "766ab82cd528b94fd2e456be29082e1794ce3db495cb85056ee45fb72814a773", "size": 670, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/3VG8.json|1785404936.0|486": {"hash": "7357a552f5939d1d11bee0d8192cd8dd5461ca7e57a78420d9f038375c4e1797", "size": 486, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/3ZIA.json|1785404936.0|2411": {"hash": "b50e1574c8c2c62c4bfb77236b9ca8fb6783753fa764529af54acffe9332c088", "size": 2411, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/3ZK1.json|1785404936.0|350": {"hash": "85b3638a6f9d526cb4d75e421e2e12b526f10f518e836981f163b1c70e871e1d", "size": 350, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/3ZNU.json|1785404936.0|343": {"hash": "761b9d9b47c24c7e20b1b8ea5705223e847d8c17d14598659398412caa081a55", "size": 343, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/3ZQM.json|1785404936.0|321": {"hash": "f92af1a93a1b8899273758265c6429e5487dd041a77371fd89018d447a7c4b9b", "size": 321, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/4BHU.json|1785404936.0|637": {"hash": "8f583123facb1c61d78b782fb7affc343672af3caba2734cb1ab2321f22e7b3f", "size": 637, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/4C2M.json|1785404936.0|7197": {"hash": "8894f7997830c6310436e9628d7658b96cc92722e0a6b24a383f9360df7b10d6", "size": 7197, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/4EJ0.json|1785404936.0|592": {"hash": "a8e023f074aa018e1a031d5374fa9f58ffa3d0e6f3c15157ec172f2a41862786", "size": 592, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/4F4S.json|1785404936.0|325": {"hash": "57f60e66b381099a02a615053a6403f3dca41011a2439ac84844bd2ee4437c05", "size": 325, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/4IC1.json|1785404936.0|456": {"hash": "bd849b011a66be118af995e75de95e432389542176d737d0b102ec073985a929", "size": 456, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/4JPN.json|1785404936.0|328": {"hash": "2373450e930a6caa8c40248d7a24730c618a0bf95d1432e9fb94b3597ee9b0da", "size": 328, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/4K2J.json|1785404936.0|390": {"hash": "bd18cc5178af7c6b5bb479effc1b4e8c26aa9c1f1426c08a4864d2354eb41489", "size": 390, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/4RAP.json|1785404936.0|680": {"hash": "b67d06352165ab2ae90b8789499b12e6f3958c3769c7094a17ae4752606a9711", "size": 680, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/4RDQ.json|1785404936.0|1402": {"hash": "47926bbd3b78403e039adedcad571a80f22cccfe274ef14e1575140faeb59aa4", "size": 1402, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/4RSU.json|1785404936.0|700": {"hash": "4180ac31a00a90d0b814b4abbcd6fe9603e5ac73c076e4a8de37eb698da4c08f", "size": 700, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/5AA5.json|1785404936.0|1331": {"hash": "7665292299d4f9c802773d515bdf2297f6665b134836882ea75b5666cda6bd90", "size": 1331, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/5B04.json|1785404936.0|3101": {"hash": "f5e3963dfe32cdba7da1210b172c6d92a277c0068bdd67b957087d5f431c2faf", "size": 3101, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/5BSE.json|1785404936.0|527": {"hash": "e4c05efbd8ce8323b8fcbd364d8b98210fd6fcaf1c7fcfd7b102c1b0c4623c04", "size": 527, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/5CW7.json|1785404936.0|619": {"hash": "a66e9b4993786a0ad5f5f21d5c47188875f1366d53c4f9409fb0e31a04615e39", "size": 619, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/5F2Q.json|1785404936.0|385": {"hash": "34db04db8073a97478147952f480fb8a139cc58c868c2315a18e802e60069535", "size": 385, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/5J09.json|1785404936.0|507": {"hash": "5cd33e1f2a253670a14ed636f5c7f202b762100b9ef2b8c4fc5beddabd69cc84", "size": 507, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/5JQL.json|1785404936.0|675": {"hash": "0c6a682683f945a4ba3b4d8ffadca3f69c18d000992346d6251d76b5927b1eb8", "size": 675, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/5JYX.json|1785404936.0|359": {"hash": "9087bed53b8e22e075363d7a3925e3079d0638ec95b8787790a38fd47525707b", "size": 359, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/5K2M.json|1785404936.0|750": {"hash": "b97e2298179c906d95b56012a3d025b41a2e3dec5a138ed0f6863718e21dd488", "size": 750, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/5LK5.json|1785404936.0|515": {"hash": "fdde88f56dc999b099c609030a7dc61d77c98ac1f8ba2bf1d85cc9eb0a80b552", "size": 515, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/5N5E.json|1785404936.0|348": {"hash": "b70ccaacdfc7317d7d2ffde0793cd912731d83d32960a03e81e66c67798032ff", "size": 348, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/5NFR.json|1785404936.0|642": {"hash": "5c51b6efb9eedfb8d7746532ea988a01a97f7cb4c84e5594a0d3978ffbcd493c", "size": 642, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/5ODC.json|1785404936.0|2950": {"hash": "3a6c6839f706411c36ce49d5a1ca4a024ded8b1d9b74629f54bfd35a06b4ed6d", "size": 2950, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/5OVS.json|1785404936.0|499": {"hash": "ba7b50a94162d14a08bf8e19e8be553d362dc380cfa7e8aae7322607fc8a120d", "size": 499, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/5T11.json|1785404936.0|434": {"hash": "94bb785307718ca0fa85ee16d0e0605dd6c0336b87ea3abbeb3ea1ed371b6144", "size": 434, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/5TRM.json|1785404936.0|586": {"hash": "ad0efc244b8e087183e807aa5d8d2442f01fadef13d986b79508af1243bd4d88", "size": 586, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/5UK3.json|1785404936.0|426": {"hash": "229ff962769aabf513d848ccb0e974d91b82bb5023f4810310daec143c1753a4", "size": 426, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/5XPB.json|1785404936.0|350": {"hash": "7a025fad05b34e03318cc681c3d2c370475addbf5242ec5d647c476bd3cd569e", "size": 350, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6E7D.json|1785404936.0|662": {"hash": "b1f79ad252f0d6a15f13a140e7ba75be12020b4034350af6d26676e08810dfef", "size": 662, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6ESQ.json|1785404936.0|1399": {"hash": "85a2fc1fe82a78a8884c767bd266d669074bf5a7dd6c91c2477366ccb0beed27", "size": 1399, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6FWH.json|1785404936.0|615": {"hash": "1b06434a799b49de0facb64809c4899f4b5b14a64cefc85b03eb325517cc013d", "size": 615, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6G94.json|1785404936.0|1656": {"hash": "9304eae4dad8f3612dddd29da302b4bd2af8304d8d4ab50076327dad91b9c465", "size": 1656, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6GRJ.json|1785404936.0|617": {"hash": "67d77b2b2ef240d04ee6a2267c2fa991ad980a7f96dfd2293f1d0a76141dfc0b", "size": 617, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6J0B.json|1785404936.0|1049": {"hash": "ec8733fe533eb2541a0738cfe5f6e7cd6193935444dba5b66621e2b0e9c3b6d0", "size": 1049, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6JCV.json|1785404936.0|607": {"hash": "7849721e473e86277cf20e0753e619449f4dda3a9c503eb1f1ef34b9314cf0b0", "size": 607, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6KRB.json|1785404936.0|364": {"hash": "1fc2817679a708530a28e9b820428662e651754c07e66b2205caa1e62ac1a15f", "size": 364, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6KWY.json|1785404936.0|7716": {"hash": "4b95f2bb2d80cef7b2809e0b05644c58263b2e18ff6034e095b54a9f7e621766", "size": 7716, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6LNI.json|1785404936.0|460": {"hash": "1660b9210c9b7df69a7d9a7a11263f003cff021bdec078892f7f462614b55653", "size": 460, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6LQH.json|1785404936.0|902": {"hash": "15eb605610cd73ffae9a8179cfe42b53595ea34b9888beaf3882f2f51330f632", "size": 902, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6MRT.json|1785404936.0|598": {"hash": "320be0f9e10f7166ae8dbe6c935b4398ef25191d410cb7312149841f44901514", "size": 598, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6M3X.json|1785404936.0|729": {"hash": "938e4d0f744c0bc1403802f59d09ab95c626da6abf76489c3adb06f4b1012056", "size": 729, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6MST.json|1785404936.0|339": {"hash": "89af2f73cfb838ac650e4f1bfacd3ad999830a93c0639e1da1b5fd5a32ec3780", "size": 339, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6N3P.json|1785404936.0|634": {"hash": "f0b619c4cb7d25545650f344110c1080a09a89fab20b74665e505c8331e9d1f3", "size": 634, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6NHT.json|1785404936.0|995": {"hash": "b59aec67856b50f215060fe7e94c5542f00c63a939da9704f2096c77cead29ab", "size": 995, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6OFG.json|1785404936.0|428": {"hash": "144334bc50be551491a6be0de0379856b23bfb794ac458e60c32f2cecc2054d9", "size": 428, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6OSJ.json|1785404936.0|390": {"hash": "db92d00e053581192a9a1d10da0a30032afcc2ddccb25874e123ed7df8ef2552", "size": 390, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6OVH.json|1785404936.0|380": {"hash": "b00bc565c0c6b3e8a9c815540a1fe6809932975229c7b3a8d6017d076c1a3498", "size": 380, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6PYT.json|1785404936.0|1098": {"hash": "291c0e0533b302bd9c77209850f31201fcabe99a270dd738af024ac72e413c79", "size": 1098, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6QLV.json|1785404936.0|504": {"hash": "02c798c3f4fcc0a8110c4c01bc78e484231998ed46e86a53d5c2030c49084d78", "size": 504, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6QSU.json|1785404936.0|1352": {"hash": "0be1d7d710e257a66fe3d347b563ee77c9ff774696fbcfde4d28f24d3d967997", "size": 1352, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6RD7.json|1785404936.0|3298": {"hash": "15569fe6aa6ffccb667beed521d6fd923f1108377cd63cae717e071a6ac30fe9", "size": 3298, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6RDB.json|1785404936.0|3213": {"hash": "ad630916d4a55accd6e8e0ccc8c640d6c5820da05685af66cbf8c66f53690677", "size": 3213, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6RKE.json|1785404936.0|945": {"hash": "4c0403f45480fe92eba5d7b49e1375498edc61180169af3eb7280fbe45150d86", "size": 945, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6RO0.json|1785404936.0|1789": {"hash": "f14036e7b080241c6b54611e268960f9f2d9545de39684918431d13b736ba5df", "size": 1789, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6S5L.json|1785404936.0|414": {"hash": "7d9317c512633e04f8b67b6f8f33ec9506106af3fab63ed5030f9573d2352f31", "size": 414, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6SDZ.json|1785404936.0|389": {"hash": "0a6069d521ce125bd62e614424ccd33c828b3478118ec10a09d6d4a9e9fcd535", "size": 389, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6SQR.json|1785404936.0|1399": {"hash": "b4fb63d9e68aed8299b041451e7e69f05f69725cdf32e1878688138dfb380f38", "size": 1399, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6SSI.json|1785404936.0|816": {"hash": "b9c6c7d175df5cd571064bd21b92892b7752bdfe7471e86330d19d3d9c659350", "size": 816, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6UIW.json|1785404936.0|593": {"hash": "afc0188df6315dfcc37c295bcb6c3dd509e1d1d1e4875d4683ccba63c25bb730", "size": 593, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6UWR.json|1785404936.0|965": {"hash": "7fb50e4a527e17626e803d70a877eb4055fdc43acadef009ef07c78356b1917b", "size": 965, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6W6M.json|1785404936.0|869": {"hash": "26bf64e166a41b90c5bc54375e926da51c64a3814da48f1c94f720a236a25d8a", "size": 869, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6WBN.json|1785404936.0|671": {"hash": "c099697027b0f7ea681b1a417f720d19e98cba7068ca324b64fba5551afeb1cd", "size": 671, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6X04.json|1785404936.0|947": {"hash": "93125194f9c37dcdbda3a55f268f8eaade9171c3a11b7d82aa3e670b84278399", "size": 947, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6YBQ.json|1785404936.0|1578": {"hash": "71cef3538904f70a4505e59bf73cc09e73a22728b6331c1fc34d768a449c7a83", "size": 1578, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6Z1F.json|1785404936.0|1523": {"hash": "7579662a31ecb5e4a4902fab9fa4751ff3432011aaf61f6605b9ab00faa7c5ef", "size": 1523, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6Z16.json|1785404936.0|2987": {"hash": "329b1ddc05d4f28659c1a5b8e47edfc3d00656c7a2f8a2211872426324026dca", "size": 2987, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6ZEE.json|1785404936.0|777": {"hash": "c0642d81b287e48fb63000c39d2c625add887f3609054c08c6c076238f4188e3", "size": 777, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6ZK9.json|1785404936.0|6808": {"hash": "24704c2aaf217936c20680912d6fb19d5d5d9ebabfeb588fc1b8152514812e5a", "size": 6808, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/6ZKB.json|1785404936.0|9572": {"hash": "b06577628b5a8399ffbf87023242e7c735a4cdb1108b101c043672d214522130", "size": 9572, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/7AJP.json|1785404936.0|482": {"hash": "83489ae32fdcb93386b6273cacba5def01e69a0f2343661fa19a742acf21eb97", "size": 482, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/7C4J.json|1785404936.0|8996": {"hash": "5f9a72cf147807e994b9db0950c725df19fc04a799ac3b2afc37dccb7bbd7a50", "size": 8996, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/7CGE.json|1785404936.0|1463": {"hash": "100542f9893bdb8295971b9366d4bd89a38e7dbca5a251f235a10b1ee9e7d1b4", "size": 1463, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/7JJP.json|1785404936.0|714": {"hash": "8fcf762946f98c4b57dd984f7cb38b596d81fbade343e9c8d7803b14acebb606", "size": 714, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/7JKC.json|1785404936.0|687": {"hash": "98fdc50e4a2d9dcee3883093d494a7b705bc65612cc8e7362480a4b165072ca8", "size": 687, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/7JQZ.json|1785404936.0|559": {"hash": "833f0dbf41a57c7c0ab0fefba25a9d2922cc5040467f94c7d3d514b77d674bea", "size": 559, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/7JZ2.json|1785404936.0|1725": {"hash": "a2f215f1af8cfc42420cd365edf3364cada31b2c2820c8466d09af2d726e1916", "size": 1725, "status": "c"}, "scripts/paper_resources/figures/data/benchmark3/pdb_infos/7NRQ.json|1785404936.0|691": {"hash": "fc0ceb98a57a21243be03bfd3be773ca5ea56bfd8448a282419798ca84d0d3b3", "size": 691, "status": "c"}, "scripts/paper_resources/figures/figure2.py|1785404936.0|11439": {"hash": "c7feaa229ee081b8ded0f27bed40d25a7eef525ccc413bd491791f9615f64391", "size": 11439, "status": "c"}, "scripts/paper_resources/figures/figure3.py|1785404936.0|15881": {"hash": "e41ef4e754bdfb573236bfa40f69939e970941399ff6571c83da57b512adcb50", "size": 15881, "status": "c"}, "scripts/paper_resources/figures/figureS2.py|1785404936.0|7351": {"hash": "f5d87b5289626af00e71917e83036d0d21f7cccd253d5504c30d79f664a4856b", "size": 7351, "status": "c"}, "scripts/paper_resources/figures/figureS3.py|1785404936.0|2358": {"hash": "662e33c572b7581a6fddcc77071dcba03eb1dda069f8473cb4c9d7122e9a3714", "size": 2358, "status": "c"}, "scripts/paper_resources/figures/figureS4.py|1785404936.0|6489": {"hash": "7be20f79aafcbe5bc468046c3b699ace40366e93f8d374fc19f4be9d282cee31", "size": 6489, "status": "c"}, "scripts/paper_resources/figures/figureS6.py|1785404936.0|5184": {"hash": "1118b403cf5c383642143ce293acffadb20f0b291618cff7c4ca3b288ac09bd1", "size": 5184, "status": "c"}, "scripts/paper_resources/figures/figureS7.py|1785404936.0|8636": {"hash": "77e8c969a46e4642c1815938443302d8732e8b777d2481b67e5de5ab539e4925", "size": 8636, "status": "c"}, "scripts/prepare_fastas.py|1785404936.0|6897": {"hash": "b45121d41c5ea283cb41256c63b484a284ba6ad655650e51461111e7c9cb429e", "size": 6897, "status": "c"}, "scripts/run_on_pdbs.py|1787795218.0|24415": {"hash": "1754ded4ea8bc635a77c2cc6775aaf68fc1d34c5ae7b4a806a83f55fe29982fe", "size": 24415, "status": "c"}, "weight/README.md|1787795225.0|937": {"hash": "407a46e00763811431cfc3012a2a35c99fe40f70c549d49ec26e7a2e72b087da", "size": 937, "status": "c"}, "weight/alphafold/params/download_complexes_multimer_v3_finished.txt|1787798263.0|0": {"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "size": 0, "status": "c"}, "weight/alphafold/params/LICENSE|1787798140.0|18657": {"hash": "9ba9550ad48438d0836ddab3da480b3b69ffa0aac7b7878b5a0039e7ab429411", "size": 18657, "status": "c"}, "weight/alphafold/params/params_model_1.npz|1787798147.0|373069562": {"hash": "f95e453e6a290ddf317ba1c9698d53fa110cf007ea979b0eae43e6ad38b4e364", "size": 373069562, "status": "c"}, "weight/alphafold/params/params_model_2.npz|1787798147.0|373069562": {"hash": "4da37ed3b71f08d935f913dfab0133977cd0d76ad37cd9333ff294d7fae43f87", "size": 373069562, "status": "c"}, "weight/alphafold/params/params_model_1_multimer_v3.npz|1787798147.0|373043148": {"hash": "611da8fc7478928f68de12e8b226260ef1f4ce62bcc29b008572e52f4f212959", "size": 373043148, "status": "c"}, "weight/alphafold/params/params_model_2_multimer_v3.npz|1787798147.0|373043148": {"hash": "51362b0844382ae0f5720c59b81dd13a43eea40fbf9995dd2573bdab88865378", "size": 373043148, "status": "c"}, "weight/alphafold/params/params_model_2_ptm.npz|1787798147.0|373103340": {"hash": "23645d9a82c4af2ed54cd48a7b3c1c2575dc6aa9fe931adb4d7203ca5f0dc398", "size": 373103340, "status": "c"}, "weight/alphafold/params/params_model_3.npz|1787798147.0|371712506": {"hash": "3cc4584dc0b0d1bdd1f7af14bd0692d00df3aa0e738d0d1b314821ad7eee4ffe", "size": 371712506, "status": "c"}, "weight/alphafold/params/params_model_3_multimer_v3.npz|1787798147.0|373043148": {"hash": "46d9bcad288edc7ad5a6362ee8e5f84307a69712e00e6c36b1ef9daf96ebc9ce", "size": 373043148, "status": "c"}, "weight/alphafold/params/params_model_4.npz|1787798147.0|371712506": {"hash": "b0a78b7e18661abc6097a905f48505004e6e3993c5dc41fb6820acf56e9c1d12", "size": 371712506, "status": "c"}, "weight/alphafold/params/params_model_4_multimer_v3.npz|1787798147.0|373043148": {"hash": "59bdabd2d69c07fe26b37882d544acbd1b9f89f196828f4220da49e0610b572c", "size": 373043148, "status": "c"}, "weight/alphafold/params/params_model_5.npz|1787798147.0|371712506": {"hash": "5d261ec6e18ba87784541f5ce113570018b3338df61ae577661b6232358d47e7", "size": 371712506, "status": "c"}, "weight/alphafold/params/params_model_5_multimer_v3.npz|1787798147.0|373043148": {"hash": "917742be5a105d6b80f13f1f13f20459f27ec3fdcd34ea088b359f4502d6177f", "size": 373043148, "status": "c"}}} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..29f81d812f3e768fa89638d1f72920dbfd1413a8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..6eac7df7342719a4665ae53107e2b3dbce1cdeab --- /dev/null +++ b/README.md @@ -0,0 +1,375 @@ +--- +frameworks: +- JAX +language: +- en +license: apache-2.0 +tags: +- OneScience +- bioscience +- protein-complex-structure-prediction +- CombFold +- AlphaFold-Multimer +- ColabFold +tasks: [] +datasets: [] +--- + +

+ + CombFold + +

+ +# Model Introduction + +CombFold is an open-source pipeline developed by dina-lab3D for predicting the structures of large protein complexes. Starting from the amino acid sequences of the individual chains in a complex, CombFold uses AlphaFold-Multimer to predict multiple candidate subcomplexes and then applies combinatorial assembly algorithms to construct the complete protein complex. + +The original publication reports support for complexes containing at least 18,000 amino acids and up to 32 subunits. + +Paper: [Assembly of protein complexes by combining AlphaFold and combinatorial optimization](https://www.nature.com/articles/s41592-024-02174-0) + +# Model Description + +CombFold consists of four main stages: + +1. Define subunits according to protein domains and chain composition and generate `subunits.json`; +2. Generate FASTA files for all subunit pairs and predict pairwise subcomplexes using AlphaFold-Multimer; +3. Optionally predict candidate subcomplexes containing more than two subunits; +4. Extract relative transformations between subunits from the predicted PDB structures and use a C++ combinatorial optimization algorithm to assemble the complete complex. + +The main components included in the Hugging Face model package are: + +- `model/CombinatorialAssembler/`: C++17 combinatorial assembler and the `AF2trans` structural transformation tool; +- `scripts/prepare_fastas.py`: generates FASTA files for pairwise or larger subunit combinations; +- `scripts/inference.py`: unified assembly entry point for the Hugging Face model package; +- `scripts/run_on_pdbs.py`: original upstream entry point for assembly from predicted PDB structures; +- `weight/`: official pretrained AlphaFold-Multimer parameters used for offline ColabFold inference; +- `requirements.txt`: additional dependencies required on top of the OneScience base environment. + +CombFold itself is not a trainable neural network. Neural-network inference is performed by pretrained AlphaFold-Multimer models, while CombFold is responsible for extracting structural transformations and performing combinatorial assembly. + +# Use Cases + +| Use Case | Description | +| :---: | :--- | +| Large protein complex prediction | Combine multiple AlphaFold-Multimer subcomplex predictions into a complete complex structure. | +| Homomer prediction | Assemble complexes containing multiple copies of the same unique subunit according to the specified stoichiometry. | +| Heteromer prediction | Integrate predictions from different subunit pairs or groups to construct the complete structure. | +| Assembly from existing AFM results | Directly use existing AlphaFold-Multimer PDB predictions without rerunning AlphaFold-Multimer. | +| Crosslink-guided assembly | Optionally incorporate crosslinking restraints to constrain candidate complex structures. | + +# Usage + +## 1. OneCode + +You can use the OneCode online environment for an intelligent one-click AI4S programming experience: + +[Try OneCode for AI4S Programming](https://web-2069360198568017922-iaaj.ksai.scnet.cn:58043/home) + +## 2. Manual Installation + +**Hardware Requirements** + +- The CombFold C++ combinatorial assembly stage requires only CPU resources; +- Local AlphaFold-Multimer subcomplex prediction typically requires an accelerator; +- Memory usage for long-sequence and multi-model inference increases with the total number of residues, MSA depth, number of models, and number of recycles; +- PyTorch is not a direct runtime dependency of CombFold or the current ColabFold inference pipeline. + +### Download the Model Package + +Install the Hugging Face command-line tool and download the model repository: + +```bash +pip install -U huggingface_hub + +hf download OneScience-Group/CombFold --local-dir ./CombFold +cd CombFold +``` + +### Install the Runtime Environment + +**OneScience DCU Base Environment** + +```bash +conda create -n onescience311 python=3.11 -y +conda activate onescience311 + +python -m pip install onescience[bio-dcu] \ + -i http://mirrors.onescience.ai:3141/pypi/simple/ \ + --trusted-host mirrors.onescience.ai +``` + +Install the additional dependencies: + +```bash +python -m pip install --no-deps -r requirements.txt +``` + +### Compile the Combinatorial Assembler + +The combinatorial assembly stage of CombFold is implemented in C++17 and requires the following system-level components. These components cannot be installed through `requirements.txt`. + +| Component | Purpose | Description | +| --- | --- | --- | +| C++17 compiler | Compile the C++ source code | `g++` is commonly used on Linux | +| GNU Make | Execute the Makefile | GNU Make 4.2.1 has been validated | +| Boost headers | Compile-time headers | The directory must contain `boost/algorithm/string.hpp` | +| Boost program_options | Link-time runtime library | Usually provided as `libboost_program_options.so` on Linux | + +Boost headers are generally portable across Linux distributions, but compiled Boost libraries depend on the operating system, CPU architecture, compiler, and `libstdc++` ABI. Therefore, compiled Boost libraries should not be copied directly between Linux, macOS, Windows, or different CPU architectures. + +It is recommended to use Boost headers and runtime libraries from the same Boost version. + +The upstream Makefile is configured primarily for macOS Homebrew. On Linux, the following command can be used directly only when Boost is already available in the compiler's default search paths: + +```bash +cd model/CombinatorialAssembler +make +cd ../.. +``` + +#### Specify a Custom Boost Path + +If Boost is not installed in the default compiler search paths, define the following environment variables: + +```bash +export COMBFOLD_BOOST_INCLUDE="" +export COMBFOLD_BOOST_LIB="" +``` + +`COMBFOLD_BOOST_INCLUDE` must point to a directory that directly contains the `boost/` subdirectory. + +`COMBFOLD_BOOST_LIB` must point to a directory that directly contains the `libboost_program_options` library. + +Before compilation, you can verify the paths using: + +```bash +test -f "${COMBFOLD_BOOST_INCLUDE}/boost/algorithm/string.hpp" \ + && echo "Boost headers OK" + +find "${COMBFOLD_BOOST_LIB}" -maxdepth 1 \ + -name 'libboost_program_options*' -print +``` + +Compile the assembler: + +```bash +cd model/CombinatorialAssembler + +make -j4 \ + BOOST_INCLUDE="${COMBFOLD_BOOST_INCLUDE}" \ + BOOST_LIB="${COMBFOLD_BOOST_LIB}" + +cd ../.. +``` + +### Weights and Data Preparation + +The CombFold combinatorial assembler itself does not require model weights. + +Local generation of AlphaFold-Multimer subcomplex predictions requires the five official AlphaFold-Multimer v3 parameter files: + +| Asset | Location in the Model Package | Purpose | +| --- | --- | --- | +| `params_model_1_multimer_v3.npz` | `weight/alphafold/params/` | AFM v3 model 1 | +| `params_model_2_multimer_v3.npz` | `weight/alphafold/params/` | AFM v3 model 2 | +| `params_model_3_multimer_v3.npz` | `weight/alphafold/params/` | AFM v3 model 3 | +| `params_model_4_multimer_v3.npz` | `weight/alphafold/params/` | AFM v3 model 4 | +| `params_model_5_multimer_v3.npz` | `weight/alphafold/params/` | AFM v3 model 5 | + +Use the following ColabFold data path: + +```text +--data weight/alphafold +``` + +If you only use existing AlphaFold-Multimer PDB predictions for combinatorial assembly, AlphaFold-Multimer weights and a DCU device are not required. + +### Define Subunits + +The input `subunits.json` is a JSON dictionary keyed by unique subunit names. + +Each subunit contains the following fields: + +- `name`: unique subunit name; +- `sequence`: amino acid sequence; +- `chain_names`: chain names corresponding to this subunit in the complete complex. The number of entries also defines its stoichiometry; +- `start_res`: starting residue index of the sequence in the original chain. + +Example: + +```json +{ + "A0": { + "name": "A0", + "chain_names": ["A", "B"], + "start_res": 1, + "sequence": "MKDILEKLEERRAQARLGGGEKRLEAQHKRGKLTARERIELLLDHGSFEE" + } +} +``` + +The Hugging Face model package provides a complete example: + +```text +scripts/example/subunits.json +scripts/example/pdbs/ +``` + +### Quick Inference: CPU Assembly from Existing PDB Files + +This is the shortest CombFold inference path and does not run AlphaFold-Multimer: + +```bash +python scripts/inference.py \ + --subunits scripts/example/subunits.json \ + --pdbs scripts/example/pdbs \ + --output output/example_assembly +``` + +The output directory must either not exist or be empty before execution. + +After successful execution, the main results are located at: + +```text +output/example_assembly/assembled_results/output_clustered_0.pdb +output/example_assembly/assembled_results/confidence.txt +``` + +You can also use the original upstream positional-argument entry point: + +```bash +python scripts/run_on_pdbs.py \ + scripts/example/subunits.json \ + scripts/example/pdbs \ + output/example_assembly +``` + +### Generate Pairwise FASTA Files + +Generate FASTA files for every pair of unique subunits defined in `subunits.json`: + +```bash +python scripts/prepare_fastas.py \ + scripts/example/subunits.json \ + --stage pairs \ + --output-fasta-folder output/pair_fastas \ + --max-af-size 1800 +``` + +The output directory must not already exist. + +The official example generates files such as: + +```text +A0_A0.fasta +A0_G0.fasta +G0_G0.fasta +``` + +### Minimal DCU Pairwise Inference + +For an offline smoke test on a compute node, you can use `single_sequence`, one model, and one recycle: + +```bash +colabfold_batch \ + output/pair_fastas \ + output/colabfold_pairs \ + --data weight/alphafold \ + --model-type alphafold2_multimer_v3 \ + --model-order 1 \ + --num-models 1 \ + --num-recycle 1 \ + --num-relax 0 \ + --msa-mode single_sequence \ + --disable-unified-memory +``` + +The configuration: + +```text +single_sequence + 1 model + 1 recycle +``` + +is intended only to verify parameter loading, JAX/DCU forward execution, and PDB output generation. + +It should not be used to evaluate formal prediction accuracy. + +For production-quality predictions, prepare appropriate MSA features and increase the number of models and recycles according to available memory and runtime constraints. + +### End-to-End DCU-to-CPU Inference + +At least one predicted PDB structure must be selected for each pair. + +To select the top-ranked ColabFold structure: + +```bash +mkdir -p output/combfold_pdbs + +find output/colabfold_pairs -maxdepth 1 \ + -type f -name '*rank_001*.pdb' \ + -exec cp {} output/combfold_pdbs/ \; +``` + +Then run the CombFold combinatorial assembly stage: + +```bash +python scripts/inference.py \ + --subunits scripts/example/subunits.json \ + --pdbs output/combfold_pdbs \ + --output output/end2end_assembly +``` + +The unified inference entry point also outputs a machine-readable summary, for example: + +```text +COMBFOLD_INFERENCE_RESULT={"assembled_structures": 5, "format": "pdb", "status": "PASS", ...} +``` + +### Use Crosslinking Restraints + +Use the `--crosslinks` option to provide a crosslink restraint file: + +```bash +python scripts/inference.py \ + --subunits scripts/example/example_xlinks/subunits.json \ + --pdbs scripts/example/example_xlinks/pdbs \ + --crosslinks scripts/example/example_xlinks/crosslinks.txt \ + --output output/crosslink_assembly +``` + +### Optional Prediction of Larger Subcomplexes + +After completing pairwise prediction, larger subcomplex FASTA files can be generated based on the pairwise results: + +```bash +python scripts/prepare_fastas.py \ + scripts/example/subunits.json \ + --stage groups \ + --output-fasta-folder output/group_fastas \ + --max-af-size 1800 \ + --input-pairs-results output/combfold_pdbs +``` + +### Training + +CombFold is an inference algorithm that performs combinatorial assembly using predictions generated by pretrained AlphaFold-Multimer models. + +CombFold itself does not contain a trainable neural network, training entry point, optimizer, or training-data pipeline. Therefore, this Hugging Face model package does not provide training commands. + +Retraining AlphaFold-Multimer is a separate upstream large-scale model-training task and is not part of the CombFold combinatorial assembly workflow. + +# OneScience Official Resources + +| Platform | OneScience Main Repository | Skills Repository | +| --- | --- | --- | +| Gitee | https://gitee.com/onescience-ai/onescience | https://gitee.com/onescience-ai/oneskills | +| GitHub | https://github.com/onescience-ai/OneScience | https://github.com/onescience-ai/oneskills | + +# Citation and License + +- Paper: [Assembly of protein complexes by combining AlphaFold and combinatorial optimization](https://www.nature.com/articles/s41592-024-02174-0) +- Official implementation: https://github.com/dina-lab3D/CombFold +- The CombFold source code is provided under the Apache License 2.0 according to the upstream repository. +- AlphaFold, AlphaFold-Multimer, ColabFold, pretrained parameters, and other third-party components are subject to their respective original copyright notices, model terms, and licenses. \ No newline at end of file diff --git a/conf/config.json b/conf/config.json new file mode 100644 index 0000000000000000000000000000000000000000..6717e1eac3834c47e348c982fd0c6c1669cb60bb --- /dev/null +++ b/conf/config.json @@ -0,0 +1,11 @@ +{ + "assembler_dir": "model/CombinatorialAssembler", + "weight_dir": "weight/alphafold/params", + "model_type": "alphafold2_multimer_v3", + "msa_mode": "single_sequence", + "num_models": 1, + "num_recycle": 1, + "num_relax": 0, + "max_results": 5, + "output_cif": false +} diff --git a/configuration.json b/configuration.json new file mode 100644 index 0000000000000000000000000000000000000000..2bcbc3b3d75501cefb6b0c5161130db93fc21599 --- /dev/null +++ b/configuration.json @@ -0,0 +1,14 @@ +{ + "model_name": "CombFold", + "framework": "C++/Python/JAX", + "task": "protein-complex-structure-prediction", + "entry_points": { + "inference": "scripts/inference.py", + "prepare_fastas": "scripts/prepare_fastas.py", + "assemble": "scripts/run_on_pdbs.py" + }, + "source_package": "model", + "config": "conf/config.json", + "weight_dir": "weight", + "license": "Apache-2.0" +} diff --git a/model/CombinatorialAssembler/AF2trans/AF2trans.cc b/model/CombinatorialAssembler/AF2trans/AF2trans.cc new file mode 100644 index 0000000000000000000000000000000000000000..7f860c80f1595614ada805767800df9a590a6611 --- /dev/null +++ b/model/CombinatorialAssembler/AF2trans/AF2trans.cc @@ -0,0 +1,134 @@ +#include "Atom.h" +#include "Match.h" +#include "Molecule.h" + +#include +#include + +#include +#include + +namespace po = boost::program_options; + +/* float calculateRMSD(Molecule& origMol, Molecule& transMol) { + float square_rmsd =0; + if((origMol.size() != transMol.size()) || (origMol.size() == 0)) { + std::cerr << "different molecules " << origMol.size() << " " << transMol.size()<< std::endl; + exit(1); + } + for(unsigned int i=0; i &mol1, Molecule &mol2, float percentile) { + std::vector temps1, temps2; + for (unsigned int i = 0; i < mol1.size(); i++) { + temps1.push_back(mol1[i].getTempFactor()); + temps2.push_back(mol2[i].getTempFactor()); + } + + std::sort(temps1.begin(), temps1.end()); + std::sort(temps2.begin(), temps2.end()); + int index1 = std::max((int)(percentile * mol1.size() - 1), 0); + int index2 = std::max((int)(percentile * mol1.size() - 1), 0); + return std::min(temps1[index1], temps2[index2]); +} + +Match calculateTrans(Molecule &origMol, Molecule &transMol) { + if ((origMol.size() != transMol.size()) || (origMol.size() == 0)) { + std::cerr << "different molecules " << origMol.size() << " " << transMol.size() << std::endl; + exit(1); + } + Match match; + float tempThreshold = std::min(80.0, getTempPercentile(origMol, transMol, 0.5)); + for (unsigned int i = 0; i < origMol.size(); i++) { + if (origMol[i].getTempFactor() < tempThreshold || transMol[i].getTempFactor() < tempThreshold) + continue; + match.add(i, i); + } + match.calculateBestFit(origMol, transMol); + // std::cout << "Got rmsd: " << match.rmsd() << std::endl; + return match; +} + +Molecule readMolecule(std::string molName, bool all_atoms) { + Molecule mol; + std::ifstream molFile(molName); + if (!molFile) { + std::cerr << "Can't open file " << molName << std::endl; + exit(0); + } + + if (!all_atoms) { + mol.readPDBfile(molFile, PDB::CAlphaSelector()); + // try nucleic acids + if (mol.size() == 0) { + molFile.clear(); + molFile.seekg(0, std::ios::beg); + mol.readPDBfile(molFile, PDB::PSelector()); + } + } + + // in case no CA were read, we use all atoms + if (all_atoms || mol.size() == 0) { + molFile.clear(); + molFile.seekg(0, std::ios::beg); + mol.readAllPDBfile(molFile); + } + molFile.close(); + + return mol; +} + +int main(int argc, char **argv) { + // output arguments + for (int i = 0; i < argc; i++) + std::cerr << argv[i] << " "; + std::cerr << std::endl; + + bool all_atoms = false; + po::options_description desc( + "Usage: AF2trans ...\n " + "translates AF2 complexes into transformations of the ligandRef onto the receptorRef\n"); + desc.add_options()("help", + "AF2mer2trans - produces ligand onto receptor docking like transformations from AF2 models\n")( + "input-files", po::value>(), "input files")("all,a", + "all atoms rmsd (default = false)"); + + po::positional_options_description p; + p.add("input-files", -1); + po::variables_map vm; + po::store(po::command_line_parser(argc, argv).options(desc).positional(p).run(), vm); + po::notify(vm); + + std::vector files; + if (vm.count("input-files")) { + files = vm["input-files"].as>(); + } + if (vm.count("help") || files.size() < 4) { + std::cout << desc << "\n"; + return 0; + } + if (vm.count("all_atoms")) { + all_atoms = true; + } + + Molecule receptorRef = readMolecule(files[0], all_atoms); + Molecule ligandRef = readMolecule(files[1], all_atoms); + + std::cout.precision(4); + for (unsigned int i = 2; i < files.size(); i += 2) { + Molecule receptorAF2 = readMolecule(files[i], all_atoms); + Molecule ligandAF2 = readMolecule(files[i + 1], all_atoms); + Match m1 = calculateTrans(receptorRef, receptorAF2); + Match m2 = calculateTrans(ligandRef, ligandAF2); + RigidTrans3 T = m1.rigidTrans() * (!m2.rigidTrans()); + std::cout << i / 2 << " | " << m1.rmsd() << "_" << files[i] << " | " << m2.rmsd() << "_" << files[i + 1] + << " | " << T << std::endl; + // std::cout << rms << std::endl; + } + + return 0; +} diff --git a/model/CombinatorialAssembler/BB.cc b/model/CombinatorialAssembler/BB.cc new file mode 100644 index 0000000000000000000000000000000000000000..de43ff910e916e86eaff9a19ccb8271da8d67aff --- /dev/null +++ b/model/CombinatorialAssembler/BB.cc @@ -0,0 +1,178 @@ +#include "BB.h" + +#include +#include + +BB::BB(int id, const std::string pdbFileName, int groupID, const ChemLib &lib, float gridResolution, float gridMargins, + float minTempFactor) + : id_(id), groupId_(groupID), pdbFileName_(pdbFileName) { + // read atoms + Common::readChemMolecule(pdbFileName_, allAtoms_, lib); + std::cout << "Done reading ChemMolecule " << allAtoms_.size() << std::endl; + // read backbone atoms + std::ifstream pdb2(pdbFileName_); + backBone_.readPDBfile(pdb2, PDB::BBSelector()); + pdb2.close(); + cm_ = backBone_.centroid(); + numOfAtoms_ = allAtoms_.size(); + + // read CA atoms + std::ifstream pdb3(pdbFileName_); + caAtoms_.readPDBfile(pdb3, PDB::CAlphaSelector()); + pdb3.close(); + + // compute ms surface + msSurface_ = get_connolly_surface(allAtoms_, 10, 1.8); + std::cout << "Surface size " << msSurface_.size() << std::endl; + + // compute grid + grid_ = new BBGrid(msSurface_, gridResolution, gridMargins, 1.5); + grid_->computeDistFromSurface(msSurface_); + grid_->markTheInside(allAtoms_); + grid_->markResidues(backBone_); + std::cout << "Done compute grid " << pdbFileName_ << std::endl; + + std::vector atomsMap; + atomsMap.push_back(&(*allAtoms_.begin())); + for (ChemMolecule::iterator i = allAtoms_.begin(); i != allAtoms_.end(); i++) { + atomsMap.push_back(&(*i)); + } + + computeFragments(minTempFactor); // get the endpoints + + for (Molecule::const_iterator it = caAtoms_.begin(); it != caAtoms_.end(); it++) { + resIndexToCAAtom[it->residueIndex()] = *it; + } + + maxRadius_ = 0.0; + for (Molecule::const_iterator it = caAtoms_.begin(); it != caAtoms_.end(); it++) { + float r = (it->position() - cm_).norm(); + if (r > maxRadius_) + maxRadius_ = r; + } + std::cout << "Max radius: " << maxRadius_ << std::endl; + + std::cout << " done reading BB " << pdbFileName_.c_str() << std::endl; +} + +void BB::computeFragments(float minTempFactor) { + // calculate endpoints + char currChain; + int firstResIndex, prevResIndex; + bool currChainSet = false; + for (auto i = allAtoms_.begin(); i != allAtoms_.end(); i++) { + // only CA atoms are considered + if (!i->isCA()) + continue; + if (i->getTempFactor() < minTempFactor) + continue; + char chain = i->chainId(); + int resIndex = i->residueIndex(); + // one more residue of the same chain - advance + // if over 20 residues diff - new fragment + if (currChainSet && currChain == chain && resIndex - prevResIndex <= 20) { + prevResIndex = resIndex; + } else { // new chain + if (currChainSet) { // save currChain + ResidueRange range(firstResIndex, prevResIndex); + fragmentEndpoints_.push_back(std::make_pair(currChain, range)); + } + // update + currChain = chain; + firstResIndex = prevResIndex = resIndex; + currChainSet = true; + } + } + // save last fragment + if (currChainSet) { // save currChain + ResidueRange range(firstResIndex, prevResIndex); + fragmentEndpoints_.push_back(std::make_pair(currChain, range)); + } + + for (int i = 0; i < (int)fragmentEndpoints_.size(); i++) { + std::cout << "Fragment " << i << " chainId " << fragmentEndpoints_[i].first << " range " + << fragmentEndpoints_[i].second.first << ":" << fragmentEndpoints_[i].second.second << std::endl; + } +} + +void BB::getChainConnectivityConstraints(const BB &otherBB, + std::vector>> &constraints) const { + for (int i = 0; i < (int)fragmentEndpoints_.size(); i++) { + char chainId1 = fragmentEndpoints_[i].first; + int resIndex1N = fragmentEndpoints_[i].second.first; + int resIndex1C = fragmentEndpoints_[i].second.second; + + for (int j = 0; j < (int)otherBB.fragmentEndpoints_.size(); j++) { + char chainId2 = otherBB.fragmentEndpoints_[j].first; + if (chainId1 != chainId2) + continue; + int resIndex2N = otherBB.fragmentEndpoints_[j].second.first; + int resIndex2C = otherBB.fragmentEndpoints_[j].second.second; + if (resIndex1N < resIndex2N) { // add constraint on 1C and 2N + constraints.push_back(std::make_pair(chainId1, std::make_pair(resIndex1C, resIndex2N))); + } else { // add constraint on 2C and 1N + constraints.push_back(std::make_pair(chainId1, std::make_pair(resIndex1N, resIndex2C))); + } + } + } +} + +bool BB::isPenetrating(const RigidTrans3 &trans, const BB &other, float threshold) const { + for (Surface::const_iterator it = other.surface_.begin(); it != other.surface_.end(); it++) { + float penetration = getDistFromSurface(trans * it->position()); + if (threshold > penetration) { + // cerr << "trans " << trans << " penetrates " << penetration << endl; + return true; + } + } + return false; +} + +float BB::maxPenetration(const RigidTrans3 &trans, const BB &other) const { + float max = 1000; + for (Surface::const_iterator it = other.surface_.begin(); it != other.surface_.end(); it++) { + float penetration = getDistFromSurface(trans * it->position()); + if (max > penetration) { + max = penetration; + } + } + return max; +} + +bool BB::isIdent(const BB &otherBB) const { + if (getNumOfAtoms() != otherBB.getNumOfAtoms()) { + std::cout << "different num of atoms" << std::endl; + return false; + } + for (unsigned int i = 0; i < allAtoms_.size(); i++) { + if (!(allAtoms_[i].position() - otherBB.allAtoms_[i].position()).isZero()) { + std::cout << "different atom position" << i << std::endl; + return false; + } + } + if (groupId_ != otherBB.groupId_) { + std::cout << "different group id" << std::endl; + return false; + } + std::cout << "checking transforms" << std::endl; + if (trans_.size() != otherBB.trans_.size()) + return false; + for (unsigned int i = 0; i < trans_.size(); i++) { + if (i == id_ || i == otherBB.id_) + continue; + + if (trans_[i].size() != otherBB.trans_[i].size()) { + std::cout << "different number of trans " << i << " " << trans_[i].size() + << " != " << otherBB.trans_[i].size() << std::endl; + return false; + } + + for (unsigned int j = 0; j < trans_[i].size(); j++) { + if (trans_[i][j]->score() != otherBB.trans_[i][j]->score()) { + std::cout << "different trans score" << i << std::endl; + return false; + } + } + } + return true; +} diff --git a/model/CombinatorialAssembler/BB.h b/model/CombinatorialAssembler/BB.h new file mode 100644 index 0000000000000000000000000000000000000000..7c2faee5b71fe442b07baa01832b574861495d6b --- /dev/null +++ b/model/CombinatorialAssembler/BB.h @@ -0,0 +1,106 @@ +#ifndef BB_H +#define BB_H + +#include "BBGrid.h" +#include "TransformationAndScore.h" +#include "BitId.h" + +#include +#include +#include +#include +#include + +#include + +class BBConstructor { + // idea taken from + // https://stackoverflow.com/questions/3220009/is-this-key-oriented-access-protection-pattern-a-known-idiom + private: + friend class BBContainer; + BBConstructor() {} +}; + +class BB { + public: + friend class SuperBB; + + BB(int id, const std::string pdbFilename, int groupID, const ChemLib &lib, float gridResolution, float gridMargins, + float minTempFactor); + + // access + int getID() const { return id_; } + BitId bitId() const { return BitId(id_); } + int groupId() const { return groupId_; } + std::string getPDBFileName() const { return pdbFileName_; } + + unsigned int getNumOfAtoms() const { return numOfAtoms_; } + const Vector3 &getCM() const { return cm_; } + const float getRadius() const { return maxRadius_; } + + const ChemAtom &getChemAtom(int atomIndex) const { return allAtoms_.getChemAtom(atomIndex); } + const ChemAtom &getChemAtomByIndex(int atomIndex) const { return allAtoms_[atomIndex]; } + const Atom &getAtomByResId(unsigned int resId) const { return resIndexToCAAtom.at(resId); } + + unsigned int getSurfaceSize() const { return surface_.size(); } + float getDistFromSurface(const Vector3 &v) const { return grid_->getDist(v); } + + + // This uses BBConstructor to make sure that only BBContainer can call this + void putTransWith(int bbIndex, const std::shared_ptr &t1, const BBConstructor &) const { + trans_[bbIndex].push_back(t1); + } + void initTrans(unsigned int numberOfBBs, const BBConstructor &) const { + trans_.insert(trans_.begin(), numberOfBBs, std::vector>()); + } + + // TODO: do we still need isPenetrating/maxPenetration ? + bool isPenetrating(const RigidTrans3 &trans, const BB &other, float threshold) const; + float maxPenetration(const RigidTrans3 &trans, const BB &other) const; + + void getChainConnectivityConstraints(const BB &bb, + std::vector>> &) const; // update + + const std::vector> &getTransformations(int bbIndex) const { + return trans_[bbIndex]; + } + + bool isIdent(const BB &otherBB) const; + + private: + // after BB is initialized, compute chains and fragment ranges + void computeFragments(float minTempFactor); + + private: + // surface points + // Different methods for computing collision + Surface surface_; // shuo + Surface msSurface_; // connolly - dense + + // BB id + unsigned int id_; + + // transformations to other BBs + // This is the edge in a graph - The result of a patch dock calculation + mutable std::vector>> trans_; + + int groupId_; + std::string pdbFileName_; + int numOfAtoms_; + + Vector3 cm_; + float maxRadius_; + + // chain id and residue numbers for each chain fragment in BB + typedef std::pair ResidueRange; + std::vector> fragmentEndpoints_; + + public: // TODO + BBGrid *grid_; + ChemMolecule backBone_; + ChemMolecule allAtoms_; + Molecule caAtoms_; + std::map resIndexToCAAtom; +}; + +#endif /* BB_H */ diff --git a/model/CombinatorialAssembler/BBContainer.cc b/model/CombinatorialAssembler/BBContainer.cc new file mode 100644 index 0000000000000000000000000000000000000000..ddf66c9f050308166e637155db678e0370f5c9a2 --- /dev/null +++ b/model/CombinatorialAssembler/BBContainer.cc @@ -0,0 +1,132 @@ +#include "BBContainer.h" +#include + +namespace { +std::string trim_extension(const std::string file_name) { + if (file_name[file_name.size() - 4] == '.') + return file_name.substr(0, file_name.size() - 4); + return file_name; +} +} // namespace + +BBContainer::BBContainer(const std::string SUFileName, std::string chemLibFileName, float minTempFactor) { + readSUFile(SUFileName); + + // prepare ChemLib + ChemLib chemLib(chemLibFileName); + + // read the building blocks + bbs_.reserve(numOfBBs_); + for (unsigned int i = 0; i < numOfBBs_; i++) { + bbs_.push_back(std::make_shared(i, pdbs_[i], groupIDs_[i], chemLib, 0.5, 5.0, minTempFactor)); + } +} + +void BBContainer::readTransformationFiles(std::string transFilePrefix, unsigned int transNumToRead) { + // init transformations vector + for (unsigned int i = 0; i < numOfBBs_; i++) { + bbs_[i]->initTrans(numOfBBs_, {}); + } + + // read the files + for (size_t i = 0; i < numOfBBs_; i++) { + for (size_t j = 0; j < numOfBBs_; j++) { + if (i == j) + continue; + std::string inFileName = transFilePrefix + trim_extension(pdbs_[i]) + "_plus_" + trim_extension(pdbs_[j]); + std::ifstream inS(inFileName); + std::cerr << "Opening transformation file " << inFileName << std::endl; + if (!inS) { + std::cerr << "Problem opening transformation file " << inFileName << std::endl; + continue; + } + + unsigned int counter = 0; + while (!inS.eof() && counter < transNumToRead) { + TransformationAndScore *t1 = new TransformationAndScore(); + if (readTrans(*t1, inS)) { + std::shared_ptr t2 = std::make_shared(*t1); + t2->refFrame_ = !t2->refFrame_; + bbs_[i]->putTransWith(j, std::make_shared(*t1), {}); + bbs_[j]->putTransWith(i, t2, {}); + counter++; + } + } + std::cerr << counter << " transforms were read from file " << inFileName << std::endl; + inS.close(); + } + } +} + +int BBContainer::readSUFile(const std::string SUFileName) { + numOfBBs_ = 0; + std::ifstream SUFile(SUFileName); + if (!SUFile) { + std::cerr << "Can't open SU file" << SUFileName << std::endl; + exit(1); + } + while (!SUFile.eof()) { + std::string line; + getline(SUFile, line); + boost::trim(line); + if (line.length() > 0) { + std::vector split_results; + boost::split(split_results, line, boost::is_any_of(" "), boost::token_compress_on); + std::string pdbName(split_results[0]); + std::cout << "PDBname " << pdbName << ":" << line << std::endl; + numOfBBs_++; + pdbs_.push_back(pdbName); + // group assignment + int groupID = 0; + if (split_results.size() == 2) { + groupID = stoi(split_results[1]); + if (groupID <= 0) { + std::cerr << "Group ID must be positive" << std::endl; + } + } + groupIDs_.push_back(groupID); + } + } + return numOfBBs_; +} + +bool BBContainer::readTrans(TransformationAndScore &trans, std::ifstream &transFile) { + std::string line; + if (!transFile.eof()) { + getline(transFile, line); + boost::trim(line); // remove all spaces + // skip comments + if (line[0] == '#' || line[0] == '\0') + return false; + + std::vector split_results; + boost::split(split_results, line, boost::is_any_of(":|\t"), boost::token_compress_on); + if (split_results.size() != 4) { + std::cerr << "Wrong number of fields in transformation file, should be: " + << "index(int) | score(float) | comment | transformation(space seperated 6 floats)" << std::endl; + return false; + } + + boost::trim(split_results[3]); + std::vector splitted_transformation; + boost::split(splitted_transformation, split_results[3], boost::is_any_of(" "), boost::token_compress_on); + + if (splitted_transformation.size() != 6) + return false; + + // extract trans + RigidTrans3 tr(Vector3(std::stof(splitted_transformation[0].c_str()), + std::stof(splitted_transformation[1].c_str()), + std::stof(splitted_transformation[2].c_str())), + Vector3(std::stof(splitted_transformation[3].c_str()), + std::stof(splitted_transformation[4].c_str()), + std::stof(splitted_transformation[5].c_str()))); + trans.refFrame_ = tr; + trans.score_.totalScore_ = 1.0; + trans.dist_ = 1.0; + + trans.score_.totalScore_ = std::stof(split_results[1].c_str()); + std::cout << "loaded score: " << trans.score_.totalScore_ << std::endl; + } + return !transFile.eof(); +} diff --git a/model/CombinatorialAssembler/BBContainer.h b/model/CombinatorialAssembler/BBContainer.h new file mode 100644 index 0000000000000000000000000000000000000000..2f0776773a9444e40641097723e2ebe9fba4b696 --- /dev/null +++ b/model/CombinatorialAssembler/BBContainer.h @@ -0,0 +1,43 @@ +// +// Created by dina on 8/1/18. +// + +#ifndef BB_CONTAINER_H +#define BB_CONTAINER_H + +#include "BB.h" +#include + +class BBContainer { + public: + // Constructor + BBContainer(std::string SUFileName, std::string chemLibFileName, float minTempFactor); + + // Group: access + std::shared_ptr getBB(unsigned int bbIndex) const { return bbs_[bbIndex]; } + const std::vector> &getBBs() const { return bbs_; } + unsigned int getBBsNumber() const { return numOfBBs_; } + + void readTransformationFiles(std::string transFilePrefix, unsigned int transNumToRead); + + private: + int readSUFile(const std::string SUFileName); + bool readTrans(TransformationAndScore &trans, std::ifstream &transFile); + + private: + // PDB filenames + std::vector pdbs_; + + // BBs + std::vector> bbs_; + + // total bbs num + unsigned int numOfBBs_; + + // BBs can be grouped according to a number provided by the user in SUlist + // BBs in the same group will be assembled first + // if no number is given, all the BBs are considered a group + std::vector groupIDs_; +}; + +#endif // BBCONTAINER_H diff --git a/model/CombinatorialAssembler/BBGrid.cc b/model/CombinatorialAssembler/BBGrid.cc new file mode 100644 index 0000000000000000000000000000000000000000..8fba83166f095b2335a73080b6e1b55b2474f166 --- /dev/null +++ b/model/CombinatorialAssembler/BBGrid.cc @@ -0,0 +1,51 @@ +#include "BBGrid.h" + +void BBGrid::markResidues(const ChemMolecule &M) { + std::vector weights; + weights.insert(weights.end(), maxEntry, 0.0); + for (Molecule::const_iterator it = M.begin(); it != M.end(); it++) { + float atomRadius = it->getRadius() + radiusAdition_; + float radius = atomRadius * 2; // may be +1 is enouph + // cout << " atomRadius " << atomRadius << endl; + int centerIndex = getIndexForPoint(it->position()); + if (!isValidIndex(centerIndex)) { + std::cerr << "Error: Point out of grid" << std::endl; + exit(1); + } + + int intRadius = getIntGridRadius(radius); + int radius2 = intRadius * intRadius; + + int i_bound, j_bound, k_bound; + i_bound = intRadius; + for (int i = -i_bound; i <= i_bound; i++) { + j_bound = (int)sqrt(radius2 - i * i); + for (int j = -j_bound; j <= j_bound; j++) { + k_bound = (int)sqrt(radius2 - i * i - j * j); + for (int k = -k_bound; k <= k_bound; k++) { + int index = centerIndex + i + xGridNum * j + xyGridNum * k; + if (isValidIndex(index) && grid[index] <= 0) { + Vector3 point = getPointForIndex(index); + float dist = point.dist(it->position()); + if (dist == 0.0) { + if (it->isBackbone()) + residues[index] = it->residueIndex() * -1; + else + residues[index] = it->residueIndex(); + continue; + } + float weight = atomRadius / dist; + if (weight <= weights[index]) + continue; + weights[index] = weight; + if (it->isBackbone()) + residues[index] = it->residueIndex() * -1; + else + residues[index] = it->residueIndex(); + } + } + } + } + } + weights.clear(); +} diff --git a/model/CombinatorialAssembler/BBGrid.h b/model/CombinatorialAssembler/BBGrid.h new file mode 100644 index 0000000000000000000000000000000000000000..fc29a66ea65917a2e1db8f208114cce5143207e9 --- /dev/null +++ b/model/CombinatorialAssembler/BBGrid.h @@ -0,0 +1,25 @@ +/** + * \file BBGrid.h + * \brief + * + * \authors Dina Schneidman + * + * + */ +#ifndef BBGRID_H +#define BBGRID_H + +#include +#include + +class BBGrid : public ResidueGrid { + public: + BBGrid(const Surface &surface, const float inDelta, const float maxRadius, float radiusAdition) + : ResidueGrid(surface, inDelta, maxRadius), radiusAdition_(radiusAdition){}; + void markResidues(const ChemMolecule &M); + + private: + float radiusAdition_; +}; + +#endif /* BBGRID_H */ diff --git a/model/CombinatorialAssembler/BestK.cc b/model/CombinatorialAssembler/BestK.cc new file mode 100644 index 0000000000000000000000000000000000000000..3e67e63a586879676af977478b539fbb176a4dbe --- /dev/null +++ b/model/CombinatorialAssembler/BestK.cc @@ -0,0 +1,82 @@ +#include "BestK.h" + + +bool BestK::push(std::shared_ptr in) { + std::lock_guard locker(_mu); + if (size() < k_) { + insert(in); + return true; + } else { + + if (internalMinScore() < score(in)) { + erase(begin()); + insert(in); + curMinScore = score(*begin()); + return true; + } + } + + return false; +} + +bool BestK::push_cluster(std::shared_ptr in, double rmsd, std::vector> &identGroups) { + std::lock_guard locker(_mu); + + if (internalMinScore() > score(in)) + return false; + + for (auto it = begin(); it != end(); it++) { + if (score(in) <= score(*it) && (*it)->calcRmsd(*in, identGroups) < rmsd) + return false; + } + + for (auto it = begin(); it != end();) { + if (score(in) > score(*it) && (*it)->calcRmsd(*in, identGroups) < rmsd) { + erase(it++); + } else { + ++it; + } + } + + if (size() >= k_) { + erase(begin()); + } + + insert(in); + curMinScore = score(*begin()); + return true; +} + +void BestK::cluster(BestK &clusteredBest, double rmsd, std::vector> &identGroups) const { + if (size() == 0) + return; + const std::shared_ptr firstSBB = *rbegin(); + if (firstSBB->size() == 2) { // don't cluster + for (auto it = rbegin(); it != rend(); it++) { + clusteredBest.insert(*it); + } + } else { + std::vector clustered(size(), false); + int i = 0; + + for (auto it = rbegin(); it != rend(); it++, i++) { + const std::shared_ptr refSBB = *it; + // std::cout << "clustering " << i << " clsutered:" << clustered[i] << std::endl; + if (!clustered[i]) { + clustered[i] = true; + clusteredBest.insert(refSBB); + } + + // TODO: maybe shouldn't cluster more if already clustered (can lead to drift) + + // cluster to other SBBs + int j = 0; + // for(auto it2 = begin(); it2!= end(); it2++, j++) { + for (auto it2 = rbegin(); it2 != rend(); it2++, j++) { + if (!clustered[j] && (*it2)->calcRmsd(*refSBB, identGroups) < rmsd) { + clustered[j] = true; + } + } + } + } +} \ No newline at end of file diff --git a/model/CombinatorialAssembler/BestK.h b/model/CombinatorialAssembler/BestK.h new file mode 100644 index 0000000000000000000000000000000000000000..32af012be74aaadf9f655f1e69b590e2c2f27208 --- /dev/null +++ b/model/CombinatorialAssembler/BestK.h @@ -0,0 +1,63 @@ +#ifndef BESTK_H +#define BESTK_H + +#include "SuperBB.h" +#include + +static float scoreSuperBB(const std::shared_ptr &sbb) { + // return sbb->getRestraintsRatio(); + // return sbb->weightedTransScore_; + + // Note: if you change this, you also needs to change in HierarchicalFold::tryToConnect which optimizes by + // summing and comparing trans scores before trying to connect + // return sbb->transScore_; + return sbb->transScore_ * sbb->getRestraintsRatio(); +} +struct comp { + bool operator()(const std::shared_ptr &lhs, const std::shared_ptr &rhs) const { + return scoreSuperBB(lhs) < scoreSuperBB(rhs); + } +}; + +/** + This class stores the best k permutations of a specific size + implemented as inheriting from a multiset with shapred_ptr and + the comp struct +*/ +class BestK : public std::multiset, comp> { + + public: + BestK(unsigned int k, bool toDel = true) : k_(k), curMinScore(-1) {} + + float score(const std::shared_ptr &sbb) const { return scoreSuperBB(sbb); } + + float minScore() const { return curMinScore; } + float maxScore() const { + if (size() == 0) + return 0; + return score(*rbegin()); + } + + void setK(int k) { k_ = k; } + + bool push(std::shared_ptr in); + + bool push_cluster(std::shared_ptr in, double rmsd, std::vector> &identGroups); + + void cluster(BestK &clusteredBest, double rmsd, std::vector> &identGroups) const; + virtual ~BestK() {} + + private: + float internalMinScore() { + if (size() < k_) + return -1; + return score(*begin()); + } + + private: + std::mutex _mu; + unsigned int k_; + float curMinScore; +}; + +#endif /* BESTK_H */ diff --git a/model/CombinatorialAssembler/BestKContainer.h b/model/CombinatorialAssembler/BestKContainer.h new file mode 100644 index 0000000000000000000000000000000000000000..d62cff2cc8e471b65cd0ed090ee6fd4876bd16b8 --- /dev/null +++ b/model/CombinatorialAssembler/BestKContainer.h @@ -0,0 +1,50 @@ +/** + * \file BestKContainer.h + * \brief + * + * \authors Dina Schneidman + * + */ +#ifndef BESTKCONTAINER_H +#define BESTKCONTAINER_H + +#include +#include "BestK.h" + +class BestKContainer : private std::vector { + public: + // N - subunit number + BestKContainer(int K) : K_(K) {} + + ~BestKContainer() { + for (auto it = begin(); it != end(); it++) + delete *it; + } + + BestK *newBestK(BitId set) { + BestK *best = new BestK(K_); + push_back(best); + set2index_[set] = size() - 1; + return best; + } + + bool isEmpty(const BitId set) const { return (set2index_.find(set) == set2index_.end()); } + + // assumes BestK for set exists, can be checked with isEmpty + const BestK &operator[](const BitId set) const { + unsigned int index = set2index_.find(set)->second; + return *((std::vector)(*this))[index]; + } + + // assumes BestK for set exists + BestK &operator[](const BitId set) { return *((std::vector)(*this))[set2index_[set]]; } + + private: + int K_; + // The long is a unique representation key for each SBB + // And it is the long type because we want to support more than 32 subunits + // int corresponds to an index in the vector that holds the BestK + std::unordered_map set2index_; +}; + +#endif /* BESTKCONTAINER_H */ diff --git a/model/CombinatorialAssembler/BitId.h b/model/CombinatorialAssembler/BitId.h new file mode 100644 index 0000000000000000000000000000000000000000..dcf54296916fd140bde491c8fc6f8959ecc4d386 --- /dev/null +++ b/model/CombinatorialAssembler/BitId.h @@ -0,0 +1,48 @@ +#ifndef BITID_H +#define BITID_H + +#include +#include + + +template +class CustomBitset : public std::bitset { +public: + CustomBitset() : std::bitset() {} + + CustomBitset(const std::bitset& b) : std::bitset(b) {} + + // assert val < n and then set the val bit, this is different from std::bitset + CustomBitset(unsigned long long val) : std::bitset(0) { + assert(val < N); + this->set(val); + } + + // don't print leading zeros + std::string to_string() const { + std::string str = std::bitset::to_string(); + size_t first_one = str.find('1'); + return first_one == std::string::npos ? "0" : str.substr(first_one); + } +}; + +template +std::ostream& operator<<(std::ostream& os, const CustomBitset& b) { + os << b.to_string(); + return os; +} + + +namespace std { + template + struct hash> { + size_t operator()(const CustomBitset& b) const { + return std::hash()(b.to_string()); + } + }; +} + + +typedef CustomBitset<128> BitId; + +#endif /* BITID_H */ \ No newline at end of file diff --git a/model/CombinatorialAssembler/ComplexDistanceConstraint.cc b/model/CombinatorialAssembler/ComplexDistanceConstraint.cc new file mode 100644 index 0000000000000000000000000000000000000000..60aa2ae4a5e7811627596da9b092fa3d01c02d35 --- /dev/null +++ b/model/CombinatorialAssembler/ComplexDistanceConstraint.cc @@ -0,0 +1,189 @@ +#include "ComplexDistanceConstraint.h" + +#include "BB.h" + +void ComplexDistanceConstraint::addConstraint(int suInd1, int suInd2, Vector3 receptorAtom, Vector3 ligandAtom, + float maxDistance, float minDistance) { + int ind = suInd1 * noOfSUs_ + suInd2; + constraints_[ind].push_back(DistanceRestraint(receptorAtom, ligandAtom, maxDistance, minDistance)); +} + +bool ComplexDistanceConstraint::areConstraintsSatisfied(int suInd1, int suInd2, RigidTrans3 &trans2) const { + int ind = suInd1 * noOfSUs_ + suInd2; + for (unsigned int index = 0; index < constraints_[ind].size(); index++) { + bool sat = constraints_[ind][index].isSatisfied(trans2); + if (!sat) { + return false; + } + } + return true; +} + +int ComplexDistanceConstraint::addChainConnectivityConstraints() { + for (int suInd1 = 0; suInd1 < noOfSUs_; suInd1++) { + for (int suInd2 = suInd1 + 1; suInd2 < noOfSUs_; suInd2++) { + std::vector>> constraints; + int counter = 0; + float epsilon = 20.0; // for shorter connections + bbs_[suInd1]->getChainConnectivityConstraints(*bbs_[suInd2], constraints); + for (int i = 0; i < (int)constraints.size(); i++) { + int su1resIndex = constraints[i].second.first; + int su2resIndex = constraints[i].second.second; + + int receptorAtomIndex = bbs_[suInd1]->allAtoms_.getFirstAtomEntryForResIndex( + constraints[i].first, std::to_string(su1resIndex)); + int ligandAtomIndex = bbs_[suInd2]->allAtoms_.getFirstAtomEntryForResIndex(constraints[i].first, + std::to_string(su2resIndex)); + + Vector3 receptorAtom = bbs_[suInd1]->getChemAtomByIndex(receptorAtomIndex).position(); + Vector3 ligandAtom = bbs_[suInd2]->getChemAtomByIndex(ligandAtomIndex).position(); + + int sequenceDist = std::fabs(su1resIndex - su2resIndex); + if (sequenceDist < 100) { + float maxDist = sequenceDist * 2.0; + if (sequenceDist <= 5) + maxDist += epsilon; + addConstraint(suInd1, suInd2, receptorAtom, ligandAtom, maxDist); + addConstraint(suInd2, suInd1, ligandAtom, receptorAtom, maxDist); + std::cerr << "DISTANCE CONSTRAINT ADDED: " << constraints[i].first << " su1 endpoint " + << su1resIndex << " su2 endpoint " << su2resIndex << " " << maxDist << std::endl; + counter++; + } + } + numberOfConstraints_ += counter; + } + } + return numberOfConstraints_; +} + +std::set ComplexDistanceConstraint::getSUs(const std::string residueSequenceID, const std::string chains, + int maxOffset) const { + + int rOffset = 0; + std::set ret; + // iterate chains + for (unsigned int chainIndex = 0; chainIndex < chains.size(); chainIndex++) { + // for each chain find corresponding SU + for (int suIndex = 0; suIndex < noOfSUs_; suIndex++) { + int atomIndex = bbs_[suIndex]->allAtoms_.getClosestAtomEntryForResIndex( + chains[chainIndex], residueSequenceID, maxOffset, rOffset); + if (atomIndex != -1) { // found + ret.insert(suIndex); + continue; + } + } + } + return ret; +} + +int ComplexDistanceConstraint::readRestraintsFile(const std::string fileName) { + // read cross links + std::vector crosslinks; + int xnum = readCrossLinkFile(fileName, crosslinks); + Logger::infoMessage() << "# of xlinks " << xnum << " read from file" << fileName << std::endl; + int MAX_OFFSET = 20; + + crosslinkIndToWeight_ = std::vector(crosslinks.size()); + + // map them to BB pairs + for (unsigned int i = 0; i < crosslinks.size(); i++) { + crosslinkIndToWeight_[i] = crosslinks[i].getWeight(); + + // SUs that have xlinks endpoints (sus1, sus2) + std::string residueSequenceID1 = std::to_string(crosslinks[i].getResidue1()); + std::set sus1 = getSUs(residueSequenceID1, crosslinks[i].getChain1()); + if (sus1.size() == 0) + sus1 = getSUs(residueSequenceID1, crosslinks[i].getChain1(), MAX_OFFSET); + std::string residueSequenceID2 = std::to_string(crosslinks[i].getResidue2()); + std::set sus2 = getSUs(residueSequenceID2, crosslinks[i].getChain2()); + if (sus2.size() == 0) + sus2 = getSUs(residueSequenceID2, crosslinks[i].getChain2(), MAX_OFFSET); + + // iterate over all pairs of SUs + for (auto suIndexIter1 = sus1.begin(); suIndexIter1 != sus1.end(); suIndexIter1++) { + for (unsigned int chainIndex1 = 0; chainIndex1 < crosslinks[i].getChain1().size(); chainIndex1++) { + char chainId1 = crosslinks[i].getChain1()[chainIndex1]; + int rOffset = 0; + int receptorAtomIndex = bbs_[*suIndexIter1]->allAtoms_.getClosestAtomEntryForResIndex( + chainId1, residueSequenceID1, MAX_OFFSET, rOffset); + if (rOffset < 0) + rOffset = -1 * rOffset; + if (receptorAtomIndex == -1) + continue; + Vector3 rcoord = bbs_[*suIndexIter1]->getChemAtomByIndex(receptorAtomIndex).position(); + + for (auto suIndexIter2 = sus2.begin(); suIndexIter2 != sus2.end(); suIndexIter2++) { + for (unsigned int chainIndex2 = 0; chainIndex2 < crosslinks[i].getChain2().size(); chainIndex2++) { + char chainId2 = crosslinks[i].getChain2()[chainIndex2]; + int lOffset = 0; + int ligandAtomIndex = bbs_[*suIndexIter2]->allAtoms_.getClosestAtomEntryForResIndex( + chainId2, residueSequenceID2, MAX_OFFSET, lOffset); + if (ligandAtomIndex == -1) + continue; + if (lOffset < 0) + lOffset = -1 * lOffset; + + Vector3 lcoord = bbs_[*suIndexIter2]->getChemAtomByIndex(ligandAtomIndex).position(); + + float maxDistance = crosslinks[i].getMaxDistance() + (rOffset + lOffset) * 3.0; + float minDistance = crosslinks[i].getMinDistance(); + + int ind1 = *suIndexIter1 * noOfSUs_ + *suIndexIter2; + DistanceRestraint d1(rcoord, lcoord, maxDistance, minDistance, crosslinks[i].getWeight()); + restraintIndsToCrosslinkInds_[ind1].push_back(i); + restraints_[ind1].push_back(d1); + + int ind2 = *suIndexIter2 * noOfSUs_ + *suIndexIter1; + DistanceRestraint d2(lcoord, rcoord, maxDistance, minDistance, crosslinks[i].getWeight()); + restraintIndsToCrosslinkInds_[ind2].push_back(i); + restraints_[ind2].push_back(d2); + + std::cout << " adding restraint to " << *suIndexIter1 << "x" << *suIndexIter2 << " :" + << residueSequenceID1 << chainId1 << " : " << residueSequenceID2 << chainId2 + << " dist " << maxDistance << " " << minDistance << " indexes " << ind1 << " : " + << ind2 << std::endl; + } + } + } + } + } + return crosslinks.size(); +} + +float ComplexDistanceConstraint::getRestraintsRatio(const std::vector> &bbs, + const std::vector &trans) const { + std::set totalSeenCrosslinks; + std::set satisfiedCrosslinks; + for (int suInd1 = 0; suInd1 < (int)bbs.size(); suInd1++) { + for (int suInd2 = 0; suInd2 < (int)bbs.size(); suInd2++) { + int index1 = bbs[suInd1]->getID(); + int index2 = bbs[suInd2]->getID(); + if (index1 == index2) + continue; + int suPairIndex = index1 * noOfSUs_ + index2; + for (unsigned int restraintIndex = 0; restraintIndex < restraints_[suPairIndex].size(); restraintIndex++) { + unsigned int crosslinkIndex = restraintIndsToCrosslinkInds_[suPairIndex][restraintIndex]; + if (satisfiedCrosslinks.find(crosslinkIndex) != satisfiedCrosslinks.end()) { + continue; + } + totalSeenCrosslinks.insert(crosslinkIndex); + if (restraints_[suPairIndex][restraintIndex].isSatisfied(trans[suInd1], trans[suInd2])) + satisfiedCrosslinks.insert(crosslinkIndex); + } + } + } + if (totalSeenCrosslinks.size() == 0) + return 1.0; + + float satisfiedWeight = 0.0; + float totalWeight = 0.0; + for (auto it = satisfiedCrosslinks.begin(); it != satisfiedCrosslinks.end(); it++) { + satisfiedWeight += crosslinkIndToWeight_[*it]; + } + for (auto it = totalSeenCrosslinks.begin(); it != totalSeenCrosslinks.end(); it++) { + totalWeight += crosslinkIndToWeight_[*it]; + } + + return satisfiedWeight / totalWeight; + // return (float)satisfiedCrosslinks.size() / (float)totalSeenCrosslinks.size(); +} \ No newline at end of file diff --git a/model/CombinatorialAssembler/ComplexDistanceConstraint.h b/model/CombinatorialAssembler/ComplexDistanceConstraint.h new file mode 100644 index 0000000000000000000000000000000000000000..544540675c48bc7161238924713eb7a21fbb7a8f --- /dev/null +++ b/model/CombinatorialAssembler/ComplexDistanceConstraint.h @@ -0,0 +1,63 @@ +#ifndef DistanceConstraint_H +#define DistanceConstraint_H + +#include +#include +#include +#include + +#include +// #include +#include +#include + +#include "BB.h" + +class ComplexDistanceConstraint { + public: + ComplexDistanceConstraint(const std::vector> &bbs) + : bbs_(bbs), noOfSUs_(bbs.size()), constraints_(noOfSUs_ * noOfSUs_), restraints_(noOfSUs_ * noOfSUs_), + restraintIndsToCrosslinkInds_(noOfSUs_ * noOfSUs_) {} + + int readRestraintsFile(const std::string fileName); + int addChainConnectivityConstraints(); + + int numberOfRestraints(int suInd1, int suInd2) const { + int ind = suInd1 * noOfSUs_ + suInd2; + return restraints_[ind].size(); + } + + int numberOfConstraints(int suInd1, int suInd2) const { + int ind = suInd1 * noOfSUs_ + suInd2; + return constraints_[ind].size(); + } + + // constraints satisfaction: every constraint needs to be satisfied + bool areConstraintsSatisfied(int suInd1, int suInd2, RigidTrans3 &trans2) const; + + // restraints satisfaction: a predefined ratio needs to be satisfied + float getRestraintsRatio(const std::vector> &bbs, + const std::vector &trans) const; + + private: + void addConstraint(int suInd1, int suInd2, Vector3 receptorAtom, Vector3 ligandAtom, float maxDistance, + float minDistance = 0); + + void addRestraint(int suInd1, int suInd2, Vector3 receptorAtom, Vector3 ligandAtom, float maxDistance, + float minDistance = 0); + + // find all SUs with residueSequenceID and chains, maxoffset is +/- few residues + std::set getSUs(const std::string residueSequenceID, const std::string chains, int maxoffset = 0) const; + + private: + const std::vector> &bbs_; + int noOfSUs_; + std::vector> constraints_; + std::vector> restraints_; + int numberOfConstraints_ = 0; + int numberOfRestraints_ = 0; + std::vector> restraintIndsToCrosslinkInds_; + std::vector crosslinkIndToWeight_; +}; + +#endif diff --git a/model/CombinatorialAssembler/DOCK.conf b/model/CombinatorialAssembler/DOCK.conf new file mode 100644 index 0000000000000000000000000000000000000000..182db0e0bb7d51a21f900eaa8fba783aaa363e3e --- /dev/null +++ b/model/CombinatorialAssembler/DOCK.conf @@ -0,0 +1,21 @@ +#Model +MODEL_BUCKET_SIZE 0.5 +MODEL_QUERY_RADIUS 1.5 +HINGE_BUCKET_SIZE 0.5 +HINGE_QUERY_RADIUS 6.0 +TRIANGLE_SIZE_DIST 2.5 +TRIANGLE_SIZE_DIST_WH 4.0 +PROBABILITY 0.05 +PROBABILITY_WH 0.2 +CLUSTER_RADIUS 4.0 +CLUSTER_RADIUS_WH 1.0 +THRESHOLD 300 +THRESHOLD_WH -100 +GRID_RESOLUTION 0.5 +GRID_MARGINS 5.0 +#TriangleIterator +TRIANGLE_MAX_SEG_SIZE 12.0 +TRIANGLE_MIN_SEG_SIZE 2.0 +TRIANGLE_SEG_RATIO 4.0 +#Cluster +CLUSTER_RADIUS_RATIO 3.0 diff --git a/model/CombinatorialAssembler/FoldStep.h b/model/CombinatorialAssembler/FoldStep.h new file mode 100644 index 0000000000000000000000000000000000000000..6ba4a5ab224240ac834e1fef8dcdc92c716da548 --- /dev/null +++ b/model/CombinatorialAssembler/FoldStep.h @@ -0,0 +1,34 @@ +#ifndef FOLDSTEP_H +#define FOLDSTEP_H + +#include + +class FoldStep { + public: + // Constructors + FoldStep(unsigned int i, unsigned int j, RigidTrans3 trans) : i_(i), j_(j), trans_(trans) {} + FoldStep(unsigned int i, unsigned int j) : i_(i), j_(j) {} + FoldStep(unsigned int i, unsigned int j, float score) : i_(i), j_(j), tScore_(score) {} + + // bool operator < (const FoldStep& s2) const { return ((i_ < s2.i_) || (i_ == s2.i_ && j_ < s2.j_)); } + + friend bool operator<(const FoldStep &s1, const FoldStep &s2) { + unsigned int max1 = std::max(s1.i_, s1.j_); + unsigned int max2 = std::max(s2.i_, s2.j_); + return (max1 < max2 || (max1 == max2 && std::min(s1.i_, s1.j_) < std::min(s2.i_, s2.j_))); + } + + static void outputFoldSteps(std::ostream &out, const std::vector &foldSteps) { + out << "foldSteps:"; + for (unsigned int i = 0; i < foldSteps.size(); i++) { + out << " (" << foldSteps[i].i_ << ", " << foldSteps[i].j_ << ")-" << foldSteps[i].tScore_; + } + } + + public: + unsigned int i_, j_; + float tScore_; + RigidTrans3 trans_; +}; + +#endif /* FOLDSTEP_H */ diff --git a/model/CombinatorialAssembler/HierarchicalFold.cc b/model/CombinatorialAssembler/HierarchicalFold.cc new file mode 100644 index 0000000000000000000000000000000000000000..7af6cf6e82ece115d17ad529e76b52372244276e --- /dev/null +++ b/model/CombinatorialAssembler/HierarchicalFold.cc @@ -0,0 +1,730 @@ +#include "HierarchicalFold.h" + +#include +#include + +Timer HierarchicalFold::timer_; +Timer HierarchicalFold::timerAll_; +unsigned int HierarchicalFold::countResults_(0); + +std::vector> createIdentGroups(unsigned int N_, BestKContainer &bestKContainer_) { + std::vector addedToGroup(N_, false); + std::vector> identGroups; + for (unsigned int i = 0; i < N_; i++) { + std::shared_ptr sbbI = *(bestKContainer_[BitId(i)].begin()); + const std::shared_ptr bbI = sbbI->bbs_[0]; + + if (addedToGroup[i]) + continue; + addedToGroup[i] = true; + std::vector identical; + + for (unsigned int j = i + 1; j < N_; j++) { + std::cout << "checking ident " << i << " & " << j << std::endl; + std::shared_ptr sbbJ = *(bestKContainer_[BitId(j)].begin()); + + const std::shared_ptr bbJ = sbbJ->bbs_[0]; + + if (bbI->isIdent(*bbJ)) { + addedToGroup[j] = true; + std::cout << "found ident " << i << " " << j << std::endl; + identical.push_back(j); + } + } + if (identical.size() > 0) { + identical.insert(identical.begin(), i); + identGroups.push_back(identical); + } + } + std::cout << "---- ident groups " << std::endl; + for (std::vector identGroup : identGroups) { + std::cout << "ident group: "; + for (unsigned int i : identGroup) { + std::cout << i << " "; + } + std::cout << std::endl; + } + + return identGroups; +} + +std::map> createAssemblyGroupsMap(unsigned int N_, + BestKContainer &bestKContainer_) { + std::map> assemblyGroupsMap; + for (unsigned int i = 0; i < N_; i++) { + std::shared_ptr sbbI = *(bestKContainer_[BitId(i)].begin()); + const std::shared_ptr bbI = sbbI->bbs_[0]; + if (assemblyGroupsMap.count(bbI->groupId()) == 0) { + std::vector newGroup; + assemblyGroupsMap[bbI->groupId()] = newGroup; + } + assemblyGroupsMap[bbI->groupId()].push_back(i); + } + std::cout << "---- assembly groups " << std::endl; + // std::vector> assemblyGroups; + for (const auto &[groupId, groupBBIds] : assemblyGroupsMap) { + std::cout << "assembly group " << groupId << ":"; + for (unsigned int i : groupBBIds) { + std::cout << i << " "; + } + std::cout << std::endl; + // assemblyGroups.push_back(groupBBIds); + } + return assemblyGroupsMap; +} + +void printBestK(unsigned int N_, BestK *bestK) { + // just for print + std::map count_new_kept_by_bb; + std::unordered_map count_new_kept_by_resSet; + for (unsigned int bit_index = 0; bit_index < N_; bit_index++) + count_new_kept_by_bb[bit_index] = 0; + for (auto it1 = bestK->begin(); it1 != bestK->end(); it1++) { + if (count_new_kept_by_resSet.count((**it1).bitIds()) == 0) + count_new_kept_by_resSet[(**it1).bitIds()] = 0; + count_new_kept_by_resSet[(**it1).bitIds()] += 1; + for (unsigned int bit_index = 0; bit_index < N_; bit_index++) + if ((**it1).bitIds().test(bit_index)) { + count_new_kept_by_bb[bit_index] += 1; + } + } + + std::cout << "scores of saved " << bestK->minScore() << ":" << bestK->maxScore() << std::endl; + std::cout << "new kept results by chain "; + for (const auto &elem : count_new_kept_by_bb) + std::cout << elem.first << ":" << elem.second << ", "; + std::cout << std::endl << "new kept results by resSet "; + for (const auto &elem : count_new_kept_by_resSet) + std::cout << elem.first << ":" << elem.second << ", "; + std::cout << std::endl; +} + +bool isValidBasedOnAssembly(std::map> assemblyGroupsMap, + BitId currResSet) { + if (assemblyGroupsMap.size() <= 1) + return true; + + bool seenPartialGroup = false; + int numberOfGroups = 0; + + bool seenGroupZero = false; + + for (const auto &[groupId, assemblyGroup] : assemblyGroupsMap) { + bool seenZero = false; + bool seenOne = false; + + for (unsigned int i = 0; i < assemblyGroup.size(); i++) { + if (currResSet.test(assemblyGroup[i])) + seenOne = true; + else + seenZero = true; + } + if (groupId == 0) { + // assembly group 0 is special, this group should not be assembled before joining others + // so we never treat it as partial, but it can't be with another partial group + if (seenOne) + seenGroupZero = true; + } else { + if (seenOne) + numberOfGroups++; + + if (seenZero && seenOne) { + seenPartialGroup = true; + } + } + } + if (numberOfGroups >= 1 && seenPartialGroup && seenGroupZero) + return false; + + + if (numberOfGroups > 1 && seenPartialGroup) + return false; + + return true; +} + +void HierarchicalFold::fold(const std::string &outFileNamePrefix) { + std::vector> identGroups = createIdentGroups(N_, bestKContainer_); + std::map> assemblyGroupsMap = createAssemblyGroupsMap(N_, bestKContainer_); + + std::map precomputedResults; + for (unsigned int i = 2; i <= N_; i++) + precomputedResults[i] = new BestK(K_); + + // populate with homomers subunits + for (std::vector identGroup : identGroups) { + for (unsigned int groupDivider = 1; (identGroup.size() / groupDivider) >= 5; groupDivider++) { + if ((identGroup.size() % groupDivider) != 0) + continue; + unsigned int groupSize = identGroup.size() / groupDivider; + std::vector> groupSBBs; + for (unsigned int j = 0; j < groupSize; j++) { + groupSBBs.push_back(*(bestKContainer_[BitId(identGroup[j])].begin())); + } + std::cout << "searching for size " << groupSBBs.size() << " has " << precomputedResults.count(groupSBBs.size()) + << std::endl; + createSymmetry(groupSBBs, *precomputedResults[groupSBBs.size()]); + } + } + + // Hierarchical Assembly + for (unsigned int length = 2; length <= N_; length++) { // # subunits iteration + std::cout << "*** running iteration " << length + << " prev kept results: " << keptResultsByLength[length - 1]->size() << std::endl; + std::unordered_map best_k_by_id; + + // populate with precomputedResults + for (auto it1 = precomputedResults[length]->begin(); it1 != precomputedResults[length]->end(); it1++) { + BitId currResSet = (**it1).bitIds(); + if (best_k_by_id.count(currResSet) == 0) + best_k_by_id[currResSet] = new BestK(K_); + best_k_by_id[currResSet]->push(*it1); + } + + // try to assemble from each pair of kept results that together have (length) subunits + for (unsigned int firstResultSize = 1; firstResultSize <= length / 2; firstResultSize++) { + unsigned int secondResultSize = length - firstResultSize; + std::cout << "** running sub-iteration " << firstResultSize << " " << secondResultSize << std::endl; + std::cout << "counters " << countFilterTrasSkipped_ << "/" << countFilterTras_ << std::endl; + + for (auto it1 = keptResultsByLength[firstResultSize]->begin(); + it1 != keptResultsByLength[firstResultSize]->end(); it1++) { + SuperBB sbb1 = **it1; + BitId setA = sbb1.bitIds(); + + for (auto it2 = keptResultsByLength[secondResultSize]->begin(); + it2 != keptResultsByLength[secondResultSize]->end(); it2++) { + + if (firstResultSize == secondResultSize && + std::distance(keptResultsByLength[firstResultSize]->begin(), it1) > + std::distance(keptResultsByLength[secondResultSize]->begin(), it2)) + continue; // Since in this case there are 2 identical loops, don't do things twice + + // If there are identical subunits in both results, rewrite the second result to not have the same + std::shared_ptr sbb2Pointer = getMatchingSBB(sbb1, **it2, identGroups); + if (sbb2Pointer == NULL) + continue; + SuperBB sbb2 = *sbb2Pointer; + + // make sure that the two results can be connected + BitId setB = sbb2.bitIds(); + if ((setA & setB) != 0) + continue; + BitId currResSet = setA | setB; + if(!isValidBasedOnAssembly(assemblyGroupsMap, currResSet)){ + std::cout << "invalid assembly " << currResSet << std::endl; + continue; + } + + // connect the two results and add all new combined results to best_k_by_id[currResSet] + if (best_k_by_id.count(currResSet) == 0) + best_k_by_id[currResSet] = new BestK(K_); + + unsigned int resCountBefore = best_k_by_id[currResSet]->size(); + float minScoreBefore = best_k_by_id[currResSet]->minScore(); + + std::promise promise1; + this->tryToConnect(1, sbb1, sbb2, *best_k_by_id[currResSet], (length < N_), promise1, identGroups); + + if (resCountBefore < best_k_by_id[currResSet]->size() || + minScoreBefore != best_k_by_id[currResSet]->minScore()) + std::cout << "found more for " << currResSet << " based on " << setA << " and " << setB + << " before: " << resCountBefore << " after: " << best_k_by_id[currResSet]->size() + << " scores " << best_k_by_id[currResSet]->minScore() << ":" + << best_k_by_id[currResSet]->maxScore() << std::endl; + } + } + } + + // cluster results and save them + std::map bestForSubunitId; + keptResultsByLength[length] = new BestK(K_); + + for (const auto &[currResSet, currBestK] : best_k_by_id) { + if (currBestK->size() > 0) { + BestK *clusteredBestK = bestKContainer_.newBestK(currResSet); + currBestK->cluster(*clusteredBestK, 1.0, identGroups); + + std::cerr << "clustering resSet " << currResSet << " before: " << currBestK->size() << " after " + << bestKContainer_[currResSet].size() << " scores " << bestKContainer_[currResSet].minScore() + << ":" << bestKContainer_[currResSet].maxScore() << std::endl; + + for (unsigned int i = 0; i < N_; i++) { + if (currResSet.test(i)) { + if (bestForSubunitId.count(i) == 0) { + bestForSubunitId[i] = new BestK(1); + } + bestForSubunitId[i]->push(*clusteredBestK->rbegin()); + } + } + + unsigned int count = 0; + for (auto it1 = bestKContainer_[currResSet].rbegin(); it1 != bestKContainer_[currResSet].rend(); it1++) { + keptResultsByLength[length]->push(*it1); + count += 1; + if (count >= maxResultPerResSet) + break; + } + } + delete currBestK; + } + + // save best from each subunit + keptResultsByLength[length]->setK(K_ + bestForSubunitId.size()); + for (const auto &[subunitId, currBestK] : bestForSubunitId) { + keptResultsByLength[length]->push(*currBestK->rbegin()); + delete currBestK; + } + + printBestK(N_, keptResultsByLength[length]); + } + + // output fully assembled results or largest subsets + if (keptResultsByLength[N_]->size() != 0) { + std::string outFileName = outFileNamePrefix + ".res"; + std::ofstream outFile(outFileName); + std::ofstream outFileClustered(outFileNamePrefix + "_clustered.res"); + BestK clusteredBestK(finalSizeLimit_); // TODO: this should also change on the best_k_by_id level + + for (auto it = keptResultsByLength[N_]->rbegin(); it != keptResultsByLength[N_]->rend(); it++) + (*it)->fullReport(outFile); + // output after clustering + keptResultsByLength[N_]->cluster(clusteredBestK, 5.0, identGroups); + for (auto it = clusteredBestK.rbegin(); it != clusteredBestK.rend(); it++) + (*it)->fullReport(outFileClustered); + outFile.close(); + outFileClustered.close(); + } else { + // output largest subsets + for (unsigned int i = N_; i > 1; i--) { + if (keptResultsByLength[i]->size() == 0) + continue; + std::string outFileName = "cb_" + std::to_string(i) + "_" + outFileNamePrefix + ".res"; + std::ofstream outFile(outFileName); + for (auto it = keptResultsByLength[i]->rbegin(); it != keptResultsByLength[i]->rend(); it++) + (*it)->fullReport(outFile); + outFile.close(); + break; + } + } + + // cleanup + for (const auto &[length, currBestK] : precomputedResults) { + delete currBestK; + } + for (const auto &[length, currBestK] : keptResultsByLength) { + delete currBestK; + } +} + +void HierarchicalFold::tryToConnect(int id, const SuperBB &sbb1, const SuperBB &sbb2, BestK &results, bool toAdd, + std::promise &output, std::vector> &identGroups) { + // iterate over pairs of BBs os SuperBB1 and SuperBB2 + for (int i = 0; i < (int)sbb1.bbs_.size(); i++) { + int firstBB = sbb1.bbs_[i]->getID(); + for (int j = 0; j < (int)sbb2.bbs_.size(); j++) { + int secondBB = sbb2.bbs_[j]->getID(); + + // loop over possible transformations between BBs + for (TransIterator2 it(sbb1, sbb2, firstBB, secondBB); !it.isAtEnd(); it++) { + // optimization - check that the score is not lower than the minimum in the current bestK + if((it.getScore() + sbb1.transScore_ + sbb2.transScore_) < results.minScore()){ + continue; + } + + // discard any invalid transformations + bool filtered = filterTrans(sbb1, sbb2, it.transformation()); + if (filtered) + continue; + + FoldStep step(firstBB, secondBB, it.getScore()); + std::shared_ptr theNew = createJoined(sbb1, sbb2, it.transformation(), 0, step, it.getScore()); + + if (theNew->getRestraintsRatio() < restraintsRatioThreshold_) { + // std::cout << "not enough restraints " << theNew->getRestraintsRatio() << " : " << + // complexConst_.getDistanceRestraintsRatioThreshold(); + continue; + } + + // results.push(theNew); + results.push_cluster(theNew, 1, identGroups); + } + } + } + output.set_value(1); +} + +bool HierarchicalFold::filterTrans(const SuperBB &sbb1, const SuperBB &sbb2, const RigidTrans3 &trans) const { + + // check distance constraints & restraints + for (unsigned int i = 0; i < sbb1.size_; i++) { + const BB &bb1 = *sbb1.bbs_[i]; + RigidTrans3 t = (!sbb1.trans_[i]) * trans; + for (unsigned int j = 0; j < sbb2.size_; j++) { + const BB &bb2 = *sbb2.bbs_[j]; + RigidTrans3 t2 = t * sbb2.trans_[j]; + // check constraints first + if (!complexConst_.areConstraintsSatisfied(bb1.getID(), bb2.getID(), t2)) + return true; + } + } + + // backbone penetrations for each pair of BBs + for (unsigned int i = 0; i < sbb1.size_; i++) { + const BB &bb1 = *sbb1.bbs_[i]; + RigidTrans3 t = (!sbb1.trans_[i]) * trans; + + for (unsigned int j = 0; j < sbb2.size_; j++) { + const BB &bb2 = *sbb2.bbs_[j]; + RigidTrans3 t2 = t * sbb2.trans_[j]; + + const BB *pBB1 = &bb1, *pBB2 = &bb2; + if (bb2.getSurfaceSize() > bb1.getSurfaceSize()) { + pBB1 = &bb2; + pBB2 = &bb1; + t2 = !t2; + } + + countFilterTras_ = countFilterTras_ + 1; + + // optimization - check if radiuses are too far apart and if so, skip check + if ((pBB1->getRadius() + pBB2->getRadius()) < (pBB1->getCM() - t2*pBB2->getCM()).norm()) { + countFilterTrasSkipped_ = countFilterTrasSkipped_ + 1; + continue; + } + + unsigned int bbPenetrations = 0; + unsigned int totalUsedAtoms = 0; + + // TODO: maybe should save Weighted bbPen using pBB1->grid_->getDist(v) as weight + for (Molecule::const_iterator it = pBB2->caAtoms_.begin(); it != pBB2->caAtoms_.end(); it++) { + if (it->getTempFactor() < minTemperatureToConsiderCollision) { + continue; + } + totalUsedAtoms++; + + Vector3 v = t2 * it->position(); + if (pBB1->getDistFromSurface(v) < 0) { + // getResidueEntry(v) when used in BBGrid.h will return -1*res_index if res_index is backbone + if (pBB1->grid_->getResidueEntry(v) < 0 && pBB1->grid_->getDist(v) < penetrationThreshold_) { + int resEntry = pBB1->grid_->getResidueEntry(v) * -1; + if (pBB1->getAtomByResId(resEntry).getTempFactor() < minTemperatureToConsiderCollision) + continue; + + bbPenetrations++; + } + } + } + float bbPenChangePercent = (float)(bbPenetrations) / (float)totalUsedAtoms; + if (bbPenChangePercent > maxBackboneCollisionPercentPerChain) { + return true; + } + } + } + + return false; +} + +void HierarchicalFold::createSymmetry(std::vector> identBBs, BestK &results) { + unsigned int transCount = 0; + std::cout << "started trans check, bb_size:" << identBBs.size() << std::endl; + for (TransIterator2 it(*identBBs[0], *identBBs[1], identBBs[0]->bbs_[0]->getID(), identBBs[1]->bbs_[0]->getID()); + !it.isAtEnd(); it++) + transCount++; + + std::cout << "number of transformations:" << transCount << std::endl; + unsigned int addedSymCount = 0; + for (unsigned int transNum = 0; transNum < transCount; transNum++) { + std::cout << "checking trans indexed" << transNum << std::endl; + // create symSBB for a trans, this is cumbersome because I don't really know how to handle transformations + std::shared_ptr symSBB = identBBs[0]; + for (unsigned int i = 1; i < identBBs.size(); i++) { + unsigned int count = -1; + + // There is a memory issue here, I create TransIterator2 with symSBB as bb1, but then I override it + // and freeing the memory(?) of bb1_, so it is important to break after chanigng symSBB + for (TransIterator2 it(*symSBB, *identBBs[i], symSBB->bbs_[i - 1]->getID(), identBBs[i]->bbs_[0]->getID()); + !it.isAtEnd(); it++) { + count++; + if (count != transNum) + continue; + float transScore = it.getScore() + it.getScore() * ((100 - it.getScore()) / 100); + + FoldStep step(symSBB->bbs_[i - 1]->getID(), identBBs[i]->bbs_[0]->getID(), transScore); + std::cout << "adding trans " << it.transformation() << " **** " << it.getScore() << std::endl; + symSBB = createJoined(*symSBB, *identBBs[i], it.transformation(), 0, step, transScore); + break; + } + } + std::cout << "created possibly symSBB" << std::endl; + + // check bb penetration between each 2 chains + double maxPenetration = 0; + bool shouldContinuePen = false; + for (unsigned int i = 0; i < symSBB->size_; i++) { + const BB &bb1 = *symSBB->bbs_[i]; + RigidTrans3 t1 = (!symSBB->trans_[i]); + for (unsigned int j = i + 1; j < symSBB->size_; j++) { + const BB &bb2 = *symSBB->bbs_[j]; + RigidTrans3 t2 = t1 * symSBB->trans_[j]; + + const BB *pBB1 = &bb1, *pBB2 = &bb2; + unsigned int totalUsedAtoms = 0; + unsigned int bbPenetrations = 0; + + for (Molecule::const_iterator it = pBB2->caAtoms_.begin(); it != pBB2->caAtoms_.end(); it++) { + if (it->getTempFactor() < minTemperatureToConsiderCollision) { + continue; + } + totalUsedAtoms++; + + Vector3 v = t2 * it->position(); + if (pBB1->getDistFromSurface(v) < 0) { + // getResidueEntry(v) when used in BBGrid.h will return -1*res_index if res_index is backbone + if (pBB1->grid_->getResidueEntry(v) < 0 && pBB1->grid_->getDist(v) < -1.0) { + int resEntry = pBB1->grid_->getResidueEntry(v) * -1; + if (pBB1->getAtomByResId(resEntry).getTempFactor() < minTemperatureToConsiderCollision) + continue; + bbPenetrations++; + } + } + } + + if ((bbPenetrations / (1.0 * totalUsedAtoms)) > 0.2) { + std::cout << "dropping " << identBBs.size() << " because penetration " + << bbPenetrations / (1.0 * totalUsedAtoms) << std::endl; + shouldContinuePen = true; + break; + } + + maxPenetration = std::max(maxPenetration, bbPenetrations / (1.0 * totalUsedAtoms)); + } + if (shouldContinuePen) + break; + } + if (shouldContinuePen) + continue; + std::cout << "checked penetrations ratio max: " << maxPenetration << std::endl; + // if above some TH (for everything, not per chain) (20%) - drop + + // if last and first centers are the farthest - drop + std::vector centroids; + for (unsigned int i = 0; i < symSBB->size_; i++) { + centroids.push_back(symSBB->trans_[i] * symSBB->bbs_[i]->getCM()); + } + std::cout << "centroids distance " << (centroids[0] - centroids[1]).norm2() << " : " + << (centroids[0] - centroids.back()).norm2() << std::endl; + + float allowedDistFactor = 1.5 + (symSBB->size_ - 3) * 0.25; + if ((centroids[0] - centroids[1]).norm2() * allowedDistFactor < (centroids[0] - centroids.back()).norm2()) { + std::cout << "dropping " << identBBs.size() << " because centroids distance " + << (centroids[0] - centroids[1]).norm2() << " : " << (centroids[0] - centroids.back()).norm2() + << std::endl; + continue; + } + + // verify that centroids are first all increasing distance from first centroid and then all decreasing distance + // from first centroid + bool increasing = true; + bool shouldContinue = false; + for (unsigned int i = 1; i < centroids.size(); i++) { + if (increasing) { + if ((centroids[i] - centroids[0]).norm2() < (centroids[i - 1] - centroids[0]).norm2()) { + increasing = false; + } + } else { + if ((centroids[i] - centroids[0]).norm2() > (centroids[i - 1] - centroids[0]).norm2()) { + std::cout << "dropping " << identBBs.size() << " because centroids not increasing and decreasing" + << std::endl; + shouldContinue = true; + break; + } + } + } + if (shouldContinue) + continue; + if (increasing) { + std::cout << "dropping " << identBBs.size() << " because centroids only increasing " << std::endl; + continue; + } + + std::cout << "added with score " << symSBB->weightedTransScore_ << std::endl; + results.push(symSBB); + addedSymCount++; + } + + BitId groupIdentifier; + for (unsigned int i = 0; i < identBBs.size(); i++) { + groupIdentifier |= identBBs[i]->bbs_[0]->bitId(); + } + std::cout << "Created " << addedSymCount << " Symmetrical for " << groupIdentifier << std::endl; +} + +// utils +std::shared_ptr HierarchicalFold::createJoined(const SuperBB &sbb1, const SuperBB &sbb2, RigidTrans3 &trans, + int bbPen, FoldStep &step, float transScore) const { + std::shared_ptr theNew = std::make_shared(sbb1); + theNew->join(trans, sbb2, bbPen, step, transScore); + theNew->setRestraintsRatio(complexConst_.getRestraintsRatio(theNew->bbs_, theNew->trans_)); + return theNew; +} + +std::shared_ptr HierarchicalFold::getMatchingSBB(SuperBB sbb1, SuperBB sbb2, + std::vector> &identGroups) { + /* + This function recieves two SuperBBs and checks if they have common BBs that are a part of the same ident group. + If so, it checks wether the total amount of BBs from the same ident group is smaller than the size of the ident + group. If so, It return a new SuperBB based on sbb2, but with the BBs from the ident group that are not in sbb1. + + It also validates that in both sbb1&sbb2, the BBs from the ident group are the smallest BBs in the ident group (This + prevents duplications of results). If they are not valid - returns NULL. + */ + std::map bbIdToNewId; + for (std::vector identGroup : identGroups) { + // verify sbb1 is valid (mostly needed to ignore initial structures of BBs that are not first in group) + bool flag = false; + int maxIdInSbb1 = -1; + for (unsigned int i = 0; i < identGroup.size(); i++) { + if (!sbb1.bitIds().test(identGroup[i])) // ident_group[i] not in currResSet + flag = true; + else if (flag) { + if (sbb1.bbs_.size() != 1) + std::cout << "sbb1 not valid " << sbb1.bitIds() << ":" << sbb2.bitIds() << std::endl; + return NULL; + } else { + maxIdInSbb1 = i; + } + } + + // compute mapping from sbb2 bb ids to new bb ids + int maxIdInSbb2 = -1; + flag = false; + for (unsigned int i = 0; i < identGroup.size(); i++) { + if (!sbb2.bitIds().test(identGroup[i])) // ident_group[i] not in currResSet + flag = true; + else if (flag) { + if (sbb2.bbs_.size() != 1) + std::cout << "sbb2 not valid " << sbb1.bitIds() << ":" << sbb2.bitIds() << std::endl; + return NULL; + } else { + maxIdInSbb2 = i; + } + } + + if (maxIdInSbb1 == -1 || maxIdInSbb2 == -1) + continue; + + // check if there are more copies in sbb1 and sbb2 than the size of the ident group + if ((maxIdInSbb1 + 1) + (maxIdInSbb2 + 1) > identGroup.size()) { + return NULL; + } + + for (unsigned int i = 0; i < maxIdInSbb2 + 1; i++) { + bbIdToNewId[identGroup[i]] = identGroup[i + maxIdInSbb1 + 1]; + } + } + + // create new SuperBB with new ids + if (bbIdToNewId.size() == 0) + return std::make_shared(sbb2); + + // std::cout << "converting " << sbb1.bitIds() << ":" << sbb2.bitIds() << " with " << bbIdToNewId.size() << + // std::endl; + std::shared_ptr newSbb = std::make_shared(sbb2); + + for (auto iter = bbIdToNewId.rbegin(); iter != bbIdToNewId.rend(); ++iter) { + unsigned int oldId = iter->first; + unsigned int newId = iter->second; + newSbb->replaceIdentBB(BitId(oldId), (*(bestKContainer_[BitId(newId)].begin()))->bbs_[0]); + } + + return newSbb; +} + +void HierarchicalFold::checkConnectivity() const { + typedef boost::adjacency_list // property associated with vertices + Graph; + + Graph g(N_); + for (unsigned int suIndex = 0; suIndex < N_; suIndex++) { + BitId index = BitId(suIndex); + std::cerr << "isEmpty " << suIndex << " " << bestKContainer_.isEmpty(index) << std::endl; + std::cerr << "SBB " << suIndex << " size " << bestKContainer_[index].size() << std::endl; + if (bestKContainer_[index].size() >= 1) { + std::shared_ptr sbb = *(bestKContainer_[index].rbegin()); + std::shared_ptr bb = sbb->bbs_[0]; + + for (unsigned int suIndex2 = 0; suIndex2 < N_; suIndex2++) { + unsigned int transSize = bb->getTransformations(suIndex2).size(); + std::cerr << suIndex << " " << suIndex2 << " trans size " << transSize << std::endl; + if (transSize > 0) + boost::add_edge(suIndex, suIndex2, g); + std::cerr << "done add_edge" << std::endl; + } + } + } + + std::vector component(boost::num_vertices(g)); + size_t num_components = boost::connected_components(g, &component[0]); + std::cerr << "Num of components " << num_components << std::endl; + if (num_components != 1) { + for (size_t i = 0; i < boost::num_vertices(g); ++i) + std::cerr << "SU " << i << " component " << component[i] << std::endl; + std::cerr << "Not enough transformations between subunits, there should be one connected component!" + << std::endl; + exit(1); + } +} + +void HierarchicalFold::outputConnectivityGraph(std::string outFileName) const { + std::ofstream outFile(outFileName); + outFile << "SU1 Prot1 size1 SU2 Prot2 size2 Restraints" << std::endl; + for (unsigned int suIndex = 0; suIndex < N_; suIndex++) { + BitId set = BitId(suIndex); + std::cerr << "SBB " << suIndex << " set " << set << " isEmpty " << bestKContainer_.isEmpty(set) << std::endl; + std::cerr << "SBB " << suIndex << " size " << bestKContainer_[set].size() << std::endl; + if (bestKContainer_[set].size() >= 1) { + std::shared_ptr sbb = *(bestKContainer_[set].rbegin()); + std::shared_ptr bb = sbb->bbs_[0]; + + for (unsigned int suIndex2 = suIndex + 1; suIndex2 < N_; suIndex2++) { + unsigned int transSize = bb->getTransformations(suIndex2).size(); + BitId set2 = BitId(suIndex2); + std::cerr << suIndex << " " << suIndex2 << " trans size " << transSize << " " << set2 << std::endl; + std::shared_ptr sbb2 = *(bestKContainer_[set2].rbegin()); + std::shared_ptr bb2 = sbb2->bbs_[0]; + + if (transSize > 0) { + std::vector results1, results2; + std::string PDBFileName1 = bb->getPDBFileName(); + boost::split(results1, PDBFileName1, [](char c) { return c == '_'; }); + std::string PDBFileName2 = bb2->getPDBFileName(); + boost::split(results2, PDBFileName2, [](char c) { return c == '_'; }); + + unsigned int xlinkNumber = complexConst_.numberOfRestraints(suIndex, suIndex2); + unsigned int connectivityConstraintsNumber = complexConst_.numberOfConstraints(suIndex, suIndex2); + + if (xlinkNumber > 0) { + outFile << bb->getPDBFileName() << " " << results1[0] << " " << bb->backBone_.size() / 4 << " "; + outFile << bb2->getPDBFileName() << " " << results2[0] << " " << bb2->backBone_.size() / 4 + << " "; + outFile << complexConst_.numberOfRestraints(suIndex, suIndex2) << std::endl; + } + if (connectivityConstraintsNumber > 0) { + outFile << bb->getPDBFileName() << " " << results1[0] << " " << bb->backBone_.size() / 4 << " "; + outFile << bb2->getPDBFileName() << " " << results2[0] << " " << bb2->backBone_.size() / 4 + << " "; + outFile << complexConst_.numberOfConstraints(suIndex, suIndex2) << std::endl; + } + /*outFile << bb->getPDBFileName() << " " + << complexConst_.numberOfRestraints(suIndex, suIndex2) << " " + << bb2->getPDBFileName() << std::endl;*/ + } + } + } + } + outFile.close(); +} diff --git a/model/CombinatorialAssembler/HierarchicalFold.h b/model/CombinatorialAssembler/HierarchicalFold.h new file mode 100644 index 0000000000000000000000000000000000000000..d2129fb5cac3f0474a178ca984efc240be6ecd3d --- /dev/null +++ b/model/CombinatorialAssembler/HierarchicalFold.h @@ -0,0 +1,86 @@ +#ifndef HIERARCHICALFOLD_H +#define HIERARCHICALFOLD_H + +#include "BestK.h" +#include "BestKContainer.h" +#include "ComplexDistanceConstraint.h" +#include "BBContainer.h" +#include +#include + +// TODO: should this class just be a namespace? because each function is called once... +class HierarchicalFold { + public: + // N - number of subunits, k - best solutions to save at each step + HierarchicalFold(BBContainer& bbContainer, unsigned int k, unsigned int maxResultPerResSet, + float minTemperatureToConsiderCollision, float maxBackboneCollisionPercentPerChain, + float penetrationThreshold, float restraintsRatio) + : countFilterTras_(0), countFilterTrasSkipped_(0), N_(bbContainer.getBBs().size()), K_(k), + maxResultPerResSet(maxResultPerResSet), minTemperatureToConsiderCollision(minTemperatureToConsiderCollision), + maxBackboneCollisionPercentPerChain(maxBackboneCollisionPercentPerChain), + restraintsRatioThreshold_(restraintsRatio), penetrationThreshold_(penetrationThreshold), + finalSizeLimit_(k * N_), bestKContainer_(k), complexConst_(bbContainer.getBBs()) { + + // initialize keptResultsByLength and bestKContainer_ + keptResultsByLength[1] = new BestK(N_); + for (unsigned int i = 0; i < bbContainer.getBBsNumber(); i++) { + std::shared_ptr sbb = std::make_shared(bbContainer.getBB(i)); + + keptResultsByLength[1]->push(sbb); + BitId set = BitId(i); + BestK *cb = bestKContainer_.newBestK(set); + cb->push(sbb); + } + } + + + void fold(const std::string &outFileNamePrefix); + + void tryToConnect(int id, const SuperBB &sbb1, const SuperBB &sbb2, BestK &results, bool toAdd, + std::promise &output, std::vector> &identGroups); + + bool filterTrans(const SuperBB &sbb1, const SuperBB &sbb2, const RigidTrans3 &trans) const; + + void createSymmetry(std::vector> identBBs, BestK &results); + + // utils + std::shared_ptr createJoined(const SuperBB &sbb1, const SuperBB &sbb2, RigidTrans3 &trans, int bbPen, + FoldStep &step, float transScore) const; + + std::shared_ptr getMatchingSBB(SuperBB sbb1, SuperBB sbb2, + std::vector> &identGroups); + + void readConstraints(const std::string fileName) { + complexConst_.readRestraintsFile(fileName); + complexConst_.addChainConnectivityConstraints(); + } + + void checkConnectivity() const; + + void outputConnectivityGraph(std::string outFileName = "graph.sif") const; + + // members + static Timer timer_, timerAll_; + static unsigned int countResults_; + + + mutable unsigned int countFilterTras_; + mutable unsigned int countFilterTrasSkipped_; + + private: + const unsigned int N_; // number of subunits + const unsigned int K_; // number of solutions to save at each stage + const unsigned int maxResultPerResSet; // number of solutions to save for each resSet + const float minTemperatureToConsiderCollision; + const float maxBackboneCollisionPercentPerChain; + const float restraintsRatioThreshold_; + + float penetrationThreshold_; // this is ignored for now + + int finalSizeLimit_; + BestKContainer bestKContainer_; + std::map keptResultsByLength; + ComplexDistanceConstraint complexConst_; +}; + +#endif /* HIERARCHICALFOLD_H */ diff --git a/model/CombinatorialAssembler/MainCombDock.cc b/model/CombinatorialAssembler/MainCombDock.cc new file mode 100644 index 0000000000000000000000000000000000000000..cf21aa473404d58f0369440d67da03ca3c0055ff --- /dev/null +++ b/model/CombinatorialAssembler/MainCombDock.cc @@ -0,0 +1,140 @@ +#include "BBContainer.h" +#include "HierarchicalFold.h" + +#include +#include +#include +#include +#include +#include +#include +// #include + +#include +namespace po = boost::program_options; + +// #include + +int main(int argc, char *argv[]) { + // output arguments + for (int i = 0; i < argc; i++) + std::cout << argv[i] << " "; + std::cout << std::endl; + + // input parsing + double penetrationThr; + float maxBackboneCollisionPerChain; + float minTemperatureToConsiderCollision; + unsigned int maxResultPerResSet; + + std::string outFileNamePrefix; + double restraintsRatio; + double clusterRMSD; + + // positional + std::string suFileName; + std::string transFilesPrefix; + int transNumToRead; + int bestK; + std::string constraintsFileName; + + po::options_description desc("Usage: \ + "); + + // optional + desc.add_options()("help,h", "Combinatoral Assembly help")("version", "CombFold 1.0 2022")( + "penetrationThr,p", po::value(&penetrationThr)->default_value(-1.0), + "maximum allowed penetration between subunit surfaces (default = -1.0)")( + "restraintsRatio,r", po::value(&restraintsRatio)->default_value(0.1), + "constraints ratio (default = 0.1)")("clusterRMSD,c", po::value(&clusterRMSD)->default_value(5.0), + "final clustering RMSD (default = 5.0)") + + ("maxBackboneCollisionPerChain,b", po::value(&maxBackboneCollisionPerChain)->default_value(0.1), + "Max percentage(0 to 1) of backbone atoms of a chain that can collide with another chain(default=0.1)")( + "minTemperatureToConsiderCollision,t", + po::value(&minTemperatureToConsiderCollision)->default_value(0), + "Minimal Bfactor required for atom to be considered when calculating collisions(default=0)")( + "maxResultPerResSet,j", po::value(&maxResultPerResSet)->default_value(0), + "number of results saved for each calculated combination of subunits (default=k)") + + ("outputFileNamePrefix,o", po::value(&outFileNamePrefix)->default_value("output"), + "output file name, default name output.res"); + + // required options: currently 5 + po::options_description hidden("Hidden options"); + hidden.add_options()("SUlist", po::value(&suFileName)->required(), "SU list file name")( + "transFilesPrefix", po::value(&transFilesPrefix)->required(), + "Trans files prefix")("transNumToRead", po::value(&transNumToRead)->required(), + "# of tranformations")("bestK", po::value(&bestK)->required(), "bestK")( + "constraintsFile", po::value(&constraintsFileName)->required(), "constraints file name"); + + po::options_description cmdline_options; + cmdline_options.add(desc).add(hidden); + + po::positional_options_description p; + p.add("SUlist", 1); + p.add("transFilesPrefix", 1); + p.add("transNumToRead", 1); + p.add("bestK", 1); + p.add("constraintsFile", 1); + + po::variables_map vm; + + try { + po::store(po::command_line_parser(argc, argv).options(cmdline_options).positional(p).run(), vm); + + if (vm.count("help")) { + std::cout << desc << "\n"; + return 0; + } + po::notify(vm); + } catch (po::required_option &e) { + std::cout << desc << "\n"; + return 0; + } catch (po::error &e) { + std::cout << desc << "\n"; + return 0; + } + + if (maxResultPerResSet == 0) + maxResultPerResSet = bestK; + + // done parsing + + auto start = std::chrono::high_resolution_clock::now(); + HierarchicalFold::timerAll_.reset(); + + std::cerr << "Before process input" << std::endl; + std::string argv_str(argv[0]); + std::string base = argv_str.substr(0, argv_str.find_last_of("/")); + std::string chemLibFileName = base + "/chem_params.txt"; + BBContainer bbContainer(suFileName, chemLibFileName, minTemperatureToConsiderCollision); + bbContainer.readTransformationFiles(transFilesPrefix, transNumToRead); + + std::cout << "Starting HierarchicalFold" << std::endl; + HierarchicalFold hierarchalFold(bbContainer, bestK, maxResultPerResSet, minTemperatureToConsiderCollision, + maxBackboneCollisionPerChain, penetrationThr, restraintsRatio); + + // read constraints + hierarchalFold.readConstraints(constraintsFileName); + + HierarchicalFold::timer_.reset(); + + hierarchalFold.outputConnectivityGraph("graph.txt"); + hierarchalFold.checkConnectivity(); + // ProfilerStart("nameOfProfile.log"); + auto startBeforeFold = std::chrono::high_resolution_clock::now(); + hierarchalFold.fold(outFileNamePrefix); + // ProfilerStop(); + auto end = std::chrono::high_resolution_clock::now(); + std::chrono::duration diff = end - start; + std::chrono::duration diffFold = end - startBeforeFold; + std::cout << "Overall time " << diff.count() << " s\n"; + std::cout << "only fold time " << diffFold.count() << " s\n"; + std::cerr << "countResults_ " << HierarchicalFold::countResults_ << std::endl; + std::cerr << "timer_ " << HierarchicalFold::timer_ << std::endl; + std::cerr << "timerAll_ " << HierarchicalFold::timerAll_ << std::endl; + // SuperBB::reportCounters(); + + return 0; +} diff --git a/model/CombinatorialAssembler/Makefile b/model/CombinatorialAssembler/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..bd3f1a6769ce80025f0f4053b0b86f4954a8731d --- /dev/null +++ b/model/CombinatorialAssembler/Makefile @@ -0,0 +1,42 @@ +BOOST_INCLUDE = /opt/homebrew/include +BOOST_LIB = /opt/homebrew/lib/ + +CC=g++ +# use -Wno-deprecated-declarations to suppress warnings from boost +# CFLAGS=-c -Wall -I./libs_gamb -I./libs_DockingLib -I$(BOOST_INCLUDE) -O2 --std=c++11 # -fexpensive-optimizations -ffast-math +CFLAGS=-c -Wall -Wno-deprecated-declarations -I./libs_gamb -I./libs_DockingLib -I$(BOOST_INCLUDE) -g -O2 --std=c++17 # -fexpensive-optimizations -ffast-math +# CFLAGS=-c -Wall -I./libs_gamb -I./libs_DockingLib -I$(BOOST_INCLUDE) -O0 -g --std=c++11 # -fexpensive-optimizations -ffast-math + +SOURCES_MAIN = $(wildcard *.cc) +SOURCES_GAMB = $(wildcard libs_gamb/*.cc) +SOURCES_DOCKLIB = $(wildcard libs_DockingLib/*.cc) +SOURCES_AF2TRANS = $(wildcard AF2trans/*.cc) + +OBJECTS_MAIN = $(SOURCES_MAIN:.cc=.o) +OBJECTS_GAMB = $(SOURCES_GAMB:.cc=.o) +OBJECTS_DOCKLIB = $(SOURCES_DOCKLIB:.cc=.o) +OBJECTS_AF2TRANS = $(SOURCES_AF2TRANS:.cc=.o) + +all: MainCombAssemble MainAf2trans + +MainCombAssemble: libgamb.a libdocklib.a $(OBJECTS_MAIN) + $(CC) $(OBJECTS_MAIN) -L. -L$(BOOST_LIB) -lgamb -ldocklib -lboost_program_options -lpthread -o CombinatorialAssembler.out + +MainAf2trans: libgamb.a libdocklib.a $(OBJECTS_AF2TRANS) + $(CC) $(OBJECTS_AF2TRANS) -L. -L$(BOOST_LIB) -lgamb -ldocklib -lboost_program_options -lpthread -o AF2trans.out + +%.o: %.cc + $(CC) $(CFLAGS) $< -o $@ + +libgamb.a: $(OBJECTS_GAMB) + ar rcs libgamb.a $(OBJECTS_GAMB) + +libdocklib.a: $(OBJECTS_DOCKLIB) libgamb.a + ar rcs libdocklib.a $(OBJECTS_DOCKLIB) $(OBJECTS_GAMB) + +clean_all: + rm -f *.o *.a AF2trans.out CombinatorialAssembler.out AF2trans/*.o libs_gamb/*.o libs_DockingLib/*.o + +clean: + rm -f *.o AF2trans/*.o AF2trans.out CombinatorialAssembler.out + diff --git a/model/CombinatorialAssembler/SuperBB.cc b/model/CombinatorialAssembler/SuperBB.cc new file mode 100644 index 0000000000000000000000000000000000000000..bd12a5239e3bc650f23f2c051d2fce99c61b3b5c --- /dev/null +++ b/model/CombinatorialAssembler/SuperBB.cc @@ -0,0 +1,315 @@ +#include "SuperBB.h" +#include "HierarchicalFold.h" + + +SuperBB::SuperBB(std::shared_ptr bb) + : backBonePen_(0), transScore_(0), weightedTransScore_(100) { + bbs_.push_back(bb); + Vector3 v(0, 0, 0); + Matrix3 M(1); + RigidTrans3 T(M, v); + trans_.push_back(T); + size_ = 1; + bitIDS_ = bb->bitId(); +} + +float getWeightedTransScore(std::vector steps, std::vector> bbs) { + unsigned int totalCa = 0; + float totalScore = 0; + std::map bbIdToCaLength; + for (std::shared_ptr bb : bbs) { + bbIdToCaLength[bb->getID()] = bb->getNumOfAtoms(); + } + + for (FoldStep step : steps) { + std::vector right; + std::vector left; + + right.push_back(step.i_); + left.push_back(step.j_); + + std::vector notUsedSteps(steps); + + while (!notUsedSteps.empty()) { + FoldStep step2 = notUsedSteps.back(); + notUsedSteps.pop_back(); + if (step2.i_ == step.i_ && step2.j_ == step.j_) + continue; + if (std::find(right.begin(), right.end(), step2.i_) != right.end() && + std::find(right.begin(), right.end(), step2.j_) == right.end()) { + right.push_back(step2.j_); + } else if (std::find(right.begin(), right.end(), step2.j_) != right.end() && + std::find(right.begin(), right.end(), step2.i_) == right.end()) { + right.push_back(step2.i_); + } else if (std::find(left.begin(), left.end(), step2.i_) != left.end() && + std::find(left.begin(), left.end(), step2.j_) == left.end()) { + left.push_back(step2.j_); + } else if (std::find(left.begin(), left.end(), step2.j_) != left.end() && + std::find(left.begin(), left.end(), step2.i_) == left.end()) { + left.push_back(step2.i_); + } else { + notUsedSteps.insert(notUsedSteps.begin(), step2); + } + } + + unsigned int rightSize = 0; + unsigned int leftSize = 0; + + for (unsigned int elem : right) + rightSize += bbIdToCaLength[elem]; + + for (unsigned int elem : left) + leftSize += bbIdToCaLength[elem]; + + unsigned int usedSize = rightSize; + if (rightSize > leftSize) + usedSize = leftSize; + + totalScore += usedSize * step.tScore_; + totalCa += usedSize; + } + return totalScore / totalCa; +} + +void SuperBB::join(const RigidTrans3 &trans, const SuperBB &other, int bbPen, FoldStep &step, float transScore) { + // now joining is simple + for (unsigned int j = 0; j < other.size_; j++) { + bbs_.push_back(other.bbs_[j]); + trans_.push_back(trans * other.trans_[j]); + } + size_ += other.size_; + bitIDS_ |= other.bitIDS_; + backBonePen_ = bbPen; + + transScore_ += other.transScore_ + transScore; + + if (!other.foldSteps_.empty()) { + foldSteps_.insert(foldSteps_.end(), other.foldSteps_.begin(), other.foldSteps_.end()); + } + foldSteps_.push_back(step); + + weightedTransScore_ = getWeightedTransScore(foldSteps_, bbs_); +} + +void SuperBB::replaceIdentBB(BitId oldBBBitId, std::shared_ptr bb) { + if((bb->bitId() & bitIDS_) != 0) + throw std::runtime_error("Tried to replace BB with BB that already exists in SuperBB"); + + std::shared_ptr oldBB; + unsigned int bbIndex = -1; + for (unsigned int i = 0; i < size_; i++) { + if (bbs_[i]->bitId() == oldBBBitId) { + oldBB = bbs_[i]; + bbIndex = i; + break; + } + } + if(bbIndex == -1) + throw std::runtime_error("SuperBB::replaceIdentBB: oldBBBitId not found in SuperBB"); + + bbs_[bbIndex] = bb; + // bitIDS_ -= oldBB->bitId(); + // bitIDS_ += bb->bitId(); + bitIDS_[oldBB->getID()] = false; + bitIDS_[bb->getID()] = true; + + for(FoldStep &step : foldSteps_) { + if(step.i_ == oldBB->getID()) + step.i_ = bb->getID(); + if(step.j_ == oldBB->getID()) + step.j_ = bb->getID(); + } +} + +bool SuperBB::isPenetrating(const RigidTrans3 &trans, const SuperBB &other, float threshold) const { + for (unsigned int i = 0; i < size_; i++) { + RigidTrans3 t = (!trans_[i]) * trans; + for (unsigned int j = 0; j < other.size_; j++) { + if (bbs_[i]->isPenetrating(t * other.trans_[j], *other.bbs_[j], threshold)) + return true; + } + } + return false; +} + +// RMSD between two SBBs (assuming same BBs in each SBB) +double SuperBB::calcRmsd(const SuperBB &other, std::vector> &identGroups) const { + std::vector> presentIdentGroups; + + for (std::vector identGroup : identGroups) { + std::vector possiblyRelevantIdentGroup; + for (unsigned int i : identGroup) { + if (bitIDS_.test(i)) + possiblyRelevantIdentGroup.push_back(i); + } + if (possiblyRelevantIdentGroup.size() >= 2) + presentIdentGroups.push_back(possiblyRelevantIdentGroup); + } + + std::map bbIdToThisBBIndex; + std::map bbIdToOtherBBIndex; + std::vector bbIdsInThis; + for (unsigned int i = 0; i < bbs_.size(); i++) { + bbIdsInThis.push_back(bbs_[i]->getID()); + bbIdToThisBBIndex[bbs_[i]->getID()] = i; + for (unsigned int j = 0; j < size_; j++) { + if (bbs_[i]->getID() == other.bbs_[j]->getID()) { // same BB + bbIdToOtherBBIndex[bbs_[i]->getID()] = j; + break; + } + } + } + + // base rmsd find + Match cmMatch; + Molecule cmA, cmB; + + for (unsigned int bbId : bbIdsInThis) { + Vector3 a = trans_[bbIdToThisBBIndex[bbId]] * bbs_[bbIdToThisBBIndex[bbId]]->cm_; + cmA.add(a); + Vector3 b = other.trans_[bbIdToOtherBBIndex[bbId]] * other.bbs_[bbIdToOtherBBIndex[bbId]]->cm_; + cmB.add(b); + } + for (unsigned int i = 0; i < size_; i++) { + cmMatch.add(i, i); + } + cmMatch.calculateBestFit(cmA, cmB); + float foundRMSD = cmMatch.rmsd(); + + int MAX_ITER = 10; + // try replacing chains with each other + for (int i = 0; i < MAX_ITER; i++) { + // std::cout << "iter " << i << std::endl; + bool somethingChanged = false; + for (std::vector identGroup : presentIdentGroups) { + for (unsigned int i = 0; i < identGroup.size(); i++) { + for (unsigned int j = i + 1; j < identGroup.size(); j++) { + Match cmMatch; + Molecule cmA, cmB; + + for (unsigned int bbId : bbIdsInThis) { + Vector3 a; + if (bbId == identGroup[i]) { + unsigned int replacedbbId = identGroup[j]; + a = trans_[bbIdToThisBBIndex[replacedbbId]] * bbs_[bbIdToThisBBIndex[replacedbbId]]->cm_; + } else if (bbId == identGroup[j]) { + unsigned int replacedbbId = identGroup[i]; + a = trans_[bbIdToThisBBIndex[replacedbbId]] * bbs_[bbIdToThisBBIndex[replacedbbId]]->cm_; + } else { + a = trans_[bbIdToThisBBIndex[bbId]] * bbs_[bbIdToThisBBIndex[bbId]]->cm_; + } + cmA.add(a); + Vector3 b = + other.trans_[bbIdToOtherBBIndex[bbId]] * other.bbs_[bbIdToOtherBBIndex[bbId]]->cm_; + cmB.add(b); + } + for (unsigned int i = 0; i < size_; i++) { + cmMatch.add(i, i); + } + cmMatch.calculateBestFit(cmA, cmB); + float newRMSD = cmMatch.rmsd(); + if (newRMSD < foundRMSD) { + // std::cout << "replacing chains in calcRMSD " << newRMSD << " " << foundRMSD << " " << + // identGroup[i] << " " << identGroup[j] << " " << bbIdToThisBBIndex[identGroup[i]] << " " << + // bbIdToThisBBIndex[identGroup[j]] << std::endl; + unsigned int tmp = bbIdToThisBBIndex[identGroup[i]]; + bbIdToThisBBIndex[identGroup[i]] = bbIdToThisBBIndex[identGroup[j]]; + bbIdToThisBBIndex[identGroup[j]] = tmp; + foundRMSD = newRMSD; + somethingChanged = true; + } else { + // std::cout << "not replacing chains in calcRMSD " << newRMSD << " " << foundRMSD << " " << + // identGroup[i] << " " << identGroup[j] << std::endl; + } + } + } + } + if (!somethingChanged) + break; + if (i == MAX_ITER - 1) + std::cout << "stops clustering because max iteration " << bitIDS_ << std::endl; + } + + if (foundRMSD > 1.5) + return foundRMSD; + + Match match; + Molecule A, B; + for (unsigned int bbId : bbIdsInThis) { + Molecule molA = bbs_[bbIdToThisBBIndex[bbId]]->caAtoms_; + molA.rigidTrans(trans_[bbIdToThisBBIndex[bbId]]); + A.concat(molA); + + Molecule molB = other.bbs_[bbIdToOtherBBIndex[bbId]]->caAtoms_; + molB.rigidTrans(other.trans_[bbIdToOtherBBIndex[bbId]]); + B.concat(molB); + } + + for (unsigned int i = 0; i < A.size(); i++) { + match.add(i, i); + } + match.calculateBestFit(A, B); + return match.rmsd(); +} + +double SuperBB::calcRmsd(const SuperBB &other) const { + std::vector> emptyIdentGroups; + return calcRmsd(other, emptyIdentGroups); +} + +void SuperBB::fullReport(std::ostream &s) { + HierarchicalFold::countResults_++; + RigidTrans3 tr; + s << "size_ " << size_ << " transScore_ " << transScore_ << " multPen_ " << multPen_ << " singlePen_ " + << singlePen_ << " diffPen " << singlePen_ - multPen_ << " backBonePen_ " << backBonePen_ << " maxPen_ " + << maxPen_ << " restraintsRatio_ " << restraintsRatio_ << " weightedTransScore " << weightedTransScore_; + s << " ["; + int i = 0; + for (auto it = bbs_.begin(); it != bbs_.end(); it++, i++) { + s << (*it)->id_ << "(" << trans_[i] << ")"; + if (it + 1 != bbs_.end()) + s << ","; + } + s << "] " << tr; + FoldStep::outputFoldSteps(s, foldSteps_); + s << std::endl; +} + +std::ostream &operator<<(std::ostream &s, const SuperBB &sbb) { + // scores + s << "size_ " << sbb.size_ << " backBonePen_ " << sbb.backBonePen_ << " restraintsRatio_ " << sbb.restraintsRatio_; + + // transformations + s << " ["; + int i = 0; + for (auto it = sbb.bbs_.begin(); it != sbb.bbs_.end(); it++, i++) { + s << (*it)->getID() << "(" << sbb.trans_[i] << ")"; + if (it + 1 != sbb.bbs_.end()) + s << ","; + } + s << "] "; + + FoldStep::outputFoldSteps(s, sbb.foldSteps_); + s << std::endl; + return s; +} + +TransIterator2::TransIterator2(const SuperBB &bb1, const SuperBB &bb2, int pbb1, int pbb2) + : bb1_(bb1), bb2_(bb2), index1_(0), index2_(0) { + + std::shared_ptr b1 = bb1_.bbs_[index1_]; + while (b1->getID() != pbb1) { + b1 = bb1_.bbs_[++index1_]; + } + std::shared_ptr b2 = bb2_.bbs_[index2_]; + while (b2->getID() != pbb2) { + b2 = bb2_.bbs_[++index2_]; + } + + mediatorTrans_ = !bb2_.trans_[index2_]; + trans_ = &(b1->getTransformations(b2->getID())); // BB::trans(*b1, *b2); + it_ = trans_->begin(); + if (!isAtEnd()) { + generateTransformation(); + } +} diff --git a/model/CombinatorialAssembler/SuperBB.h b/model/CombinatorialAssembler/SuperBB.h new file mode 100644 index 0000000000000000000000000000000000000000..8b06c9f157a095674a6869bac1c096392a5f48fc --- /dev/null +++ b/model/CombinatorialAssembler/SuperBB.h @@ -0,0 +1,91 @@ +/** + * This class represents a SuperBB - A subcomplex composed by multiple subunits (saved in bbs_) and in certain + * transformations (saved in trans_) + */ +#ifndef SUPERBB_H +#define SUPERBB_H + +#include "BB.h" +#include "FoldStep.h" + +class SuperBB { + public: + friend class BestK; + friend class HierarchicalFold; + friend class TransIterator2; + + SuperBB(std::shared_ptr bb); + + BitId bitIds() const { return bitIDS_; } + unsigned int size() const { return size_; } + float getRestraintsRatio() const { return restraintsRatio_; } + void setRestraintsRatio(float r) { restraintsRatio_ = r; } + + // This function takes Two super BBs and joins them using a transformation between to BBs + void join(const RigidTrans3 &trans, const SuperBB &other, int bbPen, FoldStep &step, float transScore); + void replaceIdentBB(BitId oldBBBitId, std::shared_ptr bb); + + // This function checks for collissions - Receives transformation and a second super BB and decides if + // they collide + bool isPenetrating(const RigidTrans3 &trans, const SuperBB &other, float threshold) const; + double calcRmsd(const SuperBB &other, std::vector> &identGroups) const; + double calcRmsd(const SuperBB &other) const; + + void fullReport(std::ostream &s); + friend std::ostream &operator<<(std::ostream &s, const SuperBB &sbb); + + private: + // members + unsigned int size_; + std::vector foldSteps_; // FoldStep is a transformation between two SUs + BitId bitIDS_; + float restraintsRatio_; + + public: // TODO: Make private + // BBs that make up the SuperBB + std::vector> bbs_; + // Holds the transformations of the BBs + std::vector trans_; + + int backBonePen_; + float maxPen_; + float transScore_; + float weightedTransScore_; + int multPen_; + int singlePen_; +}; + +class TransIterator2 { + public: + TransIterator2(const SuperBB &sbb1, const SuperBB &sbb2, int pbb1, int pbb2); + + RigidTrans3 &transformation() { return transformation_; } + + float getScore() { return (*it_)->score_.totalScore_; } + + void generateTransformation() { transformation_ = bb1_.trans_[index1_] * (*it_)->refFrame_ * mediatorTrans_; } + + TransIterator2 &operator++(int) { + while (!isAtEnd()) { + it_++; + if (it_ == trans_->end()) { + return *this; + } + generateTransformation(); + return *this; + } + return *this; + } + + bool isAtEnd() { return it_ == trans_->end(); } + + private: + const SuperBB &bb1_, &bb2_; + unsigned int index1_, index2_; + const std::vector> *trans_; + std::vector>::const_iterator it_; + RigidTrans3 mediatorTrans_; + RigidTrans3 transformation_; +}; + +#endif /* SUPERBB_H */ diff --git a/model/CombinatorialAssembler/TransformationAndScore.cc b/model/CombinatorialAssembler/TransformationAndScore.cc new file mode 100644 index 0000000000000000000000000000000000000000..20d81c06d6db0de70ccf753f9414249c105d392f --- /dev/null +++ b/model/CombinatorialAssembler/TransformationAndScore.cc @@ -0,0 +1,11 @@ +#include "TransformationAndScore.h" + +template <> +void TransformationAndScore_T::outputTrans(std::vector *> &transformations) { + int count = 0; + for (std::vector::iterator it = transformations.begin(); it != transformations.end(); + it++) { + count++; + std::cout << count << ": " << **it << std::endl; + } +} diff --git a/model/CombinatorialAssembler/TransformationAndScore.h b/model/CombinatorialAssembler/TransformationAndScore.h new file mode 100644 index 0000000000000000000000000000000000000000..2a3828025cf2a9530c84f437ee5d440db967717d --- /dev/null +++ b/model/CombinatorialAssembler/TransformationAndScore.h @@ -0,0 +1,64 @@ +#ifndef TRANSFORMATIONANDSCORE_H +#define TRANSFORMATIONANDSCORE_H + +#include +#include + +#define NO_OF_RANGES 8 +typedef struct pointsAndSurface_t { + int count_; + float surface_; +} pointsAndSurface; + +template class TransformationAndScore_T { + public: + const RigidTrans3 &trans() { return refFrame_; } + float score() { return score_.totalScore_; } + + RigidTrans3 refFrame_; + SCORE_T score_; + float dist_; // for debug + static void outputTrans(std::vector &transformations); +}; + +template std::ostream &operator<<(std::ostream &s, const TransformationAndScore_T &ts) { + return s << ts.refFrame_ << " " << ts.score_ << " , " << ts.dist_ << " , " << ((ts.dist_ == 0) ? 1 : 0); +} + +template std::istream &operator>>(std::istream &s, TransformationAndScore_T &ts) { + char c; + return s >> c >> ts.score_ >> c >> c >> ts.refFrame_ >> c >> c >> ts.dist_; +} + +class Score { + public: + Score() { init(); } + + void init() { + totalScore_ = 0; + resCount1_ = resCount2_ = 0; + interfaceSurface_ = maxPenetrate_ = s_ = c_ = e_ = 0.0; + for (int i = 0; i < NO_OF_RANGES; i++) { + ps_[i].count_ = 0; + ps_[i].surface_ = 0; + } + } + + float totalScore_; + pointsAndSurface ps_[NO_OF_RANGES]; + int resCount1_; + int resCount2_; + float interfaceSurface_; + float maxPenetrate_; + float s_, c_, e_; + Score *referenceScore_; + + friend std::ostream &operator<<(std::ostream &s, const Score &score) { return s << score.totalScore_; } + + friend std::istream &operator>>(std::istream &s, Score &ts); +}; + +// #define CLUSTER_DEBUG +typedef TransformationAndScore_T TransformationAndScore; + +#endif diff --git a/model/CombinatorialAssembler/chem_params.txt b/model/CombinatorialAssembler/chem_params.txt new file mode 100644 index 0000000000000000000000000000000000000000..dca464f07d3ad5ecf56d9780d90e712ac5bcf003 --- /dev/null +++ b/model/CombinatorialAssembler/chem_params.txt @@ -0,0 +1,412 @@ +ALA N 1.83 -0.15 1 +ALA CA 2.265 0.10 2 +ALA CB 2.165 0.00 6 +ALA C 1.87 0.60 3 +ALA O 1.55 -0.55 4 +ALA OXT 1.48 -0.57 9 +ALA OT2 1.48 -0.57 9 +ARG N 1.83 -0.15 1 +ARG CA 2.265 0.10 2 +ARG CB 2.235 0.00 6 +ARG CG 2.235 0.00 16 +ARG CD 2.235 0.10 12 +ARG NE 1.83 -0.10 12 +ARG CZ 1.87 0.50 10 +ARG NH1 1.83 0.25 10 +ARG NH2 1.83 0.25 10 +ARG C 1.87 0.60 3 +ARG O 1.55 -0.55 4 +ARG OXT 1.48 -0.57 9 +ARG OT2 1.48 -0.57 9 +ARGN N 1.83 -0.15 1 +ARGN CA 2.265 0.10 2 +ARGN CB 2.235 0.00 6 +ARGN CG 2.235 0.00 16 +ARGN CD 2.235 0.10 12 +ARGN NE 1.83 -0.10 12 +ARGN CZ 1.87 -0.20 11 +ARGN NH1 1.83 0.10 11 +ARGN NH2 1.83 0.10 11 +ARGN C 1.87 0.60 3 +ARGN O 1.55 -0.55 4 +ARGN OXT 1.48 -0.57 9 +ARGN OT2 1.48 -0.57 9 +ASN N 1.83 -0.15 1 +ASN CA 2.265 0.10 2 +ASN CB 2.265 0.00 6 +ASN CG 1.87 0.55 11 +ASN OD1 1.55 -0.55 11 +ASN ND2 1.83 0.00 11 +ASN C 1.87 0.60 3 +ASN O 1.55 -0.55 4 +ASN OXT 1.48 -0.57 9 +ASN OT2 1.48 -0.57 9 +ASP N 1.83 -0.15 1 +ASP CA 2.265 0.10 2 +ASP CB 2.235 0.00 6 +ASP CG 1.87 0.14 9 +ASP OD1 1.66 -0.57 9 +ASP OD2 1.66 -0.57 9 +ASP C 1.87 0.60 3 +ASP O 1.55 -0.55 4 +ASP OXT 1.48 -0.57 9 +ASP OT2 1.48 -0.57 9 +ASPH N 1.83 -0.15 1 +ASPH CA 2.265 0.10 2 +ASPH CB 2.235 0.00 6 +ASPH CG 1.87 0.70 11 +ASPH OD1 1.36 -0.35 11 +ASPH OD2 1.55 -0.35 11 +ASPH C 1.87 0.60 3 +ASPH O 1.55 -0.55 4 +ASPH OXT 1.48 -0.57 9 +ASPH OT2 1.48 -0.57 9 +CYS N 1.83 -0.15 1 +CYS CA 2.265 0.10 2 +CYS CB 2.235 0.19 6 +CYS SG 1.89 -0.19 18 +CYS C 1.87 0.60 3 +CYS O 1.55 -0.55 4 +CYS OXT 1.48 -0.57 9 +CYS OT2 1.48 -0.57 9 +GLN N 1.83 -0.15 1 +GLN CA 2.265 0.10 2 +GLN CB 2.235 0.00 6 +GLN CG 2.235 0.00 16 +GLN CD 1.87 0.55 11 +GLN OE1 1.55 -0.55 11 +GLN NE2 1.83 0.00 11 +GLN C 1.87 0.60 3 +GLN O 1.55 -0.55 4 +GLN OXT 1.48 -0.57 9 +GLN OT2 1.48 -0.57 9 +GLU N 1.83 -0.15 1 +GLU CA 2.265 0.10 2 +GLU CB 2.235 0.00 6 +GLU CG 2.235 0.00 16 +GLU CD 1.87 0.14 9 +GLU OE1 1.66 -0.57 9 +GLU OE2 1.66 -0.57 9 +GLU C 1.87 0.60 3 +GLU O 1.55 -0.55 4 +GLU OXT 1.48 -0.57 9 +GLU OT2 1.48 -0.57 9 +GLUH N 1.83 -0.15 1 +GLUH CA 2.265 0.10 2 +GLUH CB 2.235 0.00 6 +GLUH CG 2.235 0.00 16 +GLUH CD 1.87 0.70 11 +GLUH OE1 1.36 -0.35 11 +GLUH OE2 1.55 -0.35 11 +GLUH C 1.87 0.60 3 +GLUH O 1.55 -0.55 4 +GLUH OXT 1.48 -0.57 9 +GLUH OT2 1.48 -0.57 9 +GLY N 1.83 -0.15 1 +GLY CA 2.235 0.10 5 +GLY C 1.87 0.60 3 +GLY O 1.55 -0.55 4 +GLY OXT 1.48 -0.57 9 +GLY OT2 1.48 -0.57 9 +HIS N 1.83 -0.15 1 +HIS CA 2.265 0.10 2 +HIS CB 2.235 0.00 6 +HIS CG 2.04 0.10 14 +HIS ND1 1.72 -0.10 14 +HIS CD2 2.10 0.10 14 +HIS NE2 1.72 -0.40 14 +HIS CE1 2.10 0.30 14 +HIS C 1.87 0.60 3 +HIS O 1.55 -0.55 4 +HIS OXT 1.48 -0.57 9 +HIS OT2 1.48 -0.57 9 +ILE N 1.83 -0.15 1 +ILE CA 2.265 0.10 2 +ILE CB 2.265 0.00 6 +ILE CG2 2.165 0.00 17 +ILE CG1 2.235 0.00 16 +ILE CD 2.165 0.00 17 +ILE CD1 2.165 0.00 17 +ILE C 1.87 0.60 3 +ILE O 1.55 -0.55 4 +ILE OXT 1.48 -0.57 9 +ILE OT2 1.48 -0.57 9 +LEU N 1.83 -0.15 1 +LEU CA 2.265 0.10 2 +LEU CB 2.235 0.00 6 +LEU CG 2.265 0.00 16 +LEU CD1 2.165 0.00 17 +LEU CD2 2.165 0.00 17 +LEU C 1.87 0.60 3 +LEU O 1.55 -0.55 4 +LEU OXT 1.48 -0.57 9 +LEU OT1 1.48 -0.57 9 +LEU OT2 1.48 -0.57 9 +LYS N 1.83 -0.15 1 +LYS CA 2.265 0.10 2 +LYS CB 2.235 0.00 6 +LYS CG 2.235 0.00 16 +LYS CD 2.235 0.00 8 +LYS CE 2.235 0.25 7 +LYS NZ 1.65 0.75 7 +LYS C 1.87 0.60 3 +LYS O 1.55 -0.55 4 +LYS OXT 1.48 -0.57 9 +LYS OT2 1.48 -0.57 9 +LYSN N 1.83 -0.15 1 +LYSN CA 2.265 0.10 2 +LYSN CB 2.235 0.00 6 +LYSN CG 2.235 0.00 16 +LYSN CD 2.235 0.00 8 +LYSN CE 2.235 0.00 11 +LYSN NZ 1.65 0.00 7 +LYSN C 1.87 0.60 3 +LYSN O 1.55 -0.55 4 +LYSN OXT 1.48 -0.57 9 +LYSN OT2 1.48 -0.57 9 +MET N 1.83 -0.15 1 +MET CA 2.265 0.10 2 +MET CB 2.235 0.00 6 +MET CG 2.235 0.06 16 +MET SD 1.97 -0.12 16 +MET CE 2.165 0.06 17 +MET C 1.87 0.60 3 +MET O 1.55 -0.55 4 +MET OXT 1.48 -0.57 9 +MET OT2 1.48 -0.57 9 +PHE N 1.83 -0.15 1 +PHE CA 2.265 0.10 2 +PHE CB 2.235 0.00 6 +PHE CG 2.04 0.00 16 +PHE CD1 1.99 0.00 16 +PHE CD2 1.99 0.00 16 +PHE CE1 1.99 0.00 16 +PHE CE2 1.99 0.00 16 +PHE CZ 1.99 0.00 16 +PHE C 1.87 0.60 3 +PHE O 1.55 -0.55 4 +PHE OXT 1.48 -0.57 9 +PHE OT2 1.48 -0.57 9 +PRO N 1.83 -0.25 1 +PRO CD 2.235 0.10 6 +PRO CA 2.265 0.10 2 +PRO CB 2.235 0.00 6 +PRO CG 2.235 0.00 6 +PRO C 1.87 0.60 3 +PRO O 1.55 -0.55 4 +PRO OXT 1.48 -0.57 9 +PRO OT2 1.48 -0.57 9 +PRO OD 1.55 -0.25 13 +5HP N 1.83 -0.25 1 +5HP CD 2.235 0.10 6 +5HP CA 2.265 0.10 2 +5HP CB 2.235 0.00 6 +5HP CG 2.235 0.00 6 +5HP C 1.87 0.60 3 +5HP O 1.55 -0.55 4 +5HP OD 1.55 -0.25 13 +5HP OXT 1.48 -0.57 9 +5HP OT2 1.48 -0.57 9 +SER N 1.83 -0.15 1 +SER CA 2.265 0.10 2 +SER CB 2.235 0.25 13 +SER OG 1.55 -0.25 13 +SER C 1.87 0.60 3 +SER O 1.55 -0.55 4 +SER OXT 1.48 -0.57 9 +SER OT2 1.48 -0.57 9 +THR N 1.83 -0.15 1 +THR CA 2.265 0.10 2 +THR CB 2.265 0.25 6 +THR OG1 1.55 -0.25 13 +THR CG2 2.165 0.00 16 +THR C 1.87 0.60 3 +THR O 1.55 -0.55 4 +THR OXT 1.48 -0.57 9 +THR OT2 1.48 -0.57 9 +TRP N 1.83 -0.15 1 +TRP CA 2.265 0.10 2 +TRP CB 2.235 0.00 6 +TRP CG 2.04 -0.03 16 +TRP CD2 2.04 0.10 16 +TRP CE2 2.04 -0.04 16 +TRP CE3 1.99 -0.03 16 +TRP CD1 2.10 0.06 16 +TRP NE1 1.72 -0.06 14 +TRP CZ2 1.99 0.00 16 +TRP CZ3 1.99 0.00 16 +TRP CH2 1.99 0.00 16 +TRP C 1.87 0.60 3 +TRP O 1.55 -0.55 4 +TRP OXT 1.48 -0.57 9 +TRP OT2 1.48 -0.57 9 +TYR N 1.83 -0.15 1 +TYR CA 2.265 0.10 2 +TYR CB 2.235 0.00 6 +TYR CG 2.04 0.00 16 +TYR CD1 1.99 0.00 16 +TYR CE1 1.99 0.00 15 +TYR CD2 1.99 0.00 16 +TYR CE2 1.99 0.00 15 +TYR CZ 2.04 0.25 15 +TYR OH 1.55 -0.25 13 +TYR C 1.87 0.60 3 +TYR O 1.55 -0.55 4 +TYR OXT 1.48 -0.57 9 +TYR OT2 1.48 -0.57 9 +VAL N 1.83 -0.15 1 +VAL CA 2.265 0.10 2 +VAL CB 2.265 0.00 6 +VAL CG1 2.165 0.00 17 +VAL CG2 2.165 0.00 17 +VAL C 1.87 0.60 3 +VAL O 1.55 -0.55 4 +VAL OXT 1.48 -0.57 9 +VAL OT2 1.48 -0.57 9 +HSC N 1.83 -0.15 1 +HSC CA 2.265 0.10 2 +HSC CB 2.235 0.10 6 +HSC CG 2.04 0.15 14 +HSC CD2 1.87 0.20 14 +HSC ND1 1.54 0.05 10 +HSC CE1 1.87 0.45 14 +HSC NE2 1.54 0.05 10 +HSC C 1.87 0.60 3 +HSC O 1.55 -0.55 4 +HSC OXT 1.48 -0.57 9 +HSC OT2 1.48 -0.57 9 +HSD N 1.83 -0.15 1 +HSD CA 2.265 0.10 2 +HSD CB 2.235 0.00 6 +HSD CG 2.04 0.10 14 +HSD ND1 1.54 -0.40 14 +HSD CE1 1.87 0.30 14 +HSD CD2 1.87 0.10 14 +HSD NE2 1.54 -0.10 14 +HSD C 1.87 0.60 3 +HSD O 1.55 -0.55 4 +HSD OXT 1.48 -0.57 9 +HSD OT2 1.48 -0.57 9 +HEM FE 2.13 0.24 10 +HEM E 2.13 0.24 10 +HEM NA 1.54 -0.18 14 +HEM NB 1.54 -0.18 14 +HEM NC 1.54 -0.18 14 +HEM ND 1.54 -0.18 14 +HEM C1A 1.87 0.03 3 +HEM CHA 2.265 0.04 2 +HEM C4D 1.87 0.02 3 +HEM C1B 1.87 0.03 3 +HEM CHB 2.265 0.04 2 +HEM C4A 1.87 0.02 3 +HEM C1C 1.87 0.03 3 +HEM CHC 2.265 0.04 2 +HEM C4B 1.87 0.02 3 +HEM C1D 1.87 0.03 3 +HEM CHD 2.265 0.04 2 +HEM C4C 1.87 0.02 3 +HEM C2A 1.87 -0.02 3 +HEM CAA 2.235 0.04 6 +HEM C3A 1.87 0.02 3 +HEM CMA 2.235 -0.04 17 +HEM CBA 2.235 -0.10 6 +HEM CGA 1.87 0.30 9 +HEM O1A 1.48 -0.50 9 +HEM O2A 1.48 -0.50 9 +HEM C2B 1.87 0.02 3 +HEM CMB 2.235 -0.04 17 +HEM C3B 1.87 -0.05 3 +HEM CAB 2.265 0.03 2 +HEM CBB 2.235 -0.10 6 +HEM C2C 1.87 0.02 3 +HEM CMC 2.235 -0.04 17 +HEM C3C 1.87 -0.05 3 +HEM CAC 2.265 0.03 2 +HEM CBC 2.235 -0.10 6 +HEM C2D 1.87 0.02 3 +HEM CMD 2.235 -0.04 17 +HEM C3D 1.87 -0.02 3 +HEM CAD 2.235 0.04 6 +HEM CBD 2.235 -0.10 6 +HEM CGD 1.87 0.30 9 +HEM O1D 1.48 -0.50 9 +HEM O2D 1.48 -0.50 9 +TML CH1 2.235 0.00 17 +TML CH2 2.235 0.00 17 +TML CH3 2.235 0.00 17 +SO4 S 1.76 0.53 9 +SO4 O1 1.48 -0.62 9 +SO4 O2 1.48 -0.62 9 +SO4 O3 1.48 -0.62 9 +SO4 O4 1.48 -0.62 9 +GDP C2 2.04 0.88 14 +GDP N3 1.54 -0.71 14 +GDP C4 2.04 0.39 14 +GDP C5 2.04 -0.06 14 +GDP C6 1.87 0.69 14 +GDP N1 1.54 -0.25 14 +GDP N9 1.54 -0.11 14 +GDP C8 1.88 0.33 14 +GDP N7 1.54 -0.54 14 +GDP C2* 2.02 0.33 6 +GDP C3* 2.02 0.26 6 +GDP C4* 2.02 0.11 6 +GDP O4* 1.43 -0.37 4 +GDP C1* 2.02 0.21 6 +GDP C5* 2.00 0.01 6 +GDP O5* 1.43 -0.46 4 +GDP PA 1.70 0.90 3 +GDP O1A 1.48 -0.72 4 +GDP O2A 1.48 -0.72 4 +GDP O3A 1.43 -0.67 4 +GDP PB 1.70 0.53 9 +GDP O1B 1.48 -0.62 9 +GDP O2B 1.48 -0.62 9 +GDP O3B 1.48 -0.62 9 +GDP O6 1.55 -0.55 4 +GDP N2 1.83 -0.12 7 +GDP O2 1.55 -0.25 13 +GDP O3 1.55 -0.25 13 +GNP N1 1.54 -0.25 14 +GNP C2 2.04 0.88 14 +GNP N2 1.83 -0.12 7 +GNP N3 1.54 -0.71 14 +GNP C4 2.04 0.39 14 +GNP C5 2.04 -0.06 14 +GNP C6 1.87 0.69 14 +GNP O6 1.55 -0.55 4 +GNP N7 1.54 -0.54 14 +GNP C8 1.88 0.33 14 +GNP N9 1.54 -0.11 14 +GNP C1* 2.02 0.21 6 +GNP C2* 2.02 0.33 6 +GNP O2* 1.55 -0.25 13 +GNP C3* 2.02 0.26 6 +GNP O3* 1.55 -0.25 13 +GNP C4* 2.02 0.11 6 +GNP O4* 1.43 -0.37 4 +GNP C5* 2.00 0.01 6 +GNP O5* 1.43 -0.46 4 +GNP PA 1.70 0.90 3 +GNP O1A 1.48 -0.72 4 +GNP O2A 1.48 -0.72 4 +GNP O3A 1.43 -0.67 4 +GNP PB 1.70 1.11 3 +GNP O1B 1.48 -0.72 4 +GNP O2B 1.48 -0.72 4 +GNP N3B 1.83 -0.15 1 +GNP PG 1.70 0.53 9 +GNP O1G 1.48 -0.62 9 +GNP O2G 1.48 -0.62 9 +GNP O3G 1.48 -0.62 9 +UNK CAL 1.97 2.00 10 +XXX XXX 1.83 0.00 0 +XXX C?? 2.23 0.00 3 +XXX N?? 1.83 0.00 1 +XXX O?? 1.55 -0.55 4 +XXX S?? 1.97 -0.19 18 +XXX P?? 1.87 0.53 9 +XXX FE? 2.13 0.24 10 +XXX MG? 2.13 0.24 10 +XXX F?? 2.13 0.24 10 +XXX BR? 2.13 0.24 10 diff --git a/model/CombinatorialAssembler/libs_DockingLib/ChemAtom.cc b/model/CombinatorialAssembler/libs_DockingLib/ChemAtom.cc new file mode 100644 index 0000000000000000000000000000000000000000..04db47270dda7a076b1a8dda315ae7d5a065c800 --- /dev/null +++ b/model/CombinatorialAssembler/libs_DockingLib/ChemAtom.cc @@ -0,0 +1,69 @@ +#include "ChemAtom.h" + +const float ChemAtom::maxRadius = 3.5; +ChemAtom::ChemAtom(){} + +ChemAtom::ChemAtom(const Vector3& p, const char ch, const unsigned int aid, + const unsigned int resid, const char* const t, const char resTp, + const int chemType, const float radius, const float charge): + Atom(p, ch, aid, resid, t, resTp), + chemType_(chemType), radius_(radius), charge_(charge), epsilon_(-0.01), + hbType_(NONE), hbDirection_(Vector3(0,0,0)), ASA_(0) { + setPolarity(); +} + +ChemAtom::ChemAtom(const std::string& PDBrec, bool cif) : + Atom(PDBrec, cif), + chemType_(0), radius_(1.5), charge_(0), epsilon_(-0.01), hbType_(NONE), hbDirection_(Vector3(0,0,0)), ASA_(0) { + setPolarity(); +} + +void ChemAtom::setHBData(const HB_TYPE hbType, const Vector3& hbDirection) { + // set type + if(hbType_ == NONE) hbType_ = hbType; + if((hbType_ == DONOR && hbType == ACCEPTOR) || (hbType_ == ACCEPTOR && hbType == DONOR)) hbType_ = HB_BOTH; + + // set direction + if(hbDirection_.isZero()) { + hbDirection_ = hbDirection; + } +} + +void ChemAtom::setPolarity() { + const char res = residueType(); + const char* atomT = type(); + + bool nonPolar = true; + + if (atomT[1] == 'S') nonPolar = true; + + if (atomT[1] != 'C') nonPolar = false; + if ((atomT[2] == 'A' || atomT[2] == 'B' ) && atomT[3] == ' ') nonPolar = true; + if ( atomT[2] == 'H') nonPolar = true; + if ( atomT[2] == 'G') { + if (res == 'N') nonPolar = false; + nonPolar = true; + } + if ( atomT[2] == 'D') { + if (res == 'Q') nonPolar = false; + nonPolar = true; + } + if ( atomT[2] == 'E') { + if (res == 'H') nonPolar = false; + nonPolar = true; + } + if ( atomT[2] == 'Z') { + if (res == 'R') nonPolar = false; + nonPolar = true; + } + if ( atomT[2] == 'Z') { + if (res == 'R') nonPolar = false; + nonPolar = true; + } + + + if(fabs(charge_) > 0.4 && nonPolar) { + //Logger::warningMessage() << "Non Polar atom " << *this << " charge = " << charge_ << endl; + } + isNonPolar_ = nonPolar; +} diff --git a/model/CombinatorialAssembler/libs_DockingLib/ChemAtom.h b/model/CombinatorialAssembler/libs_DockingLib/ChemAtom.h new file mode 100644 index 0000000000000000000000000000000000000000..b20eea21388b56ff3b34937af60a92868c07662e --- /dev/null +++ b/model/CombinatorialAssembler/libs_DockingLib/ChemAtom.h @@ -0,0 +1,156 @@ +#ifndef CHEM_ATOM_H +#define CHEM_ATOM_H + +#include "Atom.h" + +enum HB_TYPE {DONOR = 0, ACCEPTOR = 1, HB_BOTH = 2, NONE = 3}; +enum MOL2_TYPE {C2 = 0, C3 =1 , Car = 2, Ccat = 3, N2 = 4, N4 = 5, Nam = 6, + Nar = 7, Npl3 = 8, O2 = 9, O3 = 10, Oco2 = 11, P3 = 12, S3 = 13, UNK_MOL2_TYPE = 14 }; + +/* +CLASS + ChemAtom + + This is a class that addes additional atrributes to Atom + +KEYWORD + ChemAtom, radius, charge, type, ASA, solvation + +AUTHORS + Dina Schneidman (duhovka@tau.ac.il) + + copyright: GAMBA Group , Tel-Aviv Univ. Israel, 2004. + +GOALS + ChemAtom extends Atom and has 4 additional attributes: chemical + type, radius, charge and probability of the atom of being steady. + +CHANGES LOG +
    +
+ +USAGE +*/ + +class ChemAtom : public Atom { //, public EnergyAtom { + public: + // GROUP: Constructors + + //// empty atom + ChemAtom(); + + //// construction with atom data + ChemAtom(const Vector3& p, const char ch, const unsigned int aid, + const unsigned int resid, const char* const t, const char resTp, + const int chem_type=0, const float rad=1.5, const float charge=0); + + ChemAtom(const std::string& PDBrec, bool cif = false); + + + // GROUP: modifiers + + //// set chemical type + void setChemType(const int chem_type) { + if(chem_type <= 0 || chem_type > 18) { + std::cerr << "Error in ChemType for atom " << *this << std::endl; + } else { + chemType_ = chem_type; + } + } + + //// set radius + void setRadius(const float r) { radius_ = r; } + + //// set epsilon + void setEpsilon(const float e) { epsilon_ = e; } + + //// set charge + void setCharge(const float c) { charge_ = c; } + + //// set HB data + void setHBData(const HB_TYPE hbType, const Vector3& hbDirection); + + //// set probability of being steady + void setSteadyProb(const float p) { steadyProb_ = p; } + + //// set ASA + void setASA(const float ASA) { ASA_ = ASA; } + + //// set atom position + void setPosition(const Vector3& v) { update(v); } + + //// set mol2 type + void setMol2Type(const MOL2_TYPE t) { mol2Type_ = t; } + + // GROUP: Inspectors + + //// get atom chemical type for ACE computation + int getChemType() const { + if(chemType_ <= 0 || chemType_ > 18) return 0; + return chemType_; + } + + //// get atom radius + float getRadius() const { return radius_; } + + //// get epsilon value + float getEpsilon() const { return epsilon_; } + + //// + bool isHydrogen() const { return isH(); } + + //// get atom charge + float getCharge() const { return charge_; } + + //// + bool isDonor() const { return (hbType_ == HB_BOTH || hbType_ == DONOR); } + + //// + bool isAcceptor() const { return (hbType_ == HB_BOTH || hbType_ == ACCEPTOR); } + + //// returns direction of h-donor/acceptor, in case of undefined direction zero vector is returned + const Vector3& getHBDirection() const { return hbDirection_; } + + //// get probability of being steady + float getSteadyProb() const { return steadyProb_; } + + //// get ASA + float getASA() const { return ASA_; } + + //// + bool isNonPolar() const { return isNonPolar_; } + + //// + MOL2_TYPE getMol2Type() const { return mol2Type_; } + + Vector3 position() const {return (Atom(*this)).position(); } + + friend Vector3& operator*=(ChemAtom &v,const RigidTrans3 &rt) { + //cout << "ChemAtom *= RigidTrans3" << endl; + v += (rt.rotation() * v) + rt.translation() - v; + if (!v.hbDirection_.isZero()) + v.hbDirection_ = rt.rotation() * v.hbDirection_; + return v; + } + + + //// maximal atom radius + static const float maxRadius; + + +private: + void setPolarity(); + + private: + int chemType_; // type required for ACE computation + float radius_; + float charge_; + float epsilon_; // for vdw computation + HB_TYPE hbType_; + Vector3 hbDirection_; + bool isNonPolar_; + float steadyProb_; + float ASA_; + MOL2_TYPE mol2Type_; +}; +#endif /* IMP_CHEMATOM_H */ diff --git a/model/CombinatorialAssembler/libs_DockingLib/ChemLib.cc b/model/CombinatorialAssembler/libs_DockingLib/ChemLib.cc new file mode 100644 index 0000000000000000000000000000000000000000..b8817778116ed3c16b207752e5a8ac701d01ccb8 --- /dev/null +++ b/model/CombinatorialAssembler/libs_DockingLib/ChemLib.cc @@ -0,0 +1,121 @@ +#include "ChemLib.h" + +#include + +bool ChemEntry::isSameEntry(const std::string residueName, const std::string atomName) const { + if(residueName == residueName_ && atomName == atomName_) + return true; + return false; +} + +ChemLib::ChemLib(const std::string libFileName) { + loadLibrary(libFileName); +} + +void ChemLib::loadLibrary(const std::string libFileName) { + std::ifstream libFile(libFileName); + if(!libFile) { + std::cerr << "Can't find library file: " << libFileName << std::endl; + exit(1); + } + + std::string line; + while (!libFile.eof()) { + getline(libFile, line); + boost::trim(line); // remove all spaces + // skip comments + if (line[0] == '#' || line[0] == '\0') continue; + std::vector < std::string > splitResults; + boost::split(splitResults, line, boost::is_any_of(" "), boost::token_compress_on); + for(unsigned int i=0; i::const_iterator iter = libVector.begin(); + iter != libVector.end(); iter++) + if((*iter).isSameEntry(residueName, atomName)) { + libEntry = &(*iter); + break; + } + return libEntry; +} + +float ChemLib::getAtomRadius(const char* residueName, const char* atomPDBType) const { + const ChemEntry* libEntry = getLibEntry(residueName, atomPDBType); + if(libEntry!=NULL) + return libEntry->getEntryRadius(); + return 1.5; +} + +float ChemLib::getAtomCharge(const char* residueName, const char* atomPDBType) const { + const ChemEntry* libEntry = getLibEntry(residueName, atomPDBType); + if(libEntry!=NULL) + return libEntry->getEntryCharge(); + return 0.0; +} + +int ChemLib::getAtomChemType(const char* residueName, const char* atomPDBType) const { + const ChemEntry* libEntry = getLibEntry(residueName, atomPDBType); + if(libEntry!=NULL) + return libEntry->getEntryChemType(); + return 0; +} + +void ChemLib::printLibrary(std::ostream& outFile) const { + for(std::vector::const_iterator iter = libVector.begin(); + iter != libVector.end(); iter++) { + outFile << *iter << std::endl; + } +} diff --git a/model/CombinatorialAssembler/libs_DockingLib/ChemLib.h b/model/CombinatorialAssembler/libs_DockingLib/ChemLib.h new file mode 100644 index 0000000000000000000000000000000000000000..7907bba0f343d59f83db9c989cc80d19cf42f52c --- /dev/null +++ b/model/CombinatorialAssembler/libs_DockingLib/ChemLib.h @@ -0,0 +1,127 @@ +#ifndef CHEM_LIB +#define CHEM_LIB + +#include +#include +#include +#include + +/* +CLASS + ChemEntry + + This is a class that keeps protein atom attributes: type, radius and charge + +KEYWORD + ChemEntry, ChemAtom radius, charge, type + +AUTHORS + Dina Schneidman (duhovka@tau.ac.il) + + copyright: GAMBA Group , Tel-Aviv Univ. Israel, 2004. + +GOALS + ChemEntry keeps atom attribures that are not given in PDB file. + +CHANGES LOG +
    +
+ +USAGE +*/ +class ChemEntry { + public: + // GROUP: Inspectors + //// + bool isSameEntry(const std::string residueName, const std::string atomName) const; + + //// returns radius + float getEntryRadius() const { return radius_; } + + //// returns charge + float getEntryCharge() const { return charge_; } + + //// returns atom type + int getEntryChemType() const { return chemType_; } + + //// read entry + friend std::istream& operator>>(std::istream& s, ChemEntry &entry) { + return s >> entry.residueName_ >> entry.atomName_ >> entry.radius_ >> entry.charge_ >> entry.chemType_; + } + + //// output entry + friend std::ostream& operator<<(std::ostream& s, const ChemEntry &entry) { + s << entry.residueName_ << ' ' << entry.atomName_ << ' ' + << entry.radius_ << ' ' << entry.charge_ << ' ' << entry.chemType_; + return s; + } + + public: + std::string residueName_; + std::string atomName_; + float radius_; + float charge_; + int chemType_; +}; + + +/* +CLASS + ChemLib + + This is a class that serves as a library for protein attributes: + type, radius and charge. + +KEYWORD + ChemEntry, ChemAtom, ChemLib radius, charge, type + +AUTHORS + Dina Schneidman (duhovka@tau.ac.il) + + copyright: GAMBA Group , Tel-Aviv Univ. Israel, 2004. + +GOALS + ChemLib keeps attribures for each of the 20 amino acids atoms. These + attribures are not given in PDB file. + +CHANGES LOG +
    +
+ +USAGE + The ChemLib is initialized with the special library file (mol/lib/gamb++/chem.lib). + +*/ +class ChemLib { + public: + // GROUP: Constructors + + //// Init and load library + ChemLib(const std::string libFileName); + void loadLibrary(const std::string libFileName); + + // GROUP: Inspectors + + //// get radius + float getAtomRadius(const char* residueName, const char* atomPDBType) const; + + //// get charge + float getAtomCharge(const char* residueName, const char* atomPDBType) const; + + //// get chemical type + int getAtomChemType(const char* residueName, const char* atomPDBType) const; + + //// print function + void printLibrary(std::ostream& outFile) const; + + //// get the whole lib entry + const ChemEntry* getLibEntry(const char* residueName, const char* atomPDBType) const; + + private: + const ChemEntry* searchLibEntry(const std::string residueName, const std::string atomName) const; + + private: + std::vector libVector; +}; + +#endif diff --git a/model/CombinatorialAssembler/libs_DockingLib/ChemMolecule.cc b/model/CombinatorialAssembler/libs_DockingLib/ChemMolecule.cc new file mode 100644 index 0000000000000000000000000000000000000000..5bbe6b79ac8a8ce368e8d6e060dc629b9ee79011 --- /dev/null +++ b/model/CombinatorialAssembler/libs_DockingLib/ChemMolecule.cc @@ -0,0 +1,309 @@ +#include "ChemMolecule.h" +#include "SASurface.h" + +/* ARG,LYS,GLU,GLN,ASP, + ASN,PRO,GLY,SER,THR, + HIS,ALA,ILE,LEU,VAL, + PHE,CYS,MET,TYR,TRP */ +const float ChemMolecule::flexProbs[MAX_AA]={0.24, 0.38, 0.14, 0.23, 0.06, + 0.11, 0.05, 0.05, 0.06, 0.07, + 0.05, 0.00, 0.13, 0.13, 0.08, + 0.015,0.02, 0.24, 0.07, 0.02, + 0.0//OTHER +}; + +const float ChemMolecule::maxCaDist[MAX_AA]={8.0, 6.3, 5.5, 5.3, 4.2, + 4.2, 3.0, 2.0, 3.0, 3.0, + 5.4, 3.0, 4.5, 4.4, 3.1, + 5.7, 3.5, 5.7, 7.0, 6.6, + 0.0//OTHER +}; + +bool compareFragment(ChemMolecule::Fragment f1, ChemMolecule::Fragment f2) { + if(f1.first != f2.first) { //different chains + return f1.first < f2.first; + } + // same chain + return f1.second.first < f2.second.first; +} + +void ChemMolecule::loadMolecule(std::istream &molFile, const ChemLib& chemLib, const PDB::Selector& selector) { + Molecule::readAllPDBfile(molFile, selector); + for(Molecule::iterator molIter = begin(); molIter != end(); molIter++) { + if(!molIter->isHydrogen()) { + const ChemEntry* entry=chemLib.getLibEntry(molIter->residueName(), molIter->type()); + if(entry != NULL) { + molIter->setRadius(entry->getEntryRadius()); + molIter->setCharge(entry->getEntryCharge()); + molIter->setChemType(entry->getEntryChemType()); + } + } + } + + centroid_ = centroid(); + setIsProtein(); +} + +void ChemMolecule::readAllPDBfile(std::istream &molFile, const PDB::Selector& selector) { + Molecule::readAllPDBfile(molFile, selector); + computeASAperAtom(); + centroid_ = centroid(); + setIsProtein(); +} + +float ChemMolecule::computeASAperAtom() { + SASurface sas(*this, 1.8, 10); //probe radius = 1.8, density = 10 + float asa = sas.computeASAForAtoms(); + Logger::infoMessage() << "ASA of the molecule = " << asa << std::endl; + return asa; +} + +void ChemMolecule::computeSteadyProbs() { + Vector3 caAtom; + for(Molecule::iterator molIter = begin(); molIter != end(); molIter++) { + RESIDUE_INDEX resIndex = RESIDUE_INDEX(*molIter); + if(molIter->isBackbone()) { + // glycine backbone can be flexible, since it has no side-chain + if(resIndex == GLY) + molIter->setSteadyProb(1-flexProbs[resIndex]); + else { + molIter->setSteadyProb(1-flexProbs[OTHER]); + if(molIter->isCA()) + caAtom = molIter->position(); + } + } else { + float distanceFromCa = caAtom.dist(molIter->position()); + float normFactor= distanceFromCa/maxCaDist[resIndex]; + molIter->setSteadyProb(1-flexProbs[resIndex]*normFactor); + } + } +} + +void ChemMolecule::setIsProtein() { + for(Molecule::iterator molIter = begin(); molIter != end(); molIter++) { + if(molIter->isCA()) { + isProtein_ = true; + return; + } + } + isProtein_ = false; +} + +unsigned int ChemMolecule::readBindingSite(const std::string fileName) { + bindingSites_.push_back(std::set()); + return readSiteFile(fileName, bindingSites_.back()); +} + +unsigned int ChemMolecule::readBlockingSite(const std::string fileName) { + blockingSites_.push_back(std::set()); + return readSiteFile(fileName, blockingSites_.back()); +} + +unsigned int ChemMolecule::readSiteFile(const std::string fileName, std::set& site) { + std::ifstream file(fileName.c_str()); + if(!file) { + std::cerr << "Can't open file " << fileName << std::endl; + Logger::errorMessage() << "Can't open file " << fileName << std::endl; + exit(1); + } + + char chainId; + std::string residueSequenceID; + + while(!file.eof()) { + std::string line; + getline(file, line); + boost::trim(line); + if(line.length() == 0) continue; + int entry = -1; + + char record[line.size()+1]; + strcpy(record, line.c_str()); + + char* splittedRecord = strtok(record, " "); + residueSequenceID = std::string(splittedRecord); + + splittedRecord = strtok (NULL, " "); + if(splittedRecord != NULL) { + chainId = splittedRecord[0]; + } else { + chainId = ' '; + } + //cerr << residueSequenceID << ";" << chainId << ";" << endl; + entry = residueEntry(chainId, residueSequenceID); + + if(entry != -1) { + site.insert(entry); + } else { + Logger::errorMessage() << "Invalid line in site file: " << line << std::endl; + std::cerr << "Invalid line in site file: " << line << std::endl; + exit(1); + } + } + + Logger::infoMessage() << site.size() << " residues were read from site file: " << fileName << std::endl; + file.close(); + return site.size(); +} + + +const std::set& ChemMolecule::getBindingSite(unsigned int siteNumber) const { + const static std::set emptySet; + if(bindingSites_.size() > siteNumber) { + return bindingSites_[siteNumber]; + } + return emptySet; +} + +const std::set& ChemMolecule::getBlockingSite(unsigned int siteNumber) const { + const static std::set emptySet; + if(blockingSites_.size() > siteNumber) { + return blockingSites_[siteNumber]; + } + return emptySet; +} + +const ChemAtom& ChemMolecule::getChemAtom(int atomIndex) const { + for(Molecule::const_iterator molIter = begin(); molIter != end(); molIter++) { + if((int)molIter->atomIndex() == atomIndex) { + return *molIter; + } + } + Logger::errorMessage() << "Input Error: Can't find atom with atom index " << atomIndex << "!" << std::endl; + std::cerr << "Input Error: Can't find atom with atom index " << atomIndex << "!" << std::endl; + exit(1); +} + +double ChemMolecule::getVolume() const { + double volume = 0.0; + double c = (4.0/3.0)*M_PI; + for(Molecule::const_iterator molIter = begin(); molIter != end(); molIter++) { + double r = molIter->getRadius(); + volume += c * r * r * r; + } + return volume; +} + +const std::vector& ChemMolecule::getFragments() const { + if(fragments_.size() == 0) { + ChemMolecule* nonConstThis = const_cast(this); + nonConstThis->computeFragments(); + } + return fragments_; +} + +void ChemMolecule::computeFragments() { + // calculate endpoints + char currChain; + int firstResIndex, prevResIndex; + bool currChainSet = false; + for(auto i = begin(); i != end(); i++) { + if(!i->isCA() || i->getAtomEntryType() == HETATM) continue; + char chain = i->chainId(); + int resIndex = i->residueIndex(); + //if(i->getAtomEntryType() == HETATM) continue; + // one more residue of the same chain - advance + if(currChainSet && currChain == chain && + resIndex > prevResIndex) { + prevResIndex = resIndex; + } else { // new chain + if(currChainSet) { // save currChain + ResidueRange range(firstResIndex, prevResIndex); + Fragment fragment(std::make_pair(currChain, range)); + fragments_.push_back(fragment); + } + // update + currChain = chain; + firstResIndex = prevResIndex = resIndex; + currChainSet = true; + } + } + // save last fragment + if(currChainSet) { // save currChain + ResidueRange range(firstResIndex, prevResIndex); + Fragment fragment(std::make_pair(currChain, range)); + fragments_.push_back(fragment); + } + + std::sort(fragments_.begin(), fragments_.end(), compareFragment); + + for(int i=0; i<(int)fragments_.size(); i++) { + std::cout << "Fragment " << i << " chainId " + << fragments_[i].first << " range " + << fragments_[i].second.first << ":" + << fragments_[i].second.second << std::endl; + } +} + +void ChemMolecule::addMol2Type() { + + + for(auto i = begin(); i != end(); i++) { + const char* type = i->type(); + char res = i->residueType(); + i->setMol2Type(UNK_MOL2_TYPE); + if(i->isN()) i->setMol2Type(N2); + if(i->isC()) i->setMol2Type(C2); + if(i->isCA()) i->setMol2Type(C3); + if(i->isO()) i->setMol2Type(O2); // TODO: the last residue should be O.co2 + if(i->isCB()) i->setMol2Type(C3); + + // carbon + if(i->isCD() && (res == 'K' || res == 'P' || res == 'R')) i->setMol2Type(C3); + if(i->isCD() && (res == 'Q' || res == 'E' )) i->setMol2Type(C2); + + if(i->isCG() && (res == 'K' || res == 'P' || res == 'R' || res == 'M' || res == 'L' || res == 'Q' || res == 'E' )) i->setMol2Type(C3); + if(i->isCG() && (res == 'D' || res == 'N')) i->setMol2Type(C2); + if(i->isCG() && (res == 'H' || res == 'F' || res == 'W' || res == 'Y')) i->setMol2Type(Car); + if(i->isCZ() && (res == 'F' || res == 'Y')) i->setMol2Type(Car); + if(i->isCZ() && res == 'R') i->setMol2Type(Ccat); + if((i->isCD1() || i->isCD2()) && (res == 'F' || res == 'W' || res == 'Y' || res == 'H')) i->setMol2Type(Car); + if((i->isCD1() || i->isCD2()) && (res == 'L' || res == 'I')) i->setMol2Type(C3); + if(i->isCE() || i->isCG1() || i->isCG2()) i->setMol2Type(C3); + if(i->isCE1() || i->isCE2() || i->isCE3() || i->isCZ2() || i->isCZ3() || i->isCH2() ) i->setMol2Type(Car); + + // for RNA + if (i->isSugarCarbon()) + i->setMol2Type(C3); + + // oxygen + if((i->isOD1() || i->isOE1()) && ((res == 'N' || res == 'Q'))) i->setMol2Type(O2); + if((i->isOD1() || i->isOE1()|| i->isOD2()|| i->isOE2()) && (res == 'D' || res == 'E')) i->setMol2Type(Oco2); + if(i->isOH() || i->isOG()|| i->isOG1()) i->setMol2Type(O3); + + // for RNA + if (i->isSugarOxygen()) + i->setMol2Type(O3); + if (i->isPhosphateOxygen()) + i->setMol2Type(Oco2); + + // nitrogen + if(i->isNH1() || i->isNH2() || i->isNE()) i->setMol2Type(Npl3); + if((i->isND2() && res == 'N') || (i->isNE2() && res == 'Q')) i->setMol2Type(Nam); + if(((i->isND1() || i->isNE2()) && res == 'H') || (i->isNE1() && res == 'W')) i->setMol2Type(Nar); + if(i->isNZ()) i->setMol2Type(N4); + + // for RNA + if (i->isNitrogenousBaseAtom()) { + if (type[1] == 'N') { + if ((type[2] == '4' && res == 'C') || (type[2] == '6' && res == 'A') || (type[2] == '2' && res == 'G')) + i->setMol2Type(Npl3); + else + i->setMol2Type(Nar); + } + else if (type[1] == 'O') + i->setMol2Type(O2); + else if (type[1] == 'C') + i->setMol2Type(Car); + } + + // sulfur + if(i->isSD() || i->isSG()) i->setMol2Type(S3); + + // phosphate + if(i->isP()) i->setMol2Type(P3); + + if(i->getMol2Type() == UNK_MOL2_TYPE) + std::cout << "Can't assign mol2 type:" << type[0] << type[1] << type[2] << type[3] << ":" << std::endl; + } +} diff --git a/model/CombinatorialAssembler/libs_DockingLib/ChemMolecule.h b/model/CombinatorialAssembler/libs_DockingLib/ChemMolecule.h new file mode 100644 index 0000000000000000000000000000000000000000..904d465219d5aaeb5851f641dcb1bc37fd59f3bf --- /dev/null +++ b/model/CombinatorialAssembler/libs_DockingLib/ChemMolecule.h @@ -0,0 +1,132 @@ +#ifndef CHEM_MOL_H +#define CHEM_MOL_H + +#include "ChemAtom.h" +#include "ResidueIndexedMolecule.h" +#include "ChemLib.h" +#include "PDB.h" +#include "Logger.h" +#include "EnergyAtom.h" + +#include +#include +#include + +/* +CLASS + ChemMolecule + + This is a class that is a container of ChemAtoms + +KEYWORD + ChemAtom, radius, charge, type + +AUTHORS + Dina Schneidman (duhovka@tau.ac.il) + + copyright: GAMBA Group , Tel-Aviv Univ. Israel, 2004. + +GOALS + ChemMolecule is a molecule of ChemAtoms. ChemAtom extends Atom and has 4 + additional attributes: chemical type, radius, charge and probability of + the atom of being steady. The type, radius and charge are taken from + ChemLib. This library is initiated with chem.lib file. + The chemical types are from: + Zhang,C., Cornette,J.L. and DeLisi,C. (1997) Determination of atomic + desolvation energies from the structures of crystallized + proteins. J. Mol. Biol., 267, 707 726. + This types are only for protein atoms! + The radii are taken from CHARMM, however they are slightly bigger + to account for missing hydrogens. The charges are from CHARMM too. + The probabilities of being steady were computed based on the maximal + distance between C-alpha and side-chain atoms of that residue. The + lower the distance the higher is the prob. of being steady. +CHANGES LOG +
    +
+ +USAGE + ChemMolecule chemMolecule; + ifstream pdbFile("mol.pdb"); + ChemLib chemLib("chem.lib"); + chemMolecule.loadMolecule(pdbFile, chemLib); +*/ +class ChemMolecule : public ResidueIndexedMolecule { +private: + static const float flexProbs[]; + static const float maxCaDist[]; + +public: + //// load molecule from PDB file, using chemLib for attributes. + void loadMolecule(std::istream &molFile, const ChemLib& chemLib, const PDB::Selector& selector = PDB::WaterHydrogenUnSelector()); + + //// reads and assigns default values for radius, charge and chemType + void readAllPDBfile(std::istream &molFile, const PDB::Selector& selector = PDB::WaterHydrogenUnSelector()); + + //// compute probabilities of being steady + void computeSteadyProbs(); + + //// read and save binding site, returns number of residues in site + unsigned int readBindingSite(const std::string fileName); + + //// read and save blocking site, returns number of residues in site + unsigned int readBlockingSite(const std::string fileName); + + //// + const std::set& getBindingSite(unsigned int siteNumber = 0) const; + + //// + const std::set& getBlockingSite(unsigned int siteNumber = 0) const; + + //// + const ChemAtom& getChemAtom(int atomIndex) const; + + //// + const Vector3& getCentroid() const { return centroid_; } + + bool isProtein() const { return isProtein_; } + + //// get the volume by summing the volume of all atoms + double getVolume() const; + + typedef std::pair ResidueRange; + typedef std::pair Fragment; + + //// get the fragments + const std::vector& getFragments() const; + + //// add mol2 atom type + void addMol2Type(); + + //// compute accessible surface area + float computeASAperAtom(); +private: + //// read site file and return number of reasidues that were read + unsigned int readSiteFile(const std::string fileName, std::set& site); + + //// checks if the molecule is protein (if there is at least one Ca atom) + void setIsProtein(); + + //// compute fragments of the same chain + void computeFragments(); + +protected: + //// entries of binding site residues according to ResidueIndexedMolecule + std::vector > bindingSites_; + + //// entries of blocking site residues according to ResidueIndexedMolecule + std::vector > blockingSites_; + + //// + Vector3 centroid_; + + //// + bool isProtein_; + + //// fragments of the same chain in the molecule + std::vector fragments_; +}; + +bool compareFragment(ChemMolecule::Fragment f1, ChemMolecule::Fragment f2); + +#endif diff --git a/model/CombinatorialAssembler/libs_DockingLib/Common.cc b/model/CombinatorialAssembler/libs_DockingLib/Common.cc new file mode 100644 index 0000000000000000000000000000000000000000..8dd93924509b7b31b5fd2e3ff2e6018ada673128 --- /dev/null +++ b/model/CombinatorialAssembler/libs_DockingLib/Common.cc @@ -0,0 +1,41 @@ +#include "Common.h" + +void Common::checkFile(const char* fileName, std::ifstream& file) { + if(!file) { + std::cerr << "Can't open file: " << fileName << std::endl; + Logger::errorMessage() << "Can't open file: " << fileName << std::endl; + exit(1); + } +} + +void Common::readChemMolecule(const std::string fileName, ChemMolecule& mol, const ChemLib& protLib) { + std::ifstream molFile(fileName.c_str()); + checkFile(fileName.c_str(), molFile); + mol.loadMolecule(molFile, protLib); + molFile.close(); + Logger::infoMessage() << "Chem molecule: " << mol.size() << " atoms were read" << std::endl; +} + +void Common::readChemMolecule(const std::string fileName, ChemMolecule& mol) { + std::ifstream molFile(fileName.c_str()); + checkFile(fileName.c_str(), molFile); + mol.readPDBfile(molFile, PDB::WaterHydrogenUnSelector()); + molFile.close(); + Logger::infoMessage() << "Chem molecule: " << mol.size() << " atoms were read" << std::endl; +} + +void Common::readHydrogenMolecule(const std::string fileName, Molecule& mol) { + std::ifstream molFile(fileName.c_str()); + checkFile(fileName.c_str(), molFile); + mol.readAllPDBfile(molFile, PDB::WaterUnSelector()); + molFile.close(); + Logger::infoMessage() << "Hydrogen molecule: " << mol.size() << " atoms were read" << std::endl; +} + +void Common::readSurface(const std::string fileName, Surface& surface) { + std::ifstream surfFile(fileName.c_str()); + checkFile(fileName.c_str(), surfFile); + surface.readShouFile(surfFile); + surfFile.close(); + Logger::infoMessage() << "Surface: " << surface.size() << " surface points were read" << std::endl; +} diff --git a/model/CombinatorialAssembler/libs_DockingLib/Common.h b/model/CombinatorialAssembler/libs_DockingLib/Common.h new file mode 100644 index 0000000000000000000000000000000000000000..f3508b6595ea91f184530530241f25f8ac492cae --- /dev/null +++ b/model/CombinatorialAssembler/libs_DockingLib/Common.h @@ -0,0 +1,21 @@ +#ifndef COMMON_H +#define COMMON_H + +#include +#include + +#include "ChemMolecule.h" + +#include + +class Common { +public: + + static void checkFile(const char* fileName, std::ifstream& file); + static void readChemMolecule(const std::string fileName, ChemMolecule& mol, const ChemLib& protLib); + static void readChemMolecule(const std::string fileName, ChemMolecule& mol); + static void readHydrogenMolecule(const std::string fileName, Molecule& mol); + static void readSurface(const std::string fileName, Surface& surface); +}; + +#endif //COMMON_H diff --git a/model/CombinatorialAssembler/libs_DockingLib/CrossLink.cc b/model/CombinatorialAssembler/libs_DockingLib/CrossLink.cc new file mode 100644 index 0000000000000000000000000000000000000000..30ed0f7b06ef835fe93d8841bce355feff159433 --- /dev/null +++ b/model/CombinatorialAssembler/libs_DockingLib/CrossLink.cc @@ -0,0 +1,140 @@ +#include "CrossLink.h" + +#include + +#include + +int readCrossLinkFile(const std::string& fileName, + std::vector& crossLinks, + bool addReverse) { + std::ifstream s(fileName); + if (!s) { + std::cerr << "Can't find cross links file " << fileName << std::endl; + exit(0); + } + CrossLink cl; + while (s >> cl) { + if(! cl.isInList(crossLinks)) { // check for duplicates + + if(!addReverse) { + // check reversed for duplicates + CrossLink clReversed = cl.getReversed(); + if(! clReversed.isInList(crossLinks)) { + crossLinks.push_back(cl); + } + } else { + crossLinks.push_back(cl); + } + + } else { + // TODO: check if shorter distance restraint and replace if needed + std::cerr << "Duplicate cross link " << cl << std::endl; + } + + // add reverse + if(addReverse) { + CrossLink clReversed = cl.getReversed(); + if(! clReversed.isInList(crossLinks)) { + crossLinks.push_back(clReversed); + } + } + } + return crossLinks.size(); +} + +void writeCrossLinkFile(const std::string& fileName, + const std::vector& crossLinks) { + std::ofstream ofile(fileName); + for (unsigned int i = 0; i < crossLinks.size(); i++) { + ofile << crossLinks[i]; + ofile << std::endl; + } + ofile.close(); +} + +void writeXLAnalizerFile(const std::string& fileName, + const std::vector& crossLinks) { + std::ofstream ofile(fileName); + ofile << "id,Protein1,Protein2,AbsPos1,AbsPos2,score" << std::endl; + for (unsigned int i = 0; i < crossLinks.size(); i++) { + ofile << i+1 << "," << crossLinks[i].getChain1() << "," << crossLinks[i].getChain2() << "," + << crossLinks[i].getResidue1() << "," << crossLinks[i].getResidue2() + << ",100" << std::endl; + ofile << std::endl; + } + ofile.close(); +} + +std::ostream& operator<<(std::ostream& s, const CrossLink& cl) { + s << cl.residueNumber1_ << " "; + if (cl.chainId1_ == " ") + s << "-"; + else + s << cl.chainId1_; + + s << " " << cl.residueNumber2_ << " "; + if (cl.chainId2_ == " ") + s << "-"; + else + s << cl.chainId2_; + s << " " << cl.minDistance_ << " " << cl.maxDistance_; + return s; +} + +std::istream& operator>>(std::istream& s, CrossLink& cl) { + + std::string line; + std::getline(s, line); + boost::trim(line); // remove spaces at the beginning/end of the line + if(line.length()==0) return s; + // skip comments + if (line[0] == '#' || line[0] == '\0' || !isdigit(line[0])) return s; + + std::vector split_results; + boost::split(split_results, line, boost::is_any_of("\t "), + boost::token_compress_on); + + if (split_results.size() >= 5 && split_results.size() <= 7) { + cl.residueSequenceID1_ = split_results[0]; + cl.chainId1_ = split_results[1]; + cl.residueSequenceID2_ = split_results[2]; + cl.chainId2_ = split_results[3]; + cl.residueNumber1_ = std::stoi(cl.residueSequenceID1_); + cl.residueNumber2_ = std::stoi(cl.residueSequenceID2_); + cl.maxDistance_ = std::stof(split_results[4]); + + if(split_results.size() >= 6) { + cl.minDistance_ = std::stof(split_results[4]); + cl.maxDistance_ = std::stof(split_results[5]); + } + + if(split_results.size() == 7) { + cl.weight_ = std::stof(split_results[6]); + } + } + + if (cl.chainId1_ == "-") cl.chainId1_ = " "; + if (cl.chainId2_ == "-") cl.chainId2_ = " "; + // std::cerr << cl << std::endl; + return s; +} + +bool CrossLink::operator==(const CrossLink& cl) const { + return (residueNumber1_ == cl.residueNumber1_ && residueNumber2_ == cl.residueNumber2_ && + residueSequenceID1_ == cl.residueSequenceID1_ && residueSequenceID2_ == cl.residueSequenceID2_ && + chainId1_ == cl.chainId1_ && chainId2_ == cl.chainId2_); +} + +bool CrossLink::isInList(const std::vector& crossLinks) const { + for(unsigned int i = 0; i +#include +#include + +class CrossLink { + public: + CrossLink() + : residueNumber1_(0), chainId1_(" "), + residueNumber2_(0), chainId2_(" "), + minDistance_(0.0), maxDistance_(0.0), weight_(1.0) {} + + CrossLink(int residueNumber1, std::string chainId1, + int residueNumber2, std::string chainId2, + float minDistance, float maxDistance, float weight=1.0) + : residueNumber1_(residueNumber1), residueSequenceID1_(std::to_string(residueNumber1)), chainId1_(chainId1), + residueNumber2_(residueNumber2), residueSequenceID2_(std::to_string(residueNumber2)), chainId2_(chainId2), + minDistance_(minDistance), maxDistance_(maxDistance), weight_(weight) {} + + + int getResidue1() const { return residueNumber1_; } + int getResidue2() const { return residueNumber2_; } + + std::string getResidueSequenceID1() const { return residueSequenceID1_; } + std::string getResidueSequenceID2() const { return residueSequenceID2_; } + + std::string getChain1() const { return chainId1_; } + std::string getChain2() const { return chainId2_; } + + float getMinDistance() const { return minDistance_; } + float getMaxDistance() const { return maxDistance_; } + float getWeight() const { return weight_; } + + bool operator==(const CrossLink& cl) const; + + bool isInList(const std::vector& crossLinks) const; + + CrossLink getReversed() const; + + friend std::ostream& operator<<(std::ostream& q, const CrossLink& cl); + friend std::istream& operator>>(std::istream& s, CrossLink& cl); + + protected: + int residueNumber1_; + std::string residueSequenceID1_; // to support numbering of type 99A + std::string chainId1_; + int residueNumber2_; + std::string residueSequenceID2_; + std::string chainId2_; + float minDistance_; + float maxDistance_; + float weight_; +}; + + +int readCrossLinkFile(const std::string& fileName, + std::vector& crossLinks, + bool addReverse = false); + + +void writeCrossLinkFile(const std::string& fileName, + const std::vector& crossLinks); + +void writeXLAnalizerFile(const std::string& fileName, + const std::vector& crossLinks); + +#endif diff --git a/model/CombinatorialAssembler/libs_DockingLib/DistanceRestraint.cc b/model/CombinatorialAssembler/libs_DockingLib/DistanceRestraint.cc new file mode 100644 index 0000000000000000000000000000000000000000..0a9bf356757e36d9a44cfb58f7d222c16025b260 --- /dev/null +++ b/model/CombinatorialAssembler/libs_DockingLib/DistanceRestraint.cc @@ -0,0 +1,40 @@ +#include "DistanceRestraint.h" +#include + +float DistanceRestraint::minDistanceIndices(unsigned int& index1, unsigned int& index2) const { + float bestdist = std::numeric_limits::max(); + for(unsigned int i = 0; i < p1_.size(); i++) { + for(unsigned int j = 0; j < p2_.size(); j++) { + float dist = p1_[i].dist(p2_[j]); + if(dist < bestdist) { + bestdist = dist; + index1 = i; + index2 = j; + } + } + } + return bestdist; +} + +std::string DistanceRestraint::getChimeraXPseudoBond() const { + unsigned int best_i = 0, best_j = 0; + float bestdist = minDistanceIndices(best_i, best_j); + + if(p1Info_.size() == p1_.size() && p2Info_.size() == p2_.size()) { + // /E:157@ca /B:58@ca red + std::string res1 = p1Info_[best_i].first; + boost::algorithm::trim(res1); + std::string res2 = p2Info_[best_j].first; + boost::algorithm::trim(res2); + + // skip self cross link + if(res1 == res2 && p1Info_[best_i].second == p2Info_[best_j].second) + return std::string(); + + std::string ret = "/" + p1Info_[best_i].second + ":" + res1 + "@ca /" + + p2Info_[best_j].second + ":" + res2 + "@ca"; + if(bestdist > maxdist_) ret += " red"; + return ret; + } + return std::string(); +} diff --git a/model/CombinatorialAssembler/libs_DockingLib/DistanceRestraint.h b/model/CombinatorialAssembler/libs_DockingLib/DistanceRestraint.h new file mode 100644 index 0000000000000000000000000000000000000000..2ca36c09170e262f3321b41e9ac3e3d6803666be --- /dev/null +++ b/model/CombinatorialAssembler/libs_DockingLib/DistanceRestraint.h @@ -0,0 +1,189 @@ +#ifndef DISTANCE_RESTRAINT_H +#define DISTANCE_RESTRAINT_H + +#include +#include + +#include +#include + +class DistanceRestraint { +public: + DistanceRestraint(const std::vector& p1, + const std::vector& p2, + float maxdist, float mindist = 0.0, float weight=1.0) + : p1_(p1), p2_(p2), mindist_(mindist), maxdist_(maxdist), + mindist2_(mindist*mindist), maxdist2_(maxdist*maxdist), + weight_(weight) {} + + DistanceRestraint(const std::vector& p1, + const std::vector& p2, + const std::vector>& p1Info, + const std::vector>& p2Info, + float maxdist, float mindist = 0.0, float weight=1.0) + : DistanceRestraint(p1, p2, maxdist, mindist, weight) { + p1Info_ = p1Info; + p2Info_ = p2Info; + } + + DistanceRestraint(const Vector3& p1, const Vector3& p2, + float maxdist, float mindist = 0.0, float weight=1.0) + : mindist_(mindist), maxdist_(maxdist), + mindist2_(mindist*mindist), maxdist2_(maxdist*maxdist), + weight_(weight) + { + p1_.push_back(p1); + p2_.push_back(p2); + } + + DistanceRestraint() = default; + + float getWeight() const { return weight_; } + float getMinDistance() const { return mindist_; } + float getMaxDistance() const { return maxdist_; } + const std::vector& getPoints1() const { return p1_; } + const std::vector& getPoints2() const { return p2_; } + + bool isViolated() const { + // calculate the distance and compare to mindist_ and maxdist_ + for(unsigned int i = 0; i < p1_.size(); i++) { + for(unsigned int j = 0; j < p2_.size(); j++) { + float dist2 = p1_[i].dist2(p2_[j]); + if(dist2 <= maxdist2_) return false; // found one in range + } + } + return true; + } + + bool isViolated(const std::vector& trans) const { + // calculate the distance and compare to mindist_ and maxdist_ + for(unsigned int i = 0; i < p1_.size(); i++) { + for(unsigned int j = 0; j < p2_.size(); j++) { + float dist2 = p1_[i].dist2(trans[j]*p2_[j]); + if(dist2 <= maxdist2_) return false; // found one in range + } + } + return true; + } + + bool isViolated(const std::vector& trans1, + const std::vector& trans2) const { + // calculate the distance and compare to mindist_ and maxdist_ + for(unsigned int i = 0; i < p1_.size(); i++) { + Vector3 transP1 = trans1[i]*p1_[i]; + for(unsigned int j = 0; j < p2_.size(); j++) { + float dist2 = transP1.dist2(trans2[j]*p2_[j]); + if(dist2 <= maxdist2_) return false; // found one in range + } + } + return true; + } + + // backward compatability + bool isSatisfied() const { return !isViolated(); } + bool isSatisfied(const RigidTrans3& trans) const { + std::vector t(p2_.size(), trans); + return !isViolated(t); + } + bool isSatisfied(const RigidTrans3& trans1, + const RigidTrans3& trans2) const { + std::vector t1(p1_.size(), trans1); + std::vector t2(p2_.size(), trans2); + return !isViolated(t1, t2); + } + + //calculate violationDistance + float violationDistance() const { + float bestdist = distance(); + if(bestdist > maxdist_) return bestdist - maxdist_; + return 0.0; + } + + float violationDistance(const std::vector& trans) const { + float bestdist = distance(trans); + if(bestdist > maxdist_) return bestdist - maxdist_; + return 0.0; + } + + float violationDistance(const std::vector& trans1, + const std::vector& trans2) const { + float bestdist = distance(trans1, trans2); + if(bestdist > maxdist_) return bestdist - maxdist_; + return 0.0; + } + + float distance2() const { + float bestdist2 = std::numeric_limits::max(); + for(unsigned int i = 0; i < p1_.size(); i++) { + for(unsigned int j = 0; j < p2_.size(); j++) { + float dist2 = p1_[i].dist2(p2_[j]); + if(dist2 < bestdist2) bestdist2 = dist2; + } + } + return bestdist2; + } + + float distance() const { return sqrt(distance2()); } + + float distance2(const std::vector& trans) const { + float bestdist2 = std::numeric_limits::max(); + for(unsigned int i = 0; i < p1_.size(); i++) { + for(unsigned int j = 0; j < p2_.size(); j++) { + float dist2 = p1_[i].dist2(trans[j]*p2_[j]); + if(dist2 < bestdist2) bestdist2 = dist2; + } + } + return bestdist2; + } + + float distance(const std::vector& trans) const { + return sqrt(distance2(trans)); + } + + float distance(const RigidTrans3& trans) const { + std::vector t(1, trans); + return distance(t); + } + + float distance2(const std::vector& trans1, + const std::vector& trans2) const { + float bestdist2 = std::numeric_limits::max(); + for(unsigned int i = 0; i < p1_.size(); i++) { + Vector3 transP1 = trans1[i]*p1_[i]; + for(unsigned int j = 0; j < p2_.size(); j++) { + float dist2 = transP1.dist2(trans2[j]*p2_[j]); + if(dist2 < bestdist2) bestdist2 = dist2; + } + } + return bestdist2; + } + + float distance(const std::vector& trans1, + const std::vector& trans2) const { + return sqrt(distance2(trans1, trans2)); + } + + // TODO: add trans versions + float minDistanceIndices(unsigned int& index1, unsigned int& index2) const; + + std::string getChimeraXPseudoBond() const; + + bool operator < (const DistanceRestraint& d) const { return (maxdist_ < d.maxdist_); } + + friend std::ostream& operator<<(std::ostream& s, const DistanceRestraint& d) { + // TODO + return s << d.p1_[0] << ' ' << d.p2_[0] << ' ' << d.mindist_ <<' ' << d.maxdist_; + } + +private: + std::vector p1_; // end point1, vector for ambiguity + std::vector p2_; // end point2, vector for ambiguity + std::vector> p1Info_, p2Info_; // residueSequenceID and chain for p1 and p2 endpoints + float mindist_; // minimal distance threshold + float maxdist_; // maximal distance threshold + float mindist2_; // minimal distance threshold (squared) + float maxdist2_; // maximal distance threshold (squared) + float weight_; +}; + +#endif /* DISTANCE_RESTRAINT_H */ diff --git a/model/CombinatorialAssembler/libs_DockingLib/DotSphere.cc b/model/CombinatorialAssembler/libs_DockingLib/DotSphere.cc new file mode 100644 index 0000000000000000000000000000000000000000..47769ca0dc762b3eee0f5741ef995a547dd22391 --- /dev/null +++ b/model/CombinatorialAssembler/libs_DockingLib/DotSphere.cc @@ -0,0 +1,22 @@ +#include "DotSphere.h" +#include + +int DotSphere::createSphereDots(float radius, float density) { + + float num_equat = 2*pi*radius*sqrt(density); + float vert_count = 0.5*num_equat; + + for(int i=0; i + +#include + +class DotSphere : public std::vector { + public: + // Constructors + DotSphere(float radius, float density) { + createSphereDots(radius, density); + } + + int createSphereDots(float radius, float density); +}; + +#endif diff --git a/model/CombinatorialAssembler/libs_DockingLib/EnergyAtom.h b/model/CombinatorialAssembler/libs_DockingLib/EnergyAtom.h new file mode 100644 index 0000000000000000000000000000000000000000..4165861175d377599b90cff26bcfe2411954f0aa --- /dev/null +++ b/model/CombinatorialAssembler/libs_DockingLib/EnergyAtom.h @@ -0,0 +1,21 @@ + +#ifndef ENERGY_ATOM_H +#define ENERGY_ATOM_H + +class EnergyAtom{ + public: + + virtual float getRadius() const = 0; + virtual float getEpsilon() const = 0; + virtual bool isHydrogen() const = 0; + virtual float getCharge() const = 0; + virtual bool isDonor() const = 0; + virtual bool isAcceptor() const = 0; + virtual const Vector3& getHBDirection() const = 0; + virtual Vector3 position() const = 0; + + virtual ~EnergyAtom() {} +}; + +#endif + diff --git a/model/CombinatorialAssembler/libs_DockingLib/GeomScore.cc b/model/CombinatorialAssembler/libs_DockingLib/GeomScore.cc new file mode 100644 index 0000000000000000000000000000000000000000..6a7e8828c473b13935b242a00f78ab263de7e9f1 --- /dev/null +++ b/model/CombinatorialAssembler/libs_DockingLib/GeomScore.cc @@ -0,0 +1,423 @@ +#include "GeomScore.h" +#include "Logger.h" + +RangeParams::RangeParams(const std::vector& wts) { + ranges.push_back(-3.6); + ranges.push_back(-2.2); + ranges.push_back(-1.0); + ranges.push_back(1.0); + weights.insert(weights.begin(), wts.begin(), wts.end()); +} + +//-------------------------------- GeomScore class ----------------------------- + +GeomScore::GeomScore(const Surface& lowLevel, MoleculeGrid* grid, + const std::vector& wts, float penetration_thr, float ns_thr, float d, float gridMargins) : + grid_(grid), rangeParams(wts), + penetrationThr_(penetration_thr), divNsThr(1.0/ns_thr), + density(d), tree(lowLevel, density, gridMargins) +{} + +GeomScore::GeomScore(const Surface& lowLevel, + const std::vector& wts, float penetration_thr, float ns_thr, float d, float gridMargins) : + grid_(NULL), rangeParams(wts), + penetrationThr_(penetration_thr), divNsThr(1.0/ns_thr), + density(d), tree(lowLevel, density, gridMargins) +{} + +GeomScore::GeomScore(MoleculeGrid* grid, + const std::vector& wts, float penetration_thr, float ns_thr, float d, float gridMargins) : + grid_(grid), rangeParams(wts), + penetrationThr_(penetration_thr), divNsThr(1.0/ns_thr), + density(d) +{} + +void GeomScore::buildTree() { + tree.buildTree(); +} + +void GeomScore::buildTree(const std::vector& as) { + tree.buildTree(as); + // countActiveSitePoints(as); +} + +float GeomScore::maxPenetration(const RigidTrans3& trans) { + float maxPenetration=MAX_FLOAT; + Level& highLevel = tree.getLevel(tree.size()-1); + std::stack st; + for (unsigned int i=0; i< highLevel.size(); i++) + st.push(&highLevel[i]); + while (!st.empty()) { + const Node *node = st.top(); + st.pop(); + float dist = grid_->getDist(trans*(node->position())); + if (dist != MAX_FLOAT) { + if (node->getLevel() == 0) { + if (dist < maxPenetration) + maxPenetration = dist; + continue; + } + if (dist - node->getRadius() <= maxPenetration) { + const std::vector& lowLevelPointers = node->getChildren(); + for (unsigned int i=0; i st; + for(unsigned int i=0; i< highLevel.size(); i++) { + float dist = grid_->getDist(trans*(highLevel[i].position())); + if(dist < penetrationThr_) { + return true; + } else { + st.push(&highLevel[i]); + } + } + while(!st.empty()) { + const Node *node = st.top(); + st.pop(); + float dist = grid_->getDist(trans*(node->position())); + if(dist < penetrationThr_) + return true; + if(node->getLevel() != 0) { + // check if dist - pointRadius is more then thr + if(dist - node->getRadius() <= penetrationThr_) { + const std::vector& lowLevelPointers = node->getChildren(); + for(unsigned int i=0; i< lowLevelPointers.size(); i++) + st.push(lowLevelPointers[i]); + } + } + } + return false; +} + +bool GeomScore::fastIsPenetrating(const RigidTrans3& trans) { + Level& highLevel = tree.getLevel(tree.size()-1); + std::stack st; + for(unsigned int i=0; i< highLevel.size(); i++) { + float dist = grid_->getDist(trans*(highLevel[i].position())); + if(dist < penetrationThr_) { + return true; + } + } + return false; +} + + +void GeomScore::getInterface(const RigidTrans3& trans, float low_thr, float high_thr, + std::vector& interface) { + Level& highLevel = tree.getLevel(tree.size()-1); + std::stack st; + for (unsigned int i=0; i< highLevel.size(); i++) + st.push(&highLevel[i]); + while (!st.empty()) { + const Node *node = st.top(); + st.pop(); + float dist = grid_->getDist(trans*(node->position())); + if (dist == MAX_FLOAT) continue; + if (node->getLevel() == 0) { + if (dist > low_thr && dist < high_thr) + interface.push_back(node->getPoint()); + continue; + } + if (dist - node->getRadius() > low_thr && dist + node->getRadius() < high_thr) { + node2interface(node, interface); + continue; + } + const std::vector& lowLevelPointers = node->getChildren(); + for (unsigned int i=0; i& interface) { + std::stack st; + st.push(node); + while (!st.empty()) { + const Node *node = st.top(); + st.pop(); + if (node->getLevel() == 0) { + interface.push_back(node->getPoint()); + continue; + } + const std::vector& lowLevelPointers = node->getChildren(); + for (unsigned int i=0; i pointsInRanges(rangeParams.weights.size(),0); + scoreRanges(trans, pointsInRanges); + //calculate total, based on weights + int score=0; + for(unsigned int i=0; i divNsThr) { + return -1; + } + return score; +} + +int GeomScore::score(const RigidTrans3& trans, const Surface& surface, float& penetration) { + std::vector pointsInRanges(rangeParams.weights.size(),0); + penetration = MAX_FLOAT; + // count number of points + for(unsigned int i=0; igetDist(trans * (surface[i].position())); + if(dist < penetrationThr_) + return -1; + if(dist < penetration) + penetration = dist; + unsigned int range = rangeParams.findRange(dist); + pointsInRanges[range]++; + } + + //calculate total, based on weights + int score=0; + for(unsigned int i=0; i divNsThr) { + return -1; + } + return score; +} + +int GeomScore::score(const RigidTrans3& trans, float& asRatio, int& asScore) { + std::vector pointsInRanges(rangeParams.weights.size(),0); + std::vector asInRanges(rangeParams.weights.size(),0); + scoreRanges(trans, pointsInRanges, asInRanges); + //calculate total, based on weights + int score=0; + int interfaceCount = 0; + int asInterfaceCount = 0; + asScore=0; + for(unsigned int i=0; i divNsThr) { + return -1; + } + + return score; +} + +void GeomScore::scoreRanges(const RigidTrans3& trans, std::vector& pointsInRanges) { + Level& highLevel = tree.getLevel(tree.size()-1); + std::stack st; + for (unsigned int i=0; i< highLevel.size(); i++) + st.push(&highLevel[i]); + while (!st.empty()) { + const Node *node = st.top(); + st.pop(); + float dist = grid_->getDist(trans*(node->position())); + if (dist == MAX_FLOAT) { //add to last range + pointsInRanges[pointsInRanges.size()-1]+=node->getSubtreeSize(); + continue; + } + unsigned int range = rangeParams.findRange(dist); + if (node->getLevel() == 0) { + pointsInRanges[range]++; + continue; + } + unsigned int range1 = rangeParams.findRange(dist + node->getRadius()); + unsigned int range2 = rangeParams.findRange(dist - node->getRadius()); + if (range == range1 && range == range2) { + pointsInRanges[range] += node->getSubtreeSize(); + continue; + } + const std::vector& lowLevelPointers = node->getChildren(); + for (unsigned int i=0; i& pointsInRanges, + std::vector& asInRanges) { + Level& highLevel = tree.getLevel(tree.size()-1); + std::stack st; + for (unsigned int i=0; i< highLevel.size(); i++) + st.push(&highLevel[i]); + while (!st.empty()) { + const Node *node = st.top(); + st.pop(); + Vector3 point = trans*(node->position()); + float dist = grid_->getDist(point); + if (dist == MAX_FLOAT) { //add to last range + pointsInRanges[pointsInRanges.size()-1]+=node->getSubtreeSize(); + asInRanges[asInRanges.size()-1]+=node->getAsNum(); + continue; + } + unsigned int range = rangeParams.findRange(dist); + if (node->getLevel() == 0) { + pointsInRanges[range]++; + asInRanges[range]+=node->getAsNum(); + continue; + } + unsigned int range1 = rangeParams.findRange(dist + node->getRadius()); + unsigned int range2 = rangeParams.findRange(dist - node->getRadius()); + if (range == range1 && range == range2) { + pointsInRanges[range]+=node->getSubtreeSize(); + asInRanges[range]+=node->getAsNum(); + continue; + } + const std::vector& lowLevelPointers = node->getChildren(); + for (unsigned int i=0; i< lowLevelPointers.size(); i++) + st.push(lowLevelPointers[i]); + } +} + +ScoreData GeomScore::fullScore(const RigidTrans3& trans, bool as) { + ScoreData scoreData; + std::vector pointsInRanges(rangeParams.weights.size(),0); + if (as) { + std::vector asInRanges(rangeParams.weights.size(),0); + scoreRanges(trans, pointsInRanges, asInRanges); + for (unsigned int i=0; i pointsInRanges(rangeParams.weights.size(),0); + std::vector asInRanges(rangeParams.weights.size(),0); + scoreRanges(trans, pointsInRanges, asInRanges); + //calculate total, based on weights + int score=0; + int asScore=0; + for (unsigned int i=0; i interface; + getInterface(trans, -2.0, 1.5, interface); //add thr - which?? + + Logger::debugMessage() << "interface size: " << interface.size() << " trans " << trans << endl; + + std::vector interfacePoints(interface.size()); + std::vector virtualPoints(interface.size()); + MyMatch matchList; + + Vector3 centerOfRotation; + for (unsigned int i=0; iposition(); + centerOfRotation += interfacePoints[i]; + matchList.add(i,i); + } + centerOfRotation /= interface.size(); + + RigidTrans3 bestTrans; + RigidTrans3 currTrans = trans; + int currScore=1000000; + int oldScore = score(trans); + MMonteCarlo::init(oldScore); + newTrans = trans; + int nums = 0; + while (nums < 1000) { + + currTrans = newTrans; + nums++; + //Logger::debugMessage() << "oldScore: " << oldScore; + for (unsigned int i=0; igetDist(point); + virtualPoints[i] = point + dist*(interface[i]->normal()); + if (dist == 0.00) + matchList.setScore(i,10.0); + else + matchList.setScore(i,1/fabs(dist)); + } + matchList.calculateBestFit(virtualPoints, interfacePoints); + newTrans = matchList.rigidTrans(); + //oldScore = currScore; + currScore = score(newTrans); + MMonteCarlo::MMC_acceptance mmc_accept = MMonteCarlo::step(currScore); + + if (mmc_accept == MMonteCarlo::ACCEPT_LOWEST) { + bestTrans = newTrans; + } + + if (mmc_accept == MMonteCarlo::REJECT) { + + + float delta_TR[6] = {0.0}; + delta_TR[0] = MMonteCarlo::getGaussianRandomNumber() * Tmag; + delta_TR[1] = MMonteCarlo::getGaussianRandomNumber() * Tmag; + delta_TR[2] = MMonteCarlo::getGaussianRandomNumber() * Tmag; + delta_TR[3] = MMonteCarlo::getGaussianRandomNumber() * Rmag; + delta_TR[4] = MMonteCarlo::getGaussianRandomNumber() * Rmag; + delta_TR[5] = MMonteCarlo::getGaussianRandomNumber() * Rmag; + + Vector3 rotVec(delta_TR[3],delta_TR[4],delta_TR[5]); + Vector3 transVec(delta_TR[0],delta_TR[1],delta_TR[2]); + + RigidTrans3 delta_rt3(rotVec,transVec); + + const Matrix3 &delta_mat3 = delta_rt3.rotation(); + RigidTrans3 delta_rt3_COR(delta_mat3,centerOfRotation-delta_mat3*centerOfRotation+delta_rt3.translation()); + newTrans = delta_rt3_COR*currTrans; + + } + + //Logger::debugMessage() << " RMSD: " << matchList.rmsd() << " newTrans " << newTrans << " newScore " << currScore << endl; + } + + newTrans = bestTrans; + + //Logger::debugMessage() << "oldScore: " << oldScore << " newScore " << currScore << endl; + return currScore;*/ +return 0; +} diff --git a/model/CombinatorialAssembler/libs_DockingLib/GeomScore.h b/model/CombinatorialAssembler/libs_DockingLib/GeomScore.h new file mode 100644 index 0000000000000000000000000000000000000000..f344218eaf72ad22e8f4cabe317b696adee9197f --- /dev/null +++ b/model/CombinatorialAssembler/libs_DockingLib/GeomScore.h @@ -0,0 +1,129 @@ +#ifndef GEOM_SCORE_H +#define GEOM_SCORE_H + +#include +#include +#include + +#include +#include +#include "MultiResolution.h" + +#include +#include +//#include "MMonteCarlo.h" + +class MyMatch : public Match { + public: + void setScore(unsigned int pairIndex, float score) { pairs[pairIndex].score = score; } +}; + +class RangeParams { + public: + RangeParams(const std::vector& wts); + unsigned int findRange(float dist) const { + for(unsigned int i=0; i< ranges.size(); i++) + if(dist <= ranges[i]) return i; + return ranges.size(); + } + public: + std::vector ranges; + std::vector weights; +}; + +class ScoreData { + public: + ScoreData() { score=0; asScore=0; interfaceArea=0;} + int score; + int asScore; + float interfaceArea; + float maxPenetration; + public: + // int getSurfaceRange() {return pointsInRanges[3];} + friend std::ostream& operator<<(std::ostream& s, const ScoreData& data) { + s.width(5); s << data.score << "|"; + s.width(5); s << data.asScore << "|"; + s.width(5); s << data.maxPenetration << "|"; + s.width(5); s << data.interfaceArea << "|"; + return s; + } +}; + +class ScorePair : public std::pair { + public: + ScorePair(ScoreData f, ScoreData s, RigidTrans3 trans) : std::pair (f,s) { + trans_ = trans; + score_ = f.score + s.score - abs(f.score - s.score); + } + int score() const { return score_; } + const RigidTrans3& rigidTrans() const { return trans_; } + friend std::ostream& operator<<(std::ostream& s, const ScorePair& p) { + s << p.first << "|"; + s << std::endl << " "; + s << p.second << "|"; + return s; + } + protected: + RigidTrans3 trans_; + int score_; +}; + +class GeomScore { + public: + typedef MultiResolution::Node Node; + typedef MultiResolution::Level Level; + + GeomScore(const Surface& lowLevel, MoleculeGrid* grid, + const std::vector& wts, float penetration_thr, float ns_thr, float density=10.0, float gridMargins=7.0); + + GeomScore(const Surface& lowLevel, + const std::vector& wts, float penetration_thr, float ns_thr, float density=10.0, float gridMargins=7.0); + + GeomScore(MoleculeGrid* grid, + const std::vector& wts, float penetration_thr, float ns_thr=0.5, float density=10.0, float gridMargins=7.0); + + void setGrid(const MoleculeGrid* grid) { grid_ = grid; } + + void setSurface(const Surface& lowLevel) { + tree.setPointSet(lowLevel); + buildTree(); + } + + void buildTree(); + void buildTree(const std::vector& as); + + //// Penetration checks + float maxPenetration(const RigidTrans3& trans); + bool isPenetrating(const RigidTrans3& trans); + + //// fast check, but can return false for penetrating trans + bool fastIsPenetrating(const RigidTrans3& trans); + + void getInterface(const RigidTrans3& trans, float low_thr, float high_thr, std::vector& interface); + + //// Scoring functions + int score(const RigidTrans3& trans); + int score(const RigidTrans3& trans, float& asRatio, int& asScore); + int score(const RigidTrans3& trans, const Surface& surface, float& penetration); + ScoreData fullScore(const RigidTrans3& trans, bool as=false); + + int refineTrans(const RigidTrans3& trans, RigidTrans3& newTrans); + + void printTree(std::ofstream& outFile); + float computePropensity(const RigidTrans3& trans); + private: + void scoreRanges(const RigidTrans3& trans, std::vector& pointsInRanges); + void scoreRanges(const RigidTrans3& trans, std::vector& pointsInRanges, + std::vector& asInRanges); + void node2interface(const Node* node, std::vector& interface); + void countActiveSitePoints(const std::vector& as); + private: + const MoleculeGrid* grid_; + const RangeParams rangeParams; + float penetrationThr_; + float divNsThr; // 1/nsThr + float density; + MultiResolution tree; +}; + +#endif diff --git a/model/CombinatorialAssembler/libs_DockingLib/MolIntersection.h b/model/CombinatorialAssembler/libs_DockingLib/MolIntersection.h new file mode 100644 index 0000000000000000000000000000000000000000..4b3bba7af6cf7f23ceda5ca578e0f43a33a19613 --- /dev/null +++ b/model/CombinatorialAssembler/libs_DockingLib/MolIntersection.h @@ -0,0 +1,116 @@ +#ifndef MOL_INTERSECTION_H +#define MOL_INTERSECTION_H + +#include +#include + +/* + Class for checking sphere intersections with atoms of the molecule +*/ +template +class MolIntersection { + public: + // GROUP: Constructors + MolIntersection(); + MolIntersection(MoleculeT* mol, float probe_radius); + + void setMolecule(MoleculeT* mol) { molecule = mol; } + + // GROUP: neighbours related queries + bool isNeighbours(unsigned int atom1, unsigned int atom2) const; + + const Interface::ParticleAdjacency& getNeighbours(unsigned int atom) const; + + // GROUP: intersections queries + bool isIntersectingSpheres(const Vector3& sphereCenter1, const Vector3& sphereCenter2, + const float radius1, const float radius2) const; + + bool isIntersecting(const Vector3& probeCenter, unsigned int atomIndex) const; + + bool isIntersecting(const Vector3& probeCenter, unsigned int atomIndex1, unsigned int atomIndex2) const; + + bool isIntersecting(const Vector3& probeCenter, + unsigned int atom1Index, unsigned int atom2Index, unsigned int atom3Index) const; + private: + void computeNeighbours(); + + private: + MoleculeT* molecule; + Interface neighbours; + float probeRadius; +}; + +template +MolIntersection::MolIntersection(MoleculeT* mol, float probe_radius) : + molecule(mol), probeRadius(probe_radius) { + computeNeighbours(); +} + +template +bool MolIntersection::isNeighbours(unsigned int atom1, unsigned int atom2) const { + if(neighbours.isAdjacent(atom1, atom2)) + return true; + return false; +} + +template +const Interface::ParticleAdjacency& MolIntersection::getNeighbours(unsigned int atom) const { + return neighbours.adjacencies(atom); +} + +template +bool MolIntersection::isIntersectingSpheres(const Vector3& sphereCenter1, const Vector3& sphereCenter2, + const float radius1, const float radius2) const { + float radiusSum2 = (radius1+radius2)*(radius1+radius2); + float dist2 = sphereCenter1.dist2(sphereCenter2); + if(fabs(radiusSum2-dist2) < 0.0001) + return false; + if(radiusSum2 > dist2) + return true; + return false; +} + +template +bool MolIntersection::isIntersecting(const Vector3& probeCenter, unsigned int atomIndex) const { + const Interface::ParticleAdjacency& atomNeighbours = neighbours.adjacencies(atomIndex); + for(Interface::ParticleAdjacency::const_iterator it = atomNeighbours.begin(); it != atomNeighbours.end(); it++) + if(isIntersectingSpheres(probeCenter, (*molecule)(it->first), probeRadius, (*molecule)[it->first].getRadius())) + return true; + return false; +} + +template +bool MolIntersection::isIntersecting(const Vector3& probeCenter, + unsigned int atom1Index, unsigned int atom2Index) const { + std::vector nUnion; + neighbours.neighboursUnion(neighbours.adjacencies(atom1Index), neighbours.adjacencies(atom2Index), nUnion); + for(std::vector::iterator it=nUnion.begin(); it!=nUnion.end(); it++) + if(isIntersectingSpheres(probeCenter, (*molecule)(*it), probeRadius, (*molecule)[*it].getRadius())) + return true; + return false; + // return (isIntersecting(probeCenter, atom1Index) || isIntersecting(probeCenter, atom2Index)); +} + +template +bool MolIntersection::isIntersecting(const Vector3& probeCenter, unsigned int atom1Index, + unsigned int atom2Index, unsigned int atom3Index) const { + return (isIntersecting(probeCenter, atom1Index) || + isIntersecting(probeCenter, atom2Index) || isIntersecting(probeCenter, atom3Index)); +} + +template +void MolIntersection::computeNeighbours() { + for(unsigned int i=0; isize(); i++) { + for(unsigned int j=i+1; jsize(); j++) { + float radiusSum = (*molecule)[i].getRadius() + (*molecule)[j].getRadius() + 2*probeRadius; + float dist2 = (*molecule)(i).dist2((*molecule)(j)); + if(dist2 < radiusSum*radiusSum) { + float dist = sqrt(dist2); + neighbours.addAdjacency(i, j, dist); + neighbours.addAdjacency(j, i, dist); + } + } + } +} + +#endif diff --git a/model/CombinatorialAssembler/libs_DockingLib/MultiResolution.h b/model/CombinatorialAssembler/libs_DockingLib/MultiResolution.h new file mode 100644 index 0000000000000000000000000000000000000000..eb76d6bce675cfc7ff9a1169730512b3e6f8c726 --- /dev/null +++ b/model/CombinatorialAssembler/libs_DockingLib/MultiResolution.h @@ -0,0 +1,279 @@ +#ifndef MULT_RES_H +#define MULT_RES_H + +#include +#include +#include +#include + +template +class MultiResolution { + public: + + //// TreeNode + class Node { + + public: + // GROUP: Constructors + Node(const PointT* point, short level=0, float lowLevelRadius=0.0, + unsigned int subtreeSize=1, unsigned int asNum=0): + point_(point), level_(level), lowLevelRadius_(lowLevelRadius), subtreeSize_(subtreeSize), asNum_(asNum) {} + + // GROUP: Modifiers + //// update asNum + void incrementAsNum(unsigned int num) { asNum_ += num; } + + //// add children to node + void addChildren(const std::vector& children) { + lowLevelPointers_.insert(lowLevelPointers_.begin(), children.begin(), children.end()); + } + + // GROUP: Queries + //// return point pointer + const PointT* getPoint() const { return point_; } + + //// return point position + Vector3 position() const { return point_->position(); } + + //// get node level + short getLevel() const { return level_; } + + //// get radius + float getRadius() const { return lowLevelRadius_; } + + //// get subtree size + unsigned int getSubtreeSize() const { return subtreeSize_; } + + //// get number of active nodes in the subtree + unsigned int getAsNum() const { return asNum_; } + + //// get pointers to node children + const std::vector& getChildren() const { return lowLevelPointers_; } + + protected: + // point + const PointT* point_; + // level + short level_; + // radius of lower level points + float lowLevelRadius_; + // number of subtree nodes + unsigned int subtreeSize_; + // number of active site nodes + unsigned int asNum_; + // pointers to lower level + std::vector lowLevelPointers_; + + }; + + typedef std::vector Level; + + // GROUP: Contructors + //// The initial bin size is computed based on density and number of tree + // levels is computed based on the size of the grid margins + MultiResolution(const std::vector& pointSet, float density=10.0, float gridMargins=6.0); + MultiResolution(float density=10.0, float gridMargins=6.0); + + // GROUP: Modifiers + void setPointSet(const std::vector& pointSet) { pointSet_ = pointSet; } + void buildTree(); + void buildTree(const std::vector& as); + void countActiveSitePoints(const std::vector& as); + + // GROUP: Queries + Level& getLevel(unsigned int num) { return tree_[num]; } + unsigned int size() const { return tree_.size(); } + int getAsPointsNumber() const { return asPointsNumber_; } + void printTree(std::ofstream& outFile); + + private: + void createHighLevel(const int lowLevelIndex, float radius); + void findMaxRadiusForPoint(const Vector3& point, const Node* currNode, float& currMaxRadius2); + + private: + std::vector pointSet_; + std::vector tree_; + int asPointsNumber_; + float binSize_; +}; + +template +MultiResolution::MultiResolution(const std::vector& pointSet, float density, float gridMargins) : + pointSet_(pointSet) { + unsigned int POINTS_PER_CUBE = 5; + binSize_ = POINTS_PER_CUBE/density; + Level l; + for(float cs=binSize_; cs <= gridMargins*2; cs*=2) { //-1 + tree_.push_back(l); + } + // cout << "Tree size: " << tree_.size() << endl; +} + +template +MultiResolution::MultiResolution(float density, float gridMargins) { + unsigned int POINTS_PER_CUBE = 5; + binSize_ = POINTS_PER_CUBE/density; + Level l; + for(float cs=binSize_; cs <= gridMargins*2; cs*=2) { //-1 + tree_.push_back(l); + } + // cout << "Tree size: " << tree_.size() << endl; +} + + +template +void MultiResolution::createHighLevel(const int lowLevelIndex, float binSize) { + const Level& lowLevel = tree_[lowLevelIndex]; + // insert to ghash + GeomHash gHash(3, binSize); + for(unsigned int i=0; i< lowLevel.size(); i++) + gHash.insert(lowLevel[i].position(), &lowLevel[i]); + + // init Level + short levelIndex = lowLevelIndex+1; + Level& highLevel = tree_[levelIndex]; + + // compute high resolution - iterate over each Bucket + typename GeomHash::BucketsPointerList *bucketList = gHash.getBuckets(); + typename GeomHash::BucketsPointerList::const_iterator bIter, bEndIter = bucketList->end(); + for(bIter = bucketList->begin(); bIter != bEndIter; bIter++) { + const std::vector& currBucket = **bIter; + // compute average bucket point + // compute subtree size + Vector3 average; + unsigned int subTreeSize=0; + typename std::vector::const_iterator currBucketIter, bucketEndIter = currBucket.end(); + for(currBucketIter = currBucket.begin(); currBucketIter != bucketEndIter; currBucketIter++) { + average+= (*currBucketIter)->position(); + subTreeSize+=(*currBucketIter)->getSubtreeSize(); + } + average/=currBucket.size(); + + // find point closest to average + float minDist2 = MAX_FLOAT; + const Node* selectedNode=NULL; + for(currBucketIter = currBucket.begin(); currBucketIter != bucketEndIter; currBucketIter++) { + float currDist2 = average.dist2((*currBucketIter)->position()); + if(currDist2 < minDist2) { + selectedNode = *currBucketIter; + minDist2 = currDist2; + } + } + + // find radius of pts == maxDist from average + float maxDist2 = MIN_FLOAT; + for(currBucketIter = currBucket.begin(); currBucketIter != bucketEndIter; currBucketIter++) + findMaxRadiusForPoint(selectedNode->position(), *currBucketIter, maxDist2); + + // create new node + Node newNode(selectedNode->getPoint(), levelIndex, sqrt(maxDist2), subTreeSize); + newNode.addChildren(currBucket); + highLevel.push_back(newNode); + } + delete bucketList; + // cout << ": " << highLevel.size() << " "; +} + +template +void MultiResolution::findMaxRadiusForPoint(const Vector3& point, const Node* currNode, float& currMaxRadius2) { + int levelIndex = currNode->getLevel(); + float dist2 = currNode->position().dist2(point); + if(levelIndex==0) { + if(dist2 > currMaxRadius2) + currMaxRadius2=dist2; + return; + } + // check if distance to (point+radius)^2 < currMaxRadius2 + float radius = currNode->getRadius(); + float dist = sqrt(dist2); + if(sqr(radius+dist) < currMaxRadius2) return; + const std::vector& lowLevelPointersForPoint = currNode->getChildren(); + typename std::vector::const_iterator iter, endIter=lowLevelPointersForPoint.end(); + if(levelIndex==1) { + for(iter=lowLevelPointersForPoint.begin(); iter!=endIter; iter++) { + float dist2=point.dist2((*iter)->position()); + if(dist2 > currMaxRadius2) + currMaxRadius2=dist2; + } + return; + } + if(levelIndex > 1) { + for(iter=lowLevelPointersForPoint.begin(); iter!=endIter; iter++) { + findMaxRadiusForPoint(point, *iter, currMaxRadius2); + } + return; + } + return; +} + +template +void MultiResolution::buildTree() { + // initiate lowest level + Level& lowLevel = tree_[0]; + for(unsigned int i=0; i +void MultiResolution::buildTree(const std::vector& as) { + buildTree(); + countActiveSitePoints(as); +} + +template +void MultiResolution::countActiveSitePoints(const std::vector& as) { + Level& lowLevel = tree_[0]; + if(as.size() != lowLevel.size()) { + std::cerr << "Error in active site array" << std::endl; + return; + } + // mark low level leaves + asPointsNumber_=0; + for(unsigned int i=0; i& lowLevelPointers = currNode.getChildren(); + for(unsigned int lowLevelIndex=0; lowLevelIndexgetAsNum()); + } + } +} + +template +void MultiResolution::printTree(std::ofstream& outFile) +{ + int count=0; + for(unsigned int levelIndex=0; levelIndex 0) { + Atom atom(currNode.position(), 'A', count++, levelIndex, "INT", 'X'); + outFile << atom << std::endl; + } else { + Atom atom(currNode.position(), 'A', count++, levelIndex, "PRB", 'X'); + outFile << atom << std::endl; + } + } + } +} + +#endif diff --git a/model/CombinatorialAssembler/libs_DockingLib/ResidueIndexedMolecule.h b/model/CombinatorialAssembler/libs_DockingLib/ResidueIndexedMolecule.h new file mode 100644 index 0000000000000000000000000000000000000000..bc5a1d2f78f6ef46dfbd3deb6543718af0f79f5f --- /dev/null +++ b/model/CombinatorialAssembler/libs_DockingLib/ResidueIndexedMolecule.h @@ -0,0 +1,188 @@ +#ifndef INDEX_MOL_H +#define INDEX_MOL_H + +#include +#include +#include + +#include + +#include +#include +#include + +template // AtomT must implement: residueSequenceID() and chainId() +class ResidueIndexedMolecule : public Molecule { + public: + ResidueIndexedMolecule() : Molecule() {isInitialized_ = false;} + + //// check if the mapping is initialized + bool isInitialized() const { return isInitialized_; } + + //// clear and init + void resetResEntries(); + + //// + int residueEntry(char chainId, std::string resSequenceID ) const { + if (!isInitialized_) { + ResidueIndexedMolecule* nonConstThis = const_cast*>(this); + nonConstThis->resetResEntries(); + } + auto aPair = resEntries_.find(indexOf(chainId, resSequenceID)); + if (aPair == resEntries_.end()) + return -1; + return (*aPair).second; + } + + //// + int getFirstAtomEntryForResEntry(unsigned int entry) const { + if (!isInitialized_) { + ResidueIndexedMolecule* nonConstThis = const_cast*>(this); + nonConstThis->resetResEntries(); + } + auto it = entries2atoms_.find(entry); + if(it == entries2atoms_.end()) + return -1; + return it->second; + } + + //// + int getFirstAtomEntryForResIndex(char chainId, std::string resSequenceID) const { + return getFirstAtomEntryForResEntry(residueEntry(chainId, resSequenceID)); + } + + //// + int getClosestAtomEntryForResIndex(char chainId, std::string resSequenceID, + int maxOffset, int& offset) const; + + //// mapping from atom entry (=index in array) to residue entry (=index in array) + int atomEntryToResEntry(unsigned int entry) const { + if (!isInitialized_) { + ResidueIndexedMolecule* nonConstThis = const_cast*>(this); + nonConstThis->resetResEntries(); + } + const AtomT &atom = (*this)[entry]; + return residueEntry(atom.chainId(), atom.residueSequenceID()); + } + + //// + unsigned int numberOfRes() const { + if (!isInitialized_) { + ResidueIndexedMolecule* nonConstThis = const_cast*>(this); + nonConstThis->resetResEntries(); + } + return resEntries_.size(); + } + +private: + bool isInitialized_; + + // mapping data structure + std::map resEntries_; + std::map entries2atoms_; + std::set chainIDs_; // chain ids of the molecules + +private: + // compute hash map key + std::string indexOf(char chainId, std::string resSequenceID) const; + + //// initialization + void initResEntries(); +}; + + +template +std::string ResidueIndexedMolecule::indexOf(char chainId, std::string resSequenceID) const { + std::string indexStr = resSequenceID; + boost::trim(indexStr); + indexStr += "_"; + indexStr += chainId; + return indexStr; +} + +template +void ResidueIndexedMolecule::resetResEntries() { + resEntries_.clear(); + entries2atoms_.clear(); + chainIDs_.clear(); + initResEntries(); +} + +template +void ResidueIndexedMolecule::initResEntries() { + std::vector > aaMol = AminoAcid::parsePDB(*this); + // cerr << "aaMol.size() = " << aaMol.size() << endl; + for(unsigned int residueCounter=0; residueCounter::const_iterator aPair = resEntries_.find(indexOf(chainId, resSequenceID)); + if (aPair != resEntries_.end()) { + int currEntry = (*aPair).second; + entries2atoms_[currEntry] = atomCounter+CAindex; + } + atomCounter+=aaMol[residueCounter].size(); + } + + isInitialized_ = true; +} + +template +int ResidueIndexedMolecule::getClosestAtomEntryForResIndex(char chainId, + std::string resSequenceId, + int maxOffset, + int& offset) const { + if (!isInitialized_) { + ResidueIndexedMolecule* nonConstThis = const_cast*>(this); + nonConstThis->resetResEntries(); + } + + if(chainIDs_.find(chainId) == chainIDs_.end()) return -1; + + // 1. look for CA of the given residue + int atomIndex = getFirstAtomEntryForResIndex(chainId, resSequenceId); + if(atomIndex != -1) + return atomIndex; + + // 2. look for +/- offset + int resIndex = std::stoi(resSequenceId); + for(int i=1; i<=maxOffset; i++) { + // try +/- i + std::string resString = std::to_string(resIndex + i); + int entry = residueEntry(chainId, resString); + if(entry != -1) { // match found + offset = i; + Logger::warningMessage() << "Can't find residue " << resSequenceId << " chain " + << chainId << " using closest " << resString << std::endl; + return getFirstAtomEntryForResEntry(entry); + } + // -i + resString = std::to_string(resIndex - i); + entry = residueEntry(chainId, resString); + if(entry != -1) { // match found + offset = -i; + Logger::warningMessage() << "Can't find residue " << resSequenceId << " chain " + << chainId << " using closest " << resString << std::endl; + return getFirstAtomEntryForResEntry(entry); + } + } + Logger::warningMessage() << "Can't find residue " << resSequenceId + << " chain " << chainId << std::endl; + return -1; +} + +#endif diff --git a/model/CombinatorialAssembler/libs_DockingLib/SASurface.cc b/model/CombinatorialAssembler/libs_DockingLib/SASurface.cc new file mode 100644 index 0000000000000000000000000000000000000000..680127c6734b72d1e4dae199853bba71eda979f7 --- /dev/null +++ b/model/CombinatorialAssembler/libs_DockingLib/SASurface.cc @@ -0,0 +1,68 @@ +#include "SASurface.h" + +SASurface::SASurface(ChemMolecule& molecule, float probeRadius, float d) : + molecule_(molecule), probeRadius_(probeRadius), density_(d), molIntersection_(&molecule_, probeRadius_) +{} + +unsigned int SASurface::buildCapsSurface(Surface& surface) { + surface.reserve(int(molecule_.size() * density_)); //CHECK!!! + for(unsigned int i=0; i +#include "ChemMolecule.h" +#include "Interface.h" +#include + + +/* + Class that computes solvent accessible surface for the molecule +*/ +class SASurface { + public: + SASurface(ChemMolecule& mol, float probe_radius, float d); + + unsigned int buildCapsSurface(Surface& surface); + + // unsigned int buildBeltsSurface(Surface& surface); + + //// computes ASA for each atom in ChemMolecule and returns ASA of the molecule + float computeASAForAtoms(); + + private: + ChemMolecule& molecule_; + float probeRadius_; + float density_; + MolIntersection molIntersection_; +}; + +#endif diff --git a/model/CombinatorialAssembler/libs_DockingLib/connolly_surface.cc b/model/CombinatorialAssembler/libs_DockingLib/connolly_surface.cc new file mode 100644 index 0000000000000000000000000000000000000000..45f3d1710e1c431acb537fa90c7a35f5fd4bbe41 --- /dev/null +++ b/model/CombinatorialAssembler/libs_DockingLib/connolly_surface.cc @@ -0,0 +1,1189 @@ + +/* Note that more complete documentation of the original Connolly method + can be found in QCPE, program 429, e.g. at + http://atlas.physbio.mssm.edu/~mezei/molmod_core/docs/connolly.html + */ + +#include "connolly_surface.h" +#include + +#include +#include + +#include +#include +#include + + +/* Put GridPoint in a non-anonymous namespace, since g++ 4.2 has a bug which + results in a compilation failure if it's in the anonymous namespace: + https://svn.boost.org/trac/boost/ticket/3729 + */ +namespace detail { +struct GridPoint { + GridPoint() : icube(-1), scube(false), sscube(false) {} + + int icube; + bool scube, sscube; +}; +} + +namespace { + +static const int MAXSPH = 1000; +static const int MAXCIR = 1000; + +struct AtomTypeInfo { + // unit vectors + std::vector ua; + + // extended vectors + std::vector eva; +}; + +struct AtomInfo { + AtomInfo() : ico(3), skip(false), icuptr(-1) {} + + // Integer cube coordinate + std::vector ico; + + // Skip from grid + bool skip; + + // Index of next atom in same cube as this one + int icuptr; +}; + +class Cube { + public: + void grid_coordinates(const std::vector &CO, float radmax, float rp) { + // calculate width of cube from maximum atom radius and probe radius + width_ = 2. * (radmax + rp); + + // minimum atomic coordinates (cube corner) + Vector3 comin(1000000.0, 1000000.0, 1000000.0); + for (unsigned n = 0; n < CO.size(); ++n) { + comin.updateX(std::min(comin[0], CO[n][0])); + comin.updateY(std::min(comin[1], CO[n][1])); + comin.updateZ(std::min(comin[2], CO[n][2])); + } + + dim_ = 0; + atom_info_.resize(CO.size()); + for (unsigned n = 0; n < CO.size(); ++n) { + for (int k = 0; k < 3; ++k) { + int ico = static_cast((CO[n][k] - comin[k]) / width_); + /*if(ico >= 0) { + std::cerr << "cube coordinate out of range" << std::endl; + }*/ + dim_ = std::max(dim_, ico); + atom_info_[n].ico[k] = ico; + } + } + dim_++; + + cube_.resize(boost::extents[dim_][dim_][dim_]); + + for (unsigned n = 0; n < CO.size(); ++n) { + AtomInfo &ai = atom_info_[n]; + if (!ai.skip) { + add_atom_to_cube(CO, n); + } + } + + update_adjacent(); + } + + bool get_neighbors(int n, const std::vector &CO, float rp, + const std::vector &IAT, + const std::vector &rtype, + std::vector &neighbors) { + AtomInfo &ai = atom_info_[n]; + neighbors.resize(0); + if (ai.skip) { + return false; + } + int ici = ai.ico[0]; + int icj = ai.ico[1]; + int ick = ai.ico[2]; + if (!cube_[ici][icj][ick].sscube) { + return false; + } + float sumi = 2 * rp + rtype[IAT[n]]; + for (int jck = ick - 1; jck <= ick + 1; ++jck) { + if (jck >= 0 && jck < dim_) { + for (int jcj = icj - 1; jcj <= icj + 1; ++jcj) { + if (jcj >= 0 && jcj < dim_) { + for (int jci = ici - 1; jci <= ici + 1; ++jci) { + if (jci >= 0 && jci < dim_) { + for (int jatom = cube_[jci][jcj][jck].icube; jatom >= 0; + jatom = atom_info_[jatom].icuptr) { + float sum = sumi + rtype[IAT[jatom]]; + if (n != jatom && CO[n].dist2(CO[jatom]) < sum * sum) { + neighbors.push_back(jatom); + } + } + } + } + } + } + } + } + return true; + } + + private: + void add_atom_to_cube(const std::vector &CO, int n) { + AtomInfo &ai = atom_info_[n]; + int existing_atom = cube_[ai.ico[0]][ai.ico[1]][ai.ico[2]].icube; + if (existing_atom < 0) { + // first atom in this cube + cube_[ai.ico[0]][ai.ico[1]][ai.ico[2]].icube = n; + cube_[ai.ico[0]][ai.ico[1]][ai.ico[2]].scube = true; + } else { + while (true) { + double dist = CO[n].dist2(CO[existing_atom]); + if (dist <= 0.) { + ai.skip = true; + std::cerr << "Skipped atom " << n << " with same coordinates as " + << existing_atom << std::endl; + return; + } + if (atom_info_[existing_atom].icuptr == -1) { + atom_info_[existing_atom].icuptr = n; + return; + } + existing_atom = atom_info_[existing_atom].icuptr; + } + } + } + + void update_adjacent() { + for (int i = 0; i < dim_; ++i) { + for (int j = 0; j < dim_; ++j) { + for (int k = 0; k < dim_; ++k) { + update_cube(i, j, k); + } + } + } + } + + void update_cube(int i, int j, int k) { + if (cube_[i][j][k].icube >= 0) { + for (int i1 = i - 1; i1 <= i + 1; ++i1) + if (i1 >= 0 && i1 < dim_) { + for (int j1 = j - 1; j1 <= j + 1; ++j1) + if (j1 >= 0 && j1 < dim_) { + for (int k1 = k - 1; k1 <= k + 1; ++k1) { + if (k1 >= 0 && k1 < dim_ && cube_[i1][j1][k1].scube) { + cube_[i][j][k].sscube = true; + return; + } + } + } + } + } + } + + int dim_; + float width_; + std::vector atom_info_; + boost::multi_array cube_; +}; + +struct YonProbe { + YonProbe(Vector3 py, Vector3 ay) + : center(py), altitude(ay) {} + Vector3 center; + Vector3 altitude; +}; + +struct SurfPoint { + bool yon; + Vector3 s; + float area; + int n1, n2, n3; +}; + +enum PointType { + VICTIM, + YON, + OTHER +}; + +struct ProbePoint { + ProbePoint() : type(OTHER) {} + + char ishape; + Vector3 position; + Vector3 to_center; + PointType type; + std::vector points; +}; + +class YonCube { + public: + YonCube(const std::vector &yon_probes, float rp, float dp, + float radmax) + : comin_(1000000.0, 1000000.0, 1000000.0) { + width_ = 2. * (radmax + rp); + dp2_ = dp * dp; + for (unsigned n = 0; n < yon_probes.size(); ++n) { + comin_.updateX(std::min(comin_[0], yon_probes[n].center[0])); + comin_.updateY(std::min(comin_[1], yon_probes[n].center[1])); + comin_.updateZ(std::min(comin_[2], yon_probes[n].center[2])); + // for (int k = 0; k < 3; ++k) { + // comin_[k] = std::min(comin_[k], yon_probes[n].center[k]); + // } + } + + dim_ = 0; + atom_info_.resize(yon_probes.size()); + for (unsigned n = 0; n < yon_probes.size(); ++n) { + atom_info_[n].ico = get_cube_coordinates(yon_probes[n].center); + for (int k = 0; k < 3; ++k) { + dim_ = std::max(dim_, atom_info_[n].ico[k]); + } + } + dim_++; + cube_.resize(boost::extents[dim_][dim_][dim_]); + for (int i = 0; i < dim_; ++i) { + for (int j = 0; j < dim_; ++j) { + for (int k = 0; k < dim_; ++k) { + cube_[i][j][k] = -1; + } + } + } + for (unsigned n = 0; n < yon_probes.size(); ++n) { + add_probe_to_cube(n); + } + } + + bool probe_overlap(const ProbePoint &probe, + const std::vector &yon_probes) { + std::vector ic = get_cube_coordinates(probe.position); + for (int k = 0; k < 3; ++k) { + ic[k] = std::max(ic[k], 0); + ic[k] = std::min(ic[k], dim_ - 1); + } + // Check adjoining cubes + for (int jci = ic[0] - 1; jci <= ic[0] + 1; ++jci) { + if (jci >= 0 && jci < dim_) { + for (int jcj = ic[1] - 1; jcj <= ic[1] + 1; ++jcj) { + if (jcj >= 0 && jcj < dim_) { + for (int jck = ic[2] - 1; jck <= ic[2] + 1; ++jck) { + if (jck >= 0 && jck < dim_) { + for (int jp = cube_[jci][jcj][jck]; jp >= 0; + jp = atom_info_[jp].icuptr) { + const YonProbe &yp = yon_probes[jp]; + if (yp.center.dist2(probe.position) < dp2_ && + yp.altitude * probe.to_center < 0) { + return true; + } + } + } + } + } + } + } + } + return false; + } + + private: + void add_probe_to_cube(int n) { + AtomInfo &ai = atom_info_[n]; + int existing_atom = cube_[ai.ico[0]][ai.ico[1]][ai.ico[2]]; + if (existing_atom < 0) { + cube_[ai.ico[0]][ai.ico[1]][ai.ico[2]] = n; + } else { + while (atom_info_[existing_atom].icuptr != -1) { + existing_atom = atom_info_[existing_atom].icuptr; + } + atom_info_[existing_atom].icuptr = n; + } + } + + std::vector get_cube_coordinates(const Vector3 &c) { + std::vector ret; + for (int k = 0; k < 3; ++k) { + ret.push_back(static_cast((c[k] - comin_[k]) / width_)); + } + return ret; + } + + Vector3 comin_; + int dim_; + float width_; + float dp2_; + std::vector atom_info_; + boost::multi_array cube_; +}; + +// Generate unit vectors over sphere +void genun(std::vector &vec, unsigned n) { + vec.reserve(n); + vec.resize(0); + + int nequat = static_cast(std::sqrt(n * 3.14159)); + int nvert = std::max(1, nequat / 2); + + for (int i = 0; i <= nvert; ++i) { + float fi = (3.14159 * i) / nvert; + float z = std::cos(fi); + float xy = std::sin(fi); + int nhor = std::max(1, static_cast(nequat * xy)); + for (int j = 0; j < nhor; ++j) { + float fj = (2. * 3.14159 * j) / nhor; + float x = std::cos(fj) * xy; + float y = std::sin(fj) * xy; + if (vec.size() >= n) return; + vec.push_back(Vector3(x, y, z)); + } + } +} + +// Triple product of three vectors +float det(const Vector3 &a, const Vector3 &b, + const Vector3 &c) { + Vector3 ab = a&b; //algebra::get_vector_product(a, b); + return ab * c; +} + +// Make identity matrix +void imatx(std::vector &ghgt) { + ghgt.resize(3); + ghgt[0] = Vector3(1., 0., 0.); + ghgt[1] = Vector3(0., 1., 0.); + ghgt[2] = Vector3(0., 0., 1.); +} + +// Concatenate matrix b into matrix a +void cat(std::vector &a, const std::vector &b) { + std::vector temp(3); + /* + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + temp[j][i] = a[0][i] * b[j][0] + a[1][i] * b[j][1] + a[2][i] * b[j][2]; + } + } + */ + + for (int j = 0; j < 3; ++j) { + temp[j].updateX(a[0][0] * b[j][0] + a[1][0] * b[j][1] + a[2][0] * b[j][2]); + temp[j].updateY(a[0][1] * b[j][0] + a[1][1] * b[j][1] + a[2][1] * b[j][2]); + temp[j].updateZ(a[0][2] * b[j][0] + a[1][2] * b[j][1] + a[2][2] * b[j][2]); + } + + a = temp; +} + +// Conjugate matrix g with matrix h giving ghgt +void conj(const std::vector &h, const std::vector &g, + std::vector &ghgt) { + // INITIALIZE GHGT MATRIX TO IDENTITY + imatx(ghgt); + // CONCATENATE G H GT + cat(ghgt, g); + cat(ghgt, h); + // CALCULATE GT + std::vector gt(3); + /*for (int k = 0; k < 3; ++k) { + for (int l = 0; l < 3; ++l) { + gt[l][k] = g[k][l]; + } + }*/ + + for (int l = 0; l < 3; ++l) { + gt[l].updateX(g[0][l]); + gt[l].updateY(g[1][l]); + gt[l].updateZ(g[2][l]); + } + cat(ghgt, gt); +} + +// Multiply v by a giving w +void multv(const Vector3 &v, const std::vector &a, + Vector3 &w) { + //for (int i = 0; i < 3; ++i) { + //w[i] = a[0][i] * v[0] + a[1][i] * v[1] + a[2][i] * v[2]; + //} + w.updateX(a[0][0] * v[0] + a[1][0] * v[1] + a[2][0] * v[2]); + w.updateY(a[0][1] * v[0] + a[1][1] * v[1] + a[2][1] * v[2]); + w.updateZ(a[0][2] * v[0] + a[1][2] * v[1] + a[2][2] * v[2]); +} + +// Return b perpendicular to a +Vector3 vperp(const Vector3 &a) { + Vector3 b(0., 0., 0.), p; + + // FIND SMALLEST COMPONENT + float small = 10000.0; + int m = -1; + for (int k = 0; k < 3; ++k) { + if (std::abs(a[k]) < small) { + small = std::abs(a[k]); + m = k; + } + } + if(m == 0) b.updateX(1.0); + if(m == 1) b.updateY(1.0); + if(m == 2) b.updateZ(1.0); + //b[m] = 1.0; + + // take projection along a and subtract from b + float dt = a[m] / a.norm2(); //a.get_squared_magnitude(); + b = b - dt * a; + // renormalize b + return b.getUnitVector(); +} + +// Collision check of probe with neighboring atoms +bool collid(const Vector3 &p, const std::vector &cnbr, + const std::vector &ernbr, int jnbr, int knbr, int lkf, + const std::vector &lknbr) { + for (int i = lkf; i >= 0; i = lknbr[i]) { + if (i == jnbr || i == knbr) continue; + float dist2 = p.dist2(cnbr[i]); + if (dist2 < ernbr[i] * ernbr[i]) { + return true; + } + } + return false; +} + +struct SurfaceInfo { + SurfaceInfo() : area(0.), npoints(0), nlost_saddle(0), nlost_concave(0) {} + + float area; + int npoints; + int nlost_saddle; + int nlost_concave; +}; + +void handle_atom(Surface &surface_points, int iatom, float d, + const std::vector inbr, const std::vector &CO, + float rp, std::vector &srs, const std::vector &up, + std::vector &yon_probes, + const std::vector &circle, const std::vector &IAT, + const std::vector &rtype, + const std::vector &attyp_info, + std::vector &beforept, SurfaceInfo &surface) { + float ri = rtype[IAT[iatom]]; + Vector3 ci = CO[iatom]; + + /* transfer data from main arrays to neighbors */ + std::vector cnbr; + std::vector rnbr; + std::vector ernbr; + std::vector disnbr; + std::vector lknbr; + for (unsigned iuse = 0; iuse < inbr.size(); ++iuse) { + int jatom = inbr[iuse]; + cnbr.push_back(CO[jatom]); + rnbr.push_back(rtype[IAT[jatom]]); + ernbr.push_back(rtype[IAT[jatom]] + rp); + disnbr.push_back(ci.dist2(CO[jatom])); + // initialize link to next farthest out neighbor + lknbr.push_back(-1); + } + + // Set up a linked list of neighbors in order of + // increasing distance from iatom + int lkf = 0; + // put remaining neighbors in linked list at proper position + for (int l = lkf + 1; l < static_cast(inbr.size()); ++l) { + int before = -1; + int after = lkf; + // step through the list until before:after bracket the current neighbor + while (after >= 0 && disnbr[l] < disnbr[after]) { + before = after; + after = lknbr[after]; + } + if (before < 0) { + lkf = l; + } else { + lknbr[before] = l; + } + lknbr[l] = after; + } + // Handle no-neighbors case + if (inbr.size() == 0) { + lkf = -1; + } + + // medium loop for each neighbor of iatom + for (unsigned jnbr = 0; jnbr < inbr.size(); ++jnbr) { + int jatom = inbr[jnbr]; + if (jatom <= iatom) continue; + + float rj = rnbr[jnbr]; + Vector3 cj = cnbr[jnbr]; + + /* HERE FOLLOW GEOMETRIC CALCULATIONS OF POINTS, VECTORS AND + DISTANCES USED FOR PROBE PLACEMENT IN BOTH SADDLE AND + CONCAVE REENTRANT SURFACE GENERATION */ + + /* CALCULATE THE INTERSECTION + OF THE EXPANDED SPHERES OF IATOM AND JATOM + THIS CIRCLE IS CALLED THE SADDLE CIRCLE + THE PLANE IT LIES IN IS CALLED THE SADDLE PLANE */ + + Vector3 vij = cj - ci; + + /* CREATE AN ORTHONORMAL FRAME + WITH UIJ POINTING ALONG THE INTER-ATOMIC AXIS + AND Q AND T DEFINING THE SADDLE PLANE */ + float dij = vij.norm(); + if (dij <= 0.) { + std::cerr << "Atoms " << iatom << " and " << jatom + << " have the same center" << std::endl; + continue; + } + Vector3 uij = vij.getUnitVector(); + Vector3 q = vperp(uij); + Vector3 t = uij&q;//algebra::get_vector_product(uij, q); + + // CALCULATE THE SADDLE CIRCLE CENTER AND RADIUS + float f = 0.5 * (1.0 + ((ri + rp) * (ri + rp) - (rj + rp) * (rj + rp)) / + (dij * dij)); + // BASE POINT + Vector3 bij = ci + f * vij; + float f1 = ri + rj + 2. * rp; + f1 = f1 * f1 - dij * dij; + // SKIP TO BOTTOM OF MIDDLE LOOP IF ATOMS ARE TOO FAR APART + if (f1 <= 0.0) continue; + + float f2 = dij * dij - (ri - rj) * (ri - rj); + // SKIP TO BOTTOM OF MIDDLE LOOP IF ONE ATOM INSIDE THE OTHER + if (f2 <= 0.0) continue; + + // HEIGHT (RADIUS OF SADDLE CIRCLE) + float hij = std::sqrt(f1 * f2) / (2. * dij); + // A STARTING ALTITUDE + Vector3 aij = hij * q; + + // CONCAVE REENTRANT SURFACE + + // GATHER MUTUAL NEIGHBORS OF IATOM AND JATOM + int mutual = 0; + std::vector mnbr(inbr.size()); + for (unsigned knbr = 0; knbr < inbr.size(); ++knbr) { + float d2 = cj.dist2(cnbr[knbr]); + float radsum = 2. * rp + rj + rnbr[knbr]; + mnbr[knbr] = d2 < radsum * radsum && knbr != jnbr; + if (mnbr[knbr]) { + ++mutual; + } + } + + // INNER LOOP FOR EACH MUTUAL NEIGHBOR OF IATOM AND JATOM + char ishape = 3; + for (unsigned knbr = 0; knbr < inbr.size(); ++knbr) { + if (!mnbr[knbr]) continue; + + int katom = inbr[knbr]; + if (katom <= jatom) continue; + + // transfer from neighbor array to katom variables + float rk = rnbr[knbr]; + Vector3 ck = cnbr[knbr]; + + /* CALCULATE INTERSECTION OF EXPANDED SPHERE OF KATOM + WITH SADDLE PLANE. WE WILL CALL THIS THE KATOM CIRCLE. */ + + /* PROJECTION OF VECTOR, + FROM KATOM TO A POINT ON THE SADDLE PLANE, + ONTO IATOM-JATOM AXIS, + IN ORDER TO GET DISTANCE KATOM IS FROM SADDLE PLANE */ + float dk = uij[0] * (bij[0] - ck[0]) + uij[1] * (bij[1] - ck[1]) + + uij[2] * (bij[2] - ck[2]); + + // CALCULATE RADIUS OF KATOM CIRCLE + float rijk = (rk + rp) * (rk + rp) - dk * dk; + // SKIP CONCAVE CALCULATION IF NO INTERSECTION + if (rijk <= 0.0) continue; + rijk = std::sqrt(rijk); + + // CALCULATE CENTER OF KATOM CIRCLE + Vector3 cijk = ck + dk * uij; + + // CALCULATE INTERSECTION OF THE KATOM CIRCLE WITH THE SADDLE CIRCLE + Vector3 vijk = cijk - bij; + float dijk = vijk.norm(); + + if (dijk <= 0.0) { + std::cerr << "Atoms " << iatom << ", " << jatom << ", and " << katom + << " have concentric circles" << std::endl; + continue; + } + float f = 0.5 * (1.0 + (hij * hij - rijk * rijk) / (dijk * dijk)); + // BASE POINT BIJK IS ON SYMMETRY PLANE AND SADDLE PLANE + Vector3 bijk = bij + f * vijk; + + float f1 = (hij + rijk) * (hij + rijk) - dijk * dijk; + // SKIP TO BOTTOM OF INNER LOOP IF KATOM TOO FAR AWAY + if (f1 <= 0.0) continue; + + float f2 = dijk * dijk - (hij - rijk) * (hij - rijk); + // SKIP TO BOTTOM OF INNER LOOP IF KATOM CIRCLE INSIDE SADDLE CIRCLE + // OR VICE-VERSA + if (f2 <= 0.0) continue; + + float hijk = std::sqrt(f1 * f2) / (2. * dijk); + Vector3 uijk = vijk.getUnitVector(); + + // UIJ AND UIJK LIE IN THE SYMMETRY PLANE PASSING THROUGH THE ATOMS + // SO THEIR CROSS PRODUCT IS PERPENDICULAR TO THIS PLANE + Vector3 aijk0 = uij&uijk; //algebra::get_vector_product(uij, uijk); + + std::vector aijk; + aijk.push_back(aijk0 * hijk); + aijk.push_back(-aijk0 * hijk); + + // PROBE PLACEMENT AT ENDS OF ALTITUDE VECTORS + std::vector pijk(2); + std::vector pair(2); + for (int ip = 0; ip < 2; ++ip) { + pijk[ip] = bijk + aijk[ip]; + // COLLISION CHECK WITH MUTUAL NEIGHBORS + pair[ip] = !collid(pijk[ip], cnbr, ernbr, jnbr, knbr, lkf, lknbr); + } + // IF NEITHER PROBE POSITION IS ALLOWED, SKIP TO BOTTOM OF INNER LOOP + if (!pair[0] && !pair[1]) continue; + bool both = pair[0] && pair[1]; + // SOME REENTRANT SURFACE FOR ALL THREE ATOMS + srs[iatom] = srs[jatom] = srs[katom] = true; + + // GENERATE SURFACE POINTS + float area = (4. * pi * rp * rp) / up.size(); + for (int ip = 0; ip < 2; ++ip) { + if (!pair[ip]) continue; + + // DETERMINE WHETHER PROBE HAS SURFACE ON FAR SIDE OF PLANE + bool yonprb = hijk < rp && !both; + // CALCULATE VECTORS DEFINING SPHERICAL TRIANGLE + // THE VECTORS ARE GIVEN THE PROBE RADIUS AS A LENGTH + // ONLY FOR THE PURPOSE OF MAKING THE GEOMETRY MORE CLEAR + Vector3 vpi = (ci - pijk[ip]) * rp / (ri + rp); + Vector3 vpj = (cj - pijk[ip]) * rp / (rj + rp); + Vector3 vpk = (ck - pijk[ip]) * rp / (rk + rp); + float sign = det(vpi, vpj, vpk); + + ProbePoint probe_point; + // GATHER POINTS ON PROBE SPHERE LYING WITHIN TRIANGLE + for (unsigned i = 0; i < up.size(); ++i) { + SurfPoint sp; + // IF THE UNIT VECTOR IS POINTING AWAY FROM THE SYMMETRY PLANE + // THE SURFACE POINT CANNOT LIE WITHIN THE INWARD-FACING TRIANGLE + if (up[i] * aijk[ip] > 0.) continue; + if (sign * det(up[i], vpj, vpk) < 0.) continue; + if (sign * det(vpi, up[i], vpk) < 0.) continue; + if (sign * det(vpi, vpj, up[i]) < 0.) continue; + // CALCULATED WHETHER POINT IS ON YON SIDE OF PLANE + sp.yon = aijk[ip] * (aijk[ip] + up[i]) < 0.; + // OVERLAPPING REENTRANT SURFACE REMOVAL + // FOR SYMMETRY-RELATED PROBE POSITIONS + if (sp.yon && both) continue; + + // CALCULATE COORDINATES OF SURFACE POINT + sp.s = pijk[ip] + up[i] * rp; + + // FIND THE CLOSEST ATOM AND PUT THE THREE ATOM NUMBERS + // IN THE PROPER ORDER + // N1 IS CLOSEST, N2 < N3 + float dsi = sp.s.dist(ci) - ri; + float dsj = sp.s.dist(cj) - rj; + float dsk = sp.s.dist(ck) - rk; + if (dsi <= dsj && dsi <= dsk) { + sp.n1 = iatom; + sp.n2 = jatom; + sp.n3 = katom; + } else if (dsj <= dsi && dsj <= dsk) { + sp.n1 = jatom; + sp.n2 = iatom; + sp.n3 = katom; + } else { + sp.n1 = katom; + sp.n2 = iatom; + sp.n3 = jatom; + } + sp.area = area; + probe_point.points.push_back(sp); + } + if (probe_point.points.size() > 0) { + probe_point.ishape = ishape; + probe_point.position = pijk[ip]; + probe_point.to_center = aijk[ip]; + probe_point.type = yonprb ? YON : OTHER; + beforept.push_back(probe_point); + + // SAVE PROBE IN YON PROBE ARRAYS + if (yonprb) { + yon_probes.push_back(YonProbe(pijk[ip], aijk[ip])); + } + } + } + } + + // SADDLE-SHAPED REENTRANT + ishape = 2; + + // SPECIAL CHECK FOR BURIED TORI + + /* IF NEITHER ATOM HAS ANY REENTRANT SURFACE SO FAR + (AFTER TRIANGLES WITH ALL KATOMS HAVE BEEN CHECKED) + AND IF THERE IS SOME MUTUAL NEIGHBOR IN THE SAME MOLECULE + CLOSE ENOUGH SO THAT THE TORUS CANNOT BE FREE, + THEN WE KNOW THAT THIS MUST BE A BURIED TORUS */ + if (!srs[iatom] && !srs[jatom] && mutual > 0) { + bool buried_torus = false; + for (unsigned knbr = 0; knbr < inbr.size() && !buried_torus; ++knbr) { + if (!mnbr[knbr]) continue; + float d2 = bij.dist2(cnbr[knbr]); + float rk2 = ernbr[knbr] * ernbr[knbr] - hij * hij; + if (d2 < rk2) { + buried_torus = true; + } + } + if (buried_torus) continue; + } + + // CALCULATE NUMBER OF ROTATIONS OF PROBE PAIR, + // ROTATION ANGLE AND ROTATION MATRIX + float rij = ri / (ri + rp) + rj / (rj + rp); + float avh = (std::abs(hij - rp) + hij * rij) / 3.; + int nrot = std::max(static_cast(std::sqrt(d) * pi * avh), 1); + float angle = pi / nrot; + + // SET UP ROTATION MATRIX AROUND X-AXIS + std::vector h(3, Vector3(0., 0., 0.)); + h[0].updateX(1.0); + h[1].updateY(std::cos(angle)); + h[2].updateZ(std::cos(angle)); + h[1].updateZ(std::sin(angle)); + h[2].updateY(-h[1][2]); + // CALCULATE MATRIX TO ROTATE X-AXIS ONTO IATOM-JATOM AXIS + std::vector g; + g.push_back(uij); + g.push_back(q); + g.push_back(t); + + // MAKE THE PROBE PAIR ROTATION MATRIX BE ABOUT THE IATOM-JATOM AXIS + std::vector ghgt; + conj(h, g, ghgt); + + // ARC GENERATION + Vector3 pij = bij + aij; + Vector3 vpi = (ci - pij) * rp / (ri + rp); + Vector3 vpj = (cj - pij) * rp / (rj + rp); + + // ROTATE CIRCLE ONTO IATOM-JATOM-PROBE PLANE + // AND SELECT POINTS BETWEEN PROBE-IATOM AND + // PROBE-JATOM VECTOR TO FORM THE ARC + int narc = 0; + std::vector ayon; + std::vector arca; + std::vector vbs0[2]; + for (unsigned i = 0; i < circle.size(); ++i) { + Vector3 vps0; + // ROTATION + multv(circle[i], g, vps0); + // IF THE VECTOR IS POINTING AWAY FROM THE SYMMETRY LINE + // THE SURFACE POINT CANNOT LIE ON THE INWARD-FACING ARC + if (vps0 * aij > 0.) continue; + Vector3 vector = vpi&vps0;//algebra::get_vector_product(vpi, vps0); + if (g[2] * vector < 0.) continue; + vector = vps0 & vpj; //algebra::get_vector_product(vps0, vpj); + if (g[2] * vector < 0.) continue; + + /* MAKE ARC POINT VECTORS ORIGINATE WITH SADDLE CIRCLE CENTER BIJ + RATHER THAN PROBE CENTER BECAUSE THEY WILL BE + ROTATED AROUND THE IATOM-JATOM AXIS */ + vbs0[0].push_back(vps0 + aij); + // INVERT ARC THROUGH LINE OF SYMMETRY + float duij = uij * vbs0[0][narc]; + vbs0[1].push_back(-vbs0[0][narc] + 2 * duij * uij); + + /* CHECK WHETHER THE ARC POINT CROSSES THE IATOM-JATOM AXIS + AND CALCULATE THE AREA ASSOCIATED WITH THE POINT */ + float ht = aij * vbs0[0][narc] / hij; + ayon.push_back(ht < 0.); + arca.push_back((2. * pi * pi * rp * std::abs(ht)) / + (circle.size() * nrot)); + narc++; + } + + // INITIALIZE POWER MATRIX TO IDENTITY + std::vector pow; + imatx(pow); + + // ROTATE THE PROBE PAIR AROUND THE PAIR OF ATOMS + for (int irot = 0; irot < nrot; ++irot, cat(pow, ghgt)) { + std::vector aijp(2); + // MULTIPLY ALTITUDE VECTOR BY POWER MATRIX + multv(aij, pow, aijp[0]); + // SET UP OPPOSING ALTITUDE + aijp[1] = -aijp[0]; + + // SET UP PROBE SPHERE POSITIONS + std::vector pijp(2); + bool pair[2]; + for (int ip = 0; ip < 2; ++ip) { + pijp[ip] = bij + aijp[ip]; + // CHECK FOR COLLISIONS WITH NEIGHBORING ATOMS + pair[ip] = !collid(pijp[ip], cnbr, ernbr, jnbr, -1, lkf, lknbr); + } + + // NO SURFACE GENERATION IF NEITHER PROBE POSITION IS ALLOWED + if (!pair[0] && !pair[1]) continue; + bool both = pair[0] && pair[1]; + // SOME REENTRANT SURFACE FOR BOTH ATOMS + srs[iatom] = srs[jatom] = true; + + /* SKIP TO BOTTOM OF MIDDLE LOOP IF IATOM AND JATOM + ARE CLOSE ENOUGH AND THE SURFACE POINT DENSITY IS + LOW ENOUGH SO THAT THE ARC HAS NO POINTS */ + if (narc <= 0) continue; + + // SURFACE GENERATION + for (int ip = 0; ip < 2; ++ip) { + if (!pair[ip]) continue; + + // DETERMINE WHETHER PROBE HAS SURFACE ON FAR SIDE OF LINE + bool yonprb = hij < rp && !both; + ProbePoint probe_point; + // THE SADDLE-SHAPED REENTRANT SURFACE POINTS COME FROM THE ARC + for (int i = 0; i < narc; ++i) { + SurfPoint sp; + /* OVERLAPPING REENTRANT SURFACE REMOVAL + FOR SYMMETRY-RELATED PROBE POSITIONS */ + if (both && ayon[i]) continue; + // ROTATE THE ARC FROM THE XY PLANE ONTO THE IATOM-JATOM-PROBE PLANE + Vector3 vbs; + multv(vbs0[ip][i], pow, vbs); + // MAKE COORDINATES RELATIVE TO ORIGIN + sp.s = bij + vbs; + // FIND THE CLOSEST ATOM AND SET UP THE ATOM NUMBERS FOR THE POINT + float dsi = sp.s.dist(ci) - ri; + float dsj = sp.s.dist(cj) - rj; + if (dsi <= dsj) { + sp.n1 = iatom; + sp.n2 = jatom; + } else { + sp.n1 = jatom; + sp.n2 = iatom; + } + sp.n3 = -1; + + // WE'VE GOT A SURFACE POINT + sp.yon = ayon[i]; + sp.area = arca[i]; + probe_point.points.push_back(sp); + } + if (probe_point.points.size() > 0) { + probe_point.ishape = ishape; + probe_point.position = pijp[ip]; + probe_point.to_center = aijp[ip]; + probe_point.type = yonprb ? YON : OTHER; + beforept.push_back(probe_point); + // SAVE PROBE IN YON PROBE ARRAYS + if (yonprb) { + yon_probes.push_back(YonProbe(pijp[ip], aijp[ip])); + } + } + } + } + } + + /* IF THE PROBE RADIUS IS GREATER THAN ZERO + AND IATOM HAS AT LEAST ONE NEIGHBOR, BUT NO REENTRANT SURFACE, + THEN IATOM MUST BE COMPLETELY INACCESSIBLE TO THE PROBE */ + if (rp > 0. && inbr.size() > 0 && !srs[iatom]) return; + + const AtomTypeInfo &attyp = attyp_info[IAT[iatom]]; + float area = (4. * pi * ri * ri) / attyp.ua.size(); + + // CONTACT PROBE PLACEMENT LOOP + for (unsigned i = 0; i < attyp.ua.size(); ++i) { + // SET UP PROBE COORDINATES + Vector3 pipt = ci + attyp.eva[i]; + // CHECK FOR COLLISION WITH NEIGHBORING ATOMS + if (collid(pipt, cnbr, ernbr, -1, -1, lkf, lknbr)) continue; + + // INCREMENT SURFACE POINT COUNTER FOR CONVEX SURFACE + surface.npoints++; + + // ADD SURFACE POINT AREA TO CONTACT AREA + surface.area += area; + Vector3 outco = ci + ri * attyp.ua[i]; + Vector3 outvec = attyp.ua[i]; + + // CONTACT + //SurfacePoint sp(iatom, -1, -1, outco, area, outvec); + SurfacePoint sp(outco, outvec, area, iatom); + surface_points.push_back(sp); + } + return; +} + +SurfaceInfo generate_contact_surface( + Surface &surface_points, const std::vector &CO, + float radmax, float rp, float d, std::vector &yon_probes, + const std::vector &IAT, const std::vector &rtype, + const std::vector &attyp_info, + std::vector &beforept) { + Cube cube; + cube.grid_coordinates(CO, radmax, rp); + + // set up probe sphere and circle + std::vector up; + int nup = static_cast(4. * pi * rp * rp * d); + nup = std::max(1, nup); + nup = std::min(MAXSPH, nup); + genun(up, nup); + + int ncirc = static_cast(2. * pi * rp * std::sqrt(d)); + ncirc = std::max(1, ncirc); + ncirc = std::min(MAXCIR, ncirc); + std::vector circle; + for (int i = 0; i < ncirc; ++i) { + float fi = (2. * pi * i) / ncirc; + circle.push_back( + Vector3(rp * std::cos(fi), rp * std::sin(fi), 0.)); + } + + SurfaceInfo surface; + std::vector srs(CO.size(), false); + for (unsigned i = 0; i < CO.size(); ++i) { + std::vector itnl; + if (cube.get_neighbors(i, CO, rp, IAT, rtype, itnl)) { + std::sort(itnl.begin(), itnl.end()); + handle_atom(surface_points, i, d, itnl, CO, rp, srs, up, yon_probes, + circle, IAT, rtype, attyp_info, beforept, surface); + } + } + return surface; +} + +void get_victim_probes(const std::vector &yon_probes, + std::vector &beforept, float rp, + float radmax, std::vector &victims) { + // NO VICTIM PROBES IF NO YON PROBES + if (yon_probes.size() == 0) return; + + // Probe diameter + float dp = 2. * rp; + + YonCube cube(yon_probes, rp, dp, radmax); + + int ivic = 0; + for (std::vector::iterator pit = beforept.begin(); + pit != beforept.end(); ++pit, ++ivic) { + if (pit->type == YON) continue; + + // CHECK IF PROBE TOO FAR FROM SYMMETRY ELEMENT FOR POSSIBLE OVERLAP + if (pit->to_center.norm2() > dp * dp) continue; + + // LOOK FOR OVERLAP WITH ANY YON PROBE IN THE SAME MOLECULE + if (cube.probe_overlap(*pit, yon_probes)) { + victims.push_back(ivic); + pit->type = VICTIM; + } + } +} + +void get_eaten_points(const std::vector &yon_probes, + const std::vector &beforept, float dp2, + const ProbePoint &probe, unsigned &neat, unsigned &nyeat, + const std::vector &victims, std::vector &eat, + int &pi) { + neat = nyeat = 0; + eat.resize(0); + if (yon_probes.size() == 0) return; + + pi++; + // DETERMINE IF PROBE IS A YON OR VICTIM PROBE + if (probe.type == OTHER) { + return; + } + + // CHECK THIS VICTIM OR YON PROBE AGAINST ALL YON PROBES + for (unsigned j = 0; j < yon_probes.size(); ++j) { + if (probe.position.dist2(yon_probes[j].center) >= dp2) continue; + if (probe.to_center * yon_probes[j].altitude >= 0.) continue; + + // THIS YON PROBE COULD EAT SOME OF THE PROBE'S POINTS + neat++; + nyeat++; + eat.push_back(yon_probes[j].center); + } + + // ONLY YON PROBES CAN HAVE THEIR POINTS EATEN BY VICTIMS + if (probe.type != YON) return; + + // CHECK THIS YON PROBE AGAINST ALL VICTIM PROBES + for (unsigned j = 0; j < victims.size(); ++j) { + const ProbePoint &victim = beforept[victims[j]]; + if (probe.position.dist2(victim.position) >= dp2) continue; + if (probe.to_center * victim.to_center >= 0.) continue; + // THIS VICTIM PROBE COULD EAT SOME OF THE PROBE'S POINTS + neat++; + eat.push_back(victim.position); + } +} + +void check_eaten_points(Surface &surface_points, + const std::vector &yon_probes, + const std::vector &beforept, float rp, + const std::vector &victims, SurfaceInfo &surface) { + float rp2 = rp * rp; + float dp = rp * 2.; + float dp2 = dp * dp; + int pi = 0; + for (std::vector::const_iterator pit = beforept.begin(); + pit != beforept.end(); ++pit) { + unsigned neat, nyeat; + std::vector eat; + get_eaten_points(yon_probes, beforept, dp2, *pit, neat, nyeat, victims, eat, + pi); + + // READ THE SURFACE POINTS BELONGING TO THE PROBE + for (std::vector::const_iterator sit = pit->points.begin(); + sit != pit->points.end(); ++sit) { + // CHECK SURFACE POINT AGAINST ALL EATERS OF THIS PROBE + bool point_eaten = false; + for (unsigned k = 0; k < eat.size(); ++k) { + // VICTIM PROBES CANNOT EAT NON-YON POINTS OF YON PROBES + if (!(pit->type == YON && !sit->yon && k >= nyeat) && + eat[k].dist2(sit->s) < rp2) { + point_eaten = true; + break; + } + } + + if (!point_eaten) { + Vector3 outvec = (pit->position - sit->s) / rp; + // REENTRANT SURFACE POINT + surface.npoints++; + surface.area += sit->area; + + //SurfacePoint sp(sit->n1, sit->n2, sit->n3, sit->s, sit->area, outvec); + SurfacePoint sp(sit->s, outvec, sit->area, sit->n1, sit->n2, sit->n3); + surface_points.push_back(sp); + } else { + if (pit->ishape == 2) { + surface.nlost_saddle++; + } else { + surface.nlost_concave++; + } + } + } + } +} + +SurfaceInfo generate_reentrant_surface(Surface &surface_points, + const std::vector &yon_probes, + std::vector &beforept, + float rp, float radmax) { + SurfaceInfo surface; + std::vector victims; + get_victim_probes(yon_probes, beforept, rp, radmax, victims); + std::cerr << yon_probes.size() << " yon and " << victims.size() + << " victim probes" << std::endl; + check_eaten_points(surface_points, yon_probes, beforept, rp, victims, + surface); + return surface; +} + +void msdots(Surface &surface_points, float d, float rp, + const std::vector &rtype, const std::vector &CO, + const std::vector &IAT) { + if(rp <= 0) { + std::cerr << "Negative probe radius: " << rp << std::endl; return; + } + float radmax = 0.; + + std::vector attyp_info(rtype.size()); + + // Read atom types + for (unsigned n = 0; n < rtype.size(); ++n) { + if(rtype[n] <= 0) { + std::cerr << "Negative atom radius: " << n << " " << rtype[n] << std::endl; + return; + } + + radmax = std::max(radmax, rtype[n]); + // number of unit vectors depends on sphere area and input density + int nua = std::max( + 1, static_cast((4. * pi * rtype[n] * rtype[n]) * d)); + nua = std::min(nua, MAXSPH); + + // create unit vector arrays + genun(attyp_info[n].ua, nua); + // compute extended vectors for later probe placement + attyp_info[n].eva.resize(attyp_info[n].ua.size()); + for (unsigned isph = 0; isph < attyp_info[n].ua.size(); ++isph) { + attyp_info[n].eva[isph] = (rtype[n] + rp) * attyp_info[n].ua[isph]; + } + } + std::cerr << "Calculation of surface of " + << CO.size() << " atoms, with surface point density " << d + << " and probe radius " << rp << std::endl; + + std::vector yon_probes; + std::vector beforept; + SurfaceInfo contact_surface = + generate_contact_surface(surface_points, CO, radmax, rp, d, yon_probes, + IAT, rtype, attyp_info, beforept); + + SurfaceInfo reentrant_surface = generate_reentrant_surface( + surface_points, yon_probes, beforept, rp, radmax); + + + std::cerr << reentrant_surface.nlost_saddle + << " saddle and " << reentrant_surface.nlost_concave + << " concave surface points removed during non-symmetry " + << "overlapping reentrant surface removal" << std::endl; + std::cerr << contact_surface.npoints + << " contact, " << reentrant_surface.npoints + << " reentrant, and " + << contact_surface.npoints + reentrant_surface.npoints + << " total surface points" << std::endl; + std::cerr << "Contact area: " << contact_surface.area << "; reentrant area: " + << reentrant_surface.area << "; total area: " + << contact_surface.area + reentrant_surface.area << std::endl; +} + +} // namespace + +Surface get_connolly_surface(const ChemMolecule& molecule, float d, float rp) { + typedef boost::unordered_map M; + M radii2type; + + std::vector IAT(molecule.size()); + std::vector CO(molecule.size()); + std::vector rvdw; + for (unsigned int i = 0; i < molecule.size(); ++i) { + float r = molecule[i].getRadius(); + M::const_iterator it = radii2type.find(r); + int type; + if (it == radii2type.end()) { + type = radii2type.size(); + radii2type[r] = type; + rvdw.push_back(r); + } else { + type = it->second; + } + IAT[i] = type; + CO[i] = molecule[i].position(); + } + std::cerr << "Number of vdW radius types: " << rvdw.size() << std::endl; + + std::cerr << "Total number of atoms: " << CO.size() << std::endl; + + /* --------- RUN CONNOLLY'S MOLECULAR SURFACE PROGRAM -------- */ + Surface surface_points; + msdots(surface_points, d, rp, rvdw, CO, IAT); + return surface_points; +} diff --git a/model/CombinatorialAssembler/libs_DockingLib/connolly_surface.h b/model/CombinatorialAssembler/libs_DockingLib/connolly_surface.h new file mode 100644 index 0000000000000000000000000000000000000000..69b3c86f4055fc0b0a12455e0f9a64ad1a5f5cf4 --- /dev/null +++ b/model/CombinatorialAssembler/libs_DockingLib/connolly_surface.h @@ -0,0 +1,21 @@ +#ifndef ConnollySurface_H +#define ConnollySurface_H + + +#include "ChemMolecule.h" +#include "Surface.h" + +/** The algorithm is taken from Connolly's original MS program, which is + freely distributable and Copyright 1983, Michael Connolly. + + M.L. Connolly, "Solvent-accessible surfaces of proteins and nucleic acids", + Science, 221, p709-713 (1983). + + M.L. Connolly, "Analytical molecular surface calculation", + J. Appl. Cryst. 16, p548-558 (1983). + */ +Surface get_connolly_surface(const ChemMolecule& molecule, + float density, float probe_radius); + + +#endif diff --git a/model/CombinatorialAssembler/libs_DockingLib/prGrid.cc b/model/CombinatorialAssembler/libs_DockingLib/prGrid.cc new file mode 100644 index 0000000000000000000000000000000000000000..9f618af76fdb1e75322b14866232ea1af47c7cd3 --- /dev/null +++ b/model/CombinatorialAssembler/libs_DockingLib/prGrid.cc @@ -0,0 +1,206 @@ +#include "prGrid.h" + +ProbGrid::ProbGrid(const Surface &surface, const float inDelta, const float maxRadius): + MoleculeGrid(surface, inDelta, maxRadius) +{ + probs.insert(probs.end(), maxEntry, 0.0); +} + +float ProbGrid::getProb(const Vector3& point) const +{ + int index = getIndexForPoint(point); + if (!isValidIndex(index)) + return MAX_FLOAT; + return probs[index]; +} + +ResidueGrid::ResidueGrid(const Surface &surface, const float inDelta, const float maxRadius): + MoleculeGrid(surface, inDelta, maxRadius) +{ + residues.insert(residues.end(), maxEntry, (int)0); +} + +int ResidueGrid::getResidueEntry(const Vector3& point) const { + int index = getIndexForPoint(point); + if(!isValidIndex(index)) + return 0; + return residues[index]; +} + +void ResidueGrid::printGrid(std::ofstream& file) const { + for(int i=0; i< (int)residues.size();i++) { + if(residues[i] < 0) { + Vector3 point = getPointForIndex(i); + Atom atom(point, 'A', i, residues[i], "PRB", 'X'); + file << atom << std::endl; + } + } +} + +// const std::vector& AtomGrid::getAtoms(const Vector3 &point) const { +// const static std::vector emptyVec; +// int index = getIndexForPoint(point); +// if(isValidIndex(index)) +// return atoms[index]; +// return emptyVec; +// } + +// unsigned int AtomGrid::getAtomsNumber(const Vector3 &point) const { +// int index = getIndexForPoint(point); +// if(!isValidIndex(index)) +// return 0; +// return atoms[index].size(); +// } + +AtomTypeGrid::AtomTypeGrid(const Surface &surface, const float inDelta, const float maxRadius): + MoleculeGrid(surface, inDelta, maxRadius), atomTypes(maxEntry) {} + +SurfacePointGrid::SurfacePointGrid(const Surface &surface, const float inDelta, const float maxRadius): + MoleculeGrid(surface, inDelta, maxRadius) +{ + spPointers.insert(spPointers.end(), maxEntry, (SurfacePoint*)NULL); +} + +void SurfacePointGrid::computeDistAndPointers(const Surface &surface, bool precise) { + std::vector layer1, layer2, *curr = &layer1, *next = &layer2, *tmp; + std::vector sps1, sps2, *currSps = &sps1, *nextSps = &sps2, *tmpSps; + std::vector gridLayer; + gridLayer.insert(gridLayer.end(),maxEntry,-1); + + layer1.reserve(surface.size()*10); + layer2.reserve(surface.size()*10); + sps1.reserve(surface.size()*10); + sps2.reserve(surface.size()*10); + + // mark zero in Grid for each surface point and insert indexes of voxels for first layer + int index; + for (Surface::const_iterator it = surface.begin(); it!= surface.end(); ++it) { + index = getIndexForPoint(it->position()); + if (gridLayer[index] != 0) { + if (precise) sps1.push_back(&(*it)); + layer1.push_back(index); + grid[index] = 0.0; + spPointers[index] = &(*it); + gridLayer[index] = 0; + } + } + + // work on every layer to be computed + const SurfacePoint* p = NULL; + for (int layer = 0; layer < maxGridRadius; layer++) { + std::cerr << " In Layer " << layer << " out of " << maxGridRadius << " curr->size() " << curr->size() << std::endl; + // update voxels with current layer distance and insert indices for next layer + for (unsigned int i = 0; i < curr->size(); i++) { + int index = (*curr)[i]; + for (int j = 0; j < 26; j++) { + int nIndex = index + neigbor[j]; + if (isValidIndex(nIndex)) { + if (precise) { + p = (*currSps)[i]; + if (setDistAndPointer(nIndex, p)) { + if (gridLayer[nIndex] < layer + 1) { + next->push_back(nIndex); + nextSps->push_back(p); + gridLayer[nIndex] = layer + 1; + } + } + } else { + float dist = grid[index] + neigborDist[j]; + if ( dist <= 0 ) { + std::cerr << " dist = grid[index] + neigborDist[j] grid[nIndex] " << dist << " " << grid[index] << " " << neigborDist[j] << " j " << j << " nIndex " << nIndex << " grid[nIndex] " << grid[nIndex] << std::endl; + } + if (grid[nIndex] > dist) { + grid[nIndex] = dist; + spPointers[nIndex] = spPointers[index]; + if (gridLayer[nIndex] < layer + 1) { + next->push_back(nIndex); + gridLayer[nIndex] = layer + 1; + } + } + } + } + } + } + curr->clear(); + tmp = curr; curr = next; next = tmp; + if (precise) { + currSps->clear(); + tmpSps = currSps; currSps = nextSps; nextSps = tmpSps; + } + } +} + +VolumeGrid::VolumeGrid(const Surface &surface, const float inDelta, const float maxRadius): + MoleculeGrid(surface, inDelta, maxRadius) +{ + volumeNormals.insert(volumeNormals.end(), maxEntry, Vector3()); + volumeFunctions.insert(volumeFunctions.end(), maxEntry, 0); +} + +void VolumeGrid::getVolumeFuncAndNormal(const Vector3 ¢er, float &volFunc, Vector3& volNormal) const { + int centerIndex = getIndexForPoint(center); + if (!isValidIndex(centerIndex)) { + std::cerr << "Error: Point out of grid" << std::endl; + exit(1); + } + volFunc = volumeFunctions[centerIndex]; + volNormal = volumeNormals[centerIndex]; +} + +float VolumeGrid::getVolumeFunc(const Vector3 ¢er) const { + int centerIndex = getIndexForPoint(center); + if (!isValidIndex(centerIndex)) { + std::cerr << "Error: Point out of grid" << std::endl; + exit(1); + } + return volumeFunctions[centerIndex]; +} + +const Vector3& VolumeGrid::getVolumeNormal(const Vector3 ¢er) const { + int centerIndex = getIndexForPoint(center); + if (!isValidIndex(centerIndex)) { + std::cerr << "Error: Point out of grid" << std::endl; + exit(1); + } + return volumeNormals[centerIndex]; +} + +void VolumeGrid::computeVolumes(const float radius, const float maxRadius) { + for(unsigned int gridIndex=0; gridIndex < grid.size(); gridIndex++) { + if(fabs(grid[gridIndex]) <= maxRadius) { + float r = (radius > 1+fabs(grid[gridIndex])? radius : 1+fabs(grid[gridIndex])); + int intRadius = getIntGridRadius(r); + int radius2 = intRadius*intRadius; + + int outsideNum = 0; + int insideNum = 0; + int xOut(0), yOut(0), zOut(0); + + int i_bound,j_bound,k_bound; + i_bound = intRadius; + for (int i = -i_bound; i <= i_bound; i++ ) { + j_bound = (int)sqrt(radius2 - i*i); + for (int j = -j_bound; j <= j_bound; j++) { + k_bound = (int)sqrt(radius2 - i*i - j*j); + for (int k = -k_bound; k <= k_bound; k++) { + int index = gridIndex + i + xGridNum * j + xyGridNum * k; + if(isValidIndex(index) && grid[index] <= 0.0) { + insideNum++; + } else { + outsideNum++; + xOut+=i; + yOut+=j; + zOut+=k; + } + } + } + } + volumeFunctions[gridIndex] = ((float)insideNum) / (insideNum + outsideNum); + volumeNormals[gridIndex] = Vector3((float)xOut, (float)yOut, (float)zOut); + if(volumeNormals[gridIndex].norm() == 0) { + std::cerr << "Error in calculateVolumeNormFunc " << std::endl; + } + volumeNormals[gridIndex] = volumeNormals[gridIndex]/volumeNormals[gridIndex].norm(); + } + } +} diff --git a/model/CombinatorialAssembler/libs_DockingLib/prGrid.h b/model/CombinatorialAssembler/libs_DockingLib/prGrid.h new file mode 100644 index 0000000000000000000000000000000000000000..94861c5b3507117c32b3e986d66b0463b7fa9e82 --- /dev/null +++ b/model/CombinatorialAssembler/libs_DockingLib/prGrid.h @@ -0,0 +1,376 @@ +#ifndef PR_GRID_H +#define PR_GRID_H + +#include +#include + +#include + +class ProbGrid : public MoleculeGrid { + public: + ProbGrid(const Surface &surface, const float inDelta, const float maxRadius); + + template + void markProbs(const MoleculeT &M); + + float getProb(const Vector3 &point) const; + private: + std::vector< float > probs; +}; + +class VolumeGrid : public MoleculeGrid { + public: + VolumeGrid(const Surface &surface, const float inDelta, const float maxRadius); + void computeVolumes(const float radius, const float maxRadius); + void getVolumeFuncAndNormal(const Vector3 ¢er, float &volFunc, Vector3& volNormal) const; + float getVolumeFunc(const Vector3 ¢er) const; + const Vector3& getVolumeNormal(const Vector3 ¢er) const; + private: + std::vector< Vector3 > volumeNormals; + std::vector< float > volumeFunctions; +}; + +class ResidueGrid : public MoleculeGrid { + public: + ResidueGrid(const Surface &surface, const float inDelta, const float maxRadius); + + template + void markResidues(const MoleculeT &M); + int getResidueEntry(const Vector3 &point) const; + void printGrid(std::ofstream& file) const; + protected: + std::vector< int > residues; +}; + +class AtomsGrid : public MoleculeGrid { + public: + AtomsGrid(const Surface &surface, const float inDelta, const float maxRadius); + + template + void markAtoms(const MoleculeT &M, float radius); + const std::vector& getAtoms(const Vector3 &point) const; + unsigned int getAtomsNumber(const Vector3 &point) const; + private: + std::vector > atoms; +}; + +// stores closest atom number for every grid point +class AtomGrid : public MoleculeGrid { + public: + AtomGrid(const Surface &surface, const float inDelta, const float maxRadius): + MoleculeGrid(surface, inDelta, maxRadius), atoms_(maxEntry) {} + template + void markAtoms(const MoleculeT &mol, std::vector& radii); + + // get volume corresponding to each atom + template + void getVolume(const MoleculeT &mol, std::vector& volume); + + unsigned int getAtom(const Vector3 &point) const; + + private: + std::vector atoms_; +}; + +class AtomTypeGrid : public MoleculeGrid { + public: + AtomTypeGrid(const Surface &surface, const float inDelta, const float maxRadius); + + template + void markAtomTypes(const MoleculeT &M); + unsigned int getAtomType(const Vector3 &point) const { + int index = getIndexForPoint(point); + if(!isValidIndex(index)) + return 0; + return atomTypes[index]; + } + private: + std::vector atomTypes; +}; + + +class SurfacePointGrid : public MoleculeGrid { + public: + SurfacePointGrid(const Surface &surface, const float inDelta, const float maxRadius); + void computeDistAndPointers(const Surface &surface, bool precise = false); + + const SurfacePoint* getSpPointer(const Vector3& point) const { + int index = getIndexForPoint(point); + if (!isValidIndex(index)) + return NULL; + return spPointers[index]; + } + + bool getDistAndSpPointer(const Vector3& point, float& distance, SurfacePoint* spPoint) const { + int index = getIndexForPoint(point); + if (!isValidIndex(index)) + return false; + distance=grid[index]; + spPoint = (SurfacePoint *)spPointers[index]; + return true; + } + + float getExactDist2(const Vector3& point) const { + int index = getIndexForPoint(point); + if(!isValidIndex(index) || spPointers[index]==NULL) + return MAX_FLOAT*MAX_FLOAT; + return point.dist2(spPointers[index]->position()); + } + + float getExactDist(const Vector3& point) const { + int index = getIndexForPoint(point); + if(!isValidIndex(index)) + return MAX_FLOAT; + if(spPointers[index]==NULL) + return grid[index]; + if(grid[index] < 0) + return -(point.dist(spPointers[index]->position())); + else + return point.dist(spPointers[index]->position()); + } + + protected: + // sets distance func. falue for index to be the distance from p. + // and p to be the closest point + bool setDistAndPointer(int index, const SurfacePoint* p) { + float d = getPointForIndex(index).dist(p->position()); + if (d >= grid[index]) return false; + grid[index] = d; + spPointers[index] = p; + return true; + } + private: + std::vector spPointers; +}; + +template +void ProbGrid::markProbs(const MoleculeT &M) { + std::vector weights; + weights.insert(weights.end(), maxEntry, 0.0); + for(typename MoleculeT::const_iterator it=M.begin(); it!=M.end(); it++) { + float atomRadius = it->getAtomRadius(); + float radius = atomRadius*2; //may be +1 is enough + + int centerIndex = getIndexForPoint(it->position()); + if (!isValidIndex(centerIndex)) { + std::cerr << "Error: Point out of grid" << std::endl; + exit(1); + } + + int intRadius = getIntGridRadius(radius); + int radius2 = intRadius*intRadius; + + int i_bound,j_bound,k_bound; + i_bound = intRadius; + for (int i = -i_bound; i <= i_bound; i++ ) { + j_bound = (int)sqrt(radius2 - i*i); + for (int j = -j_bound; j <= j_bound; j++) { + k_bound = (int)sqrt(radius2 - i*i - j*j); + for (int k = -k_bound; k <= k_bound; k++) { + int index = centerIndex + i + xGridNum * j + xyGridNum * k; + if(isValidIndex(index) && grid[index] <= 0) { + Vector3 point = getPointForIndex(index); + float dist = point.dist(it->position()); + if(dist == 0.0) { + probs[index]=it->getSteadyProb(); + continue; } + float weight = atomRadius/dist; + if(weight <= weights[index]) + continue; + weights[index]=weight; + probs[index]=it->getSteadyProb(); + } + } + } + } + } + weights.clear(); +} + +template +void ResidueGrid::markResidues(const MoleculeT &M) { + std::vector weights; + weights.insert(weights.end(), maxEntry, 0.0); + for(typename MoleculeT::const_iterator it=M.begin(); it!=M.end(); it++) { + float atomRadius = it->getRadius(); + float radius = atomRadius*2; //may be +1 is enough + + int centerIndex = getIndexForPoint(it->position()); + if (!isValidIndex(centerIndex)) { + std::cerr << "Error: Point out of grid" << std::endl; + exit(1); + } + + int intRadius = getIntGridRadius(radius); + int radius2 = intRadius*intRadius; + + int i_bound,j_bound,k_bound; + i_bound = intRadius; + for (int i = -i_bound; i <= i_bound; i++ ) { + j_bound = (int)sqrt(radius2 - i*i); + for (int j = -j_bound; j <= j_bound; j++) { + k_bound = (int)sqrt(radius2 - i*i - j*j); + for (int k = -k_bound; k <= k_bound; k++) { + int index = centerIndex + i + xGridNum * j + xyGridNum * k; + if(isValidIndex(index) && grid[index] <= 0) { + Vector3 point = getPointForIndex(index); + float dist = point.dist(it->position()); + if(dist == 0.0) { + if(it->isBackbone()) + residues[index] = M.residueEntry(it->chainId(), it->residueSequenceID()) * -1; + else + residues[index] = M.residueEntry(it->chainId(), it->residueSequenceID()); + continue; + } + float weight = atomRadius/dist; + if(weight <= weights[index]) + continue; + weights[index] = weight; + if(it->isBackbone()) + residues[index] = M.residueEntry(it->chainId(), it->residueSequenceID()) * -1; + else + residues[index] = M.residueEntry(it->chainId(), it->residueSequenceID()); + } + } + } + } + } + weights.clear(); +} + +template +void AtomsGrid::markAtoms(const MoleculeT &M, float radius) { + int atomIndex=0; + int intRadius = getIntGridRadius(radius); + int radius2 = intRadius*intRadius; + + for(typename MoleculeT::const_iterator it=M.begin(); it!=M.end(); it++, atomIndex++) { + int centerIndex = getIndexForPoint(it->position()); + if (!isValidIndex(centerIndex)) { + std::cerr << "Error: Point out of grid" << std::endl; + exit(1); + } + + int i_bound,j_bound,k_bound; + i_bound = intRadius; + for (int i = -i_bound; i <= i_bound; i++ ) { + j_bound = (int)sqrt(radius2 - i*i); + for (int j = -j_bound; j <= j_bound; j++) { + k_bound = (int)sqrt(radius2 - i*i - j*j); + for (int k = -k_bound; k <= k_bound; k++) { + int index = centerIndex + i + xGridNum * j + xyGridNum * k; + if(isValidIndex(index)) { + atoms[index].push_back(atomIndex); + } + } + } + } + } +} + +template +void AtomGrid::markAtoms(const MoleculeT &mol, std::vector& radii) { + std::vector weights; + weights.insert(weights.end(), maxEntry, 0.0); + int atomIndex=0; + for(typename MoleculeT::const_iterator it=mol.begin(); it!=mol.end(); it++, atomIndex++) { + int centerIndex = getIndexForPoint(it->position()); + if (!isValidIndex(centerIndex)) { + std::cerr << "Error: Point out of grid" << std::endl; + exit(1); + } + float atomRadius = radii[atomIndex]; + float radius = atomRadius*2; //may be +1 is enough + int intRadius = getIntGridRadius(radius); + int radius2 = intRadius*intRadius; + + int i_bound,j_bound,k_bound; + i_bound = intRadius; + for (int i = -i_bound; i <= i_bound; i++ ) { + j_bound = (int)sqrt(radius2 - i*i); + for (int j = -j_bound; j <= j_bound; j++) { + k_bound = (int)sqrt(radius2 - i*i - j*j); + for (int k = -k_bound; k <= k_bound; k++) { + int index = centerIndex + i + xGridNum * j + xyGridNum * k; + if(isValidIndex(index)) { + Vector3 point = getPointForIndex(index); + float dist = point.dist(it->position()); + if(dist == 0.0) { + atoms_[index] = atomIndex; + continue; + } + float weight = radius/dist; + if(weight <= weights[index]) continue; + weights[index] = weight; + atoms_[index] = atomIndex; + } + } + } + } + } + weights.clear(); +} + +template +void AtomGrid::getVolume(const MoleculeT &mol, std::vector& volume) { + std::vector fullCube(mol.size(), 0), halfCube(mol.size(), 0); + for(int i=0; i< (int)grid.size();i++) { + if(grid[i] < 0) + fullCube[atoms_[i]]++; + if(grid[i] == 0) + halfCube[atoms_[i]]++; + } + float cubeVolume = delta*delta*delta; + float halfVolume = cubeVolume/2; + volume.resize(mol.size(), 0.0); + for(unsigned int i=0; i +void AtomTypeGrid::markAtomTypes(const MoleculeT &M) { + std::vector weights; + weights.insert(weights.end(), maxEntry, 0.0); + int atomIndex=0; + for(typename MoleculeT::const_iterator it=M.begin(); it!=M.end(); it++, atomIndex++) { + float atomRadius = it->getRadius(); + float radius = atomRadius*2; //may be +1 is enough + int centerIndex = getIndexForPoint(it->position()); + if (!isValidIndex(centerIndex)) { + std::cerr << "Error: Point out of grid" << std::endl; + exit(1); + } + + int intRadius = getIntGridRadius(radius); + int radius2 = intRadius*intRadius; + + int i_bound,j_bound,k_bound; + i_bound = intRadius; + for (int i = -i_bound; i <= i_bound; i++ ) { + j_bound = (int)sqrt(radius2 - i*i); + for (int j = -j_bound; j <= j_bound; j++) { + k_bound = (int)sqrt(radius2 - i*i - j*j); + for (int k = -k_bound; k <= k_bound; k++) { + int index = centerIndex + i + xGridNum * j + xyGridNum * k; + if(isValidIndex(index)) { + Vector3 point = getPointForIndex(index); + float dist = point.dist(it->position()); + if(dist == 0.0) { + atomTypes[index] = it->getChemType(); + continue; + } + float weight = atomRadius/dist; + if(weight <= weights[index]) + continue; + weights[index] = weight; + atomTypes[index] = it->getChemType(); + } + } + } + } + } + weights.clear(); +} + +#endif diff --git a/model/CombinatorialAssembler/libs_gamb/AminoAcid.h b/model/CombinatorialAssembler/libs_gamb/AminoAcid.h new file mode 100644 index 0000000000000000000000000000000000000000..cf475f4c78b47afd87698b3688d872d312441e6e --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/AminoAcid.h @@ -0,0 +1,318 @@ +#ifndef AminoAcid_hpp +#define AminoAcid_hpp + +#include +#include +#include +#include + +#include "PDB.h" +#include "RigidTrans3.h" + +class Atom; + +enum RESIDUE_INDEX { + ARG,LYS,GLU,GLN,ASP,ASN,PRO,GLY,SER,THR, + HIS,ALA,ILE,LEU,VAL,PHE,CYS,MET,TYR,TRP,OTHER,GAP,MAX_AA +}; + +template +/* +CLASS + AminoAcid + + Container that stores Atom's of amino acid. + In addition the class provides a general information about amino acid residues. + +KEYWORD + AminoAcid, SeqScore + +AUTHORS + Maxim Shatsky (maxshats@math.tau.ac.il) + + copyright: SAMBA Group , Tel-Aviv Univ. Israel, 1997, 1999, 2002 + +CHANGES LOG + 19.12.02 Changed to inherit from vector. Added function 'parsePDB'. + 16.01.03 Added new residue index OTHER, to support PCA, PTR ... + 10.05.05 parsePDB was changed to differentiate between residues with the same residueId but different residueSeqId + (for example, 45 and 45A) + +GOALS + Container that stores Atom's of amino acid. + In addition, AminoAcid class should help to construct user-defined classes + that deal with amino acid residues. For example it provides one-to-one mapping + function from character that identifies the amino acid residue to + index which defined in range of [0,19] (see USAGE for more details),for + this purpose class provides global enum : + RESIDUE_INDEX {ARG,LYS,GLU,GLN,ASP,ASN,PRO,GLY,SER, + THR,HIS,ALA,ILE,LEU,VAL,PHE,CYS,MET,TYR,TRP,OTHER,MAX_AA}; + + + AminoAcid can provide statistical information about amino acid residues, + like "Frequency of Occurrence in Proteins". +USAGE + See SeqScore. + Complexity of all functions is O(1) besides: + parsePDB - O(n) +*/ +class AminoAcid + : public std::vector { +private: + static const float m_fFreqOfOccurrence[]; + static const RESIDUE_INDEX m_indexes[]; + static const char m_revIndexes[]; +public: + + ////returns CA atom position (-1 if not found) + int getCA() const; + + ////returns N atom position (-1 if not found) + int getN() const; + + ////returns C atom position (-1 if not found) + int getC() const; + + ////returns O atom position (-1 if not found) + int getO() const; + + ////returns CA atom position (-1 if not found) + int getCB() const; + + //// Applies a rigid transformation on all atom coordinates. + AminoAcid& operator*=(const RigidTrans3 &rt); + + ////Returns frequency of occurrence of each amino acid residue in proteins + //taken from M.H.Klapper,Biochem.Biophys.Res.Commun.78:1018-1024,1977 + static float getFreqOfOccurrence(RESIDUE_INDEX index); + + ////Receives one-letter abbreviation of amino acid residue. + //Returns index as appears in enum RESIDUE_INDEX. + inline static RESIDUE_INDEX getResidueTypeIndx(const char cRes){ + if ( cRes == '-' || cRes == '.' ) return GAP; + + int indx=(int)cRes-(int)'A'; + + if (indx<0 || indx>25) { //RESIDUE_INDEX AminoAcid::m_indexes[26] + std::cerr<<"SequenceScore: unknown residue type:"<< cRes < > parsePDB(const std::vector& i_contAtom); + + ////Parses PDB file and extracts AminoAcid objects + //possible to extract even 'alt' residues (alternative residue entries having the same id) + static std::vector > parseFilePDB(const std::string& pdbFile); + + template + friend std::ostream& operator<<(std::ostream& s, const AminoAcid& pm); +}; + +template +std::vector > AminoAcid::parsePDB(const std::vector& i_contAtom) { + + std::vector > o_vAA; + + //residue index in order to differentiate between + //different residues + int iCurrRes; + std::string sCurrRes; + char chainCurrRes; + + if (i_contAtom.size()==0) + return o_vAA; + + iCurrRes=i_contAtom[0].residueIndex(); + sCurrRes=i_contAtom[0].residueSequenceID(); + chainCurrRes=i_contAtom[0].chainId(); + + AminoAcid vRes; + + for (int i=0;i<(int)i_contAtom.size();i++) { + + if (i_contAtom[i].residueIndex()!=iCurrRes || i_contAtom[i].residueSequenceID()!=sCurrRes || + i_contAtom[i].chainId()!=chainCurrRes) { + + o_vAA.push_back(vRes); + vRes.clear(); + + iCurrRes=i_contAtom[i].residueIndex(); + sCurrRes=i_contAtom[i].residueSequenceID(); + chainCurrRes=i_contAtom[i].chainId(); + } + + vRes.push_back(i_contAtom[i]); + } + + //last residue + if (vRes.size() > 0) { + o_vAA.push_back(vRes); + } + + return o_vAA; +} + +template +std::vector > AminoAcid::parseFilePDB(const std::string& pdbFile) { + std::ifstream pdbStream(pdbFile.c_str()); + + if (!pdbStream) { + std::cerr << "File does not exist: " << pdbFile << std::endl; + exit(1); + } + + std::vector > o_vAA; + + //residue index in order to differentiate between + //different residues + int iCurrRes=-99999; + char cCurrAlt='-'; + char cCurrChain='9'; + + AminoAcid vRes; + + while (!pdbStream.eof()) { + std::string line; + getline(pdbStream, line); + if (! PDB::isATOMrec(line.c_str())) + continue; + + char alt=line.c_str()[26]; + AtomT atom(line.c_str()); + + if (cCurrAlt=='-') + cCurrAlt=alt; + if (iCurrRes == -99999) + iCurrRes=atom.residueIndex(); + if (cCurrChain == '9') + cCurrChain = atom.chainId(); + + if (atom.residueIndex()!=iCurrRes || cCurrAlt!=alt || cCurrChain!=atom.chainId()) { + o_vAA.push_back(vRes); + iCurrRes=atom.residueIndex(); + cCurrAlt=alt; + cCurrChain=atom.chainId(); + vRes.clear(); + } + + vRes.push_back(atom); + } + + if (vRes.size()>0) { + o_vAA.push_back(vRes); + } + + pdbStream.close(); + + return o_vAA; +} + +//ALA,RES_TMP0,CYS,ASP,GLU,PHE,GLY,HIS,ILE,RES_TMP1,LYS,LEU,MET,ASN,RES_TMP2,PRO,GLN,ARG,SER,THR,RES_TMP3,VAL,TRP,RES_TMP4,TYR +template +const RESIDUE_INDEX AminoAcid::m_indexes[26]={ALA,OTHER,CYS,ASP,GLU,PHE,GLY,HIS,ILE,OTHER,LYS,LEU,MET,ASN,OTHER,PRO,GLN,ARG,SER,THR,OTHER,VAL,TRP,OTHER,TYR,OTHER}; + + +//ARG,LYS,GLU,GLN,ASP,ASN,PRO,GLY,SER,THR,HIS,ALA,ILE,LEU,VAL,PHE,CYS,MET,TYR,TRP,OTHER,GAP,MAX_AA +template +const char AminoAcid::m_revIndexes[MAX_AA]={'R','K','E','Q','D','N','P','G','S','T','H','A','I','L','V', + 'F','C','M','Y','W','.','-'}; + +//ARG,LYS,GLU,GLN,ASP,ASN,PRO,GLY,SER,THR,HIS,ALA,ILE,LEU,VAL,PHE,CYS,MET,TYR,TRP +template +const float AminoAcid::m_fFreqOfOccurrence[MAX_AA]={0.047, + 0.07, + 0.062, + 0.039, + 0.055, + 0.044, + 0.046, + 0.075, + 0.071, + 0.06, + 0.021, + 0.09, + 0.046, + 0.075, + 0.069, + 0.035, + 0.028, + 0.017, + 0.035, + 0.011, + 0.0,//OTHER + 0.0//GAP + }; + +template +int AminoAcid::getCA() const{ + for(int i=0;i<(int)(this->size());++i){ + if((*this)[i].isCA()) + return i; + } + return -1; +} + +template +int AminoAcid::getC() const{ + for(int i=0;i<(int)(this->size());++i){ + if((*this)[i].isC()) + return i; + } + return -1; +} + +template +int AminoAcid::getN() const{ + for(int i=0;i<(int)(this->size());++i){ + if((*this)[i].isN()) + return i; + } + return -1; +} + +template +int AminoAcid::getO() const{ + for(int i=0;i<(int)(this->size());++i){ + if((*this)[i].isO()) + return i; + } + return -1; +} + +template +int AminoAcid::getCB() const{ + for(int i=0;i<(int)(this->size());++i){ + if((*this)[i].isCB()) + return i; + } + return -1; +} + +template +float AminoAcid::getFreqOfOccurrence(RESIDUE_INDEX index){ + return m_fFreqOfOccurrence[index]; +} + +template +std::ostream& operator<<(std::ostream& s, const AminoAcid& i_aa){ + for(int i=0;i<(int)i_aa.size();++i) + s<< i_aa[i] << std::endl; + + return s; +} + +template +AminoAcid& AminoAcid::operator*=(const RigidTrans3 &rt){ + for(int i=0;i<(int)(this->size());++i) (*this)[i]*=rt; + return *this; +} + +#endif diff --git a/model/CombinatorialAssembler/libs_gamb/Atom.cc b/model/CombinatorialAssembler/libs_gamb/Atom.cc new file mode 100644 index 0000000000000000000000000000000000000000..96b7f7931e595b9cb2bfb23ba5e47b4c5a4692fa --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/Atom.cc @@ -0,0 +1,585 @@ +#include "Atom.h" +#include "PDB.h" +#include "CIF.h" + +#include +#include +#include +#include "macros.h" + +const unsigned short Atom::resNameLen = 3; +const unsigned short Atom::atomNameLen = 4; +const float Atom::defaultOccupancy = 1.0; +const float Atom::defaultTempFactor = 0.0; + +std::string Atom::parseAtomName(const char* const atomName_) { + std::string atomname; + switch (strlen(atomName_)) { + case 1: + atomname = " " + std::string(atomName_) + " "; + break; + case 2: + atomname = " " + std::string(atomName_) + " "; + break; + case 3: + atomname = " " + std::string(atomName_ ); + break; + default: + atomname = std::string(atomName_).substr(0,4); + } + + return atomname; +} + + +Atom::Atom() + : Vector3(), atomEntryType(ATOM), chId(' '), altLoc(' '), atomId(0), + residueId(0), residueSeqICode(' '), atomName(" "), resType(' '), resName(" "), + occupancy(defaultOccupancy), tempFactor(defaultTempFactor),sseInfo(std::pair(0,0)) +{} + +Atom::Atom(const Vector3& p, const char ch, const unsigned int aid, + const unsigned int resid, + const char* const atomName_, const char resTp, const char resICode) : + Vector3(p), atomEntryType(ATOM), chId(ch), altLoc(' '), atomId(aid), + residueId(resid), residueSeqICode(resICode), resType(resTp), + occupancy(defaultOccupancy), tempFactor(defaultTempFactor),sseInfo(std::pair(0,0)) +{ + atomName = parseAtomName(atomName_); + resName = std::string(PDB::residueLongName(resTp)).substr(0,3); +} + +Atom::Atom(const Vector3& p, const AtomEntryType atp, const char ch, + const unsigned int aid, const unsigned int resid, + const char* const atomName_, const char resTp, const char* const res, const char resICode) : + Vector3(p), atomEntryType(atp), chId(ch), altLoc(' '), atomId(aid), + residueId(resid), residueSeqICode(resICode), resType(resTp), resName(" "), + occupancy(defaultOccupancy), tempFactor(defaultTempFactor),sseInfo(std::pair(0,0)) +{ + atomName = parseAtomName(atomName_); + resName = std::string(res).substr(0,3); +} + +Atom::Atom(const Vector3& p, const AtomEntryType atp, const char ch, + const unsigned int aid, const unsigned int resid, + const char* const atomName_, const char resTp, const char* const res, + const float occup, const float tmp, const char resICode) : + Vector3(p), atomEntryType(atp), chId(ch), altLoc(' '), atomId(aid), + residueId(resid), residueSeqICode(resICode), resType(resTp), resName(" "), + occupancy(occup), tempFactor(tmp), sseInfo(std::pair(0,0)) +{ + atomName = parseAtomName(atomName_); + resName = std::string(res).substr(0,3); +} + +Atom::Atom(const Vector3& p, unsigned int aid) : + Vector3(p), atomEntryType(ATOM), chId(' '), altLoc(' '), atomId(aid), residueId(0), + residueSeqICode(' '), atomName(" "), resType(' '), resName(" "), + occupancy(defaultOccupancy), tempFactor(defaultTempFactor), sseInfo(std::pair(0,0)) +{} + +Atom::Atom(const std::string& line, bool cif) { + if(cif) initCIF(line); + else initPDB(line); +} + +void Atom::initPDB(const std::string& PDBrec) { + (*this)[0] = PDB::atomXCoord(PDBrec); + (*this)[1] = PDB::atomYCoord(PDBrec); + (*this)[2] = PDB::atomZCoord(PDBrec); + atomEntryType = PDB::getAtomEntryType(PDBrec); + chId = PDB::atomChainId(PDBrec); + altLoc = PDB::atomAltLocIndicator(PDBrec); + atomId = PDB::atomIndex(PDBrec); + residueId = PDB::atomResidueIndex(PDBrec); + residueSeqICode = PDB::atomResidueICode(PDBrec); + resType = PDB::atomResidueType(PDBrec); + occupancy = PDB::getOccupancy(PDBrec); + tempFactor = PDB::getTempFactor(PDBrec); + sseInfo = std::pair(0,0); + atomName = PDB::atomType(PDBrec); + resName = PDB::getAtomResidueLongName(PDBrec); +} + +void Atom::initCIF(const std::string& CIFrec) { + (*this)[0] = CIF::atomXCoord(CIFrec); + (*this)[1] = CIF::atomYCoord(CIFrec); + (*this)[2] = CIF::atomZCoord(CIFrec); + atomEntryType = CIF::getAtomEntryType(CIFrec); + chId = CIF::atomChainIdAuthor(CIFrec)[0]; + //altLoc = CIF::atomAltLocIndicator(CIFrec); //TODO + atomId = CIF::atomIndex(CIFrec); + residueId = CIF::atomResidueIndex(CIFrec); + //residueSeqICode = CIF::atomResidueICode(CIFrec); + occupancy = CIF::getOccupancy(CIFrec); + tempFactor = CIF::getTempFactor(CIFrec); + sseInfo = std::pair(0,0); + atomName = parseAtomName(CIF::atomType(CIFrec).c_str()); + resName = CIF::getAtomResidueLongName(CIFrec); + resType = PDB::residueShortName(resName); +} + +char Atom::chainId() const { + return chId; +} + +char Atom::altLocIndicator() const { + return altLoc; +} + +unsigned int Atom::atomIndex() const { + return atomId; +} + +int Atom::residueIndex() const { + return residueId; +} + +std::string Atom::residueSequenceID() const { + static const size_t MAX_CHARS_LENGTH = 5; + char cResId[MAX_CHARS_LENGTH]; + sprintf(cResId, "%d", residueId); + std::string sResId(cResId); + sResId += residueSeqICode; + return sResId; +} + +char Atom::residueICode() const { + return residueSeqICode; +} + +const char *const Atom::type() const { + return atomName.c_str(); +} + +char Atom::residueType() const { + return resType; +} + +const char *const Atom::residueName() const +{ + return resName.c_str(); +} + +void Atom::setChainId(const char newChainId) { + chId = newChainId; +} + +void Atom::setAtomId(unsigned int newAtomId) { + atomId = newAtomId; +} + +AtomEntryType Atom::getAtomEntryType() const { + return atomEntryType; +} + +float Atom::getOccupancy() const { + return occupancy; +} + +float Atom::getTempFactor() const { + return tempFactor; +} + +void Atom::setTempFactor(float ftemp) { + tempFactor=ftemp; +} + +bool Atom::isBackbone() const +{ + const char* atomType = type(); + if (atomType[1] == 'N' && atomType[2] == ' ') + return true; + else if (atomType[1] == 'C' && atomType[2] == 'A' && atomType[3] == ' ') + return true; + else if (atomType[1] == 'C' && atomType[2] == ' ') + return true; + else + return atomType[1] == 'O' && atomType[2] == ' '; +} + +bool Atom::isCA() const +{ + const char* atomType = type(); + return atomType[1] == 'C' && atomType[2] == 'A' && atomType[3] == ' '; +} + +bool Atom::isCB() const +{ + const char* atomType = type(); + return atomType[1] == 'C' && atomType[2] == 'B' && atomType[3] == ' '; +} + +bool Atom::isN() const +{ + const char* atomType = type(); + return atomType[1] == 'N' && atomType[2] == ' '; +} + +bool Atom::isC() const +{ + const char* atomType = type(); + return atomType[1] == 'C' && atomType[2] == ' '; +} + +bool Atom::isO() const +{ + const char* atomType = type(); + return atomType[1] == 'O' && atomType[2] == ' '; +} + +bool Atom::isCG() const +{ + const char* atomType = type(); + return atomType[1] == 'C' && atomType[2] == 'G' && atomType[3] == ' '; +} + +bool Atom::isCD() const +{ + const char* atomType = type(); + return atomType[1] == 'C' && atomType[2] == 'D' && atomType[3] == ' '; +} + +bool Atom::isCE() const +{ + const char* atomType = type(); + return atomType[1] == 'C' && atomType[2] == 'E' && atomType[3] == ' '; +} + +bool Atom::isCD1() const +{ + const char* atomType = type(); + return atomType[1] == 'C' && atomType[2] == 'D' && atomType[3] == '1'; +} + +bool Atom::isCD2() const +{ + const char* atomType = type(); + return atomType[1] == 'C' && atomType[2] == 'D' && atomType[3] == '2'; +} + +bool Atom::isCG1() const +{ + const char* atomType = type(); + return atomType[1] == 'C' && atomType[2] == 'G' && atomType[3] == '1'; +} + +bool Atom::isCG2() const +{ + const char* atomType = type(); + return atomType[1] == 'C' && atomType[2] == 'G' && atomType[3] == '2'; +} + +bool Atom::isCE1() const +{ + const char* atomType = type(); + return atomType[1] == 'C' && atomType[2] == 'E' && atomType[3] == '1'; +} + +bool Atom::isCE2() const +{ + const char* atomType = type(); + return atomType[1] == 'C' && atomType[2] == 'E' && atomType[3] == '2'; +} + +bool Atom::isCE3() const +{ + const char* atomType = type(); + return atomType[1] == 'C' && atomType[2] == 'E' && atomType[3] == '3'; +} + +bool Atom::isCZ() const +{ + const char* atomType = type(); + return atomType[1] == 'C' && atomType[2] == 'Z' && atomType[3] == ' '; +} + +bool Atom::isCZ2() const +{ + const char* atomType = type(); + return atomType[1] == 'C' && atomType[2] == 'Z' && atomType[3] == '2'; +} + +bool Atom::isCZ3() const +{ + const char* atomType = type(); + return atomType[1] == 'C' && atomType[2] == 'Z' && atomType[3] == '3'; +} + +bool Atom::isCH2() const +{ + const char* atomType = type(); + return atomType[1] == 'C' && atomType[2] == 'H' && atomType[3] == '2'; +} + +bool Atom::isOD1() const +{ + const char* atomType = type(); + return atomType[1] == 'O' && atomType[2] == 'D' && atomType[3] == '1'; +} + +bool Atom::isOD2() const +{ + const char* atomType = type(); + return atomType[1] == 'O' && atomType[2] == 'D' && atomType[3] == '2'; +} + +bool Atom::isOE1() const +{ + const char* atomType = type(); + return atomType[1] == 'O' && atomType[2] == 'E' && atomType[3] == '1'; +} + +bool Atom::isOE2() const +{ + const char* atomType = type(); + return atomType[1] == 'O' && atomType[2] == 'E' && atomType[3] == '2'; +} + +bool Atom::isOH() const +{ + const char* atomType = type(); + return atomType[1] == 'O' && atomType[2] == 'H' && atomType[3] == ' '; +} + +bool Atom::isOG() const +{ + const char* atomType = type(); + return atomType[1] == 'O' && atomType[2] == 'G' && atomType[3] == ' '; +} + +bool Atom::isOG1() const +{ + const char* atomType = type(); + return atomType[1] == 'O' && atomType[2] == 'G' && atomType[3] == '1'; +} + +bool Atom::isNE() const +{ + const char* atomType = type(); + return atomType[1] == 'N' && atomType[2] == 'E' && atomType[3] == ' '; +} + +bool Atom::isND1() const +{ + const char* atomType = type(); + return atomType[1] == 'N' && atomType[2] == 'D' && atomType[3] == '1'; +} + +bool Atom::isND2() const +{ + const char* atomType = type(); + return atomType[1] == 'N' && atomType[2] == 'D' && atomType[3] == '2'; +} + +bool Atom::isNE1() const +{ + const char* atomType = type(); + return atomType[1] == 'N' && atomType[2] == 'E' && atomType[3] == '1'; +} + +bool Atom::isNE2() const +{ + const char* atomType = type(); + return atomType[1] == 'N' && atomType[2] == 'E' && atomType[3] == '2'; +} + +bool Atom::isNH1() const +{ + const char* atomType = type(); + return atomType[1] == 'N' && atomType[2] == 'H' && atomType[3] == '1'; +} + +bool Atom::isNH2() const +{ + const char* atomType = type(); + return atomType[1] == 'N' && atomType[2] == 'H' && atomType[3] == '2'; +} + +bool Atom::isNZ() const +{ + const char* atomType = type(); + return atomType[1] == 'N' && atomType[2] == 'Z' && atomType[3] == ' '; +} + +bool Atom::isSD() const +{ + const char* atomType = type(); + return atomType[1] == 'S' && atomType[2] == 'D' && atomType[3] == ' '; +} + +bool Atom::isSG() const +{ + const char* atomType = type(); + return atomType[1] == 'S' && atomType[2] == 'G' && atomType[3] == ' '; +} + +bool Atom::isH() const +{ + const char* atomType = type(); + return atomType[1] == 'H' || atomType[1] == 'D' || atomType[0] == 'H' || atomType[0] == 'D'; +} + +bool Atom::isWater() const +{ + const char* resName = residueName(); + return strncmp(resName, "HOH", 3) || strncmp(resName,"DOD", 3); +} + +/* RNA backbone +// OP1 H5' H4' O4' residue +// | | \ / \ / +// -P-O5'-C5'---C4' C1' +// | | \ / \ +// OP2 H5'' C3'--C2' H1' +// / \ / \ +// O3' H3' O2' H2'' +// | | +// H2' +*/ +bool Atom::isRNABackbone() const { + const char* atomType = type(); + return ((atomType[1] == 'O' && atomType[2] == 'P') || isSugarAtom() || isP()); +} + +bool Atom::isP() const { + const char* atomType = type(); + return atomType[1] == 'P' && atomType[2] == ' '; +} + +bool Atom::isSugarAtom() const { + const char* atomType = type(); + // '*' for old convention + return atomType[3] == '*' || atomType[3] == '\''; +} + +bool Atom::isSugarCarbon() const +{ + const char *atomType = type(); + return atomType[1] == 'C' && atomType[3] == '\''; +} + +bool Atom::isSugarOxygen() const +{ + const char *atomType = type(); + return atomType[1] == 'O' && atomType[3] == '\''; +} + +bool Atom::isPhosphateOxygen() const +{ + const char *atomType = type(); + return atomType[1] == 'O' && atomType[2] == 'P'; +} + +bool Atom::isNitrogenousBaseAtom() const +{ + const char *atomType = type(); + return (atomType[1] == 'N' || atomType[1] == 'O' || atomType[1] == 'C') + && atomType[2] >= '0' && atomType[2] <= '9' + && (atomType[3] == ' ') + ; +} + + +std::ostream& operator<<(std::ostream& s, const Atom& at) +{ + // first 6 chars (1-6) stand for field type + s.setf(std::ios::left, std::ios::adjustfield); + s.width(6); + if (at.atomEntryType == ATOM) + s << "ATOM"; + else if (at.atomEntryType == HETATM) + s << "HETATM"; + else + s << "UNK"; + + // next 5 chars (7-11) stand for atom id + s.setf(std::ios::right, std::ios::adjustfield); + s.width(5); + s << at.atomId; + + // skip an undefined position (12) + s.width(1); + s << " "; + + // 4 positions (13-16) for atom name + s.width(4); //worked well with gcc-2.95 + s << at.atomName; + + // skip the alternate indication position (17) + s.width(1); + s << at.altLoc; + + // 3 positions for residue name (18-20) + //s.width(3); worked well with gcc-2.95 + if(at.resName.length()==3) { + s << at.resName; + } else { + s << " "; + } + + // 1 position for chain identifier (22) + 1 skipped (21) + s.width(1); + s << " " << at.chId; // skip the chain identifier + + // 4 position for residue number (23-26) + s.width(4); + s << at.residueIndex(); // residue sequence number + s.width(1); + s << at.residueICode(); // residue insertion code (27) + s.setf(std::ios::fixed, std::ios::floatfield); + s << " "; // skip 3 undefined positions (28-30) + + // coordinates (31-38,39-46,47-54) + s.width(8); + s.precision(3); + s << at[0]; + s.width(8); + s.precision(3); + s << at[1]; + s.width(8); + s.precision(3); + s << at[2]; + // occupancy (55-60) + s.width(6); + s.precision(2); + s << at.occupancy; + // temp. factor (61-66) + s.width(6); + s.precision(2); + s << at.tempFactor; + + // s << endl; + return s; +} + +void Atom::output2cif(std::ostream& s, int modelNum) const +{ + if (atomEntryType == ATOM) + s << "ATOM "; + else if (atomEntryType == HETATM) + s << "HETATM "; + + s << atomId; //_atom_site.id + s << " ? "; // we don't save the element (_atom_site.type_symbol) + s << atomName; // _atom_site.label_atom_id + s << " . "; // _atom_site.label_alt_id + s << resName;//_atom_site.label_comp_id + s << " "; + s << chId; //_atom_site.label_asym_id + s << " ? "; //_atom_site.label_entity_id - chain number + s << residueId; //_atom_site.label_seq_id + s << " ? "; //_atom_site.pdbx_PDB_ins_code + + s << (*this)[0] << " " << (*this)[1] << " " << (*this)[2] << " "; // coordinates + s << occupancy << " " << tempFactor << " ? "; + s << residueId; //_atom_site.auth_seq_id + s << " "; + s << resName;//_atom_site.auth_comp_id + s << " "; + s << chId; //_atom_site.auth_asym_id + s << " "; + s << atomName; // _atom_site.auth_atom_id + s << " "; + s << modelNum; // _atom_site.pdbx_PDB_model_num +} diff --git a/model/CombinatorialAssembler/libs_gamb/Atom.h b/model/CombinatorialAssembler/libs_gamb/Atom.h new file mode 100644 index 0000000000000000000000000000000000000000..08474b0b1719034c0d1d8b017efa4a07f9974e7b --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/Atom.h @@ -0,0 +1,384 @@ +#ifndef _Atom_h +#define _Atom_h + +#include "Vector3.h" +#include "RigidTrans3.h" +#include "PDB.h" +#include "AminoAcid.h" +#include + +/* +CLASS + Atom + + This is a general class to hold an atom as it appears in a PDB file. + +KEYWORD + atom, PDB, particle + +AUTHORS + Meir Fuchs (meirfux@math.tau.ac.il), Zipi Fligelman (zipo@math.tau.ac.il) + + copyright: SAMBA Group , Tel-Aviv Univ. Israel, 1997, 1999. + +CHANGES LOG +
    + +
  • +13.11.07 Elad Donsky +Adding alternate location field +<\LI> +
  • +2.08.07 Efrat +Adding isCB method +<\LI> +
  • +10.05.05 Nelly Bluvshtein +1. +String data member for residueSeqID ( = "45A") was replaced by residueSeqICode ( = 'A'). +The residue number is stored in residueId (as it was). +Atom constructors were changed to get resICode (with default = ' '). +residueSequenceID() returns concatenation of residueId and residueSeqICode. +2. +isH(),isWater() added for Molecule::WaterHydrogenUnSelector +<\LI> +
  • +10.10.04 Dina Schneidman +Residue name is stored in a 3-characters string instead of char[3] +<\LI> +
  • +16.07.04 Oranit Dror +A small update in the << operator +<\LI> +
  • +17.3.04 +pair(char,short) sseInfo added. +
  • +13.01.04 Dina and Oranit +Atom name is stored in a 4-characters string instead of char[4]. If +the name length is shorter than 4 characters then blanks are appended. +The "type" method is now returns a terminated char[] ('\0' at the end). +
  • +15.01.03 Max +

    +class Atom : public Particle -> class Atom : public Vector3 +

    +All functions are changed accordingly. +<\LI> +

  • +Occupancy and temperature factor were added. 8.01.03 Alex +<\LI> +
  • +getAtomEntryType() - function that tells if an atom is +ATOM or HETATM record in PDB file. 8.01.03 Alex +<\LI> +
  • +isBackbone() - function that returns true for backbone atoms + 8.01.03 Dina +<\LI> +
  • +Added: operator RESIDUE_INDEX()const (14.12.03 Max) +<\LI> +
+ +USAGE +*/ +class Atom : public Vector3 +{ + +public: + // GROUP: Constructors + + //// An empty constructor that gives the atom members null values + Atom(); + + //// An explicit constructor + Atom(const Vector3& p, const char ch, const unsigned int aid, + const unsigned int resid, + const char* const t, + const char resTp, + const char resICode = ' '); + + //// + Atom(const Vector3& p, const AtomEntryType atp, const char ch, + const unsigned int aid, const unsigned int resid, + const char* const t, const char resTp, const char* const res, const char resICode = ' '); + + //// + Atom(const Vector3& p, const AtomEntryType atp, const char ch, + const unsigned int aid, const unsigned int resid, + const char* const atomName_, const char resTp, const char* const res, + const float occup, const float tmp, const char resICode = ' '); + + //// + Atom(const Vector3& p, unsigned int aid); + + //// A constructor that intiate an atom type from a line of a PDB or CIF record + explicit Atom(const std::string& line, bool cif = false); + + static const unsigned short resNameLen; + static const unsigned short atomNameLen; + static const float defaultOccupancy; + static const float defaultTempFactor; + + // GROUP: Inspectors + + //// Returns the chainId - important for working with complexes + char chainId() const; + + //// Returns the alternative location indicator. + char altLocIndicator() const; + + //// returns the atom index in the PDB file + unsigned int atomIndex() const; + + //// returns the residue (thas the atom belongs to) index in the PDB file + int residueIndex() const; + + // Return the value stored in the ResSeq field of the given PDB + // record (columns 23-26). + // The returned value is the same as the value returned by the + // residueIndex method, except for cases like: + // ATOM 279 CA SER 60 8.961 9.167 37.481 1.00 20.10 1SGT 477 + // ATOM 285 CA GLY 60A 5.640 10.901 38.407 1.00 21.41 1SGT 483 + std::string residueSequenceID() const; + + //// returns space if there are no alternatives for this atom, column 26 in PDB format + char residueICode() const; + + //// Returns the atom type + const char *const type() const; + + //// Returns the residue type in one letter code. + char residueType() const; + + //// Returns the residue type in 3-letter mnemonic. If no matching mnemonic + // is found a NULL pointer is returned. + const char *const residueName() const; + + //// Return the type of the PDB entry, currently only ATOM and HETATM are identified. + AtomEntryType getAtomEntryType() const; + + //// Returns the occupancy, which is a measure of the fraction of molecules in the + // crystal in which the current atom actually occupies the specified position + float getOccupancy() const; + + //// Returns the temperature factor, which is a measure of how much an atom + // oscillates or vibrates around the specified position + float getTempFactor() const; + + //// Sets new temperature factor + void setTempFactor(float ftemp); + + //// Returns true if the atom is backbone atom: N, Ca, C, O + bool isBackbone() const; + + //// Returns true if the atom is CA atom + bool isCA() const; + + //// Returns true if the atom is CB atom + bool isCB() const; + + //// Returns true if the atom is N atom + bool isN() const; + + //// Returns true if the atom is C atom + bool isC() const; + + //// Returns true if the atom is O atom + bool isO() const; + + //// Returns true if the atom is CG atom + bool isCG() const; + + //// Returns true if the atom is CD atom + bool isCD() const; + + //// Returns true if the atom is CE atom + bool isCE() const; + + //// Returns true if the atom is CZ atom + bool isCZ() const; + + //// Returns true if the atom is CD1 atom + bool isCD1() const; + + //// Returns true if the atom is CD2 atom + bool isCD2() const; + + //// Returns true if the atom is CG1 atom + bool isCG1() const; + + //// Returns true if the atom is CG2 atom + bool isCG2() const; + + //// Returns true if the atom is CE1 atom + bool isCE1() const; + + //// Returns true if the atom is CE2 atom + bool isCE2() const; + + //// Returns true if the atom is CE3 atom + bool isCE3() const; + + //// Returns true if the atom is CZ2 atom + bool isCZ2() const; + + //// Returns true if the atom is CZ3 atom + bool isCZ3() const; + + //// Returns true if the atom is CH2 atom + bool isCH2() const; + + + //// Returns true if the atom is OD1 atom + bool isOD1() const; + + //// Returns true if the atom is OD2 atom + bool isOD2() const; + + //// Returns true if the atom is OE1 atom + bool isOE1() const; + + //// Returns true if the atom is OE2 atom + bool isOE2() const; + + //// Returns true if the atom is OH atom + bool isOH() const; + + //// Returns true if the atom is OG atom + bool isOG() const; + + //// Returns true if the atom is OG1 atom + bool isOG1() const; + + + //// Returns true if the atom is NE atom + bool isNE() const; + + //// Returns true if the atom is ND1 atom + bool isND1() const; + + //// Returns true if the atom is ND2 atom + bool isND2() const; + + //// Returns true if the atom is ND1 atom + bool isNE1() const; + + //// Returns true if the atom is ND2 atom + bool isNE2() const; + + //// Returns true if the atom is ND1 atom + bool isNH1() const; + + //// Returns true if the atom is ND2 atom + bool isNH2() const; + + //// Returns true if the atom is NZ atom + bool isNZ() const; + + //// Returns true if the atom is SD atom + bool isSD() const; + + //// Returns true if the atom is SG atom + bool isSG() const; + + //// Returns true if the atom is H atom + bool isH() const; + + //// Returns true if the atom is water's atom + bool isWater() const; + + //// Returns true if the atom is phosphate + bool isP() const; + + //// Returns true if the atom is a backbone atom + bool isRNABackbone() const; + + //// Returns true if the atom belongs to the sugar group of a nucleotide + bool isSugarAtom() const; + + //// Returns true if the atom is a sugar Carbon atom + bool isSugarCarbon() const; + + //// Returns true if the atom is a sugar Oxygen atom + bool isSugarOxygen() const; + + //// Returns true if the atom is a phosphate Oxygen atom + bool isPhosphateOxygen() const; + + //// Returns true if the atom belongs to a nitrogenous base + bool isNitrogenousBaseAtom() const; + + // GROUP: Adaptors + + //// Sets the chain ID for various implementation like the VMD coloring + // scheme, or RasMol web assignment. Please make sure you use it with + // accordance to the appropriate software util. + void setChainId(const char newChainId); + + //// Set atomId + void setAtomId(unsigned int newAtomId); + + //// set residue index + void setResidueIndex(int residueIndex) { residueId = residueIndex; } + + ////applies transformation on Atom coordinates + Atom& operator*=(const RigidTrans3& rt) { + (Vector3 &)(*this) *= rt; + return *this; + } + + ////Converts Atom into RESIDUE_INDEX + //(atoms of the same residue type will have the same RESIDUE_INDEX) + operator RESIDUE_INDEX() const { + return AminoAcid<>::getResidueTypeIndx(resType); + } + + ////Updates SSE information + void setSSE(const std::pair& isseInfo){sseInfo=isseInfo;} + + ////Updates SSE information + std::pair getSSE() const{ return sseInfo; } + + //// + static std::string parseAtomName(const char* const atomName_); + + //// Writing back the PBD line - without the endline + friend std::ostream& operator<<(std::ostream& s, const Atom& at); + + //// + void output2cif(std::ostream& s, int modelNum=1) const; + +protected: + void initPDB(const std::string& PDBrec); + void initCIF(const std::string& PDBrec); + +protected: + + AtomEntryType atomEntryType; // The type of the entity (ATOM/HETATM) + char chId; // chain. + char altLoc = ' '; // alternative location indicator. + unsigned int atomId; // atom index. + int residueId; // residue index. + char residueSeqICode = ' '; // code for insertion of residues (for example for residue "45A" + // residueId = 45 and residueSeqICode = 'A') + std::string atomName; // Four-characters string. If the name + // is less than four characters, spaces + // are appended (this is the standard + // length according to the PDB). + char resType; // One letter Amino Acid Code + std::string resName; // Three-characters string. The full name of the entity: + // Residue name for ATOM record or ligand name for HETATM + float occupancy; // The occupancy n(j) of atom j is a measure of the fraction + // of molecules in the crystal in which atom j actually + // occupies the position specified in the model + float tempFactor; // The temperature factor or B-factor can be thought of as a + // measure of how much an atom oscillates or vibrates around + // the position specified in the model + std::pair sseInfo; // secondary structure information: char sse_type - according to SecondStruct class, + // int sse_id - index of SSE in molecule + +}; + +#endif diff --git a/model/CombinatorialAssembler/libs_gamb/CIF.cc b/model/CombinatorialAssembler/libs_gamb/CIF.cc new file mode 100644 index 0000000000000000000000000000000000000000..300232365956956fbde9e0bd0b805d033b4a774c --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/CIF.cc @@ -0,0 +1,181 @@ +#include +#include +#include + +#include "CIF.h" +#include +#include + + +std::map CIF::atomSiteTable_; + +bool CIF::readAtomSiteTable(std::istream& ifile) +{ + unsigned int index = 0; + int place = 0; + atomSiteTable_.clear(); + while (!ifile.eof()) { + std::string line; + getline(ifile, line); + if(isAtomSiteLine(line)) { + boost::trim(line); + std::string key = line.substr(11); + atomSiteTable_[key] = index; + index++; + } else { + // we are done reading the atom site dictionary, go back to the beg. of the line + if(index > 0) { + ifile.seekg(place); + break; + } + } + place = ifile.tellg(); + } + if(index > 0) return true; + return false; +} + +void CIF::writeAtomSiteTable(std::ostream& ofile) { + ofile << "#\n loop_\n"; + + ofile << "_atom_site.group_PDB " << std::endl; + ofile << "_atom_site.id " << std::endl; + ofile << "_atom_site.type_symbol " << std::endl; + ofile << "_atom_site.label_atom_id " << std::endl; + ofile << "_atom_site.label_alt_id " << std::endl; + ofile << "_atom_site.label_comp_id " << std::endl; + ofile << "_atom_site.label_asym_id " << std::endl; + ofile << "_atom_site.label_entity_id " << std::endl; + ofile << "_atom_site.label_seq_id " << std::endl; + ofile << "_atom_site.pdbx_PDB_ins_code " << std::endl; + ofile << "_atom_site.Cartn_x " << std::endl; + ofile << "_atom_site.Cartn_y " << std::endl; + ofile << "_atom_site.Cartn_z " << std::endl; + ofile << "_atom_site.occupancy " << std::endl; + ofile << "_atom_site.B_iso_or_equiv " << std::endl; + ofile << "_atom_site.pdbx_formal_charge " << std::endl; + ofile << "_atom_site.auth_seq_id " << std::endl; + ofile << "_atom_site.auth_comp_id " << std::endl; + ofile << "_atom_site.auth_asym_id " << std::endl; + ofile << "_atom_site.auth_atom_id " << std::endl; + ofile << "_atom_site.pdbx_PDB_model_num " << std::endl; +} + +bool CIF::isAtomSiteLine(const std::string& CIFline) +{ + return CIFline.substr(0,11) == "_atom_site."; +} + +std::string CIF::getColumn(const std::string& CIFline, const std::string& key) +{ + std::vector result; + boost::split(result, CIFline, boost::is_any_of("\t "), boost::token_compress_on); + if(atomSiteTable_.find(key) != atomSiteTable_.end()) { + unsigned int col = atomSiteTable_[key]; + if(col < result.size()) + return result[col]; + } + return std::string(); +} + +bool CIF::isATOMrec(const std::string& CIFline) +{ + return getColumn(CIFline, "group_PDB") == "ATOM"; +} + +bool CIF::isHETATMrec(const std::string& CIFline) +{ + return getColumn(CIFline, "group_PDB") == "HETATM"; +} + +float CIF::atomXCoord(const std::string& CIFline) +{ + try { + return std::stof(getColumn(CIFline, "Cartn_x")); + } catch(...) { + return 0.0; + } +} + +float CIF::atomYCoord(const std::string& CIFline) +{ + try { + return std::stof(getColumn(CIFline, "Cartn_y")); + } catch(...) { + return 0.0; + } +} + +float CIF::atomZCoord(const std::string& CIFline) +{ + try { + return std::stof(getColumn(CIFline, "Cartn_z")); + } catch(...) { + return 0.0; + } +} + +int CIF::atomIndex(const std::string& CIFline) +{ + try { + return std::stof(getColumn(CIFline, "id")); + } catch(...) { + return 0; + } +} + +std::string CIF::atomChainId(const std::string& CIFline) +{ + return getColumn(CIFline, "label_asym_id"); +} + +std::string CIF::atomChainIdAuthor(const std::string& CIFline) +{ + return getColumn(CIFline, "auth_asym_id"); +} + +int CIF::atomResidueIndex(const std::string& CIFline) +{ + int ret = 0; + try { + ret = std::stoi(getColumn(CIFline, "auth_seq_id")); + } catch(std::exception& err) { + try { + ret = std::stoi(getColumn(CIFline, "label_seq_id")); + } + catch(std::exception& err) { + return ret; + } + } + return ret; +} + +std::string CIF::atomType(const std::string& CIFline) +{ + return getColumn(CIFline, "label_atom_id"); +} + +const std::string CIF::getAtomResidueLongName(const std::string& CIFline) +{ + return getColumn(CIFline, "label_comp_id"); +} + +AtomEntryType CIF::getAtomEntryType(const std::string& CIFline) +{ + if(isHETATMrec(CIFline)) + return(HETATM); + else if (isATOMrec(CIFline)) + return(ATOM); + else + return(UNK); +} + +float CIF::getOccupancy(const std::string& CIFline) +{ + return std::stof(getColumn(CIFline,"occupancy")); +} + +float CIF::getTempFactor(const std::string& CIFline) +{ + return std::stof(getColumn(CIFline,"B_iso_or_equiv")); +} diff --git a/model/CombinatorialAssembler/libs_gamb/CIF.h b/model/CombinatorialAssembler/libs_gamb/CIF.h new file mode 100644 index 0000000000000000000000000000000000000000..a27c904444b0c6befb876911596777eb5b93a54c --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/CIF.h @@ -0,0 +1,162 @@ +#ifndef _CIF_h +#define _CIF_h + +#include +#include +#include +#include +#include + +#include "PDB.h" + +/* +CLASS + CIF + + The CIF class defines a set of tools used for reading the Protein Data Bank + file format, used to describe molecular structures, like proteins, + drugs and Nucleic Acids +KEYWORDS + CIF, protein, Nucleic Acids (RNA, DNA) , record, atom, residue, + structure, coordinate, field + +AUTHORS + Dina Schneidman + +GOALS + Class CIF is designed to encapsulate a set of methods used for reading the + CIF file format. Methods are static requiring no object instantiation + before their use. + +USAGE + Similar to PDB + Selectors: uses PDB selectors by converting CIF line to PDB line +*/ +class CIF { + +public: + + // GROUP: CIF record type determination methods. + //// + // fill in the atom_site columns table + static bool readAtomSiteTable(std::istream& ifile); + static void writeAtomSiteTable(std::ostream& ofile); + + // + static bool isAtomSiteLine(const std::string& CIFline); + + //// + // Returns true if the given record is an ATOM record. + static bool isATOMrec(const std::string& CIFline); + + //// + // Returns true if the given record is a HETATM record. + static bool isHETATMrec(const std::string& CIFline); + + + + //// Returns an ATOM record's X coordinate. + static float atomXCoord(const std::string& CIFline); + + //// Returns an ATOM record's Y coordinate. + static float atomYCoord(const std::string& CIFline); + + //// Returns an ATOM record's Z coordinate. + static float atomZCoord(const std::string& CIFline); + + //// Returns an ATOM record's atom index number. + static int atomIndex(const std::string& CIFline); + + //// Returns atomType as a string + static std::string atomType(const std::string& CIFline); + + //// Returns an ATOM record's chain id. + static std::string atomChainId(const std::string& CIFline); + + //// Chain id named by author + static std::string atomChainIdAuthor(const std::string& CIFline); + + //// Returns an ATOM record's residue index. + static int atomResidueIndex(const std::string& CIFline); + + //// Returns the full string of the Residue Name field + static std::string const getAtomResidueLongName(const std::string& CIFline); + + //// Return the type of the CIF entry, currently only ATOM and HETATM are identified. + static AtomEntryType getAtomEntryType(const std::string& CIFline); + + //// Returns the occupancy, which is a measure of the fraction of molecules in the + // crystal in which the current atom actually occupies the specified position + static float getOccupancy(const std::string& CIFline); + + //// Returns the temperature factor, which is a measure of how much an atom + // oscillates or vibrates around the specified position + static float getTempFactor(const std::string& CIFline); + + static std::string getColumn(const std::string& CIFline, const std::string& key); + + /* TODO: + // Returns true if the given record is an ATOM record of a C-alpha atom. + static bool isCaATOMrec(const std::string& CIFline); + + // Returns true if the given record is a record of a nucleic acid residue + static bool isNucleicRecord(const std::string& CIFline); + + //// returns true if the residue name is of nucleic acid + static bool isNucleicAcid(const std::string& residueName); + + static bool isCONECTrec(const std::string& CIFline); + + static bool isMODELrec(const std::string& CIFline); + + static bool isENDMDLrec(const std::string& CIFline); + + //// Returns an ATOM record's alternative location indicator. + static char atomAltLocIndicator(const std::string& CIFline); + + // code for insertion of residues (looks like not needed for cif) + static char atomResidueICode(const std::string& CIFline); + //// + // Gets a CIF record of an atom and returns a one-letter + // abbreviation of the type of the residue that the atom belongs + // to.
+ // For a nucleic acid atom returns A for adenine, G for guanine, T + // for thymine, U for uracil and X for unknown type. + static char atomResidueType(const std::string& CIFline); + + //// + // Gets a one-letter abbreviation of the type of the residue that + // the atom belongs to
+ // For a protein atom the method returns the 3-letter abbreviation + // of its residue. Returns 0 for other types of atoms. + //static const char* const residueLongName(const char resChar); + static std::string const residueLongName(const char resChar); + //// the same for nucleic acid + static std::string const nucleicAcidLongName(const char resChar); + + + //// + // Given a charachter string with a CIF residue type code, the + // method returns a one-letter abbreviation of the type of the + // residue that the atom belongs to:
+ // - For a nucleic acid atom returns A for adenine, G for guanine, T + // for thymine, U for uracil and X for unknown type.
+ // - For a protein atom, returns a 3-letter abbreviation of the + // residue name
+ //static char residueShortName(const char *const resName); + + //// + // + //static std::vector connectedAtoms(const std::string& CIFline); + */ +private: + // Private constructor to prevent object instantiation. Class was meant to + // be use as a static method pool. + CIF() {} + + // A CIF dictionary for ATOM records: key = field_name, value = column number + static std::map atomSiteTable_; + +}; + +#endif diff --git a/model/CombinatorialAssembler/libs_gamb/DebugStream.cc b/model/CombinatorialAssembler/libs_gamb/DebugStream.cc new file mode 100644 index 0000000000000000000000000000000000000000..fdb8bed03016d34b6a2b1b1f237549dd1417b08d --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/DebugStream.cc @@ -0,0 +1,135 @@ +#include "DebugStream.h" + +DebugStream::DebugStream(std::ostream& outStream) : + out(&outStream), dbgLevel(0), withTimer(false), withNewLine(false), + withLevel(false), withIndent(false), timer(), active(false), + ownStream(false) {} + +DebugStream::DebugStream(const char *filename): + out(&std::cerr), /* temporary initilization for a reference can't be blank */ + dbgLevel(0), withTimer(false), withNewLine(false), + withLevel(false), withIndent(false), timer(), active(false), + ownStream(false) { + std::ofstream *outstream=new std::ofstream(filename); + if (!(*outstream)) std::cerr << "Can't open file : " + << filename + << " as DebugStream." + << std::endl; + else { + ownStream = true; + out=outstream; + } +} + +DebugStream::~DebugStream() +{ + if (ownStream){ + //own the output stream - has to close it. + ((std::ofstream *)out)->close(); + delete out; + } +} + +void DebugStream::setDebugLevel(unsigned int debugLevel) +{ + dbgLevel = debugLevel; +} + +void DebugStream::newLines(const bool print) +{ + withNewLine = print; +} + +void DebugStream::messageLevels(const bool print) +{ + withLevel = print; +} + +void DebugStream::messageTimes(const bool print) +{ + withTimer = print; +} + +void DebugStream::indent(const bool print) +{ + withIndent = print; +} + +void DebugStream::resetTimer() +{ + timer.reset(); +} + +void DebugStream::setFloatPrecision(unsigned int prec) +{ + (*out).setf(std::ios::fixed, std::ios::floatfield); + (*out).precision(prec); +} + +void DebugStream::setWidth(unsigned int perc) +{ + (*out).width(perc); +} + + +void DebugStream::setWidth(unsigned int numOfChars, + unsigned int adjustment) { + if (adjustment == ADJUSTMENT_LEFT) { + (*out).setf(std::iostream::left, std::iostream::adjustfield); + } else { + (*out).setf(std::iostream::right, std::iostream::adjustfield); + } + + (*out).width(numOfChars); +} + +void DebugStream::autoFlush(const bool autoflush) +{ + if (autoflush) + out->setf(std::ios::unitbuf); + else + out->unsetf(std::ios::unitbuf); +} + +DebugStream& DebugStream::operator()(unsigned int messageLevel) +{ + active = (messageLevel <= dbgLevel); + + if (active) { + if (withNewLine) + *out << '\n'; + if (withLevel) { + out->width(3); + *out << messageLevel; + } + if (withTimer) { + if (withLevel) + *out << ' '; + *out << timer << '|' << ' '; + } + else if (withLevel) + *out << ':' << ' '; + if (withIndent) + for (unsigned int i=0; i +#include +#include +#include +#include "Timer.h" + +/* +CLASS + DebugStream + + Defines a class for printing logs and debug information. User may determine + the extent of the debugging information actually printed by setting the debug + level. + +KEYWORDS + debug + +AUTHORS + Meir Fuchs. (meirfux@math.tau.ac.il) + + Copyright: SAMBA group, Tel-Aviv Univ. Israel, 1997. + +CHANGES LOG +
    +
  • Defining a streamer operator that supports printing endl +16/02/04 Oranit Dror +
  • +
  • Using unitbuf flag of Stream for autoflash instead of keeping our own flag +15/02/04 Dina Duhovny and Oranit Dror +
  • +
  • Added two functions that allows the usage of percision both regular and +in fixed floating point output +30/8/99 Zipi Fligelman +
  • +
  • Added the mode of the DebugStream owning it's ostream. This enables the + user to construct the DebugStream with a file name and open/close the file +with the DebugStream object liveness. +5/9/99 Ram Nathaniel +
  • Added the possibility to autoFlush - to make sure that debug messages are +not stuck in the buffers when the program crushes. +5/9/99 Ram Nathaniel +
+ DebugStream& operator<<(T& data); + + template + DebugStream& operator<<(const T& data); + + // Zipi added to previos version since having problems + //template + //DebugStream& operator<<(const T*& data); + + // Special overload to const char * const + DebugStream& operator<<(const char* const data); + + DebugStream& operator<<(std::ostream& (*__pf)(std::ostream&)); + +private: + // can't be a reference since it can be an ofstream or an ostream + //according to the constructor used. + std::ostream *out; + unsigned int dbgLevel; + bool withTimer; + bool withNewLine; + bool withLevel; + bool withIndent; + Timer timer; + bool active; + + bool ownStream; //for use if constructed with a file name. +}; +/************************************** + * Inline Function Implementation * + **************************************/ +unsigned DebugStream::getDebugLevel() const +{ + return dbgLevel; +} + +void DebugStream::flush() +{ + out->flush(); +} + +/************************************** + * Template Function Implementation * + **************************************/ +template +DebugStream& DebugStream::operator<<(T& data) +{ + if (active) + *out << data; + + return *this; +} + +template +DebugStream& DebugStream::operator<<(const T& data) +{ + if (active) + *out << data; + + return *this; +} + + +#endif diff --git a/model/CombinatorialAssembler/libs_gamb/GeomHash.h b/model/CombinatorialAssembler/libs_gamb/GeomHash.h new file mode 100644 index 0000000000000000000000000000000000000000..31ac3fbd7fd7ab875553402d83b7b387a71014df --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/GeomHash.h @@ -0,0 +1,399 @@ +#ifndef _GeomHash_h +#define _GeomHash_h + +#include "HashLink.h" + +template > +/* +CLASS + GeomHash + + The template defines a geometric hash table class. The class is declared + using a key type with which the data type instances are inserted and + accessed. The geometric hash table may be instantiated using any dimension + and grid-size. + +KEYWORDS + geometric, hash, iterator, query, container + +AUTHORS + Meir Fuchs (meirfux@math.tau.ac.il). + Zipi Fligelman (zipo@math.tau.ac.il). + +CHANGES LOG +
    +
  • 27/02/02 - Dina Duhovny & Oranit Shem-Tov: + - GeomHashParams - We refer to the dimension attribute as the dimension + of the overall geometric hash. Previously, this attribute was reffered + to as the index of the last axis (i.e. dimension-1). + Axis explanation by an example: + Assume a 2D geometric hash. It's axes are numbered as 0 (the x-axis) & 1 (the y-axis). + - Defining cube size for each axis. Previously, there was only one value + for all the axis. It is usefull for storing data and a key, composed of + several components, each one of them has a different scale. For example, + a 4D key, composed of a 3D coordinates and a type. + - Defining query ranges - for querying, within a different radius for each axis. +
  • +
  • 8/8/99 - Ram Nathaniel:
    + Add bucket iteration - method vector *getBuckets() const; +
  • +
  • 7/11/99 - Zipi Fligelman + Changed the method by Ram and added parameter at the whole table query + In the full table query there is a flag that by default is set to false, that + means you collect only non empty buckets. But when it is set to true non-empty + buckets are also collected. The same is true for the method getBuckets. + That returns a vector of const Bucket * with all the buckets + The empty backets are returned only if the flag is set to true. +
  • +
  • 15/5/03 - M. Shatsky + Copy constructor is added +
  • +
+ Copyright: SAMBA group, Tel-Aviv Univ. Israel, 1997. + +GOALS + GeomHash defines a high performance geometric hash table with an intuitive + interface. The class can be defined for any key type that supports the [] + operator for any data type. The hash table can be instantiated using any + dimension and any cube or grid size. It is possible to specify different + cube size for each axis. The query function also may be called with a set of + radii, one for each axis. User may also decide how the data + elements are collected in each bucket by determining the container used in + each bucket. + + GeomHash actually is a wrap for the HashLink class which does most of + the work of hash table traversal, insertion and querying. The user need + not know anything about HashLink to use the hash table. + + Geometric hash table results are returned using an STL-like container class + called HashResult. This class and its accompanying iterator class + HashIterator are used to collect hash table query results and to iterate + through the result elements. + +USAGE + Instantiation and declaration are done using a key type and a data type. + The key type must support the [] operator. For example an array of floats + or a Vector3 for 3 dimensional hash tables. User may also choose to determine + the bucket type. By default the bucket type is a vector. The user + may decide on other STL containers or write one of his own. The bucket type + must support the push_back, begin, end, size methods and the iterator and + const_iterator typedefs. + + Instantiation may include a grid size and a dimension. + Here is an example using Vector3's as the keys, + integers as the data stored in the hash table, 3 is the dimension and 1.3 is + the cube size of the grid. + EXAMPLE + GeomHash gHash(3, 1.3); + END + + Members may be inserted using the insert command. The insert command + recieves a key and a data element to be inserted into the proper bucket + in the hash table. + EXAMPLE + gHash.insert(Vector3(1.2, 2.3, 3.4), -1); + END + + When querying a geometric hash table one searches for data that was + inserted into the hash table using keys that were close to the query + key. A radius may be given to the query defining how far the neighboring + points should be. The query returns all points within that distance but + will return other points that lie in neighboring grid cubes. If you wish + to extract points that are within a given euclidean radius you'll have + to sift through the query results. + + Before running a query one must declare a HashResult container in which the + results will be collected. The container is sent with the query request + and results are found in the container after the query is done. + + An example of querying the hash table with a key and sifting through the + data to find the data's that are within a euclidean radius of the key is + given. The example assumes 10000 points are given inside a Vector3 array + called points and a query point is given in Vector3 q. + EXAMPLE + float radius=1.0; + GeomHash gHash(3, 2*radius); // grid cube size is 2*radius + for (int i=0; i<10000; i++) // insert 10000 points + gHash.insert(points[i], i); + + Vector3 q(1,1,1); + HashResult result; + gHash.query(q, radius, result); // radius = half of cube size. + + // traverse list STL iterator style. + HashResult::iterator it; + for (it = result.begin(); it != result.end(); it++) + if (q | points[*it]) <= radius + cout << *it << " : " << points[*it] << '\n'; + END + + In case of more complex key, the user can specify the cube size of every axis. + For example, in case of transformation: each transformation can be represented + as 3 rotation parameters and 3 translation parameters. The rotation parameters + are values between -PI and PI, while the translational parameters are not limited. + The user may want smaller bin size for rotation then for translation. + EXAMPLE + vector radii(6); + radii[0]=radii[1]=radii[2]=0.1; + radii[3]=radii[4]=radii[5]=2.0; + GeomHash gHash(6, radii); + + The same idea works in query: + HashResult result; + gHash.query(t, radii, result); + END +*/ + +class GeomHash : + private HashLink +{ + public: + typedef HashResult Result; + typedef typename Result::iterator Iterator; + typedef BucketT Bucket; + typedef typename HashLink::BucketsPointerList BucketsPointerList; + + typedef enum { + manhattanNorm, + euclideanNorm, + infiniteNorm + } NormType; + + //// The hash-table is instantiated by a given dimension (defualt 1) + // and a given cube-size (default 1.0). + explicit GeomHash(const unsigned short d=1, const float cs=1.0); + + //// The hash-table is instantiated by a given dimension and + //// a given cube size for each dimension. + explicit GeomHash(const unsigned short d, const std::vector& cs); + + //// Destructor. Frees all hash table memory. + ~GeomHash() { + erase(); + } + + //// Copy constructor + GeomHash(const GeomHash&); + + //// Data elements can be inserted according to keys using the insert method. + // The key determines in which d-dimensional cube the data is inserted. + void insert(const KeyT& key, const DataT& data); + + //// Searches for all data elements lying in radius neighborhood to key. + // Note that query will return all points lying within a given radius + // but may also return extra points that were not inserted with keys + // that are within a radius to the query key. + // Moreover, the default norm is the infinite norm + // All results are collected using a results container of type HashResult. + void query(const KeyT &key, const float radius, Result& result) const; + + //// Searches for all data elements according to a given key. + // The user may specify the type of the norm: Euclidean, Manhattan or Infinite. + // Note that query will return all points lying within a given radius + // but may also return extra points that were not inserted with keys + // that are within a radius to the query key. + // All results are collected using a results container of type HashResult. + void query(const KeyT &key, const float radius, const NormType ballType, + Result& result) const; + + + //// Searches for all data elements according to a given key and a set of radii. + // the size of the radii vector must be equal to dimension. Each axis is searches + // with a radius specified for it. + // This query may return extra points as well. + void query(const KeyT &key, const std::vector& radii, Result& result) const; + + //// Searches for all data elements lying in radius=cube size neighborhood + // to key. The query will return all data's that were inserted with keys + // whose distance to the query key is less then radius. More data + // elements may be returned due to the grid structure of the hash table. + // All results are + // collected using a results container of type HashResult. + void query(const KeyT &key, Result& result) const; + + //// Query the hash table with an infinite radius. Calling this method + // results in a hash table results container which points to all hash table + // elements. There is a choice whether to collect only non empty + // buckets (ehich is also the default), but if one wished to check for + // sparsity of the table you can set the includeEmpty to true and receive all + // the empty buckets. Notice that travesing the result must be done with caution + // mind the NULLs .... + void query(Result& result, bool includeEmpty=false) const; + + //// Return a vector of all the buckets currently used in the Hash. This is + // usefull for traversing the hash one bucket at a time, and also usefull for + // creating a set of all the buckets now in use for follow up checks. There + // is a flag that can be set if you want to add all the empty buckets in the + // last dimesion and thus check the change in the sparsity of the table.(The + // Hash class never changes the buckets but only copy a constant version of the + // pointers so that even after insertions and deletions in the Hash the vector + // will indicate the same set of buckets). + BucketsPointerList *getBuckets(bool includeEmpty=false) const; + + //// Returns a pointer to a bucket at the location given by key. If no such + // bucket exists a NULL pointer is returned. + const Bucket* bucket(const KeyT& key) const; + + //// frees all memory used by hash table elements. + void erase(); + + //// Sets the hash table insertion speed. This parameter determines how + // large the hash table arrays are made when an array resize is forced. + // When this parameter is set to 0, arrays will be resized to be slightly + // larger then what is currently needed by the hash table. If the parameter + // is set to > 0, resizes will be more generous. Array sizes will grow + // causing a decrease in the number of resize operations and hence an + // increase in insertion speed. + // + // This feature should be useful for large but sparsely populated hash tables + // In densely populated hash tables, a gain in speed should be noticed only + // with the first insertions. Once the table is populated resize operations + // are rare and hence the speed gain is limited. + // + // Note that raising the parameter too high could actually cause the + // insertion operations to slow down because of the overhead of managing + // a large hash table. This parameter should probably not exceed 4.0. + void setInsertSpeed(const float speed); + + //// returns hash table dimension + unsigned short dimension() const; + + //// returns hash table resolution or cube size. + float resolution(unsigned short axis=0) const; + + //// Returns hash table insertion speed. See setInsertSpeed. + float insertSpeed() const; + +private: + GeomHashParams params; +}; + +/******************************************* + * Template class GeomHash implementation. * + *******************************************/ + +template +GeomHash::GeomHash(const unsigned short d, + const float cs) + : HashLink(), params(d, cs, 1.0) { +} + + +template +GeomHash::GeomHash(const unsigned short d, + const std::vector& cs) + : HashLink(), params(d, cs, 1.0) { +} + +template +GeomHash::GeomHash(const GeomHash& gh) + : HashLink(gh,gh.params.dimension-1),params(gh.params){} + +template +void GeomHash::insert(const KeyT& key, const DataT& data) +{ + // dim-1 for starting with the last axis + HashLink::put(key, data, params, params.dimension-1); +} + +template +void GeomHash::query(const KeyT &key, const float radius, + const NormType ballType, Result& result) const +{ + switch (ballType) { + case euclideanNorm: + this->getEuclidean(key, radius, params, params.dimension-1, result); + break; + case manhattanNorm: + this->getManhattan(key, radius, params, params.dimension-1, result); + break; + default: + this->getInfinite(key, radius, params, params.dimension-1, result); + break; + } +} + +template +void GeomHash::query(const KeyT& key, const float radius, + HashResult& result) const +{ + this->getInfinite(key, radius, params, params.dimension-1, result); +} + +template +void GeomHash::query(const KeyT& key, const std::vector& radii, + HashResult& result) const +{ + HashLink::get(key, radii, params, params.dimension-1, result); +} + +template +void GeomHash::query(const KeyT& key, HashResult& result) const +{ + HashLink::get(key, params.cubeSizes, params, params.dimension-1, result); +} + +template +void GeomHash::query(Result& result,bool includeEmpty) const +{ + BucketsPointerList bresult; + + if (!includeEmpty) + this->getAllNotEmpty(params.dimension-1, &bresult); + else + this->getAll(params.dimension-1, &bresult); + + for(int i=0;i<(int)bresult.size();++i) + result.push_back((Bucket*)bresult[i]); +} + + +template +typename GeomHash::BucketsPointerList* +GeomHash::getBuckets(bool includeEmpty) const +{ + BucketsPointerList *result=new BucketsPointerList(); + if (!includeEmpty) + this->getAllNotEmpty(params.dimension-1, result); + else + this->getAll(params.dimension-1 , result); + return result; +} + +template +const BucketT* GeomHash::bucket(const KeyT& key) const +{ + return getBucket(key, params, params.dimension-1); +} + +template +void GeomHash::erase() +{ + HashLink::erase(params.dimension-1); +} + +template +void GeomHash::setInsertSpeed(const float speed) +{ + params.resizeCoefficient = max2(0.0f, speed); +} + +template +unsigned short GeomHash::dimension() const +{ + return params.dimension; +} + +template +float GeomHash::resolution(unsigned short axis) const +{ + return params.cubeSizes[axis]; +} + +template +float GeomHash::insertSpeed() const +{ + return params.resizeCoefficient; +} + +#endif diff --git a/model/CombinatorialAssembler/libs_gamb/HashLink.h b/model/CombinatorialAssembler/libs_gamb/HashLink.h new file mode 100644 index 0000000000000000000000000000000000000000..593dbe4c0c306d1772d9cf34a7f5aa424bad61f9 --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/HashLink.h @@ -0,0 +1,579 @@ +#ifndef _HashLink_h +#define _HashLink_h + +#include "HashResult.h" +#include "macros.h" + +#include +#include +#include +#include + +struct GeomHashParams { + GeomHashParams(int dim, std::vector cube_sizes, float resize_coefficient) + : dimension(dim), cubeSizes(cube_sizes), resizeCoefficient(resize_coefficient) + { + if (cubeSizes.size() != (unsigned int) dimension) { + std::cerr << "Invalid Parameter: the size of the cube_sizes vector has to be equal to the dimension of the hash" << std::endl; + exit(1); + } + } + + GeomHashParams(int dim, float cube_size, float resize_coefficient) + : dimension(dim), cubeSizes(dim, cube_size), resizeCoefficient(resize_coefficient) + {} + + int dimension; + std::vector cubeSizes; + float resizeCoefficient; +}; + +template +/* +CLASS + HashLink + + Template serves as the main engine of the geometric hash table. Classes + defined are used by GeomHash to build, traverse and query a multi-dimensional + hash-table. + +KEYWORDS + geometric, hash, iterator, query, container + +AUTHORS + Meir Fuchs (meirfux@math.tau.ac.il). + Zipi Fligelman (zipo@math.tau.ac.il). + +CHANGES LOG +
    +
  • 5/12/13 - change short to int +
  • +
  • 27/02/02 - Dina Duhovny & Oranit Shem-Tov: + - GeomHashParams - We refer to the dimension attribute as the dimension + of the overall geometric hash. Previously, this attribute was reffered + to as the index of the last axis (i.e. dimension-1). + Axis explanation by an example: + Assume a 2D geometric hash. It's axes are numbered as 0 (the x-axis) & 1 (the y-axis). + - Defining cube size for each axis. Previously, there was only one value + for all the axis. It is usefull for storing data and a key, composed of + several components, each one of them has a different scale. For example, + a 4D key, composed of a 3D coordinates and a type. + - Defining query ranges - for querying, within a different radius for axis. + the previous get function turned to getInfinite. New get function returns + the results of query by cutting each axis with different radii +
  • +
  • 8/8/99 - Ram Nathaniel:
    + Add support for GeomHash bucket iteration - method + getAll(int dim, vector* result) const; +
  • +
  • 7/11/99- Zipi Fligelman
    + getAll added by Ram does not exist in that format anymore. + There are 4 new functions: + 1. getAllNotEmpty - that recieves either a hash result or a pointer to a vector of + pointers to buckets, this will return all the non empty backets in the ALL + the table + 2. getAll - that really gets it all including the empty buckets in the last level + it can recieve the same parameters as getAllNotEmpty, but the user must take + extra care when processing it later not to fall into a segmentation fault. + 3. There is a defintion of vector as BucketsPointerList. +
  • +
  • 15/5/03 - M. Shatsky + Copy constructor is added +
  • +
+ + Copyright: SAMBA group, Tel-Aviv Univ. Israel, 1997. + +GOALS + HashLink is the main engine of the geometric hash table. It is responisble + for construction, traversal and querying of a multi-dimensional hash table. + The class is wrapped with the GeomHash class which packages the complex + methods and intricate implementation of HashLink with simple methods + that can be used immediately. No methods of the class are public because + this class was not meant to be used directly. + + A template instance is declared using a key class KeyT which defines the + means by which the hash table is referenced and queried and a data class + DataT which defines the data the hash table holds. It is assumed that DataT + has an operator[] which will return floating point coordinates for each + dimension. An optional template parameter, allows the user to define + different bucket types. The default bucket type is a vector. + + The HashLink holds a dynamic array of HashLinks. This array is dynamic in + the sense that it may grow but it is also dynamic in the sense that it + has a virtual starting point. It is a "ranged" array. Although + there may be a slight performance cost during data insertion, this + implementation saves memory and rids the user of the need to pre-define + sizes and ranges. + +USAGE + Since the class is not meant for direct use usage will not be described. + See the GeomHash implementation for usage examples. The GeomHash methods + pretty much coincide with the HashLink methods. +*/ +class HashLink +{ +public: + typedef std::vector BucketsPointerList; + +private: + HashLink(const HashLink&){ + std::cerr<<"HashLinkCC" << std::endl; exit(0);} +protected: + //// initialized as NULL. empty HashLink. + HashLink(); + + //// + HashLink(const HashLink& hl,const int axis); + + //// Returns the cube index of coord using cube size cs. The + // cube index is returned without normalizing to range. + static int cube(const float coord, const float cs); + + //// resize the array of HashLinks using a new range defined by newStart + // and newStop. It is assumed that the new range is bigger than the old one. + void resize(const int newStart, const int newStop); + + //// Resize for the bottom axis. This resize is used for the last + // axis which only contains pointers to buckets. + void buckResize(const int newStart, const int newStop); + + //// put will insert a data element into the hash table. put is recursive + // it calls itself until at axis=0. it adds the data element to a bucket + // object it either finds or creates. The bucket objects are by default of + // vector type. + // + // put must make sure that it can continue to recurse until axis = 0. + // this means that the array of HashLinks must be not null, and the the + // cube index must fall in the array's range. If this is not the case put + // must resize the array. Arrays are allocated with extra elements at + // their head and tail in an order to reduce the number of resize + // operations. + void put(const KeyT& key, const DataT& data, const GeomHashParams& params, + int axis); + + //// getInfinite returns a list of elements that may be within a given radius to a + // given key. The elements returned are all the elements found in the + // neighboring cubes to the given key's cube. The data elements from all + // these cubes are gathered in a HashResult container class passed on + // to the method as an argument. get is a recursive method that recurses + // until axis=0 at which point a bucket pointer is added to the HashResult. + void getInfinite(const KeyT &key, const float radius, const GeomHashParams& params, int axis, + HashResult& result) const; + + //// getEuclidean returns a list of elements according to Euclidean norm + void getEuclidean(const KeyT &key, const float radius, const GeomHashParams& params, + int axis, + HashResult& result) const; + + //// getManhattan returns a list of elements according to Manhattan norm + void getManhattan(const KeyT &key, const float radius, const GeomHashParams& params, + int axis, + HashResult& result) const; + + + //// get returns a list of elements that may be within a given set of radii to a + // given key. The elements returned are all the elements found in the + // neighboring cubes to the given key's cube. The data elements from all + // these cubes are gathered in a HashResult container class passed on + // to the method as an argument. get is a recursive method that recurses + // until axis=0 at which point a bucket pointer is added to the HashResult. + // + // params: + // ranges - a pointer to an array of size equal to the axis of the hash. + // The user is the responsible for a valid array. + // axis - is an index of the current checked axis. + void get(const KeyT &key, const std::vector& radii, + const GeomHashParams& params, int axis, + HashResult& result) const; + + //// This version creates a vector of all the non-empty buckets. + // The buckets are never changed during the use of the GeomHash, and therefore + // this function create a snapshot of total buckets currently in use in the hash. + // The function is also usefull for collecting information about the buckets + // which may be traversed in a Bucket iterator now. + void getAllNotEmpty(int axis, BucketsPointerList *result) const; + + //// This funciton will return all the buckets including the empty (null) ones + // that exists in the hash table. This function enable to check the saprsity + // of the hash table at least in the last level of the recursion + void getAll(int axis, BucketsPointerList *result) const; + + //// Using get with radius = 0 makes things simpler in terms of code and + // method interface. This method returns a bucket pointer to the bucket + // found in the position pointed to by the given key. If no bucket is found + // a NULL pointer is returned. This method should be faster then the general + // get method. + const Bucket* getBucket(const KeyT &key, const GeomHashParams& params, int axis) const; + + //// erase recursively erases all the data elements in the array. + // This method is not called by the destructor since the HashLink elements + // are destructed during the insertion operations in the put function. + /// need to add a possibilty win axis 0 when the data is a pointer + void erase(const int axis); + +private: + HashLink* next; + int start, stop; +}; + +/******************************************* + * Template class HashLink implementation. * + *******************************************/ + +template +HashLink::HashLink(): next(NULL) {} + +template +HashLink::HashLink(const HashLink &hl,const int axis) +{ + if(hl.next == NULL){ + next=NULL; + return; + } + + start=hl.start; + stop=hl.stop; + unsigned int size = stop - start + 1; + + if (axis) { + // allocate memory for new array. + next = (HashLink*)::operator new(size*sizeof(HashLink)); + + // copy elements + HashLink* copyEnd = next+(stop -start); + for (HashLink* p=next, *q=hl.next; p<=copyEnd; ++p, ++q) + *p = HashLink(*q,axis-1); + + }else{ + Bucket** newnext = (Bucket**)::operator new(size*sizeof(Bucket*)); + + // copy elements and instantiate empty ones. + Bucket** copyEnd = newnext + (stop -start); + for (Bucket** p=newnext, **q=(Bucket**)hl.next; p<=copyEnd; ++p, ++q){ + if(*q) + *p = new Bucket(**q); + else + *p=NULL; + } + + next=(HashLink*)newnext; + } +} + +template +int HashLink::cube(const float coord, + const float cs) +{ + // There is a problem with truncation of negative numbers. Negative numbers + // are truncated and one recieves the number rounded up while positive + // numbers are rounded down. + // There is a slight unimportant bug here. But I've ignored it for the sake + // of speed. Assuming cs=1.0 then coord=0.0 will return 0 while coord=-1.0 + // will return -2. Cube -1 is "infinitesimally smaller". Since all HashLink + // methods use the cube method this should have no bearings on the actual + // output of the hash table. + if (coord < 0) return (int)(coord/cs) - 1; + else return (int)(coord/cs); +} + +template +void HashLink::resize(const int newStart, + const int newStop) +{ + // allocate memory for new array. + unsigned int newSize = newStop - newStart + 1; + HashLink* newNext = (HashLink*)::operator new(newSize*sizeof(HashLink)); + + // copy elements and instantiate empty ones. + HashLink* copyStart = newNext + (start-newStart); + HashLink* copyEnd = copyStart + (stop -start + 1); + HashLink* newEnd = newNext + newSize; + for (HashLink* p=newNext; p +void HashLink::buckResize(const int newStart, + const int newStop) +{ + unsigned int newSize = newStop - newStart + 1; + Bucket** newNext = (Bucket**)::operator new(newSize*sizeof(Bucket*)); + + // copy elements and instantiate empty ones. + Bucket** copyStart = newNext + (start-newStart); + Bucket** copyEnd = copyStart + (stop -start + 1); + Bucket** newEnd = newNext + newSize; + for (Bucket** p=newNext; p +void HashLink::put(const KeyT& key, const DataT& data, + const GeomHashParams& params, + const int axis) +{ + int pos = cube(key[axis], params.cubeSizes[axis]); + + if (axis) { + // axis is not 0. have to go deeper. + // check that new element fits into array. if not resize with a + // few extra elements (to prevent constant resizing). number of + // extra elements depends on axis. + + if (next==NULL) { // if array not started create new array + start = pos; stop = pos-1; + resize(pos-axis, pos+axis); + } + else if (posstop) + resize(start, pos+(int)(params.resizeCoefficient*axis)); + // activate the put method on the correct element within the array. + next[pos-start].put(key, data, params, axis-1); + } else { + // axis=0. time to insert the data element into a bucket. at axis=0 next + // points to a bucket. make sure one exists and add the data to the + // bucket. + if (next==NULL) { // if array not started create new array + start = pos; stop = pos-1; + buckResize(pos-1, pos+1); + } + else if (posstop) + buckResize(start, pos+(int)(params.resizeCoefficient+1)); + // activate the put method on the correct element within the array. + Bucket** bucket = ((Bucket**)next)+(pos-start); + if (*bucket==NULL) *bucket = new Bucket; + (*bucket)->push_back(data); + } +} + +template +void HashLink::getInfinite(const KeyT &key, const float radius, + const GeomHashParams& params, int axis, + HashResult& result) const +{ + if (next) { + // go over all cubes in the array that may contain members that are in + // radius proximity to key and recursively call get. + int i = cube(key[axis]-radius, params.cubeSizes[axis]); + i = ((start > i) ? start : i) - start; + int to = cube(key[axis]+radius, params.cubeSizes[axis]); + to = ((stop < to) ? stop : to) - start; + if (axis) + for (; i<=to; ++i) + next[i].getInfinite(key, radius, params, axis-1, result); + else + for (Bucket** buckets = (Bucket**)next; i<=to; ++i) + if (buckets[i] != NULL) + result.push_back(buckets[i]); + } + return; +} + + +template +void HashLink::get(const KeyT &key, const std::vector& radii, + const GeomHashParams& params, int axis, + HashResult& result) const +{ + if (next) { + // go over all cubes in the array that may contain members that are in + // radius proximity to key and recursively call get. + int i = cube(key[axis]-radii[axis], params.cubeSizes[axis]); + i = ((start > i) ? start : i) - start; + int to = cube(key[axis] + radii[axis], params.cubeSizes[axis]); + to = ((stop < to) ? stop : to) - start; + if (axis) + for (; i<=to; ++i) + next[i].get(key, radii, params, axis-1, result); + else + for (Bucket** buckets = (Bucket**)next; i<=to; ++i) + if (buckets[i] != NULL) + result.push_back(buckets[i]); + } + return; +} + + +template +void HashLink::getManhattan(const KeyT &key, const float radius, + const GeomHashParams& params, int axis, + HashResult& result) const +{ + if (next) { + // go over all cubes in the array that may contain members that are in + // radius proximity to key and recursively call get. + int i = cube(key[axis]-radius, params.cubeSizes[axis]); + i = ((start > i) ? start : i); + int to = cube(key[axis]+radius, params.cubeSizes[axis]); + to = ((stop < to) ? stop : to); + if (axis) { + int center = cube(key[axis], params.cubeSizes[axis]); + for (; i<=to; ++i) { + if (i==center) + next[i-start].getManhattan(key, radius, params, axis-1, result); + else { + float rdiff; + if (i +void HashLink::getEuclidean(const KeyT &key, const float radius, + const GeomHashParams& params, int axis, + HashResult& result) const +{ + if (next) { + // go over all cubes in the array that may contain members that are in + // radius proximity to key and recursively call get. + int i = cube(key[axis]-radius, params.cubeSizes[axis]); + i = ((start > i) ? start : i); + int to = cube(key[axis]+radius, params.cubeSizes[axis]); + to = ((stop < to) ? stop : to); + if (axis) { + int center = cube(key[axis], params.cubeSizes[axis]); + for (; i<=to; ++i) { + if (i==center) + next[i-start].getEuclidean(key, radius, params, axis-1, result); + else { + float rdiff; + if (i +void HashLink::getAllNotEmpty(int axis, BucketsPointerList* result) const +{ + if (next) { + // go over all cubes in the array that may contain members that are in + // radius proximity to key and recursively call get. + int i = 0; + int to = stop - start; + if (axis) + for (; i<=to; ++i) + next[i].getAllNotEmpty(axis-1, result); + else + for (Bucket** buckets = (Bucket**)next; i<=to; ++i) + if (buckets[i] != NULL) + result->push_back(buckets[i]); + } + return; +} + +template +void HashLink::getAll(int axis, BucketsPointerList* result) const +{ + if (next) { + // go over all cubes in the array that may contain members that are in + // radius proximity to key and recursively call get. + int i = 0; + int to = stop - start; + if (axis) + for (; i<=to; ++i) + next[i].getAll(axis-1, result); + else + for (Bucket** buckets = (Bucket**)next; i<=to; ++i) + result->push_back(buckets[i]); + } + return; +} + +template +const Bucket* HashLink::getBucket(const KeyT &key, const GeomHashParams& params, + int axis) const { + if (next) { + // go over all cubes in the array that may contain members that are in + // radius proximity to key and recursively call get. + int pos = cube(key[axis], params.cubeSizes[axis]); + if (pos >= start && pos <= stop) { + if (axis) + return next[pos-start].getBucket(key, params, axis-1); + else + return *(((Bucket**)next)+pos-start); + } + } + return NULL; +} + +template +void HashLink::erase(const int axis) +{ + // recursively call for each element in the array and then free the array's + // memory. + if (next){ + if (axis) { + HashLink* from = next; + HashLink* to = next+(stop-start); + for (; from<=to; ++from) + from->erase(axis-1); + } + else { + Bucket** from = (Bucket**)next; + Bucket** to = from+(stop-start); + for (; from<=to; ++from) + if (*from != NULL) delete(*from); + } + ::operator delete(next); + + next=NULL; + } +} + +#endif diff --git a/model/CombinatorialAssembler/libs_gamb/HashResult.h b/model/CombinatorialAssembler/libs_gamb/HashResult.h new file mode 100644 index 0000000000000000000000000000000000000000..982e67f2f70fb7cc85cf94f359a4024e752951ee --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/HashResult.h @@ -0,0 +1,426 @@ +#ifndef _HashResult_h +#define _HashResult_h + +#include +#include + +template > +/* +CLASS + HashIterator + + Template defines an STL-like iterator over geometric hash table query + results. (See GeomHash). The iterator is used within the HashResult + container. + +KEYWORDS + geometric, hash, iterator, query, container + +AUTHORS + Meir Fuchs. (meirfux@math.tau.ac.il) + + Copyright: SAMBA group, Tel-Aviv Univ. Israel, 1997. + +CHANGES LOG +
    +
  • +
const_iterator; + + typedef DataT value_type; + typedef value_type* pointer; + typedef value_type& reference; + typedef const value_type& const_reference; + + // GROUP: typedefs & contructors. + + //// Default constructor. Seldom used. + HashIterator(); + + //// Constructor recieves pointer to Bucket and an index within the + // Bucket. A Bucket is an STL vector and idx is the index within that + // vector. + HashIterator(Bucket* const* ptr); + + //// Copy constructor. + HashIterator(const HashIterator& hi); + + //// Destructor. + ~HashIterator(); + + // GROUP: Equality operators. + + //// Equality operator. Hash iterators from different queries but pointing + // to the same element in the hash table are equal. If the hash iterators + // are at the end point of the HashResult container then the equality + // operator will return true only if both hash iterators were created + // from the same HashResult object. + bool operator== (const HashIterator &hi2) { + if ((*data_ptr != NULL) && (*hi2.data_ptr != NULL)) + return (index == hi2.index); + else + return (data_ptr == hi2.data_ptr); + } + + bool operator!= (const HashIterator &hi2) { return !((*this)==hi2);} + + // GROUP: Iterating operators. + + //// Contents of. Returns data pointed to by const_iterator. + DataT& operator*(); + + //// Contents of. Returns data pointed to by const_iterator. + const DataT& operator*() const; + + //// Iterator increment operator. prefix. + HashIterator& operator++(); + + //// Iterator decrement operator. prefix. + HashIterator& operator--(); + + //// Iterator increment operator. postfix. + HashIterator operator++(int); + + //// Iterator decrement operator. postfix. + HashIterator operator--(int); + + HashIterator nextBucket() const; + HashIterator prevBucket() const; + +private: + Bucket* const* data_ptr; // pointer to Bucket - a vector of data elements. + typename Bucket::iterator index; +}; + + +template > +/* +CLASS + HashResult + + Template defines an STL-like container class used to hold geometric hash + table query results. (See GeomHash). The results are iterated through using + the HashIterator STL-like iterator class. + +KEYWORDS + geometric, hash, iterator, query, container + +AUTHORS + Meir Fuchs. (meirfux@math.tau.ac.il) + + Copyright: SAMBA group, Tel-Aviv Univ. Israel, 1997. + +GOALS + The class defines an STL-like container that holds the geometric hash table + query results. A HashResult is formed when a query is posted to a geometric + hash table (GeomHash). A HashResult holds a set of pointers into hash + Buckets within the hash table that are the query's results. The container + elemenets are constant and no attempt to change them should be made because + changes to the container elements are changes within the hash table itself. + +USAGE + The begin() and end() methods are used to generate head and tail iterators + to the result list. The HashIterator-s can then be used to iterate through + the result list using the ++, -- operators and using the * operator to + reference the data itself. + + For an example of using the query result container and iterator class see + GeomHash. +*/ +class HashResult +{ +public: + // STL like typedef iterators found in containers. + typedef HashIterator iterator; + typedef DataT value_type; + typedef value_type* pointer; + typedef const value_type& const_reference; + typedef const value_type& reference; + + //// The default size of the Bucket pointers array. The array grows + // dynamically as more results are collected. The default size and + // increment size are defined by defSize but could be changed using the + // constructor. Note that the pointers are to Buckets of elements and not + // to the elements themselves so the pointer array size should not be as + // large as the number of results. + static const int defSize = 32; + + //// Instantiate a HashResult. Allocates space for defSize bucket pointers. + HashResult(); + + //// Instantiate a HashResult. Allocates space for defAlloc bucket pointers. + HashResult(const unsigned int defAlloc); + + //// Destructor. + ~HashResult(); + + //// Add a Bucket pointer to the dynamic pointer array held by the container. + // Generally, for normal use of the geometric hash tables this method + // should not be called. It is used by the HashLink template class to collect + // query results. + void push_back(Bucket* bkt); + + //// Returns number of elements in the query result container. It should be + // stressed that the method does not return the number of Bucket pointers + // or the size of the pointer array but the number of actual data elements + // in the query results which could be much larger. + size_t size() const; + + //// Returns an iterator to the beginning of the query results list. + iterator begin(); + + //// Returns an iterator to the end of the query results list. + iterator end(); + + //// Returns true if container is empty. + bool empty() const; + + //// Same as size(). Here for STL compliance. + size_t max_size() const; + + //// Returns first element of the container (the query results- + // assuming its a Bucket) + reference front() const; + + //// Returns last element of the container (the query results - + // assuming its a Bucket). + reference back() const; + + //// Returns the number of Buckets referenced by the results container. + unsigned int bucketCount() const; + + //// Clears hash result container. Does not affect the geometric hash table + // itself. After a clear call a HashResult object may be used for another + // query. Memory used is not deallocated until object destruction. + void clear(); + +private: + Bucket** buckets; + unsigned int count; + unsigned int alloc; +}; + +/*********************************************** + * Template class HashIterator implementation. * + ***********************************************/ + +template +HashIterator::HashIterator() {} + +template +HashIterator::HashIterator(Bucket* const* ptr) + : data_ptr(ptr) +{ + if (*data_ptr != NULL) + index = (*data_ptr)->begin(); +} + +template +HashIterator::HashIterator(const HashIterator& hi) + : data_ptr(hi.data_ptr), index(hi.index) {} + +template +HashIterator::~HashIterator() {} + +template +DataT& HashIterator::operator*() +{ + return *index; +} + +template +const DataT& HashIterator::operator*() const +{ + return *index; +} + +template +HashIterator& HashIterator::operator++() +{ + if (++index == (*data_ptr)->end()) + if (*(++data_ptr) != NULL) + index = (*data_ptr)->begin(); + return *this; +} + +template +HashIterator& HashIterator::operator--() +{ + if (*data_ptr == NULL || index == (*data_ptr)->begin()) + index = (*(--data_ptr))->end(); + --index; + return *this; +} + +template +HashIterator HashIterator::operator++(int) +{ + HashIterator temp = *this; + ++*this; + return temp; +} + +template +HashIterator HashIterator::operator--(int) { + HashIterator temp = *this; + --*this; + return temp; +} + +template +HashIterator HashIterator::nextBucket() const +{ + return HashIterator(data_ptr+1); +} + +template +HashIterator HashIterator::prevBucket() const +{ + if (*data_ptr != NULL && index != (*data_ptr)->begin()) + return *this; + else + return HashIterator(data_ptr-1); +} + +// template > +// inline bool operator==(const HashIterator& hi1, +// const HashIterator& hi2) +// { +// if ((*hi1.data_ptr != NULL) && (*hi2.data_ptr != NULL)) +// return (hi1.index == hi2.index); +// else +// return (hi1.data_ptr == hi2.data_ptr); +// } + +// template > +// inline bool operator!=(const HashIterator& hi1, +// const HashIterator& hi2) +// { +// return !(hi1==hi2); +// } + +/********************************************* + * Template class HashResult implementation. * + *********************************************/ + +template +HashResult::HashResult() + : count(0), alloc(defSize) +{ + buckets = (Bucket**)operator new(alloc*sizeof(Bucket*)); + *buckets = NULL; +} + +template +HashResult::HashResult(const unsigned int defAlloc) + : count(0), alloc(defAlloc+1) +{ + buckets = (Bucket**)operator new(alloc*sizeof(Bucket*)); + *buckets = NULL; +} + +template +HashResult::~HashResult() +{ + if (buckets) operator delete(buckets); +} + +template +void HashResult::push_back(Bucket* bkt) +{ + if (count == alloc-1) { + if (count) alloc *= 2; + else alloc = defSize; + Bucket** newBuckets = (Bucket**)operator new(alloc*sizeof(Bucket*)); + for (unsigned int i=0; i +size_t HashResult::size() const +{ + size_t sz = 0; + for (unsigned int i=0; isize(); + return sz; +} + +template +HashIterator HashResult::begin() +{ + return iterator(buckets); +} + +template +HashIterator HashResult::end() +{ + return iterator(buckets+count); +} + +template +bool HashResult::empty() const +{ + return (count==0); +} + +template +size_t HashResult::max_size() const +{ + return size(); +} + +template +const DataT& HashResult::front() const +{ + return *((*buckets)->begin()); +} + +template +const DataT& HashResult::back() const +{ + return *(--end()); +} + +template +unsigned int HashResult::bucketCount() const +{ + return count; +} + +template +void HashResult::clear() +{ + count=0; +} +#endif diff --git a/model/CombinatorialAssembler/libs_gamb/Interface.cc b/model/CombinatorialAssembler/libs_gamb/Interface.cc new file mode 100644 index 0000000000000000000000000000000000000000..64f590caf4e277b099742667ac3664d6da84d76a --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/Interface.cc @@ -0,0 +1,169 @@ +#include "Interface.h" + +Interface::Interface(): adjacencyCounter(0){} + +void Interface::addAdjacency(unsigned int index1, unsigned int index2, float dist){ + //If an exact entry exists (both indexes are found) - update distance if needed + InterfaceAdjacency::iterator it1=interfaceAdjacency.find(index1); + if(it1!=interfaceAdjacency.end()) {//index1 exists + ParticleAdjacency& pAdjacency = it1->second; + ParticleAdjacency::iterator it2=pAdjacency.find(index2); + if(it2!= pAdjacency.end()) {//index2 exists + if((it2->second).dist() > dist) { // update distance + (it2->second).setDistance(dist); + } + return; + } + else{//index2 does not exist + Adjacency adjacency(index1, index2, dist); + adjacencyCounter++; + pAdjacency[index2]= adjacency; + } + } + else{//index1 doesn't exist + Adjacency adjacency(index1, index2, dist); + adjacencyCounter++; + ParticleAdjacency particleAdjacency; + particleAdjacency[index2]= adjacency; + interfaceAdjacency [index1]=particleAdjacency; + } +} + +void Interface::buildHigherLevel(Interface& highLevelInterface, + const std::vector& group1, + const std::vector& group2) { + for(iterator iter = begin(); iter!= end(); ++iter) { + const Adjacency& adj=*iter; + int first = group1[adj.first()]; + int second = group2[adj.second()]; + highLevelInterface.addAdjacency(first, second, adj.dist()); + } +} + +bool Interface::isAdjacent(unsigned int index1, unsigned int index2) const { + InterfaceAdjacency::const_iterator interfaceIt=interfaceAdjacency.find(index1); + if(interfaceIt!=interfaceAdjacency.end()) { + ParticleAdjacency::const_iterator particleIt = interfaceIt->second.find(index2); + if(particleIt!=interfaceIt->second.end()) + return true; + } + return false; +} + +bool Interface::isInterface(unsigned int index1) const { + InterfaceAdjacency::const_iterator it=interfaceAdjacency.find(index1); + if(it!=interfaceAdjacency.end()) + return true; + return false; +} + +unsigned int Interface::size() const { + return interfaceAdjacency.size(); +} + +unsigned int Interface::neighboursNumber(unsigned int index1) const { + InterfaceAdjacency::const_iterator it=interfaceAdjacency.find(index1); + if(it==interfaceAdjacency.end()) + return 0; + return it->second.size(); +} + +void Interface::adjacencies(std::vector& vAdj, unsigned int index1) const { + InterfaceAdjacency::const_iterator interfaceIt=interfaceAdjacency.find(index1); + if(interfaceIt != interfaceAdjacency.end()) { + vAdj.reserve(interfaceIt->second.size()); + for(ParticleAdjacency::const_iterator particleIt = interfaceIt->second.begin(); + particleIt!=interfaceIt->second.end(); particleIt++) + vAdj.push_back(particleIt->second); + } +} + +const Interface::ParticleAdjacency& Interface::adjacencies(unsigned int index1) const { + InterfaceAdjacency::const_iterator interfaceIt=interfaceAdjacency.find(index1); + static const ParticleAdjacency empty; + if(interfaceIt != interfaceAdjacency.end()) + return interfaceIt->second; + return empty; +} + +void Interface::neighboursIntersection(const Interface::ParticleAdjacency& pa1, + const Interface::ParticleAdjacency& pa2, + std::vector& intersection) const { + if(pa1.size() > pa2.size()) + return neighboursIntersection(pa2, pa1, intersection); + intersection.reserve(pa1.size()); + for(ParticleAdjacency::const_iterator it = pa1.begin(); it!=pa1.end(); it++) + if(pa2.find(it->first)!= pa2.end()) + intersection.push_back(it->first); +} + +void Interface::neighboursIntersection(unsigned int index1, unsigned int index2, + std::vector& intersection) const { + neighboursIntersection(adjacencies(index1), adjacencies(index2), intersection); +} + +void Interface::neighboursUnion(const Interface::ParticleAdjacency& pa1, + const Interface::ParticleAdjacency& pa2, + std::vector& nUnion) const { + if(pa1.size() > pa2.size()) + return neighboursUnion(pa2, pa1, nUnion); + nUnion.reserve(pa1.size() + pa2.size()); + for(ParticleAdjacency::const_iterator it = pa1.begin(); it!=pa1.end(); it++) + nUnion.push_back(it->first); + for(ParticleAdjacency::const_iterator it = pa2.begin(); it!=pa2.end(); it++) + if(pa1.find(it->first) == pa1.end()) + nUnion.push_back(it->first); +} + +void Interface::neighboursUnion(unsigned int index1, unsigned int index2, + std::vector& nUnion) const { + neighboursUnion(adjacencies(index1), adjacencies(index2), nUnion); +} + +Interface::iterator::iterator(InterfaceAdjacency::iterator iInterfaceAdjIt, + ParticleAdjacency::iterator iParticleAdjIt, + InterfaceAdjacency::iterator iInterfaceAdjacencyEnd) { + interfaceAdjIt=iInterfaceAdjIt; + particleAdjIt=iParticleAdjIt; + interfaceAdjacencyEnd=iInterfaceAdjacencyEnd; +} + +Interface::iterator Interface::iterator::operator++() { + if ( ++particleAdjIt == interfaceAdjIt->second.end() )//promote line index. if end of line than + if( (++interfaceAdjIt) != interfaceAdjacencyEnd )//promote to next line. If not the last line than + particleAdjIt=interfaceAdjIt->second.begin();//set line index to beginning of line + return *this; +} + +const Interface::Adjacency& Interface::iterator::operator*() const{ + return particleAdjIt->second; +} + +inline bool Interface::iterator::operator==(const Interface::iterator& it1) const{ + if(it1.interfaceAdjIt == it1.interfaceAdjacencyEnd && + this->interfaceAdjIt == this->interfaceAdjacencyEnd) + return true; + else + return (it1.interfaceAdjIt == this->interfaceAdjIt && + it1.particleAdjIt == this->particleAdjIt ); +} + +bool Interface::iterator::operator!=(const Interface::iterator& it1) const{ + return !(it1 == *this); +} + + +Interface::iterator Interface::begin() { + InterfaceAdjacency::iterator interfaceStart=interfaceAdjacency.begin(); + if ( interfaceAdjacency.empty() ){//interface hash map empty + ParticleAdjacency::iterator emptyParticleIt=ParticleAdjacency::iterator(); + return iterator(interfaceStart, emptyParticleIt, interfaceStart ); + } + return iterator( interfaceStart, (interfaceStart->second).begin(), interfaceAdjacency.end() ); +} + +Interface::iterator Interface::end() { + InterfaceAdjacency::iterator interfaceEnd=interfaceAdjacency.end(); + ParticleAdjacency::iterator emptyParticleIt=ParticleAdjacency::iterator(); + return iterator( interfaceEnd, emptyParticleIt, interfaceEnd ); +} diff --git a/model/CombinatorialAssembler/libs_gamb/Interface.h b/model/CombinatorialAssembler/libs_gamb/Interface.h new file mode 100644 index 0000000000000000000000000000000000000000..b79e2a3222a59d51b8b288d2dbd20d03696bd793 --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/Interface.h @@ -0,0 +1,165 @@ +#ifndef INTERFACE_H +#define INTERFACE_H + +#include +#include +#include + +/* +CLASS + Interface + + Defines an interface container and used to store interfaces between + molecules. + + +KEYWORDS + Interface, Molecule, Particle + +AUTHORS + Inbal Halperin (inbalhal@math.tau.ac.il), + Dina Duhovny (duhovka@math.tau.ac.il) and + Yuval Inbar (inbaryuv@math.tau.ac.il) + + Copyright: SAMBA group, Tel-Aviv Univ. Israel, 1997. + +CHANGES LOG +
    +
  • 10/01/05 Dina + in addAdjacency function: if the adjacency already exists, we now check + if we can update the distance. if the new distance is smaller, we update it. + setDistance was added to Adjacency for this purpose. + buildHigherLevel function was re-written, to iterate over interface instead of group1 +
  • 25/12/03 Dina + added union and intersection functions for neighbours of two particles + added adjacencies function that returns ParticleAdjacency for particle, + instead of previous option that copied them into vector. +
& group1, + const std::vector& group2); + + //// Class that stores a pair of adjacent particles and their distance + class Adjacency { + public: + Adjacency() {} + Adjacency(unsigned int p1, unsigned int p2, float d): + particle1(p1), particle2(p2), distance(d) {} + Adjacency(const Interface::Adjacency& a): + particle1(a.particle1), particle2(a.particle2), distance(a.distance) {} + unsigned int first() const { return particle1; } + unsigned int second() const { return particle2; } + float dist() const { return distance; } + void setDistance(float dist) { distance = dist; } + friend std::ostream& operator<<(std::ostream& s, const Adjacency& adjacency) { + s << adjacency.first() << ' ' << adjacency.second() << ' ' <& vAdj, unsigned int index1) const; + + typedef std::unordered_map ParticleAdjacency;//int = index2 + typedef std::unordered_map InterfaceAdjacency;//int = index1 + + //// return ParticleAdjacency for a given index + const ParticleAdjacency& adjacencies(unsigned int index1) const; + + //// return the intersection of neighbours of two particle adjacencies + void neighboursIntersection(const ParticleAdjacency& pa1, const ParticleAdjacency& pa2, + std::vector& intersection) const; + + //// return the intersection of neighbours of two indices + void neighboursIntersection(unsigned int index1, unsigned int index2, + std::vector& intersection) const; + + //// return the union of neighbours of two particle adjacencies + void neighboursUnion(const ParticleAdjacency& pa1, const ParticleAdjacency& pa2, + std::vector& nUnion) const; + + //// return the union of neighbours of two indices + void neighboursUnion(unsigned int index1, unsigned int index2, + std::vector& nUnion) const; + + //// Interface iterator + class iterator { + public: + iterator(); + iterator(InterfaceAdjacency::iterator iInterfaceAdjIt, + ParticleAdjacency::iterator iParticleAdjIt, + InterfaceAdjacency::iterator iInterfaceAdjacencyEnd); + iterator operator++(); + const Adjacency& operator*() const; + bool operator==(const Interface::iterator& it1) const; + bool operator!=(const Interface::iterator& it1) const; + protected: + InterfaceAdjacency::iterator interfaceAdjIt; + ParticleAdjacency::iterator particleAdjIt; + InterfaceAdjacency::iterator interfaceAdjacencyEnd; + }; + + iterator begin(); + iterator end(); + + protected: + //// Interface container + InterfaceAdjacency interfaceAdjacency; + //// Number of pairs in interface + unsigned int adjacencyCounter; +}; + + +#endif diff --git a/model/CombinatorialAssembler/libs_gamb/Logger.cc b/model/CombinatorialAssembler/libs_gamb/Logger.cc new file mode 100644 index 0000000000000000000000000000000000000000..19c00b129873385e381aefea7fe4d4d89d69aee4 --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/Logger.cc @@ -0,0 +1,65 @@ +#include "Logger.h" + +#include "macros.h" +#include + +#include "Parameters.h" + +Logger* Logger::instance = NULL; + +Logger& Logger::getInstance() { + if (instance == NULL) { + init(); + } + + return *instance; +} + + +void Logger::init() { + std::string logFile = Parameters::getString("log-file", "log"); + unsigned int logLevel = Parameters::getInt("log-level", WARNING_LEVEL); + instance = new Logger(logFile, logLevel); +} + +Logger::Logger(const std::string logFile, unsigned int logLevel) : log(logFile.c_str()) { + log.autoFlush(true); + log.setDebugLevel(logLevel); + log.newLines(false); + log.messageTimes(true); +} + + +Logger& Logger::operator() (MSG_LEVEL msgLevel) { + std::string msgHeader = ""; + switch(msgLevel) { + case ERROR_LEVEL: + msgHeader = "ERROR: "; + break; + case WARNING_LEVEL: + msgHeader = "WARNING: "; + break; + case INFO_LEVEL: + msgHeader = "INFO: "; + break; + case DEBUG_LEVEL: + msgHeader = "DEBUG: "; + break; + } + + log(msgLevel) << msgHeader; + return *this; +} + + +Logger& Logger::operator<<(std::ostream& (*__pf)(std::ostream&)) { + log << *__pf; + return *this; + +} + +Logger::~Logger() { + // delete log; + delete instance; + instance = NULL; +} diff --git a/model/CombinatorialAssembler/libs_gamb/Logger.h b/model/CombinatorialAssembler/libs_gamb/Logger.h new file mode 100644 index 0000000000000000000000000000000000000000..66d3d166d3ca6d9282e669fecc3b1959b0b51fcd --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/Logger.h @@ -0,0 +1,178 @@ +#ifndef _LOGGER_H +#define _LOGGER_H + +#include "DebugStream.h" + +#include + +/* + CLASS + Logger + + A singleton utility class that is used to print messages to a log + file. It is based on the DebugStream class. This means that a level + is attached to each message passed to the logger and the extent of + the information actually printed by the logger is controlled by the + log-level parameter (defined in the program configuratin + file). Specifically, the message will be printed to the log file + only if its level is smaller then log-level. + + The user should note that the class is not thread safe. + + KEYWORDS + Log, DebugStream + + AUTHORS + Oranit Dror (mailto: oranit@post.tau.ac.il) + + Copyright: SAMBA group, Tel-Aviv Univ. Israel, 2004. +CHANGES LOG +
    +
  • 9.01.05 Dina: add setLevel method +
  • +
+ + USAGE + The Logger is unitialized with two parameters logLevel and logFile, + which are defined in the program configuration file (i.e. defined by + the "Parameters" singleton class). + + The syntax for printing a message is one of the following: + Logger::getInstance()(Logger::MSG_LEVEL) << msg; + Logger::errorMessage() << msg; + Logger::warningMessage() << msg; + Logger::infoMessage() << msg; + Logger::debugMessage() << msg; +*/ +class Logger { +public: + //// + // The different message levels + enum MSG_LEVEL {ERROR_LEVEL = 0, + WARNING_LEVEL = 1, + INFO_LEVEL = 2, + DEBUG_LEVEL = 3}; + + + //// + static Logger& getInstance(); + + //// + // Gets a reference to the Logger and starts a new error message + // One should note that using this method and only then streaming + // the message is not thread safe. + static Logger& errorMessage() { + return getInstance()(ERROR_LEVEL); + } + + //// + // Gets a reference to the Logger and starts a new warning message + // One should note that using this method and only then streaming + // the message is not thread safe. + static Logger& warningMessage() { + return getInstance()(WARNING_LEVEL); + } + + //// + // Gets a reference to the Logger and starts a new info message + // One should note that using this method and only then streaming + // the message is not thread safe. + static Logger& infoMessage() { + return getInstance()(INFO_LEVEL); + } + + //// + // Gets a reference to the Logger and starts a new debug message + // One should note that using this method and only then streaming + // the message is not thread safe. + static Logger& debugMessage() { + return getInstance()(DEBUG_LEVEL); + } + + //// + // Returns true if messages with the given level will be printed to + // the log file + static bool isActiveLevel(MSG_LEVEL level) { + if (level <= getInstance().getLevel()) { + return true; + } + + return false; + } + + //// + MSG_LEVEL getLevel() { + return (MSG_LEVEL) log.getDebugLevel(); + } + + //// set level + void setLevel(MSG_LEVEL msgLevel) { + log.setDebugLevel(msgLevel); + } + + //// + // Starts a new message and specifies its level. + // One should note that setting the level outside of the actual + // message (i.e., first calling this operator and only then + // streaming the message) is not thread safe. + Logger& operator()(MSG_LEVEL msgLevel); + + + //// + // If a message was started with a low enough message level then + // given data will be printed to the log file + template + Logger& operator<<(const T& data); + + //// + // If a message was started with a low enough message level then + // given data will be printed to the log file + template + Logger& operator<<(T& data); + + //// + // The Logger can stream a function that accepts an ostream and + // returns one. + Logger& operator<<(std::ostream& (*__pf)(std::ostream&)); + + //// + ~Logger(); + +private: + + //// + static void init(); + + + //// + static Logger* instance; + + //// + DebugStream log; + + + //// + Logger(const std::string logFile, unsigned int logLevel); +}; + + +/************************************** + * Template Function Implementation * + **************************************/ + +template +Logger& Logger::operator<<(const T& data) { + log << data; + return *this; +} + + +template +Logger& Logger::operator<<(T& data) { + log << data; + return *this; +} + + + +#endif //_LOGGER_H diff --git a/model/CombinatorialAssembler/libs_gamb/Match.cc b/model/CombinatorialAssembler/libs_gamb/Match.cc new file mode 100644 index 0000000000000000000000000000000000000000..6a4afc6fcc4ff20dfe7965cdeedc329c728af6f3 --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/Match.cc @@ -0,0 +1,169 @@ +#include +#include "Match.h" +#include "macros.h" + +typedef Match::ParticleMatch ParticleMatch; +typedef Match::MatchList MatchList; +typedef MatchList::const_iterator const_iterator; +typedef MatchList::iterator iterator; + +ParticleMatch::ParticleMatch() + : model(0), scene(0), score(0.0), priority(0.0) {} +ParticleMatch::ParticleMatch(unsigned int modelParticle, + unsigned int sceneParticle, + float sc, float pri) : + model(modelParticle), scene(sceneParticle), score(sc), priority(pri) {} + + +std::ostream& operator<<(std::ostream& s, const ParticleMatch& pm) +{ + s.setf(std::ios::fixed, std::ios::floatfield); + s.setf(std::ios::right, std::ios::adjustfield); + s.precision(3); + + s.width(8); + s << pm.model; + s.width(8); + s << pm.scene; + s.width(10); + s << pm.score; + s.width(10); + s << pm.priority; + return s; +} + +Match::Match() : rms(0.0), sc(0.0), trans() {} + +Match::Match(const RigidTrans3& rt) : rms(0.0), sc(0.0), trans(rt) {} + +Match::Match(const Match& m) : + pairs(m.pairs), rms(m.rms), sc(m.sc), trans(m.trans) {} + +// The add method is complicated by the fact that the pair being added may +// clash with a pair that has already been inserted into the match. This +// is handled using the priority parameter. Pairs with higher priorities +// are preferred. +bool Match::add(const unsigned int modelParticle, + const unsigned int sceneParticle, + const float score, + const float priority) +{ + iterator sceneClash = pairs.end(); + iterator modelClash = pairs.end(); + bool canAdd = true; + + // Find if there are any clashes when adding the pair. + // Developer's Note: I've tried this class out using two binary search + // trees (STL map.h) instead of a vector. The idea was to speed up the + // search for clashes using binary trees. I tested the two implementations + // using random match additions to a set of matches. Besides being at + // least 4 times more costly in terms of memory the binary tree + // implementation also ran almost twice as slow! So, if you're going to + // try it remember I was there first. + for (iterator it = pairs.begin(); it != pairs.end() && canAdd; + it++) { + if ((*it).scene == sceneParticle) { // check for clashes with scene part. + sceneClash = it; + canAdd = (priority > (*sceneClash).priority); + if (modelClash != pairs.end()) break; // if both scene and model clashes found break + } + else if ((*it).model == modelParticle) { // check for model part. clash + modelClash = it; + canAdd = (priority > (*modelClash).priority); + if (sceneClash != pairs.end()) break; // if both scene and model clashes found break + } + } + + // if pair has higher priority or no clashes were found then add the new + // pair. if clashes occurred remove old pair or pairs from the data + // structures + if (canAdd) { + if (sceneClash != pairs.end()) { + // replace the scene clashed pair with the new pair. + *sceneClash = ParticleMatch(modelParticle, sceneParticle, + score, priority); + // if new pair clashed both with a model particle and a scene particle + // of different two pairs then then remove one pair. + if (modelClash != pairs.end()) pairs.erase(modelClash); + } + else if (modelClash != pairs.end()) + // only model particle clashed. replace the clashed pair with the + // new pair. + *modelClash = ParticleMatch(modelParticle, sceneParticle, + score, priority); + + else // no clashes. + pairs.push_back(ParticleMatch(modelParticle, sceneParticle, + score, priority)); + } + return canAdd; +} + +void Match::add(const unsigned int modelParticle, + const unsigned int sceneParticle, + const float score) +{ + pairs.push_back(ParticleMatch(modelParticle, sceneParticle, score, 0.0)); +} + + +void Match::add(const ParticleMatch& pm){ + pairs.push_back(pm); +} + +unsigned int Match::size() const +{ + return pairs.size(); +} + +float Match::rmsd() const +{ + return rms; +} + +float Match::totalScore() const +{ + return sc; +} + +const RigidTrans3& Match::rigidTrans() const +{ + return trans; +} + +int Match::sceneParticle(const unsigned int modelParticle) const +{ + for (const_iterator i = pairs.begin(); i != pairs.end(); i++) + if ((*i).model == modelParticle) + return (*i).scene; + return -1; +} + +int Match::modelParticle(const unsigned int sceneParticle) const +{ + for (const_iterator i = pairs.begin(); i != pairs.end(); i++) + if ((*i).scene == sceneParticle) + return (*i).model; + return -1; +} + +float Match::calculateTotalScore() +{ + sc = 0.0; + for (iterator it = pairs.begin(); it != pairs.end(); it++) + sc+= (*it).score; + return sc; +} + +std::ostream& operator<<(std::ostream& s, const Match& m) +{ + s << "#model #scene #score #priority" << std::endl; + for(std::vector::const_iterator it=m.pairs.begin() ; + it != m.pairs.end(); ++it) + s << *it << std::endl; + s << std::endl; + s << "#The transformation is:" << std::endl; + s << m.trans << std::endl; + s << "#score: " << m.sc << "#Rms: " << m.rms << std::endl; + return s; +} diff --git a/model/CombinatorialAssembler/libs_gamb/Match.h b/model/CombinatorialAssembler/libs_gamb/Match.h new file mode 100644 index 0000000000000000000000000000000000000000..b38a0fdd5788b95d44aa21d08ffb89f54523cb5c --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/Match.h @@ -0,0 +1,731 @@ +#ifndef _Match_h +#define _Match_h + +#include "RigidTrans3.h" +#include +#include + +/* +CLASS + Match + + Defines a match object used to build and refine matches between Molecule + type objects. Matches are build by matching pairs of Particles from each + Molecule. + +KEYWORDS + Match, Molecule, RMSD, linear, algebra, rigid + +AUTHORS + Meir Fuchs. (meirfux@math.tau.ac.il) + + Copyright: SAMBA group, Tel-Aviv Univ. Israel, 1997. + +CHANGES LOG +
    +
  • +
objects, model and scene this + program will randomly match pairs of particles from the molecules using the + inverse distance for both score and priority. Every 200 attempts to + add random pairs to the match the program recalculates the best linear + transformation. This process is repeated 1000 times for a total of 200,000 + attempted pair additions. + EXAMPLE + Match M; + for (int i=0; i<1000; i++) { + for (int j=0; j<200; j++) { + // randomly pick pair + unsigned int modelParticle = model.size()*drand48(); + unsigned int sceneParticle = scene.size()*drand48(); + // score is pair distance inverse. + float score = (1 / (1+(model[modelParticle] | scene[sceneParticle]))); + // add pair according to score + M.add(modelParticle, sceneParticle, score ,score); + } // j + M.calculateBestFit(model, scene); // every 200 random pairs recalc + cout << M.rigidTrans() << '\n'; // rig. trans. and print it. + } // i + + int matchingParticle; + for(int i=0; i= 0) + cout << i << '\t' << matchingParticle << '\n'; + END + + Comment: Generally, it is expected (By me at least) + that if a very large linear match infact exists between model and scene + (e.g. try model=scene) the calculated rigid transformation will + probablistically tend to such a match. + +*/ +class Match +{ +public: + + //// ParticleMatch is used by the class implementation to store matching + // pairs within an STL vector. + class ParticleMatch + { + public: + ParticleMatch(); + ParticleMatch(unsigned int model, unsigned int scene, + float sc, float pri); + + friend std::ostream& operator<<(std::ostream& s, const ParticleMatch& pm); + unsigned int model; + unsigned int scene; + float score; + float priority; + }; + + // A vector of ParticleMatch is used to store the matching pairs of + // particles with their score and priority + typedef std::vector MatchList; + + + // GROUP: Contructors + + //// Construct a new match. + Match(); + + //// Contruct a new Match object using the candidate rigid transformation + // used to match the two sets of points. This rigid transformation may be + // further enhanced after a set of matching pairs is added using the + // calculateBestFit method. + Match(const RigidTrans3& rt); + + //// Match copy contructor. Implicitely declared to make sure that all + // data referred to by the Match object is actually copied. + Match(const Match& m); + + // GROUP: Match construction and refinement methods. + + //// Add a match pair. The pair is defined using two integers (possibly + // indices into some array of objects), a match score and a priority. The + // match score defines how much matching the model particle to the scene + // particle contributes to the total match score. This score should + // reflect "how well the pair of particles fit together". A second + // parameter sent with the pair is their priority. Two particles may + // have a high score but matching one of the particles with a different + // particle might be "more correct". For example one might use inverse + // distance as a priority - prefering particles that are closer and use + // some bio-chemical properties for score (e.g. hydrophobicity, residue + // match using PAM scores etc.). + // The function returns true if the pair of particles was actually added. + bool add(const unsigned int modelParticle, const unsigned int sceneParticle, + const float score, + const float priority); + + //// This version of add does not use priority and does not check for double + // references to the same particles in the match list. The user may prefer + // to overlook doubly referenced particles in many cases. This method is + // also faster because no search is performed on the existing pairs. + void add(const unsigned int modelParticle, const unsigned int sceneParticle, + const float score = 1.0); + + //// Adds ParticleMatch object + void add(const ParticleMatch& pm); + + //// Calculate the total score of the match using the pair scores given + // for each pair in the match (See the add method). + float calculateTotalScore(); + + + // GROUP: Inspection methods. + + //// Returns the match size. + unsigned int size() const; + + //// After calculating the best linear transformation using + // calculateBestFit this method will return the RMSD of the match. + float rmsd() const; + + //// After calculating the total score of the match using calculateTotalScore + // this method will return the total score. + float totalScore() const; + + //// At any point, the rigid transformation currently used by the match will + // be returned. + const RigidTrans3& rigidTrans() const; + + //// Given a model particle returns the matching scene particle. If no such + // particle is found the method will return a negative value. + int sceneParticle(const unsigned int model) const; + + //// Given a scene particle returns the matching model particle. If no such + // particle is found the method will return a negative value. + int modelParticle(const unsigned int scene) const; + + //// Return information about the pair indexed by index in the match in + // the form of a ParticleMatch object reference. + const ParticleMatch& operator[](const unsigned int index) const{return pairs[index];} + + //// Return information about the pair indexed by index in the match in + // the form of a ParticleMatch object reference. + ParticleMatch& operator[](const unsigned int index){return pairs[index];} + + //// Calculate the best rigid transformation to minimize the inter-point + // RMSD using the pair scores as weights. The method accepts the two + // TMolecules from which the particles are taken from and from which it + // will get the particles positions. + // TMolecule should support operator[], like vector<>. + template + void calculateBestFit(const TMolecule& model, const TMolecule& scene); + + + //// + // Calculates the least-squares error between the two given + // TMolecules. A negative value is returned in case of an error. + //
+ // TMolecule is assumed to be a collection of elements with 3D + // coordinates. Thus, TMolecule should support operator[] that + // returns an element with a cast Vector3() opertator. Examples for + // TMolecule are: vector and Molecule.
+ // Author: Oranit Dror (oranit@tau.ac.il) + template + static float calculateLeastSquaresError(const TMolecule& model, const TMolecule& scene); + + //// + // Calculates the RMSD between the two given TMolecules. A negative + // is be returned in case of an error.
+ // TMolecule is assumed to be a collection of elements with 3D + // coordinates. Thus, TMolecule should support operator[] that + // returns an element with a cast Vector3() opertator. Examples for + // TMolecule are: vector and Molecule.
+ // Author: Oranit Dror (oranit@tau.ac.il) + template + static float calculateRMSD(const TMolecule& model, const TMolecule& scene); + + //// + // Sort the match list according to the given Comperator object. The + // Comperator object is a Binary Predicate that compares two + // objects, returning true if the first precedes the second. This + // predicate must satisfy the standard mathematical definition of a + // strict weak ordering. The precise requirements are stated in the + // STL StrictWeakOrdering documentation, but what they roughly mean + // is that the predicate has to behave the way that "less than" + // behaves: if a is less than b then b is not less than a, if a is + // less than b and b is less than c then a is less than c, and so + // on. + // Author: Oranit Dror (oranit@tau.ac.il) + template + void sortMatchList(const TComperator& comperator); + + //// + // Erase the particle pairs of the match list + void clear() { + pairs.clear(); + } + + // The rest of this class is left protected in order to allow match to + // evolve freely. + + friend std::ostream& operator<<(std::ostream& s, const Match& m); + +protected: + MatchList pairs; + + float rms; + float sc; + RigidTrans3 trans; + +}; + + +template +void Match::sortMatchList(const TComperator& comperator) { + sort(pairs.begin(), pairs.end(), comperator); +} + +template +float Match::calculateLeastSquaresError(const TMolecule& model, const TMolecule& scene) { + if(model.size() != scene.size()) + return -1; + + float leastSquaresError = 0; + for(unsigned int i = 0 ; i < model.size() ; i++) { + leastSquaresError += ((Vector3) model[i]).dist2((Vector3) scene[i]); + } + + return leastSquaresError; +} + + +template +float Match::calculateRMSD(const TMolecule& model, const TMolecule& scene) { + float leastSquaresError = calculateLeastSquaresError(model, scene); + + if (leastSquaresError < 0) { + return leastSquaresError; + } + + if (model.size() == 0) { + return 0.0; + } + + return sqrt(leastSquaresError/model.size()); +} + +template +void Match::calculateBestFit(const TMolecule &model, + const TMolecule &scene) +{ + /* Initialized data */ + + static double sqrt3 = 1.73205080756888; + static int ip[9] = { 1,2,4,2,3,5,4,5,6 }; + double* u = new double[9]; + double* t = new double[3]; + int n; + + /* System generated locals */ + int i__1; + double d__1, d__2, d__3; + static double equiv_5[6], equiv_11[6], equiv_14[3]; + + + /* Local variables */ + static double spur, a[9] /* was [3][3] */, b[9] /* was [3][3] + */, d__; +#define e (equiv_14) + static double g, h__; + static int i__, j, k, m, l; + static double p, r__[9] /* was [3][3] */; + static double sigma; + static double e0; +#define e1 (equiv_14) +#define e2 (equiv_14 + 1) +#define e3 (equiv_14 + 2) + static int m1, m2, m3; + static double sqrth, wc, xc[3], yc[3]; +#define rr (equiv_5) +#define ss (equiv_11) +#define rr1 (equiv_5) +#define rr2 (equiv_5 + 1) +#define rr3 (equiv_5 + 2) +#define rr4 (equiv_5 + 3) +#define rr5 (equiv_5 + 4) +#define rr6 (equiv_5 + 5) +#define ss1 (equiv_11) +#define ss2 (equiv_11 + 1) +#define ss3 (equiv_11 + 2) +#define ss4 (equiv_11 + 3) +#define ss5 (equiv_11 + 4) +#define ss6 (equiv_11 + 5) + static double cof, det, cth, sth; + +/* **** CALCULATES BEST ROTATION & TRANSLATION BETWEEN TWO VECTOR SETS */ +/* **** SUCH THAT U*X+T IS THE BEST APPROXIMATION TO Y. */ +/* **** THIS VERSION OF THE ALGORITHM IS OPTIMIZED FOR THREE-DIMENSIONAL +*/ +/* **** FLOAT VECTOR SPACE. */ +/* **** USE OF THIS ROUTINE IS RESTRICTED TO NON-PROFIT ACADEMIC */ +/* **** APPLICATIONS. */ +/* **** PLEASE REPORT ERRORS TO */ +/* **** PROGRAMMER: W.KABSCH MAX-PLANCK-INSTITUTE FOR MEDICAL RESEARCH +*/ +/* JAHNSTRASSE 29, 6900 HEIDELBERG, FRG. */ +/* **** REFERENCES: W.KABSCH ACTA CRYST.(1978).A34,827-828 */ +/* W.KABSCH ACTA CRYST.(1976).A32,922-923 */ + +/* W - W(M) IS WEIGHT FOR ATOM PAIR # M (GIVEN) +*/ +/* X - X(I,M) ARE COORDINATES OF ATOM # M IN SET X (GIVEN) +*/ +/* Y - Y(I,M) ARE COORDINATES OF ATOM # M IN SET Y (GIVEN) +*/ +/* N - N IS NUMBER OF ATOM PAIRS (GIVEN) +*/ +/* MODE - 0:CALCULATE RMS ONLY (GIVEN) +*/ +/* 1:CALCULATE RMS,U,T (TAKES LONGER) */ +/* RMS - SUM OF W*(UX+T-Y)**2 OVER ALL ATOM PAIRS (RESULT) +*/ +/* U - U(I,J) IS ROTATION MATRIX FOR BEST SUPERPOSITION (RESULT) +*/ +/* T - T(I) IS TRANSLATION VECTOR FOR BEST SUPERPOSITION (RESULT) +*/ +/* IER - 0:NO ERROR; -1:N WAS <2; -2:ILLEGAL WEIGHTS W(M) (RESULT) +*/ + +/* ----------------------------------------------------------------------- + */ + /* Parameter adjustments */ + --t; + u -= 4; + n = size(); + + /* Function Body */ + if (n < 3) { + return; + } + wc = 0.; + for (i__ = 1; i__ <= 3; ++i__) { + xc[i__ - 1] = 0.; +/* L1: */ + yc[i__ - 1] = 0.; + } +/* DIR$ NEXT SCALAR */ + i__1 = n; + for (m = 1; m <= i__1; ++m) { +/* L222: */ + if (pairs[m-1].score < (float)0.) { + return; + } + } + i__1 = n; + for (m = 1; m <= i__1; ++m) { + wc += pairs[m-1].score; + for (i__ = 1; i__ <= 3; ++i__) { + xc[i__ - 1] += pairs[m-1].score * scene[pairs[m-1].scene][i__-1]; +/* L2: */ + yc[i__ - 1] += pairs[m-1].score * model[pairs[m-1].model][i__-1]; + } + } + if (wc <= 0.) { + return; + } + for (i__ = 1; i__ <= 3; ++i__) { + xc[i__ - 1] /= wc; + yc[i__ - 1] /= wc; + for (j = 1; j <= 3; ++j) { +/* L3: */ + r__[i__ + j * 3 - 4] = 0.; + } + } + e0 = 0.; + i__1 = n; + for (m = 1; m <= i__1; ++m) { +/* DO 4 I=1,3 */ +/* Computing 2nd power */ + d__1 = scene[pairs[m-1].scene][0] - xc[0]; +/* Computing 2nd power */ + d__2 = model[pairs[m-1].model][0] - yc[0]; + e0 += pairs[m-1].score * (d__1 * d__1 + d__2 * d__2); + d__ = pairs[m-1].score * (model[pairs[m-1].model][0] - yc[0]); +/* DO 4 J=1,3 */ +/* 4 R(I,J)=R(I,J)+D*(X(J,M)-XC(J)) */ + r__[0] += d__ * (scene[pairs[m-1].scene][0] - xc[0]); + r__[3] += d__ * (scene[pairs[m-1].scene][1] - xc[1]); + r__[6] += d__ * (scene[pairs[m-1].scene][2] - xc[2]); +/* Computing 2nd power */ + d__1 = scene[pairs[m-1].scene][1] - xc[1]; +/* Computing 2nd power */ + d__2 = model[pairs[m-1].model][1] - yc[1]; + e0 += pairs[m-1].score * (d__1 * d__1 + d__2 * d__2); + d__ = pairs[m-1].score * (model[pairs[m-1].model][1] - yc[1]); + r__[1] += d__ * (scene[pairs[m-1].scene][0] - xc[0]); + r__[4] += d__ * (scene[pairs[m-1].scene][1] - xc[1]); + r__[7] += d__ * (scene[pairs[m-1].scene][2] - xc[2]); +/* Computing 2nd power */ + d__1 = scene[pairs[m-1].scene][2] - xc[2]; +/* Computing 2nd power */ + d__2 = model[pairs[m-1].model][2] - yc[2]; + e0 += pairs[m-1].score * (d__1 * d__1 + d__2 * d__2); + d__ = pairs[m-1].score * (model[pairs[m-1].model][2] - yc[2]); + r__[2] += d__ * (scene[pairs[m-1].scene][0] - xc[0]); + r__[5] += d__ * (scene[pairs[m-1].scene][1] - xc[1]); + r__[8] += d__ * (scene[pairs[m-1].scene][2] - xc[2]); +/* L4: */ + } +/* **** CALCULATE DETERMINANT OF R(I,J) */ + det = r__[0] * (r__[4] * r__[8] - r__[7] * r__[5]) - r__[3] * (r__[1] * + r__[8] - r__[7] * r__[2]) + r__[6] * (r__[1] * r__[5] - r__[4] * + r__[2]); + sigma = det; +/* **** FORM UPPER TRIANGLE OF TRANSPOSED(R)*R */ + m = 0; +/* DIR$ NEXT SCALAR */ + for (j = 1; j <= 3; ++j) { + i__1 = j; + for (i__ = 1; i__ <= i__1; ++i__) { + ++m; +/* L5: */ + rr[m - 1] = r__[i__ * 3 - 3] * r__[j * 3 - 3] + r__[i__ * 3 - 2] * + r__[j * 3 - 2] + r__[i__ * 3 - 1] * r__[j * 3 - 1]; + } + } +/* ***************** EIGENVALUES ***************************************** + */ +/* **** FORM CHARACTERISTIC CUBIC X**3-3*SPUR*X**2+3*COF*X-DET=0 */ + spur = (*rr1 + *rr3 + *rr6) / 3.; + cof = (*rr3 * *rr6 - *rr5 * *rr5 + *rr1 * *rr6 - *rr4 * *rr4 + *rr1 * * + rr3 - *rr2 * *rr2) / 3.; + det *= det; +/* **** REDUCE CUBIC TO STANDARD FORM Y**3-3HY+2G=0 BY PUTTING X=Y-SPUR */ + d__ = spur * spur; + h__ = d__ - cof; + g = spur * (cof * 1.5 - d__) - det * .5; +/* **** SOLVE CUBIC. ROOTS ARE E1,E2,E3 IN DECREASING ORDER */ + if (h__ <= d__ * 1e-9) { + goto L8; + } + sqrth = sqrt(h__); + d__ = -g / (h__ * sqrth); + if (d__ > .9999999) { + goto L6; + } + if (d__ < -.9999999) { + goto L7; + } +/* .....HANDLE CASE OF THREE DISTINCT ROOTS */ + d__ = acos(d__) / 3.; + cth = sqrth * cos(d__); + sth = sqrth * sqrt3 * sin(d__); + *e1 = spur + cth + cth; + *e2 = spur - cth + sth; + *e3 = spur - cth - sth; + if (*e3 < 0.) { + *e3 = 0.; + } + m1 = 3; + m2 = 1; + m3 = 2; + goto L10; +/* .....HANDLE SPECIAL CASE OF TWO IDENTICAL ROOTS */ +L6: + *e1 = spur + sqrth + sqrth; + *e2 = spur - sqrth; + *e3 = *e2; + m = 1; + m1 = 3; + m2 = 1; + m3 = 2; + goto L20; +L7: + *e1 = spur + sqrth; + *e2 = *e1; + *e3 = spur - sqrth - sqrth; + if (*e3 < 0.) { + *e3 = 0.; + } + m = 3; + m1 = 1; + m2 = 2; + m3 = 3; + goto L20; +/* .....HANDLE SPECIAL CASE OF 3 IDENTICAL ROOTS */ +L8: + *e1 = spur; + *e2 = spur; + *e3 = spur; + + goto L26; + +/* **************** EIGENVECTORS ***************************************** + */ +/* .....EIGENVECTORS IN CASE OF THREE DISTINCT ROOTS */ +/* DIR$ NEXT SCALAR */ +L10: + for (l = 1; l <= 2; ++l) { + d__ = e[l - 1]; + *ss1 = (d__ - *rr3) * (d__ - *rr6) - *rr5 * *rr5; + *ss2 = (d__ - *rr6) * *rr2 + *rr4 * *rr5; + *ss3 = (d__ - *rr1) * (d__ - *rr6) - *rr4 * *rr4; + *ss4 = (d__ - *rr3) * *rr4 + *rr2 * *rr5; + *ss5 = (d__ - *rr1) * *rr5 + *rr2 * *rr4; + *ss6 = (d__ - *rr1) * (d__ - *rr3) - *rr2 * *rr2; + j = 1; + if (tabs(*ss1) >= tabs(*ss3)) { + goto L12; + } + j = 2; + if (tabs(*ss3) >= tabs(*ss6)) { + goto L13; + } +L11: + j = 3; + goto L13; +L12: + if (tabs(*ss1) < tabs(*ss6)) { + goto L11; + } +L13: + d__ = 0.; + j = (j - 1) * 3; + for (i__ = 1; i__ <= 3; ++i__) { + k = ip[i__ + j - 1]; + a[i__ + l * 3 - 4] = ss[k - 1]; +/* L14: */ + d__ += ss[k - 1] * ss[k - 1]; + } + d__ = sqrt(d__); + for (i__ = 1; i__ <= 3; ++i__) { +/* L15: */ + a[i__ + l * 3 - 4] /= d__; + } + } +L16: + a[m1 * 3 - 3] = a[m2 * 3 - 2] * a[m3 * 3 - 1] - a[m3 * 3 - 2] * a[m2 * 3 + - 1]; + a[m1 * 3 - 2] = a[m2 * 3 - 1] * a[m3 * 3 - 3] - a[m3 * 3 - 1] * a[m2 * 3 + - 3]; + a[m1 * 3 - 1] = a[m2 * 3 - 3] * a[m3 * 3 - 2] - a[m3 * 3 - 3] * a[m2 * 3 + - 2]; + goto L30; +/* .....EIGENVECTORS IN CASE OF TWO DISTINCT ROOTS */ +L20: + p = 0.; +/* ccccccccccccccc H=SPUR+G */ + h__ = *e2; +/* DIR$ NEXT SCALAR */ + for (i__ = 1; i__ <= 3; ++i__) { + k = (i__ * i__ + i__) / 2; + d__ = (d__1 = rr[k - 1] - h__, tabs(d__1)); + if (d__ < p) { + goto L21; + } + j = i__; + p = d__; +L21: + ; + } + p = 0.; + d__ = 0.; + l = (j - 1) * 3; + for (i__ = 1; i__ <= 3; ++i__) { + k = ip[i__ + l - 1]; + a[i__ + 2] = 1.; + if (i__ != j) { + goto L22; + } + a[i__ + m * 3 - 4] = rr[k - 1] - h__; + goto L23; +L22: + a[i__ + m * 3 - 4] = rr[k - 1]; + p -= a[i__ + m * 3 - 4]; +L23: +/* Computing 2nd power */ + d__1 = a[i__ + m * 3 - 4]; + d__ += d__1 * d__1; + } + a[j + 2] = p / a[j + m * 3 - 4]; + d__ = sqrt(d__); +/* Computing 2nd power */ + d__1 = a[3]; +/* Computing 2nd power */ + d__2 = a[4]; +/* Computing 2nd power */ + d__3 = a[5]; + p = sqrt(d__1 * d__1 + d__2 * d__2 + d__3 * d__3); + for (i__ = 1; i__ <= 3; ++i__) { + a[i__ + 2] /= p; +/* L24: */ + a[i__ + m * 3 - 4] /= d__; + } + goto L16; +/* .....EIGENVECTORS IN CASE OF THREE IDENTICAL ROOTS */ +L26: + for (i__ = 1; i__ <= 3; ++i__) { + for (j = 1; j <= 3; ++j) { + d__ = 0.; + if (i__ == j) { + d__ = 1.; + } +/* L27: */ + a[i__ + j * 3 - 4] = d__; + } + } +/* **** CALCULATE ROTATION MATRIX */ +/* DIR$ NEXT SCALAR */ +L30: + for (l = 1; l <= 2; ++l) { + d__ = 0.; + for (i__ = 1; i__ <= 3; ++i__) { + b[i__ + l * 3 - 4] = r__[i__ - 1] * a[l * 3 - 3] + r__[i__ + 2] * + a[l * 3 - 2] + r__[i__ + 5] * a[l * 3 - 1]; +/* L31: */ +/* Computing 2nd power */ + d__1 = b[i__ + l * 3 - 4]; + d__ += d__1 * d__1; + } + d__ = sqrt(d__); + for (i__ = 1; i__ <= 3; ++i__) { +/* L32: */ + b[i__ + l * 3 - 4] /= d__; + } + } + b[6] = b[1] * b[5] - b[4] * b[2]; + b[7] = b[2] * b[3] - b[5] * b[0]; + b[8] = b[0] * b[4] - b[3] * b[1]; + for (i__ = 1; i__ <= 3; ++i__) { + for (j = 1; j <= 3; ++j) { +/* L33: */ + u[i__ + j * 3] = b[i__ - 1] * a[j - 1] + b[i__ + 2] * a[j + 2] + + b[i__ + 5] * a[j + 5]; + } + } +/* **** CALCULATE TRANSLATION VECTOR */ + for (i__ = 1; i__ <= 3; ++i__) { +/* L34: */ + t[i__] = yc[i__ - 1] - u[i__ + 3] * xc[0] - u[i__ + 6] * xc[1] - u[ + i__ + 9] * xc[2]; + } +/* **** CALCULATE RMS ERROR */ + d__ = sqrt(*e3); + if (sigma < (float)0.) { + d__ = -d__; + } + d__ = d__ + sqrt(*e2) + sqrt(*e1); + rms = (float)sqrt((d__1 = e0 - d__ - d__, tabs(d__1))/n); + + ++t; + u+= 4; + trans = RigidTrans3(Matrix3(Vector3((float)u[0], (float)u[3], (float)u[6]), + Vector3((float)u[1], (float)u[4], (float)u[7]), + Vector3((float)u[2], (float)u[5], (float)u[8])), + Vector3((float)t[0], (float)t[1], (float)t[2])); + delete[] u; + delete[] t; +/* **** NORMAL EXIT */ + +#undef ss6 +#undef ss5 +#undef ss4 +#undef ss3 +#undef ss2 +#undef ss1 +#undef rr6 +#undef rr5 +#undef rr4 +#undef rr3 +#undef rr2 +#undef rr1 +#undef ss +#undef rr +#undef e3 +#undef e2 +#undef e1 +#undef e +} + + +#endif diff --git a/model/CombinatorialAssembler/libs_gamb/Matrix3.cc b/model/CombinatorialAssembler/libs_gamb/Matrix3.cc new file mode 100644 index 0000000000000000000000000000000000000000..7086a009b217a92b3e55c0e84c7729f93b7c9309 --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/Matrix3.cc @@ -0,0 +1,178 @@ +#include "Matrix3.h" + +typedef Matrix3::real real; + +Matrix3::Matrix3(const real &d) +{ + vrow[0] = Vector3(d, 0, 0); + vrow[1] = Vector3(0, d, 0); + vrow[2] = Vector3(0, 0, d); +} + +Matrix3::Matrix3(const real &xr, const real &yr, const real &zr) +{ + real cx = cos(xr); real cy = cos(yr); real cz = cos(zr); + real sx = sin(xr); real sy = sin(yr); real sz = sin(zr); + vrow[0] = Vector3(cz*cy, -sy*sx*cz - sz*cx, -sy*cx*cz + sz*sx); + vrow[1] = Vector3(sz*cy, -sy*sx*sz + cx*cz, -sy*cx*sz - sx*cz); + vrow[2] = Vector3(sy, cy*sx, cy*cx ); +} + +Matrix3::Matrix3(const real &psi, const real &theta, const real &phi, const int &) +{ + real cpsi = cos(psi); real ctheta = cos(theta); real cphi = cos(phi); + real spsi = sin(psi); real stheta = sin(theta); real sphi = sin(phi); + vrow[0] = Vector3( cpsi*cphi - spsi*sphi*ctheta, cpsi*sphi + spsi*cphi*ctheta, stheta*spsi); + vrow[1] = Vector3(-spsi*cphi - cpsi*sphi*ctheta, -spsi*sphi + cpsi*cphi*ctheta, stheta*cpsi); + vrow[2] = Vector3( sphi*stheta, -cphi*stheta, ctheta); +} + +Matrix3::Matrix3(const real a11, const real a12, const real a13, + const real a21, const real a22, const real a23, + const real a31, const real a32, const real a33) { + vrow[0] = Vector3(a11, a12, a13); + vrow[1] = Vector3(a21, a22, a23); + vrow[2] = Vector3(a31, a32, a33); +} + +Matrix3::Matrix3(const real trn[9]) { + vrow[0] = Vector3(trn); + vrow[1] = Vector3(trn+3); + vrow[2] = Vector3(trn+6); +} + +Matrix3::Matrix3(const double trn[9]) { + vrow[0] = Vector3(trn); + vrow[1] = Vector3(trn+3); + vrow[2] = Vector3(trn+6); +} + +Matrix3::Matrix3(const real trn[3][3]) { + vrow[0] = Vector3(trn[0]); + vrow[1] = Vector3(trn[1]); + vrow[2] = Vector3(trn[2]); +} + +Matrix3::Matrix3(const double trn[3][3]) { + vrow[0] = Vector3(trn[0]); + vrow[1] = Vector3(trn[1]); + vrow[2] = Vector3(trn[2]); +} + +Matrix3::Matrix3(const Vector3 &diag) +{ + vrow[0] = Vector3(diag[0], 0, 0); + vrow[1] = Vector3(0, diag[1], 0); + vrow[2] = Vector3(0, 0, diag[2]); +} + +Matrix3::Matrix3(const Vector3 &v1, const Vector3 &v2) +{ + vrow[0] = v1[0] * v2; + vrow[1] = v1[1] * v2; + vrow[2] = v1[2] * v2; +} + + + + +real Matrix3::determinant() const +{ + return + element(1,1) * (element(2,2)*element(3,3) - element(2,3)*element(3,2)) + - element(1,2) * (element(2,1)*element(3,3) - element(2,3)*element(3,1)) + + element(1,3) * (element(2,1)*element(3,2) - element(2,2)*element(3,1)); +} + +Matrix3& Matrix3::operator*=(const real &m) +{ + vrow[0]*= m; + vrow[1]*= m; + vrow[2]*= m; + return *this; +} + +Matrix3& Matrix3::operator/=(const real &m) +{ + vrow[0]/= m; + vrow[1]/= m; + vrow[2]/= m; + return *this; +} + +Matrix3& Matrix3::operator+=(const Matrix3 &t) +{ + vrow[0]+= t.vrow[0]; + vrow[1]+= t.vrow[1]; + vrow[2]+= t.vrow[2]; + return *this; +} + +Matrix3& Matrix3::operator-=(const Matrix3 &t) +{ + vrow[0]-= t.vrow[0]; + vrow[1]-= t.vrow[1]; + vrow[2]-= t.vrow[2]; + return *this; +} + +Matrix3& Matrix3::operator*=(const Matrix3 &t) +{ + Vector3 c1 = t.column(1); + Vector3 c2 = t.column(2); + Vector3 c3 = t.column(3); + vrow[0] = Vector3(vrow[0]*c1, vrow[0]*c2, vrow[0]*c3); + vrow[1] = Vector3(vrow[1]*c1, vrow[1]*c2, vrow[1]*c3); + vrow[2] = Vector3(vrow[2]*c1, vrow[2]*c2, vrow[2]*c3); + return *this; +} + +Vector3 Matrix3::eigenvalues() const +{ + const real pi23 = (const real)2.0943951023; // pi*2/3 + real a0 = -determinant(); + real a1 = (element(1,1)*element(2,2) - element(1,2)*element(2,1) + + element(1,1)*element(3,3) - element(1,3)*element(3,1) + + element(2,2)*element(3,3) - element(2,3)*element(3,2)); + real a2 = -trace(); + real b1 = (3*a1-sqr(a2))/3; + if (b1 > 0) return Vector3(); // Error! no eigenvalues. + real b0 = (2*a2*a2*a2 - 9*a2*a1 + 27*a0)/27; + real d2 = (b1*b1*b1/27 + sqr(b0)/4); + if (d2 > 0) return Vector3(); // Error! no eignevalues. + real phi = acos(-(b0/2)*pow(-3/b1, (real)1.5))/3; + real sqrtb1 = 2*sqrt(-b1/3); + a2 /= 3; + return Vector3(sqrtb1*cos(phi ) - a2, + sqrtb1*cos(phi+2*pi23) - a2, + sqrtb1*cos(phi+pi23 ) - a2); +} + +Matrix3 Matrix3::adjoint() const +{ + return Matrix3(Vector3(element(2,2)*element(3,3) - element(2,3)*element(3,2), + element(1,3)*element(3,2) - element(1,2)*element(3,3), + element(1,2)*element(2,3) - element(1,3)*element(2,2) + ), + Vector3(element(2,3)*element(3,1) - element(2,1)*element(3,3), + element(1,1)*element(3,3) - element(1,3)*element(3,1), + element(1,3)*element(2,1) - element(1,1)*element(2,3) + ), + Vector3(element(2,1)*element(3,2) - element(2,2)*element(3,1), + element(1,2)*element(3,1) - element(1,1)*element(3,2), + element(1,1)*element(2,2) - element(1,2)*element(2,1) + ) + ); +} + +/* if B is the inverse of A then + Bij = Aji / |A| where Aji is the determinant of A without row i and col j */ +Matrix3 operator!(const Matrix3 &t1) +{ + + real det = t1.determinant(); + if (det == 0) return Matrix3(); + Matrix3 inv = t1.adjoint(); + inv *= ((real)1/det); + return inv; +} diff --git a/model/CombinatorialAssembler/libs_gamb/Matrix3.h b/model/CombinatorialAssembler/libs_gamb/Matrix3.h new file mode 100644 index 0000000000000000000000000000000000000000..625bf29e313e3fb3e74df5f6a754b203dca2e401 --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/Matrix3.h @@ -0,0 +1,307 @@ +#ifndef _Matrix3_h + +#define _Matrix3_h + +#include +#include + +#include "macros.h" +#include "numerics.h" +#include "Vector3.h" + +/* +CLASS + Matrix3 + + Defines a 3x3 matrix with a load of constructors, operators, and methods. + +KEYWORDS + linear, algebra, matrix, real, vector, rotation, determinant, adjoint, + inverse, angle, vector + +AUTHORS + Meir Fuchs. (meirfux@math.tau.ac.il) + + Copyright: SAMBA group, Tel-Aviv Univ. Israel, 1997. + +CHANGES LOG +
    +
  • Added support for ZXZ rotation: rotZXZ() function that returns rotational +angles for this type of rotation and constructor that can reconstruct matrix +given rotational angles for ZXZ. 21.7.2003 Dina +<\LI> +
+ +GOALS + Matrix3 was written in order to define a 3x3 matrix. The class was written + so that vector-matrix operations in 3D will be performed with highest + efficiency. The Matrix3 class can be implemented without any loops since + the dimension is small and known. + +USAGE + Matrix3 hosts a set of constructors, operators and inspection methods. Below + are just several examples of how one may use the class. + EXAMPLE + Matrix3 A(Vector3(1, 2, 3), // construct matric out of 3 vector3 + Vector3(1, 0, 1), + Vector3(4, 5, 6)); + Matrix3 B=A; + B.transpose(); + C=A+B; // + operator. C is symmetric. + Vector3 v = C.eigenvalues(); // eigenvalues. + Vector3 u = C*v; // multiplication by vector. + Matrix3 D(v); // D has v in the diagonal. 0's elsewhere. + Matrix3 E(v,u); // E=v*u' + END + A special note should be made regarding the rotational matrix constructors + and methods. No special rotational matrix class was defined for reasons of + conciseness and efficiency. Matrix3 hosts constructors for rotational + matrices and inspectors for inspecting angular parameters. These inspection + methods are probably meaningless when invoked on non-rotational matrices, but + will not cause run-time errors and their likes. + EXAMPLE + Matrix3 A(pi, pi, pi); // rotational parameters for x-y-z. We get I. + Matrix3 B(0, 0, 0 ); // again we get I. + if (A.rotX() == 0) cout << "ok\n"; + END +*/ +class Matrix3 { + +public: + // GROUP: define real type + + //// real is defined using the real defined in Vector3 class. By default + // this is of type float. For example , changing Vector3::real's defintion + // to double will turn the gamb library's vector operations to double + // precision. + typedef Vector3::real real; + + // GROUP: Constructors + + //// Default constructor: 0 matrix. + Matrix3() { + vrow[0] = vrow[1] = vrow[2] = Vector3(0, 0, 0); + } + + //// Constructs a Matrix3 with d's in the diagonal and 0's else where. + explicit Matrix3(const real &d); + + //// Creates a rotational Matrix3. given rotational values around the + // x axis (xr), around the y axis (yr) and around the z axis (zr) a + // linear transformation that rotates space in the order x-y-z is created. + // Rotation in this order is the default representation for the gamb++ + // library rotational transformations. + Matrix3(const real &xr, const real &yr, const real &zr); + + //// Creates a rotational Matrix3. given rotational values around the + // z axis (psi), around the x axis (theta) and around the z axis (phi), a + // linear transformation that rotates space in the order z-x-z is created. + // Rotation in this order is NOT the default representation for the gamb++ + // library rotational transformations. The int zxz specifies that the + // rotation order is ZXZ and not default XYZ. + Matrix3(const real &psi, const real &theta, const real &phi, const int &zxz); + + + //// Initializes a Matrix3 object by stating all the matrix elements + // explicitly + Matrix3(const real a11, const real a12, const real a13, + const real a21, const real a22, const real a23, + const real a31, const real a32, const real a33); + + //// Creates a Matrix3 out of a real type array with first 3 elements + // for the 1st row, 2nd 3 elements for second row and last 3 elements for + // the 3rd row. + explicit Matrix3(const real trn[9]); + + //// Creates a Matrix3 out of a double type array with first 3 elements + // for the 1st row, 2nd 3 elements for second row and last 3 elements for + // the 3rd row. + explicit Matrix3(const double trn[9]); + + //// Use a 3x3 real 2D array to initialize matrix. + explicit Matrix3(const real trn[3][3]); + + //// Use a 3x3 double 2D array to initialize matrix. + explicit Matrix3(const double trn[3][3]); + + //// Constructs a Matrix3 using the 3 given row vectors. For construction + // with column vectors use this constructor and then the transpose method. + Matrix3(const Vector3 &v1, const Vector3 &v2, const Vector3 &v3) { + vrow[0] = Vector3(v1); + vrow[1] = Vector3(v2); + vrow[2] = Vector3(v3); + } + + //// Constructs a Matrix3 with Vector3 diag's values in the matrix diagonal + // and 0's elsewhere. + explicit Matrix3(const Vector3 &diag); + + //// Creates the matrix v1*v2' from the two given Vector3s + Matrix3(const Vector3 &v1, const Vector3 &v2); + + // GROUP: Inspection methods and properties. + + //// Returns the requested row of the matrix. r must be within 0..2. If + // not run-time errors may be caused. Notice that a const reference is + // returned so that this method will perform better than the row method + // and should be used when const rows are good enough. + const Vector3& operator[](const unsigned short r) const { + return vrow[r]; + } + + //// Returns a vector matching the r'th row of the matrix. r must be in + // the range 1..3 or run time errors may occur. + Vector3 row(const unsigned short r) const { + return Vector3(vrow[r-1]); + } + + //// Returns a vector matching the col'th column of the matrix. col must + // be in the range of 1..3 or run time errors may occur. + Vector3 column(const unsigned short col) const { + return Vector3(vrow[0].x(col), vrow[1].x(col), vrow[2].x(col)); + } + + //// Returns a Vector3 with eigenvalues of Matrix3. If eigenvalues are not + // real (i.e. complex) the function will return the 0 Vector3. So if the + // Matrix3 is not a 0-matrix and the function returned a 0-vector the + // Matrix's eigenvalues are not real. + Vector3 eigenvalues() const; + + //// Returns the numerical value at position row, col in the 3X3 matrix + // row, col should be within the range of 1..3 or run time errors may occur. + real element(const unsigned short r, const unsigned short col) const { + return vrow[r-1][col-1]; + } + + //// Returns the rotation around the x-axis for a rotational matrix assuming + // rotations are performed in the order of x-y-z. + // Note that results are meaningless if Matrix3 is not a rotational matrix. + real rotX() const { + return atan2(element(3,2), element(3,3)); + } + + //// Returns the rotation around the y-axis for a rotational matrix assuming + // rotations are performed in the order of x-y-z. + // Note that results are meaningless if Matrix3 is not a rotational matrix. + real rotY() const { + return atan2(element(3,1), sqrt(sqr(element(2,1))+sqr(element(1,1)))); + } + + //// Returns the rotation around the z-axis for a rotational matrix assuming + // rotations are performed in the order of x-y-z. + // Note that results are meaningless if Matrix3 is not a rotational matrix. + real rotZ() const { + return atan2(element(2,1), element(1,1)); + } + + //// Returns 3 rotation angles assuming + // rotations are performed in the order of x-y-z. + // Note that results are meaningless if Matrix3 is not a rotational matrix. + Vector3 rot() const { + return Vector3(rotX(), rotY(), rotZ()); + } + + //// Returns 3 rotation angles assuming + // rotations are performed in the order of z-x-z. + // Note that results are meaningless if Matrix3 is not a rotational matrix. + Vector3 rotZXZ() const { + real psi = atan2(element(1,3), element(2,3)); + real theta = atan2(sqrt(sqr(element(1,3))+sqr(element(2,3))), element(3,3)); + real phi = atan2(element(3,1), -element(3,2)); + return Vector3(psi, theta, phi); + } + + //// Returns the determinant of the matrix + real determinant() const; + + //// Returns the trace of the matrix + real trace() const { + return element(1,1) + element(2,2) + element(3,3); + } + + //// Returns the adjoint matrix. + Matrix3 adjoint() const; + + // GROUP: Operators + + //// Transposes the given matrix. + void transpose(){ + real temp12, temp13, temp23; + + temp12 = element(1,2); + temp13 = element(1,3); + temp23 = element(2,3); + vrow[0] = Vector3(element(1,1), element(2,1), element(3,1)); + vrow[1] = Vector3(temp12, element(2,2), element(3,2)); + vrow[2] = Vector3(temp13, temp23, element(3,3)); + } + + //// Returns the matrix transpose. + Matrix3 transposeMatrix() const{ + return Matrix3(Vector3(vrow[0][0], vrow[1][0], vrow[2][0]), + Vector3(vrow[0][1], vrow[1][1], vrow[2][1]), + Vector3(vrow[0][2], vrow[1][2], vrow[2][2])); + } + + //// In place addition of given matrice's members. + Matrix3& operator+=(const Matrix3&); + + //// In-place subtraction of give matrice's members. + Matrix3& operator-=(const Matrix3&); + + //// Addition of two matrices. + friend Matrix3 operator+(const Matrix3& t1, const Matrix3& t2) { + return Matrix3(t1.vrow[0]+t2.vrow[0], t1.vrow[1]+t2.vrow[1], + t1.vrow[2]+t2.vrow[2]); + } + + //// Substraction of two matrices + friend Matrix3 operator-(const Matrix3& t1, const Matrix3& t2) { + return Matrix3(t1.vrow[0]-t2.vrow[0], t1.vrow[1]-t2.vrow[1], + t1.vrow[2]-t2.vrow[2]); + } + + //// In place multiplication with given matrix. + Matrix3& operator*=(const Matrix3 &); + + //// Matrix multiplication of two matrices. + friend Matrix3 operator*(const Matrix3& t1, const Matrix3& t2) { + Vector3 c1 = t2.column(1); + Vector3 c2 = t2.column(2); + Vector3 c3 = t2.column(3); + return Matrix3(Vector3(t1.vrow[0]*c1, t1.vrow[0]*c2, t1.vrow[0]*c3), + Vector3(t1.vrow[1]*c1, t1.vrow[1]*c2, t1.vrow[1]*c3), + Vector3(t1.vrow[2]*c1, t1.vrow[2]*c2, t1.vrow[2]*c3)); + } + + //// In-place matrix mutiplication by real scalar. + Matrix3& operator*=(const real &m); + + //// In-place matrix division by real scalar. + Matrix3& operator/=(const real &m); + + //// Matrix mutiplication by real scalar. + friend Matrix3 operator*(const Matrix3& t, const real &m) { + return Matrix3(m*t.vrow[0], m*t.vrow[1], m*t.vrow[2]); + } + + //// Matrix inverse. If matrix inverse is un-defined a 0-matrix is returned + friend Matrix3 operator!(const Matrix3&); + + //// Matrix by vector multiplication. + friend Vector3 operator*(const Matrix3& t, const Vector3& v) { + return Vector3(t.vrow[0]*v, t.vrow[1]*v, t.vrow[2]*v); + } + + //// Default Matrix output. 3 row vectors are output using Vector3's << + // operator with new-lines between rows. + friend std::ostream& operator<<(std::ostream& s, const Matrix3 &t) { + return (s << t.vrow[0] << '\n' << t.vrow[1] << '\n' << t.vrow[2] << '\n'); + } + + +private: + Vector3 vrow[3]; /* 3 columns of the matrix are held in 3 vectors */ +}; + +#endif diff --git a/model/CombinatorialAssembler/libs_gamb/Molecule.h b/model/CombinatorialAssembler/libs_gamb/Molecule.h new file mode 100644 index 0000000000000000000000000000000000000000..4e7fef878879c8ceb5d37d7684e32d7dbf4547cf --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/Molecule.h @@ -0,0 +1,614 @@ +#ifndef _Molecule_h +#define _Molecule_h + +#include +#include +#include +#include +#include "PDB.h" +#include "CIF.h" +#include "RigidTrans3.h" +// #include "Line.h" + +template +/* +CLASS + Molecule + + Template defines a collection of Particles that make up a Molecule. + Among others class hosts a PDB file readr and linear algebra operators. + +KEYWORDS + particle, molecule, atom, vector, linear, rigid, matrix, centroid, PDB, + file, rotation, translation + +AUTHORS + Meir Fuchs. (meirfux@math.tau.ac.il) and Zipi Fligelman (zipo@math.tau.ac.il) + Copyright: SAMBA group, Tel-Aviv Univ. Israel, 1997-9. + +GOALS + Serves as a container to a set of similarly typed class Particle descendants. + The Molecule container hosts all methods of the STL vector container. It + allows linear transfomrations to be applied to all Particles in the molecule. + A PDB reader, enabling selected atoms to be read off a PDB format file is + also supplied. + +USAGE + Construction is done using the default constructor. An empty Molecule object + is returned. + + The Molecule may be built using the add method or using any other method + supplied by the STL vector. Alternatively Particles may be added from a PDB + format file using the readPDBfile method. This method may recieve a + function object which will determine which of the atoms are loaded into + memory. + + Matrices, rigid transformations, vector additions may be applied to all + particles simultaneously. See the operators. + + The following is an example of using a Molecule object to read in + all C-alpha atom from a PDB file and print their coordinates after a + rigid-transformation is applied to all the particles. + EXAMPLE + class CAlphaSelector : public PDB::Selector + { // This class defines a selector that will pick only C-alpha atoms. + public: + bool operator()(const char *PDBrec) const { + const char *type = PDB::atomType(PDBrec); + return (*(type+1) == 'C' && *(type+2) == 'A'); + } + }; + + main() { + Molecule M; + ifstream pdb("pdb1tpo.ent"); + M.readPDBfile(pdb, CAlphaSelector()); + pdb.close(); + RigidTrans3 rt(Whatever); + M*= rt; + for (Molecule::iterator i= M.begin(); i != M.end(); i++) + cout << *i << '\n'; + + } + END + Note that Molecule has 2 base classes. The MoleculeBase class is a virtual + class that has a sole virtual method - the () operator. This class may be + used to pass Molecule objects of unknown type T into functions that + access Molecule vector positions only. This tool is used in the Match + class. + +EXAMPLE + main() { + Molecule mol; + Molecule antibody; + + ifstream pdb("complex.pdb"); + mol.readPDBfile(pdb); + pdb.close(); + + Molecule::ChainSelector selectorAntibody("LH"); + Molecule::ChainSelector selectorAntigen("A"); + + mol.select(selectorAntibody,antibody); + mol.select(selectorAntigen); + } + END + + Note: + If the complex contained chains L,H,A than + after the first select mol contains L,H,A and antibody contains L,H + after the second select mol contains A. +*/ +class Molecule : + public std::vector { +public: + // GROUP: Constructors + + //// Default constructor - empty molecule. + Molecule() : std::vector() {} + + virtual ~Molecule() {} + + // GROUP: Adding Particles. + + // Reads all ATOM records from the given PDB file according to the + // given atom selector and returns the number of read atoms (a + // negative value is returned in case of an error).
The default + // atom selector is PDB::Selector that selects all ATOM records. To + // write a different selection function one has to define a + // descendant of PDB::Selector() and redefine the 'operator()(const + // char* PDBrec) const' function to operate as desired. + int readPDBfile(const std::string& pdbFile, const PDB::Selector& selector = PDB::Selector()); + + //// + // Reads all ATOM records from the given PDB file according to the + // given atom selector and returns the number of read atoms (a + // negative value is returned in case of an error).
The default + // atom selector is PDB::Selector that selects all records. To write + // a different selection function one has to define a descendant of + // PDB::Selector() and redefine the 'operator()(const char* PDBrec) + // const' function to operate as desired. + int readPDBfile(std::istream& PDBstream, const PDB::Selector& selector = PDB::Selector()); + + // read single MODEL + int readModelFromPDBfile(std::istream &PDBstream, const PDB::Selector& selector = PDB::Selector()); + + //// readHetAtomsFromPDBfile accepts an input stream (file or cin) a + // PDB file format. Only HETATM records are read. + int readHetAtomsFromPDBfile(std::istream& PDBstream, const PDB::Selector& selector = PDB::Selector()); + + //// + // Reads all ATOM and HETATM records from the given PDB file + // according to the given atom selector and returns the number of + // read atoms (a negative value is returned in case of an + // error).
The default atom selector is PDB::Selector that + // selects all records. To write a different selection function one + // has to define a descendant of PDB::Selector() and redefine the + // 'operator()(const char* PDBrec) const' function to operate as + // desired.
Note that to filter out hydrogen, water molecules + // or anything else, use the PDB::WaterHydrogenUnSelector + int readAllPDBfile(const std::string& pdbFile, const PDB::Selector& selector = PDB::Selector()); + + //// + // Reads all ATOM and HETATM records from the given PDB file + // according to the given atom selector and returns the number of + // read atoms (a negative value is returned in case of an + // error).
The default atom selector is PDB::Selector that + // selects all records. To write a different selection function one + // has to define a descendant of PDB::Selector() and redefine the + // 'operator()(const char* PDBrec) const' function to operate as + // desired.
Note that to filter out hydrogen, water molecules + // or anything else, use the PDB::WaterHydrogenUnSelector + int readAllPDBfile(std::istream& PDBstream, const PDB::Selector& selector = PDB::Selector()); + + //// Enables to create selectors that can operate on a Molecule + //after the Molecule object was created. For example a pdb file containing + //several chains can be read only once to create a Molecule. + //Each chain would be selected into a new Molecule object. + //The selector can be operated on a Molecule using the select function. + class Selector { + public: + virtual bool operator()(const ParticleT*) const { return true;} + virtual ~Selector() {} + }; + + ////Selects particles from a Molecule according to their chainId. + class ChainSelector : public Selector { + public: + ChainSelector(const std::string &chainID): chains(chainID) {} + bool operator()(const ParticleT* particle) const { + for (int i=0; i< (int)chains.length(); i++) + if (particle->chainId() == chains[i]) + return true; + return false; + } + private: + std::string chains; + }; + + ////Selects particles from a Molecule, which are not water or hydrogens atoms. + class WaterHydrogenUnSelector : public Selector { + public: + bool operator()(const ParticleT* particle) const { + if (!(particle->isWater() || particle->isH())) + return true; + return false; + } + }; + + ////Selects CA atoms Molecule + class CaSelector : public Selector { + public: + bool operator()(const ParticleT* particle) const { + if (particle->isCA()) + return true; + return false; + } + }; + + ////Selects backbone atoms Molecule + class BackboneSelector : public Selector { + public: + bool operator()(const ParticleT* particle) const { + if (particle->isBackbone()) + return true; + return false; + } + }; + + //// Excludes particles that + //are not selected by the Selector. The operating Molecule object may + //be changed as a result of this function. + int select (const Selector& selector); + + ////Adds particles that + //are selected by the Selector to the input Molecule. + //The operating Molecule object is NOT changed as a result of this function. + int select (const Selector& selector, Molecule& m) const; + + //// Shorthand for vector push_back. Adds a new Particle at the end of the + // Particle array. + void add(const ParticleT& p); + + //// + // Return a triple of the positions of the most distant atoms + std::vector getDiameterTriple() const; + + // GROUP: Inspection. + + //// The () operator returns a Particle's position according to index. + Vector3 operator()(unsigned int particle) const; + + //// Returns centroid of all particle positions. + Vector3 centroid() const; + + // GROUP: Operators. + + //// Transforms molecule using a linear transformation of type Matrix3. + void transform(const Matrix3 <); + + //// Moves all atom coordinates by given vector3 move. + inline void translate(const Vector3 &move); + + //// Applies a rigid transformation on all molecule's Particles. + void rigidTrans(const RigidTrans3 &rt); + + //// Calculates the principal components of the molecules particles. + // Returns the axis system along which the covariances are 0. Also + // returns the variance along each axis. + //void getPrincipalComponents(Matrix3& axes, Vector3& variance); + + //// Concating two molecules together molecule m is added at the + // end of *this molecule + void concat(const Molecule& m); + + //// Splitting a molecule on index i all the particles + // from 0-i (included) will stay in this molecule + // the other particles : i+1-end-of-molecule are returned + // as a different molecule + Molecule splitOnIndex(const int i); + + //// Moves all atom coordinates by given vector3 move. + Molecule& operator+=(const Vector3 &v); + + //// Applies a linear transformation on all protein coordinates. + Molecule& operator*=(const Matrix3 <); + + //// Applies a rigid transformation on all atom coordinates. + Molecule& operator*=(const RigidTrans3 &rt); + + //// Equality operator. + Molecule& operator=(const Molecule& m); + + //// Output operator assuming the ParticleT class has its own + // overload of the output operator. + template + friend std::ostream& operator<< (std::ostream& s, const Molecule& m); + + void output2cif(std::ostream& s, int modelNum=1) const; + +private: + typedef std::vector BaseC; + typedef typename BaseC::iterator base_iterator; + typedef typename BaseC::const_iterator base_const_iterator; +}; + + +template +int Molecule::readPDBfile(std::istream &PDBstream, + const PDB::Selector& selector) +{ + bool cif = CIF::readAtomSiteTable(PDBstream); // check if CIF + PDBstream.clear(); + PDBstream.seekg(0); + + while (!PDBstream.eof()) { + std::string line; + getline(PDBstream, line); + // check that line is an ATOM rec and that selector accepts line. + // if this is the case construct a new Particle using line and add the + // Particle to the Molecule. + if(cif) { + if(CIF::isATOMrec(line)) { + std::stringstream os; + os << ParticleT(line, true); + if(selector(os.str().c_str())) { + add(ParticleT(line, true)); + } + } + } else { + if (PDB::isATOMrec(line.c_str()) && selector(line.c_str())) { + add(ParticleT(line)); + } + } + } + + return std::vector::size(); +} + +template +int Molecule::readModelFromPDBfile(std::istream &PDBstream, + const PDB::Selector& selector) +{ + while (!PDBstream.eof()) { + std::string line; + getline(PDBstream, line); + // check that line is an ATOM rec and that selector accepts line. + // if this is the case construct a new Particle using line and add the + // Particle to the Molecule. + if (PDB::isATOMrec(line.c_str()) && selector(line.c_str())) { + add(ParticleT(line.c_str())); + } + // finish if ENDMDL + if (PDB::isENDMDLrec(line)) break; + } + + return std::vector::size(); +} + +template +int Molecule::readPDBfile(const std::string& pdbFile, + const PDB::Selector& selector) { + std::ifstream pdbStream(pdbFile.c_str()); + + if (!pdbStream) { + return -1; + } + + unsigned int numOfAtoms = readPDBfile(pdbStream, selector); + pdbStream.close(); + return numOfAtoms; +} + + +template +int Molecule::readHetAtomsFromPDBfile(std::istream& PDBstream, + const PDB::Selector& selector) +{ + bool cif = CIF::readAtomSiteTable(PDBstream); // check if CIF + PDBstream.clear(); + PDBstream.seekg(0); + + while (!PDBstream.eof()) { + std::string line; + getline(PDBstream, line); + // check that line is an ATOM rec and that selector accepts line. + // if this is the case construct a new Particle using line and add the + // Particle to the Molecule. + if(cif) { + if(CIF::isHETATMrec(line)) { + std::stringstream os; + os << ParticleT(line, true); + if(selector(os.str().c_str())) { + add(ParticleT(line, true)); + } + } + } else { + if (PDB::isHETATMrec(line.c_str()) && selector(line.c_str())) { + add(ParticleT(line)); + } + } + } + + return std::vector::size(); +} + +template +int Molecule::readAllPDBfile(std::istream& PDBstream, + const PDB::Selector& selector) +{ + bool cif = CIF::readAtomSiteTable(PDBstream); // check if CIF + PDBstream.clear(); + PDBstream.seekg(0); + + std::string line; + while (!PDBstream.eof()) { + getline(PDBstream, line); + // check that line is an HETATM or ATOM rec and that selector accepts line. + // if this is the case construct a new Particle using line and add the + // Particle to the Molecule. + if(cif) { + if((CIF::isATOMrec(line) || CIF::isHETATMrec(line))) { + std::stringstream os; + os << ParticleT(line, true); + if(selector(os.str().c_str())) { + add(ParticleT(line, true)); + } + } + } else { + if((PDB::isATOMrec(line) || PDB::isHETATMrec(line)) && selector(line.c_str())) { + add(ParticleT(line)); + } + } + } + return std::vector::size(); +} + + +template +int Molecule::readAllPDBfile(const std::string& pdbFile, + const PDB::Selector& selector) { + std::ifstream pdbStream(pdbFile.c_str()); + + if (!pdbStream) { + return -1; + } + + unsigned int numOfAtoms = readAllPDBfile(pdbStream, selector); + pdbStream.close(); + return numOfAtoms; +} + +template +int Molecule::select(const Selector& selector){ + Molecule m; + for (base_const_iterator it= std::vector::begin(); it != std::vector::end(); ++it) { + if (selector (&(*it))) + m.add( (*it) ); + } + *this=m; + return std::vector::size(); +} + +template +int Molecule::select(const Selector& selector, Molecule& m)const{ + for (base_const_iterator it=std::vector::begin(); it!=std::vector::end(); ++it) { + if (selector (&(*it))) + m.add( (*it) ); + } + return m.size(); +} + +template +void Molecule::add(const ParticleT& p) +{ + this->push_back(p); +} +/* +template +std::vector Molecule::getDiameterTriple() const{ + std::vector triple(3); + + float maxDist2 = 0; + for (unsigned int i = 0 ; i < this->size() ; i++) { + for (unsigned int j = 0 ; j < this->size() ;j++) { + float dist2 = (*this)(i).dist2((*this)(j)); + + if (dist2 > maxDist2) { + maxDist2 = dist2; + triple[0] = (*this)(i); + triple[1] = (*this)(j); + } + } + } + + float maxDist = 0; + for (unsigned int i = 0 ; i < this->size() ; i++) { + Line line(triple[0], triple[1]); + float dist = line.distance(operator()(i)); + + if (dist > maxDist) { + maxDist = dist; + triple[2] = (*this)(i); + } + } + + return triple; +} +*/ +template +Vector3 Molecule::operator()(unsigned int particle) const +{ + return(*(this->begin()+particle)).position(); +} + +template +Vector3 Molecule::centroid() const +{ + Vector3 sum; + for (base_const_iterator i = this->begin(); i != this->end(); ++i) + sum+= *i; + sum /= this->size(); + return sum; +} + +template +void Molecule::transform(const Matrix3& lt) +{ + for (base_iterator i=this->begin(); i!=this->end(); ++i) (*i)*=lt; +} + +template +void Molecule::translate(const Vector3 &move) +{ + for (base_iterator i=this->begin(); i!=this->end(); ++i) (*i)+=move; +} + +template +void Molecule::rigidTrans(const RigidTrans3& rt) +{ + for (base_iterator i=this->begin(); i!=this->end(); ++i) (*i)*=rt; +} + +// template +// void Molecule::getPrincipalComponents(Matrix3& axes, +// Vector3& variance) +// { +// Vector3 mean = centroid(); +// Matrix3 cov; +// for(base_const_iterator i= begin(); i!= end(); ++i) { +// Vector3 v = (*i).position() - mean; +// cov+= Matrix3(v, v); +// } +// cov/= size(); +// Matrix3 dummy; +// cov.svd(dummy, variance, axes); +// } + +template +void Molecule::concat(const Molecule& m) +{ + for (base_const_iterator i = m.begin(); i!= m.end(); ++i) + this->push_back(*i); +} + +template +Molecule Molecule::splitOnIndex(const int i) +{ + if (i > this->size()) + return Molecule(); + Molecule mol; + for (base_iterator it = this->begin()+i; it != this->end(); ++it) + mol.push_back(*it); + erase(this->begin()+i, this->end()); + return mol; +} + +template +Molecule& Molecule::operator+=(const Vector3 &v) +{ + translate(v); + return *this; +} + +template +Molecule& Molecule::operator*=(const Matrix3 <) +{ + transform(lt); + return *this; +} + +template +Molecule& Molecule::operator*=(const RigidTrans3 &rt) +{ + rigidTrans(rt); + return *this; +} + +template +Molecule& Molecule::operator=(const Molecule& m) { + return(Molecule&)std::vector::operator=(m); +} + +template +std::ostream& operator<< (std::ostream& s, const Molecule& m) +{ + for (typename std::vector::const_iterator it = m.begin(); + it != m.end() ; ++it) { + s << *it << std::endl; + } + return s; +} + +template +void Molecule::output2cif(std::ostream& s, int modelNum) const { + s << "data_1XXX" << std::endl; + CIF::writeAtomSiteTable(s); + for (typename std::vector::const_iterator it = this->begin(); it != this->end() ; ++it) { + it->output2cif(s, modelNum); + s << std::endl; + } + s << "#" << std::endl; +} + +#endif diff --git a/model/CombinatorialAssembler/libs_gamb/MoleculeGrid.cc b/model/CombinatorialAssembler/libs_gamb/MoleculeGrid.cc new file mode 100644 index 0000000000000000000000000000000000000000..7451db13be9ca4946cd22f7e2f42d7e13d30798c --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/MoleculeGrid.cc @@ -0,0 +1,307 @@ +#include +#include "MoleculeGrid.h" +#include "Atom.h" + +#define MAXIMIZE(a,b) if (a < b) a = b; +#define MINIMIZE(a,b) if (a > b) a = b; + +MoleculeGrid::MoleculeGrid(const Surface& surface, + const float inDelta, const float maxRadius): delta(inDelta) +{ + maxGridRadius = getIntGridRadius(maxRadius) + 1; + + computeBoundingValues(surface); + xMin = xMin - maxRadius - delta; + yMin = yMin - maxRadius - delta; + zMin = zMin - maxRadius - delta; + xMax = xMax + maxRadius + delta; + yMax = yMax + maxRadius + delta; + zMax = zMax + maxRadius + delta; + + xGridNum = (int) ((xMax - xMin) / delta + 2); + yGridNum = (int) ((yMax - yMin) / delta + 2); + zGridNum = (int) ((zMax - zMin) / delta + 2); + xyGridNum = xGridNum * yGridNum ; + + if ( xGridNum < 1 || yGridNum < 1 || zGridNum < 1 ) + { + std::cerr << "Invalid parameters for grid generation" << std::endl; + exit(1); + } + maxEntry = xGridNum * yGridNum * zGridNum; + //cerr << " maxEntry " << maxEntry << endl; + + grid.insert(grid.end(), maxEntry, (float)MAX_FLOAT) ; + int nc = 0; + for (int x = -1; x <= 1; x++) { + for (int y = -1; y <= 1; y++) { + for (int z = -1; z <= 1; z++) { + if (x == 0 && y == 0 && z == 0) continue; + neigbor.push_back(x + xGridNum*y + xyGridNum*z); + float d = delta * sqrt((float)(x*x + y*y + z*z)); + neigborDist.push_back(d); + nc++; + } + } + } +} + +void MoleculeGrid::computeBoundingValues(const Surface& surface) +{ + xMin=yMin=zMin=(float)MAX_FLOAT; + xMax=yMax=zMax=(float)MIN_FLOAT; + for(Surface::const_iterator it = surface.begin(); it!= surface.end(); ++it){ + const Vector3 &v = it->position(); + MINIMIZE(xMin, v[0]); + MINIMIZE(yMin, v[1]); + MINIMIZE(zMin, v[2]); + MAXIMIZE(xMax, v[0]); + MAXIMIZE(yMax, v[1]); + MAXIMIZE(zMax, v[2]); + } +} + +void MoleculeGrid::computeDistFromSurface(const Surface &surface, bool precise) +{ + std::vector sps; + for (Surface::const_iterator it = surface.begin(); it!= surface.end(); ++it) { + sps.push_back(it->position()); + } + + std::vector layer1, layer2, *curr = &layer1, *next = &layer2, *tmp; + std::vector sps1, sps2, *currSps = &sps1, *nextSps = &sps2, *tmpSps; + std::vector gridLayer; + gridLayer.insert(gridLayer.end(),maxEntry,-1); + + layer1.reserve(sps.size()*10); + layer2.reserve(sps.size()*10); + sps1.reserve(sps.size()*10); + sps2.reserve(sps.size()*10); + + // mark zero in Grid for each surface point and insert indexes of voxels for first layer + int index; + for (std::vector::iterator it = sps.begin(); it!= sps.end(); ++it) { + index = getIndexForPoint(*it); + if (gridLayer[index] != 0) { + if (precise) sps1.push_back( &(*it)); + layer1.push_back(index); + grid[index] = 0.0; + gridLayer[index] = 0; + } + } + + // work on every layer to be computed + Vector3* p = NULL; + for (int layer = 0; layer < maxGridRadius; layer++) { + std::cerr << " In Layer " << layer << " out of " << maxGridRadius + << " curr->size() " << curr->size() << std::endl; + // update voxels with current layer distance and insert indexes for next layer + for (unsigned int i = 0; i < curr->size(); i++) { + int index = (*curr)[i]; + for (int j = 0; j < 26; j++) { + int nIndex = index + neigbor[j]; + if (isValidIndex(nIndex)) { + if (precise) { + p = (*currSps)[i]; + if (setDIST(nIndex, *p)) { + if (gridLayer[nIndex] < layer + 1) { + next->push_back(nIndex); + nextSps->push_back(p); + gridLayer[nIndex] = layer + 1; + } + } + } else { + float dist = grid[index] + neigborDist[j]; + if (dist <= 0) { + std::cerr << " dist = grid[index] + neigborDist[j] grid[nIndex] " + << dist << " " << grid[index] << " " << neigborDist[j] + << " j " << j << " nIndex " << nIndex << " grid[nIndex] " << grid[nIndex] << std::endl; + } + if (grid[nIndex] > dist) { + grid[nIndex] = dist; + if (gridLayer[nIndex] < layer + 1) { + next->push_back(nIndex); + gridLayer[nIndex] = layer + 1; + } + } + } + } + } + } + curr->clear(); + tmp = curr; curr = next; next = tmp; + if (precise) { + currSps->clear(); + tmpSps = currSps; currSps = nextSps; nextSps = tmpSps; + } + } +} + + +float MoleculeGrid::calcVolumeFunc(const Vector3 ¢er, const float radius) const +{ + int centerIndex = getIndexForPoint(center); + if (!isValidIndex(centerIndex)) { + std::cerr << "Error: Point out of grid" << std::endl; + exit(1); + } + + int intRadius = getIntGridRadius(radius); + int radius2 = intRadius*intRadius; + //maybe compute exact sphere volume?? + + int insideNum = 0; + int outsideNum = 0; + int i_bound,j_bound,k_bound; + i_bound = intRadius; + for (int i = -i_bound; i <= i_bound; i++ ) + { + j_bound = (int)sqrt((float)radius2 - i*i); + for (int j = -j_bound; j <= j_bound; j++) + { + k_bound = (int)sqrt((float)radius2 - i*i - j*j); + for (int k = -k_bound; k <= k_bound; k++) + { + int index = centerIndex + i + xGridNum * j + xyGridNum * k; + if(isValidIndex(index)) + { + if (grid[index] > 0) + outsideNum++; + else + insideNum++; + } + else + outsideNum++; + } + } + } + + if(insideNum==0 && outsideNum == 0) { + std::cerr << "Error in calculateVolumeFunc" << std::endl; + } + return ((float)insideNum) / ( insideNum + outsideNum ); +} + +Vector3 MoleculeGrid::calcVolumeNormal(const Vector3 ¢er, const float radius) const +{ + int centerIndex = getIndexForPoint(center); + if (!isValidIndex(centerIndex)) { + std::cerr << "Error: Point out of grid" << std::endl; + exit(1); + } + + int intRadius = getIntGridRadius(radius); + int radius2 = intRadius*intRadius; + + int xOut = 0; + int yOut = 0; + int zOut = 0; + + int i_bound,j_bound,k_bound; + i_bound = intRadius; + for (int i = -i_bound; i <= i_bound; i++ ) + { + j_bound = (int)sqrt((float)radius2 - i*i); + for (int j = -j_bound; j <= j_bound; j++) + { + k_bound = (int)sqrt((float)radius2 - i*i - j*j); + for (int k = -k_bound; k <= k_bound; k++) + { + int index = centerIndex + i + xGridNum * j + xyGridNum * k; + if(isValidIndex(index)) + { + if(grid[index] <= 0) + continue; + } + xOut+=i; + yOut+=j; + zOut+=k; + } + } + } + Vector3 v((float)xOut, (float)yOut, (float)zOut); + + if(v.norm() == 0) { + std::cerr << "Error in calculateVolumeNormFunc center" << center << " dist " << grid[centerIndex] << std::endl; + } + return v/v.norm(); +} + +void MoleculeGrid::calcVolumeFuncAndNormal(const Vector3 ¢er, const float radius, + float &volFunc, Vector3& volNormal) const +{ + int centerIndex = getIndexForPoint(center); + if (!isValidIndex(centerIndex)) { + std::cerr << "Error: Point out of grid" << std::endl; + exit(1); + } + + int intRadius = getIntGridRadius(radius); + int radius2 = intRadius*intRadius; + + int outsideNum = 0; + int insideNum = 0; + int xOut = 0; + int yOut = 0; + int zOut = 0; + + int i_bound,j_bound,k_bound; + i_bound = intRadius; + for (int i = -i_bound; i <= i_bound; i++ ) + { + j_bound = (int)sqrt((float)radius2 - i*i); + for (int j = -j_bound; j <= j_bound; j++) + { + k_bound = (int)sqrt((float)radius2 - i*i - j*j); + for (int k = -k_bound; k <= k_bound; k++) + { + int index = centerIndex + i + xGridNum * j + xyGridNum * k; + if(isValidIndex(index) && grid[index] <= 0.0) + { + insideNum++; + } + else + { + outsideNum++; + xOut+=i; + yOut+=j; + zOut+=k; + } + } + } + } + volFunc= ((float)insideNum) / (insideNum + outsideNum); + volNormal.updateX((float)xOut); + volNormal.updateY((float)yOut); + volNormal.updateZ((float)zOut); + + if(volNormal.norm() == 0) { + std::cerr << "Error in calculateVolumeNormFunc " << center << std::endl; + } + volNormal=volNormal/volNormal.norm(); +} + +void MoleculeGrid::printGrid(std::ofstream& file) const +{ + const float SURFACE_THRESHOLD = 0;//delta * sqrt(3.0); + for(int i=0; i< (int)grid.size();i++) + { + if (grid[i] > SURFACE_THRESHOLD || grid[i] < -SURFACE_THRESHOLD) continue; + Vector3 point = getPointForIndex(i); + Atom atom(point, 'A', i, i % 20, "PRB", 'X'); + file << atom << std::endl; + } +} + +float MoleculeGrid::volume() const { + int fullCube(0), halfCube(0); + for(int i=0; i< (int)grid.size();i++) { + if(grid[i] < 0) + fullCube++; + if(grid[i] == 0) + halfCube++; + } + float cubeVolume = delta*delta*delta; + float halfVolume = cubeVolume/2; + return fullCube*cubeVolume + halfCube*halfVolume; +} diff --git a/model/CombinatorialAssembler/libs_gamb/MoleculeGrid.h b/model/CombinatorialAssembler/libs_gamb/MoleculeGrid.h new file mode 100644 index 0000000000000000000000000000000000000000..a89681689900996fe4a83dff4b908da4a7c9c2a5 --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/MoleculeGrid.h @@ -0,0 +1,267 @@ +#ifndef _GRID_H +#define _GRID_H + +#include +#include +#include "numerics.h" +#include "Surface.h" +#include +#include + +/* +CLASS + MoleculeGrid + + This class provides discrete representation of the molecule. + The molecule is represented by a grid. + Each voxel of the grid is marked as follows: +
    +
  • Negative value for the inside of the molecule +
  • Zero for the surface +
  • Positive for outside voxels +
+ In addition the distance function can be calculated for each voxel. + The distances are zero at the surface voxels and change as the distance + from the surface increases/decreases. The distance function is negative + for inside molecule voxels and positive for outside voxels. + +KEYWORDS + Grid, Molecule, Surface + +AUTHORS + Yuval Inbar + Dina Duhovny , + + Copyright: SAMBA group, Tel-Aviv Univ. Israel, 2001. + +CHANGES LOG +
    +
  • +12/01/04 - Dina +added method for volume computation +
+ +GOALS + The class is designed to represent a molecule as accurate as possible + using small amount of space and fast algorithms for grid calculations. + + Distance function calculation: + initialization: + The grid voxels are initialized to MAX_VALUE. + Then the surface voxels are marked with zero. + The algorithm used for distance function computation is grass-fire algorithm. + The analogy is that a fire spreads out from some voxels (surface voxels in this case), + igniting each voxel in the fire's path. + The distance function is computed for each layer starting from + surface voxels. The radius for distance function computation is specified + by the user. There is also a possibility to compute exact distance function: + each voxel will have the exact distance to the closest surface point. + Since the algorithm is iterative, the time complexity is linear to the + number of voxels. + + Marking the inside of the molecule: + The same algorithm of grass-fire is used here. + The signs of the voxel distance function is changed layer by layer, starting + from the centers of molecule atoms. The procedure stops when it reaches + surface voxels (distance function is zero or very small). + + Space Complexity of the Grid: + The density of the grid: delta, is specified by the user. + Assume that the width of the molecule along each of the axis is: + x_width, y_width, z_width. The space used by the grid will be: + x_width*y_width*z_width/(delta^3). + + Volume function and Volume normal: + The class also supports volume function and normal calculation for a point + on molecule surface. + The volume function is the ratio of voxels outside the + molecule in the sphere of radius R (in most articles it is suggested to use + radius of 5A~), and the total number of voxels in the sphere. + The volume normal is the average of the points outside the sphere of radius R. + This normal is more accurate than the one given by MS or Shuo program. + +USAGE + + The MoleculeGrid class uses Surface representation of the molecule + generated by Connolly MS program. The density of the points needed + for dense grid is 5A or more. The density of the grid (delta) should + be 0.5-1.0. As the density of Connolly points increases, you can decrease + the delta value of the grid. + + EXAMPLE + + Surface msSurface; + Molecule mol; + // ... read msSurface + // ... read mol + // initialization of grid + MoleculeGrid* grid = new MoleculeGrid(msSurface, delta, maxDist) ; + // distance function calculation + grid->computeDistFromSurface(msSurface); + // marking the molecule + grid->markTheInside(mol); + + Vector3 point(1.2, 2.3, 3.4); + float vf = grid->calcVolumeFunc(point,vfRadius) ; + Vector3 normal = grid->calcVolumeNormal(point,vfRadius) ; + + END +*/ + +class MoleculeGrid +{ + public: + // GROUP: Constructors + //// The MoleculeGrid constructor receives Connoly MS Surface, delta - grid + // density and maxRadius - maximum radius for distance function calculation. + MoleculeGrid(const Surface &surface, const float inDelta, const float maxRadius); + + // GROUP: Functions + //// Returns distance from surface for a given point. + float getDist(const Vector3& point) const { + int index = getIndexForPoint(point); + if (!isValidIndex(index)) + return (float)MAX_FLOAT; + return grid[index]; + } + + //// Calculates volume normal for Vector3 center in a given radius. + Vector3 calcVolumeNormal(const Vector3 ¢er, const float radius = 5.0) const; + + //// Calculates volume function for Vector3 center in a given radius. + float calcVolumeFunc(const Vector3 ¢er, const float radius = 5.0) const; + + //// Calculates volume function and normal at one pass. + // Usefull when both of them need to be calculated. + void calcVolumeFuncAndNormal(const Vector3 ¢er, const float radius, + float& volFunc, Vector3& volNormal) const; + + void calcVolumeFuncAndNormal(const Vector3 ¢er, + float& volFunc, Vector3& volNormal) const { + calcVolumeFuncAndNormal(center, 5.0, volFunc, volNormal); + } + //// Computes distance function for the molecule. + // The voxels corresponding to surface points are given zero distance + // function value and serve as a first layer. + // If precise value is true, than the exact dist. function is calculated. + void computeDistFromSurface(const Surface &surface, bool precise = false); + + //// Marks the inside of the molecule by changing the sign of + // the distance function for inner voxels to negative. + // the molecule M shouls contain the coordinates of the centers of all atoms + // of the molecule. + template + bool markTheInside(const MoleculeT &M); + + //// Computes the volume of the molecule based on grid + float volume() const; + + + //// Prints each surface voxel as a coordinate in PDB format. + // Usefull for grid visualization. + void printGrid(std::ofstream& file) const; + + protected: + //// computes index in grid for a point. + int getIndexForPoint(const Vector3 &point) const { + if (xMin > point[0] || yMin > point[1] || zMin > point[2] || xMax < point[0] || yMax < point[1] || zMax < point[2]) + return -1; + return ((int)( (point[0] - xMin ) / delta + 0.5)) + + xGridNum * ((int) ( (point[1] - yMin ) / delta + 0.5)) + + xyGridNum * ((int) ( (point[2] - zMin ) / delta + 0.5)) ; + } + + //// computes a point corresponding to an index. + Vector3 getPointForIndex(int index) const { + int x = index % xGridNum; + index /= xGridNum; + int y = index % yGridNum; + int z = index / yGridNum; + return Vector3(x * delta + xMin, y * delta + yMin, z * delta + zMin); + } + + //// checks if the index is valid for the grid + bool isValidIndex(int index) const { return (index >= 0 && index < maxEntry); } + + //// convert radius to grid valid radius (number of voxels) + int getIntGridRadius( float radius ) const { return (int) (radius / delta + 0.5) ; } + + private: + // computes xMin,yMin,zMin,xMax,yMax,zMax + void computeBoundingValues(const Surface &surface); + + // sets distance func. falue for index to be the distance from p. + bool setDIST(int index, Vector3 &p) { + float d = getPointForIndex(index).dist(p); + if (d >= grid[index]) return false; + grid[index] = d; + return true; + } + + protected: + //// grid resolution + float delta ; + + int maxGridRadius ; + int maxEntry ; + + int xGridNum ; + int yGridNum ; + int xyGridNum ; + int zGridNum ; + float xMin ; + float yMin ; + float zMin ; + float xMax ; + float yMax ; + float zMax ; + + //// cube neighbors + // there are 26 neighbors for each voxel + std::vector neigbor; + + //// cube neighbors' distances + std::vector neigborDist; + + //// distances grid + std::vector grid; +}; + +template +bool MoleculeGrid::markTheInside(const MoleculeT &M) +{ + std::vector layer1, layer2, *curr = &layer1, *next = &layer2, *tmp; + const float SURFACE_THRESHOLD = delta * sqrt(3.0); + layer1.reserve(100); + layer2.reserve(100); + for (typename MoleculeT::const_iterator it = M.begin(); it!= M.end(); ++it) { + int index = getIndexForPoint(it->position()); + if (isValidIndex(index) && grid[index] > 0) { + layer1.push_back(index); + grid[index] = -grid[index]; + } else { + std::cerr << "isValidIndex(index)" << isValidIndex(index) << " index " << index << " grid[index] " << grid[index] << std::endl; + } + } + int layer = 0; + // cerr << "molecule atoms count: " << curr->size() << endl; + while(!curr->empty()) { + layer++; + if(layer > maxGridRadius) return false; + std::cerr << " markTheInside: in Layer " << layer << " out of " << maxGridRadius << " curr->size() " << curr->size() << std::endl; + for (std::vector::iterator it = curr->begin(); it!= curr->end(); ++it) { + for (int j = 0; j < 26; j++) { + int nIndex = *it + neigbor[j]; + if (isValidIndex(nIndex) && grid[nIndex] > 0) { + if (grid[nIndex] > SURFACE_THRESHOLD) next->push_back(nIndex); + grid[nIndex] = -grid[nIndex]; + } + } + } + curr->clear(); + tmp = curr; curr = next; next = tmp; + } + return true; +} + +#endif diff --git a/model/CombinatorialAssembler/libs_gamb/PDB.cc b/model/CombinatorialAssembler/libs_gamb/PDB.cc new file mode 100644 index 0000000000000000000000000000000000000000..11e6066c386c307bc816bc33e0d233e4ee545394 --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/PDB.cc @@ -0,0 +1,235 @@ +#include +#include +#include + +#include "PDB.h" +#include + +static const unsigned short NUM_OF_PROTEIN_RESIDUES = 23; +static const std::string PROTEIN_RESIDUES = +"ALAA ARGR ASPD ASNN CYSC GLNQ GLUE GLYG HISH ILEI LEUL LYSK METM PHEF PROP SERS THRT TYRY TRPW VALV AS B GL Z UNKX"; + +static const unsigned short NUM_OF_NUCLEIC_ACIDS = 10; +static const std::string NUCLEIC_ACIDS = " AA GG TT UU CC ADEA CYTC GUAG THYT URAU DAA DCC DGG DTT DUU UNKX"; + +static const unsigned short NUM_OF_NUCLEIC_RESIDUES = 27; +static const char* NUCLEIC_RESIDUES[NUM_OF_NUCLEIC_RESIDUES] = {" A", " +A", " C", " +C", " G", " +G", " I", " +I", " T", " +T", " U", " +U", "1MA", "5MC", "OMC", "1MG", "2MG", "M2G", "7MG", "OMG", " YG", "H2U", "5MU", "PSU", "4SU", "MIA", "UNK"}; + +bool PDB::isATOMrec(const std::string& PDBline) +{ + return(PDBline[0]=='A' && PDBline[1]=='T' && + PDBline[2]=='O' && PDBline[3]=='M'); +} + +bool PDB::isHETATMrec(const std::string& PDBline) +{ + return(PDBline[0]=='H' && PDBline[1]=='E' && PDBline[2]=='T' && + PDBline[3]=='A' && PDBline[4]=='T' && PDBline[5]=='M'); +} + +bool PDB::isCaATOMrec(const std::string& PDBline) +{ + return(isATOMrec(PDBline) && PDBline[13] == 'C' && PDBline[14] == 'A'); +} + +bool PDB::isNucleicRecord(const std::string& PDBline) { + const std::string residueName = getAtomResidueLongName(PDBline); + return isNucleicAcid(residueName); +} + +bool PDB::isNucleicAcid(const std::string& residueName) +{ + if (residueName[0] == 'U' && residueName[1] == 'N' && residueName[2] == 'K') { + std::cerr << "PDB::isNucleicRecord - The given residue name is unknown :" + << residueName<< ":" << std::endl; + return false; + } + + for (unsigned int i = 0 ; i < NUM_OF_NUCLEIC_RESIDUES ; i++) { + if (residueName[0] == NUCLEIC_RESIDUES[i][0] && residueName[1] == NUCLEIC_RESIDUES[i][1] && + residueName[2] == NUCLEIC_RESIDUES[i][2]) { + return true; + } + } + + return false; +} + +bool PDB::isCONECTrec(const std::string& PDBline) +{ + return(PDBline[0]=='C' && PDBline[1]=='O' && + PDBline[2]=='N' && PDBline[3]=='E' && PDBline[4]=='C' && PDBline[5]=='T'); +} + +bool PDB::isMODELrec(const std::string& PDBline) +{ + return (PDBline[0] == 'M' && PDBline[1] == 'O' && PDBline[2] == 'D' && + PDBline[3] == 'E' && PDBline[4] == 'L'); +} + +bool PDB::isENDMDLrec(const std::string& PDBline) +{ + return (PDBline[0] == 'E' && PDBline[1] == 'N' && PDBline[2] == 'D' && + PDBline[3] == 'M' && PDBline[4] == 'D' && PDBline[5] == 'L'); +} + +char PDB::residueShortName(const std::string& resName) +{ + for (unsigned int i = 0 ; i < NUM_OF_PROTEIN_RESIDUES*5; i += 5) { + if (resName[0] == PROTEIN_RESIDUES[i] && resName[1] == PROTEIN_RESIDUES[i+1] && + resName[2] == PROTEIN_RESIDUES[i+2]) { + return PROTEIN_RESIDUES[i+3]; + } + } + + for (unsigned int i = 0 ; i < NUM_OF_NUCLEIC_RESIDUES ; i++) { + if (resName[0] == NUCLEIC_RESIDUES[i][0] && + resName[1] == NUCLEIC_RESIDUES[i][1] && + resName[2] == NUCLEIC_RESIDUES[i][2]) { + return NUCLEIC_RESIDUES[i][2]; + } + } + // for (unsigned int i = 0 ; i < NUM_OF_NUCLEIC_ACIDS*5 ; i += 5) { + // if (resName[0] == NUCLEIC_ACIDS[i] && + // resName[1] == NUCLEIC_ACIDS[i+1] && + // resName[2] == NUCLEIC_ACIDS[i+2]) { + // return NUCLEIC_ACIDS[i+3]; + // } + // } + + return 'X'; +} + +std::string const PDB::residueLongName(const char resChar) +{ + std::string retName; + for (int i = 3 ; i < NUM_OF_PROTEIN_RESIDUES*5 ; i += 5) { + if (resChar == PROTEIN_RESIDUES[i]) { + retName = PROTEIN_RESIDUES.substr(i-3,3); + return retName; + } + } + + std::cerr << "PDB::residueLongName - A unique long name does not exist for non-protein residue :" + << resChar << ":" << std::endl; + return retName; +} + +std::string const PDB::nucleicAcidLongName(const char resChar) +{ + std::string retName; + for (int i = 3 ; i < NUM_OF_NUCLEIC_ACIDS*5 ; i += 5) { + if (resChar == NUCLEIC_ACIDS[i]) { + retName = NUCLEIC_ACIDS.substr(i-3,3); + return retName; + } + } + + std::cerr << "PDB::nucleicAcidLongName - A unique long name does not exist for nucleic acid:" + << resChar << ":" << std::endl; + return retName; +} + +float PDB::atomXCoord(const std::string& PDBline) +{ + return (float)atof(PDBline.substr(atomXCoordField, 8).c_str()); +} + +float PDB::atomYCoord(const std::string& PDBline) +{ + return (float)atof(PDBline.substr(atomYCoordField, 8).c_str()); +} + +float PDB::atomZCoord(const std::string& PDBline) +{ + return (float)atof(PDBline.substr(atomZCoordField, 8).c_str()); +} + +int PDB::atomIndex(const std::string& PDBline) +{ + return atoi(PDBline.substr(atomIndexField).c_str()); +} + +char PDB::atomAltLocIndicator(const std::string& PDBline) +{ + return PDBline[atomAltLocField]; +} + +char PDB::atomChainId(const std::string& PDBline) +{ + return PDBline[atomChainIdField]; +} + +short PDB::atomResidueIndex(const std::string& PDBline) +{ + return (short)atoi(PDBline.substr(atomResIndexField, 4).c_str()); +} + +char PDB::atomResidueICode(const std::string& PDBline) +{ + return PDBline[atomResIndexCharField]; +} + +char PDB::atomResidueType(const std::string& PDBline) +{ + return residueShortName(PDBline.substr(atomResTypeField, 3).c_str()); +} + +std::string PDB::atomType(const std::string& PDBline) +{ + return PDBline.substr(atomTypeField, 4); +} + +const std::string PDB::getAtomResidueLongName(const std::string& PDBline) +{ + return PDBline.substr(atomResTypeField, 3); +} + +AtomEntryType PDB::getAtomEntryType(const std::string& PDBline) +{ + int field_len = atomIndexField - atomEntryType; + std::string entryType = PDBline.substr(atomEntryType, field_len); + if (strcmp (entryType.c_str(), "HETATM") == 0) + return(HETATM); + else if (strncmp (entryType.c_str(), "ATOM", field_len - 2) == 0) + return(ATOM); + else + return(UNK); +} + +float PDB::getOccupancy(const std::string& PDBline) +{ + if(PDBline.length() >= atomOccupancy + 6) + return (float)atof(PDBline.substr(atomOccupancy, 6).c_str()); + return 0.0; +} + +float PDB::getTempFactor(const std::string& PDBline) +{ + if(PDBline.length() >= atomTempFactor + 6) + return (float)atof(PDBline.substr(atomTempFactor, 6).c_str()); + return 0.0; +} + +std::vector PDB::connectedAtoms(const std::string& PDBline) +{ + std::vector connAtoms; + if (isCONECTrec(PDBline)) { + // (1-6) - "CONECT" + // (7-11), (12-16), ...,(57-61) - serial numbers of connected atoms + const unsigned short atomIndexLen = 5; + unsigned short atomIndexStart = 6; + unsigned short lineLen = (unsigned short)PDBline.length(); + if (lineLen > 61) lineLen = 61; + + while ((atomIndexStart+atomIndexLen) <= lineLen) { + //char atomIndex[atomIndexLen+1]; + //strncpy(atomIndex, PDBline + atomIndexStart, atomIndexLen); + //atomIndex[atomIndexLen] = '\0'; + short sh_atomIndex = (short)atoi(PDBline.substr(atomIndexStart, atomIndexLen).c_str()); + if (sh_atomIndex > 0) connAtoms.push_back(sh_atomIndex); + atomIndexStart += atomIndexLen; + } + } + return connAtoms; +} diff --git a/model/CombinatorialAssembler/libs_gamb/PDB.h b/model/CombinatorialAssembler/libs_gamb/PDB.h new file mode 100644 index 0000000000000000000000000000000000000000..dbd08518ffcaec45a4d545b7e3c064732abab59b --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/PDB.h @@ -0,0 +1,398 @@ +#ifndef _PDB_h +#define _PDB_h + +#include +#include +#include + +//// +enum AtomEntryType {UNK = 0, ATOM = 1, HETATM = 2}; + +/* +CLASS + PDB + + The PDB class defines a set of tools used for reading the Protein Data Bank + file format, used to describe molecular structures, like proteins, + drugs and Nucleic Acids +KEYWORDS + PDB, protein, Nucleic Acids (RNA, DNA) , record, atom, residue, + structure, coordinate, field + +AUTHORS + Meir Fuchs. (meirfux@math.tau.ac.il) + + Copyright: SAMBA group, Tel-Aviv Univ. Israel, 1997. + +CHANGES LOG + +GOALS + Class PDB is designed to encapsulate a set of methods used for reading the + PDB file format. Methods are static requiring no object instantiation + before their use. + +USAGE + A PDB file is comprised of 80-char lines called records. These + records describe different properties of a molecular structure + (mainly proteins). A record can describe an atom coordinate, an + amino-acid sequence, an alpha helix or some other chemical or + geometrical properties of the molecule, depending on the record's + type. For more information see the PDB_site. + + Class PDB's methods are all static. Hence the class's constructor was + declared private so that no objects of type PDB will be declared. PDB's + methods recieve a PDB record line as an argument and return requested data + found in the record. + EXAMPLE + char rec[81]; pdbFile.getline(line, 81); // read line from stream. + if (PDB::isATOMrec(rec)) // if record is of type ATOM + Vector3 v(PDB::atomXCoord(rec), // instantiate a vector with + PDB::atomYCoord(rec), // atom's coordinates. + PDB::atomZCoord(rec)); + END + Note that one must make sure the record is of the correct type before asking + for data found in the record. For example you cannot ask for an x-coordinate + of an atom before making sure that you are dealing with an ATOM record. + Method names are prefixed by the type of record they are meant to handle + (e.g. atomXCoord is meant to extract the X coordinate from an ATOM record). + Methods beginning with the is prefix (e.g. isATOMrec) check for record type. +*/ +class PDB { + +public: + + // GROUP: PDB record type determination methods. + + //// + // Returns true if the given record is an ATOM record. + static bool isATOMrec(const std::string& pdb_line); + + //// + // Returns true if the given record is a HETATM record. + static bool isHETATMrec(const std::string& pdb_line); + + //// + // Returns true if the given record is an ATOM record of a C-alpha atom. + static bool isCaATOMrec(const std::string& pdb_line); + + //// + // Returns true if the given record is a record of a nucleic acid residue + //
Author: Oranit Dror (oranit@tau.ac.il) + static bool isNucleicRecord(const std::string& pdb_line); + + //// returns true if the residue name is of nucleic acid + static bool isNucleicAcid(const std::string& residueName); + + //// + // Returns true if the given record is a record of a connection between atoms + //
Author: Bluvshtein Nelly (bluvsh@post.tau.ac.il) + static bool isCONECTrec(const std::string& pdb_line); + + static bool isMODELrec(const std::string& pdb_line); + + static bool isENDMDLrec(const std::string& pdb_line); + + + // GROUP: ATOM record costants and methods. + // ATOM Record Format (from PDB) + // COLUMNS DATA TYPE CONTENTS + // -------------------------------------------------------------------------------- + // 1 - 6 Record name "ATOM " + // 7 - 11 Integer Atom serial number. + // 13 - 16 Atom Atom name. + // 17 Character Alternate location indicator. + // 18 - 20 Residue name Residue name. + // 22 Character Chain identifier. + // 23 - 26 Integer Residue sequence number. + // 27 AChar Code for insertion of residues. + // 31 - 38 Real(8.3) Orthogonal coordinates for X in Angstroms. + // 39 - 46 Real(8.3) Orthogonal coordinates for Y in Angstroms. + // 47 - 54 Real(8.3) Orthogonal coordinates for Z in Angstroms. + // 55 - 60 Real(6.2) Occupancy. + // 61 - 66 Real(6.2) Temperature factor (Default = 0.0). + // 73 - 76 LString(4) Segment identifier, left-justified. + // 77 - 78 LString(2) Element symbol, right-justified. + // 79 - 80 LString(2) Charge on the atom. + + static const unsigned short atomEntryType = 0; + static const unsigned short atomIndexField = 6; + static const unsigned short atomTypeField = 12; + static const unsigned short atomAltLocField = 16; + static const unsigned short atomResTypeField = 17; + static const unsigned short atomChainIdField = 21; + static const unsigned short atomResIndexField = 22; + static const unsigned short atomResIndexCharField = 26; + static const unsigned short atomXCoordField = 30; + static const unsigned short atomYCoordField = 38; + static const unsigned short atomZCoordField = 46; + static const unsigned short atomOccupancy = 54; + static const unsigned short atomTempFactor = 60; + + //// Returns an ATOM record's X coordinate. + static float atomXCoord(const std::string& pdb_line); + + //// Returns an ATOM record's Y coordinate. + static float atomYCoord(const std::string& pdb_line); + + //// Returns an ATOM record's Z coordinate. + static float atomZCoord(const std::string& pdb_line); + + //// Returns an ATOM record's atom index number. + static int atomIndex(const std::string& pdb_line); + + //// Returns a pointer to an ATOM's record atom type string. + // The atom type is a 4 charachter long field. A pointer is returned to + // the correct position withing the given string PDBrec. Hence the pointer + // should not be freed or deleted. + static std::string atomType(const std::string& pdb_line); + + //// Returns an ATOM record's alternative location indicator. + static char atomAltLocIndicator(const std::string& pdb_line); + + //// Returns an ATOM record's chain id. + static char atomChainId(const std::string& pdb_line); + + //// Returns an ATOM record's residue index. + static short atomResidueIndex(const std::string& pdb_line); + + //// + // code for insertion of residues + // Returns the value stored in the atomResIndexCharField of the given PDB + // record (column 26). + //
+ // ATOM 279 CA SER 60 8.961 9.167 37.481 1.00 20.10 1SGT 477
+ // ATOM 285 CA GLY 60A 5.640 10.901 38.407 1.00 21.41 1SGT 483 + //
In this case for atom no. 285 the atomResidueICode method + // will return 'A' and for the atom no. 279 it will return ' ' + static char atomResidueICode(const std::string& pdb_line); + + //// + // Gets a PDB record of an atom and returns a one-letter + // abbreviation of the type of the residue that the atom belongs + // to.
+ // For a nucleic acid atom returns A for adenine, G for guanine, T + // for thymine, U for uracil and X for unknown type. + static char atomResidueType(const std::string& pdb_line); + + //// + // Gets a one-letter abbreviation of the type of the residue that + // the atom belongs to
+ // For a protein atom the method returns the 3-letter abbreviation + // of its residue. Returns 0 for other types of atoms. + //static const char* const residueLongName(const char resChar); + static std::string const residueLongName(const char resChar); + + //// the same for nucleic acid + static std::string const nucleicAcidLongName(const char resChar); + + //// Returns the full string of the Residue Name field + static std::string const getAtomResidueLongName(const std::string& pdb_line); + + //// Return the type of the PDB entry, currently only ATOM and HETATM are identified. + static AtomEntryType getAtomEntryType(const std::string& pdb_line); + + //// Returns the occupancy, which is a measure of the fraction of molecules in the + // crystal in which the current atom actually occupies the specified position + static float getOccupancy(const std::string& pdb_line); + + //// Returns the temperature factor, which is a measure of how much an atom + // oscillates or vibrates around the specified position + static float getTempFactor(const std::string& pdb_line); + + //// + // Given a charachter string with a PDB residue type code, the + // method returns a one-letter abbreviation of the type of the + // residue that the atom belongs to:
+ // - For a nucleic acid atom returns A for adenine, G for guanine, T + // for thymine, U for uracil and X for unknown type.
+ // - For a protein atom, returns a 3-letter abbreviation of the + // residue name
+ static char residueShortName(const std::string& resName); + + //// + // + static std::vector connectedAtoms(const std::string& pdb_line); + + + //// Selector is a general purpose class used to select records from a PDB + // file. Using descendants of this class one may implement arbitrary + // selection functions with operator() and pass them to PDB reading functions + // for object selection. (See Molecule for examples). + class Selector { + public: + virtual bool operator()(const char*) const { + return true; + } + virtual ~Selector() {} + }; + + //// Defines a selector that will pick only C-alpha atoms. + class CAlphaSelector : public Selector{ + public: + bool operator()(const char *PDBrec)const{ + std::string type = atomType(PDBrec); + char alt = atomAltLocIndicator(PDBrec); + if(type[1] == 'C' && type[2] == 'A' && type[3] == ' ' && + (alt == ' ' || alt == 'A')) { + // && (*(type+4) == ' ' || *(type+4) == 'A')){ + return true; + }else + return false; + } + }; + + //// Defines a selector that will pick only C-beta atoms. + class CBetaSelector: public Selector{ + public: + bool operator()(const char *PDBrec) const { + const std::string residueName = getAtomResidueLongName(PDBrec); + if (residueName[0] == 'G' && residueName[1] == 'L' && residueName[2] == 'Y') { + return CAlphaSelector()(PDBrec); + } + + std::string type = atomType(PDBrec); + char alt = atomAltLocIndicator(PDBrec); + if(type[1] == 'C' && type[2] == 'B' && type[3] == ' ' && + (alt == ' ' || alt == 'A')) + return true; + else + return false; + } + }; + + //// Defines a selector that will pick only C atoms. (not Ca or Cb) + class CSelector: public Selector { + public: + bool operator()(const char *PDBrec) const { + std::string type = atomType(PDBrec); + return (type[1] == 'C' && type[2] == ' '); + } + }; + + //// Defines a selector that will pick only N atoms. + class NSelector: public Selector { + public: + bool operator()(const char *PDBrec) const { + std::string type = atomType(PDBrec); + return (type[1] == 'N' && type[2] == ' '); + } + }; + + //// Defines a selector that picks backbone atoms + class BBSelector : public Selector { + public: + bool operator()(const char *PDBrec) const { + std::string type = atomType(PDBrec); + if (type[1] == 'N' && type[2] == ' ') + return true; + else if (type[1] == 'C' && type[2] == 'A' && type[3] == ' ') + return true; + else if (type[1] == 'C' && type[2] == ' ') + return true; + else if (type[1] == 'O' && type[2] == ' ') + return true; + return false; + } + }; + + //// Defines a selector that will pick every atom. + class AllSelector : public Selector { + public: + bool operator()(const char* ) const{ + return true; + } + }; + + //// Selector that picks atoms of a given chain. + class ChainSelector : public Selector { + public: + // constructor + ChainSelector(const std::string &chainID): chains(chainID) {} + virtual ~ChainSelector() {} + bool operator()(const char* PDBrec) const { + for(int i=0; i< (int)chains.length(); i++) + if(atomChainId(PDBrec) == chains[i]) + return true; + return false; + } + private: + std::string chains; + }; + + + //// Selector that check if the line is water record + class WaterSelector : public Selector { + public: + bool operator()(const char* PDBrec) const { + return ((PDBrec[17]=='H' && PDBrec[18]=='O' && PDBrec[19]=='H') || + (PDBrec[17]=='D' && PDBrec[18]=='O' && PDBrec[19]=='D')); + } + }; + + + //// Selector that check if the line is hydrogen record + class HydrogenSelector : public Selector { + public: + bool operator()(const char* PDBrec) const { + return (PDBrec[13] == 'H' || PDBrec[13] == 'D'); + } + }; + + //// Selector that picks non water and non hydrogen atoms + class WaterHydrogenUnSelector : public Selector { + public: + bool operator()(const char* PDBrec) const { + return( !((PDBrec[17]=='H' && PDBrec[18]=='O' && PDBrec[19]=='H') || + (PDBrec[17]=='D' && PDBrec[18]=='O' && PDBrec[19]=='D')) && + ! (PDBrec[13] == 'H' || PDBrec[13] == 'D' || PDBrec[12] == 'H' || PDBrec[12] == 'D') && + (PDB::atomAltLocIndicator(PDBrec) == ' ' || PDB::atomAltLocIndicator(PDBrec) == 'A') ); // skip alternatives + } + }; + + //// Selector that picks non water atoms + class WaterUnSelector : public Selector { + public: + bool operator()(const char* PDBrec) const { + return( !((PDBrec[17]=='H' && PDBrec[18]=='O' && PDBrec[19]=='H') || + (PDBrec[17]=='D' && PDBrec[18]=='O' && PDBrec[19]=='D'))); + } + }; + + //// + // A PDB Selector that picks only Phosphate atoms.
+ class PSelector : public Selector { + public: + bool operator()(const char *PDBrec) const { + std::string type = PDB::atomType(PDBrec); + return ((type[1] == 'P' && type[2] == ' ') || (type[0] == 'P' && type[1] == ' ')); + } + }; + + //// + // A PDB Selector that picks only nucleic acid atoms.
+ class NucleicSelector : public Selector { + public: + bool operator()(const char *PDBrec) const { + return PDB::isNucleicRecord(PDBrec); + } + }; + + //// + // A PDB Selector that ignores all alternative location atoms.
+ class IgnoreAlternativesSelector : public Selector { + public: + bool operator()(const char *PDBrec) const { + return ((PDB::atomAltLocIndicator(PDBrec) == ' ') || + (PDB::atomAltLocIndicator(PDBrec) == 'A')); + } + }; + +private: + // Private constructor to prevent object instantiation. Class was meant to + // be use as a static method pool. + PDB() {} + + +}; + +#endif diff --git a/model/CombinatorialAssembler/libs_gamb/Parameters.cc b/model/CombinatorialAssembler/libs_gamb/Parameters.cc new file mode 100644 index 0000000000000000000000000000000000000000..f09df5aba535320b8621cfd3f008e0a28e21892b --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/Parameters.cc @@ -0,0 +1,356 @@ +#include +#include +#include +#include "Parameters.h" +#include + +typedef Parameters::ParamType ParamType; + +class Parameter +{ +public: + + Parameter(); + Parameter(const std::string& name, const int val); + Parameter(const std::string& name, const float val); + Parameter(const std::string& name, const std::string& val); + Parameter(const Parameter& param); + + void dump(FILE* outputFile) const; + + ~Parameter() {} + const std::string& paramLabel() const; + ParamType paramType() const; + + int intValue() const; + float floatValue() const; + const std::string& stringValue() const; + + Parameter& operator=(const Parameter& param); + + friend bool operator<(const Parameter& p, const Parameter& q); + friend std::ostream& operator<<(std::ostream& out, const Parameter& p); + +private: + std::string paramName; + ParamType type; + union { + int i; + float f; + }; + std::string s; +}; + +typedef std::vector ParamList; + +static ParamList paramList; + +Parameter::Parameter() : paramName(), type(Parameters::Undef) +{} + +Parameter::Parameter(const std::string& name, const int val) +{ + paramName = name; + i = val; + type = Parameters::Int; +} + +Parameter::Parameter(const std::string& name, const float val) +{ + paramName = name; + f = val; + type = Parameters::Float; +} + +Parameter::Parameter(const std::string& name, const std::string& val) +{ + paramName = name; + s = val; + type = Parameters::Str; +} + +Parameter::Parameter(const Parameter& param) +{ + paramName = param.paramName; + type = param.type; + if (type == Parameters::Int) + i = param.i; + else + f = param.f; + s = param.s; +} + +const std::string& Parameter::paramLabel() const +{ + return paramName; +} + +ParamType Parameter::paramType() const +{ + return type; +} + +int Parameter::intValue() const +{ + return i; +} + +float Parameter::floatValue() const +{ + return f; +} + +const std::string& Parameter::stringValue() const +{ + return s; +} + +Parameter& Parameter::operator=(const Parameter& param) +{ + paramName = param.paramName; + type = param.type; + if (type == Parameters::Int) + i = param.i; + else + f = param.f; + s = param.s; + return *this; +} + +bool operator<(const Parameter& p, const Parameter& q) +{ + return (p.paramName < q.paramName); +} + +std::ostream& operator<<(std::ostream& out, const Parameter& p) { + switch(p.type) { + case Parameters::Int: + return out << p.paramName << '\t' << "(Int)" << '\t' << p.i; + case Parameters::Float: + return out << p.paramName << '\t' << "(Float)" << '\t' << p.f; + case Parameters::Str: + return out << p.paramName << '\t' << "(Str)" << '\t' << p.s; + case Parameters::Undef: + break; + } + return out << '\n'; +} + + +void Parameter::dump(FILE* outputFile) const { + switch(type) { + case Parameters::Int: + fprintf(outputFile, "%s = %d", paramName.c_str(), i); + case Parameters::Float: + fprintf(outputFile, "%s = %f", paramName.c_str(), f); + case Parameters::Str: + fprintf(outputFile, "%s = %s", paramName.c_str(), s.c_str()); + case Parameters::Undef: + break; + } +} + + +ParamList::iterator findInsertionPoint(ParamList& paramList, + const std::string& paramName) +{ + unsigned short start = 0; + unsigned short stop = (unsigned short)paramList.size(); + while (stop != start) { + unsigned short pos = start + (stop-start)/2; + int comp = paramName.compare(paramList[pos].paramLabel()); + if (comp == 0) + stop = start = pos; + else if (comp > 0) + start = pos + 1; + else + stop = pos; + } + + ParamList::iterator it=paramList.begin(); + it+=stop; + return it; +} + +Parameters::Parameters() +{} + +void Parameters::readParameters(std::istream& paramStream) +{ + while (!paramStream.eof()) { + std::string param; + getline(paramStream, param); + boost::trim(param); + std::string paramName = nextToken(param); + + if (paramName.length() == 0) continue; + + if (*(paramName.data()) == '#') continue; + + updateParameter(paramName, param.c_str()); + } +} + + +bool Parameters::empty() { + return paramList.empty(); +} + +void Parameters::addParameter(const std::string& paramName, const int value) +{ + ParamList::iterator pos = findInsertionPoint(paramList, paramName); + if (pos != paramList.end() && (*pos).paramLabel() == paramName) + (*pos) = Parameter(paramName, value); + else + paramList.insert(pos, Parameter(paramName, value)); +} + +void Parameters::addParameter(const std::string& paramName, const double value) +{ + ParamList::iterator pos = findInsertionPoint(paramList, paramName); + if (pos != paramList.end() && (*pos).paramLabel() == paramName) + (*pos) = Parameter(paramName, (float)value); + else + paramList.insert(pos, Parameter(paramName, (float)value)); +} + +void Parameters::addParameter(const std::string& paramName, const std::string& value) +{ + ParamList::iterator pos = findInsertionPoint(paramList, paramName); + if (pos != paramList.end() && (*pos).paramLabel() == paramName) + (*pos) = Parameter(paramName, value); + else + paramList.insert(pos, Parameter(paramName, value)); +} + +void Parameters::updateParameter(const std::string& paramName, + const char* const value) +{ + ParamList::iterator pos = findInsertionPoint(paramList, paramName); + if (pos != paramList.end() && (*pos).paramLabel() == paramName) + switch ((*pos).paramType()) { + case Int: + (*pos) = Parameter(paramName, atoi(value)); + break; + case Float: + (*pos) = Parameter(paramName, (float)atof(value)); + break; + case Str: + (*pos) = Parameter(paramName, std::string(value)); + case Undef: + (*pos) = Parameter(paramName, std::string(value)); + } + else + paramList.insert(pos, Parameter(paramName, std::string(value))); +} + +ParamType Parameters::paramType(const std::string& paramName) +{ + ParamList::iterator pos = findInsertionPoint(paramList, paramName); + if (pos != paramList.end() && (*pos).paramLabel() == paramName) + return (*pos).paramType(); + else + return Undef; +} + + +int Parameters::getInt(const std::string& paramName, const int& defaultValue) +{ + ParamList::iterator pos = findInsertionPoint(paramList, paramName); + if (pos != paramList.end() && (*pos).paramLabel() == paramName) + switch ((*pos).paramType()) { + case Int: + return (*pos).intValue(); + case Float: + return (int)(*pos).floatValue(); + case Str: + return atoi((*pos).stringValue().data()); + case Undef: + break; + } + return defaultValue; +} + +float Parameters::getFloat(const std::string& paramName, const float& defaultValue) +{ + ParamList::iterator pos = findInsertionPoint(paramList, paramName); + if (pos != paramList.end() && (*pos).paramLabel() == paramName) + switch ((*pos).paramType()) { + case Float: + return (*pos).floatValue(); + case Int: + return (float)(*pos).intValue(); + case Str: + return (float)atof((*pos).stringValue().data()); + case Undef: + break; + } + return defaultValue; +} + +std::string Parameters::getString(const std::string& paramName, const std::string& defaultValue) +{ + ParamList::iterator pos = findInsertionPoint(paramList, paramName); + if (pos != paramList.end() && (*pos).paramLabel() == paramName) + switch ((*pos).paramType()) { + case Str: + return (*pos).stringValue(); + case Float: { + return std::to_string((*pos).floatValue()); + } + case Int: { + return std::to_string((*pos).intValue()); + } + case Undef: + break; + } + return defaultValue; +} + +void Parameters::dump(std::ostream& out) +{ + for (ParamList::iterator i=paramList.begin(); i != paramList.end(); ++i) + out << *i << '\n'; +} + +void Parameters::dump(DebugStream& out, const unsigned int msgLevel) +{ + for (ParamList::iterator i=paramList.begin(); i != paramList.end(); ++i) + out(msgLevel) << *i; +} + +void Parameters::dump(FILE* outputFile) { + for (ParamList::iterator i = paramList.begin() ; i != paramList.end() ; i++) { + i->dump(outputFile); + fprintf(outputFile, "\n"); + } + + fprintf(outputFile, "\n"); +} + +std::string Parameters::nextToken(std::string& str) +{ + unsigned int start = 0; + while (start < str.length() && + (str[start] == ' ' || str[start] == '\t' || str[start] == '\n')) + ++start; + + if (start >= str.length()) { + str = ""; + return ""; + } + + unsigned int stop = start+1; + while (stop < str.length() && + str[stop] != ' ' && str[stop] != '\t' && str[stop] != '\n') + ++stop; + + unsigned int next = stop; + while (next < str.length() && + (str[next] == ' ' || str[next] == '\t' || str[next] == '\n')) + ++next; + + std::string result = str.substr((int)start, stop-start); + str = str.substr((int)next); + return result; +} diff --git a/model/CombinatorialAssembler/libs_gamb/Parameters.h b/model/CombinatorialAssembler/libs_gamb/Parameters.h new file mode 100644 index 0000000000000000000000000000000000000000..d45684bb1758ebb7ac2196e1d782a10b5b816137 --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/Parameters.h @@ -0,0 +1,239 @@ +#ifndef _Parameters_h +#define _Parameters_h + +#include +#include +#include "macros.h" +#include "DebugStream.h" + +/* +CLASS + Parameters + + A utility class used to manage program parameters. The class supports + setting default values for parameters, reading values from a parameters + file and accessing parameters values from other parts of the program. + +KEYWORDS + parameters + +AUTHORS + Meir Fuchs (mailto: meirfux@math.tau.ac.il) + + Copyright: SAMBA group, Tel-Aviv Univ. Israel, 1997. + +CHANGES LOG +
    +
  • 9.01.05 Dina: +Bug fix: adding check to iterator end() to findInsertionPoint result +to paramType, getInt, getString, getFloat functions +
  • +
  • 17.05.04 Oranit Dror: +Adding new methods: dump() and empty() +
  • +
+ +GOALS + Aid in managing program parameters. The Parameters class's main goal is to + relieve programmers from the need to rewrite specialized parameters reading + code sections for each of the programs. The Parameters class holds integer, + floating point or string values in static storage indexed using the + parameter's name. Class also supplies method for parsing strings. + +USAGE + The following section covers several issues regarding the Parameters class + and its usage. Users should understand the issues covered below before + using the class. + +USAGE: SETTING DEFAULT PARAMETERS + Default parameters are set using the addParameter methods. Note that the + type of the parameter is set according to the addParameter arguments. If + a parameter is set using addParameter with an integer argument then + subsequent updates (using updateParameter) to the same parameter will all + be stored as integers. Therefore the following code should output a 0: + EXAMPLE + Parameters::addParameter("Dummy", 3); + Parameters::updateParameter("Dummy", "This should set it to zero"); + cout << Parameters::getstring("Dummy"); + END + + Note also that when setting defuault values of float parameters always use + a decimal point or else these parameters will be added as intgers. For + example: + EXAMPLE + Parameters::addParameter("CubeSize", 1.0); OK + Parameters::addParameter("CubeSize", 1); Not OK. Integer parameter + END + +USAGE: READING PARAMETERS FROM FILE + The readParameters method recieves an input stream from which parameters are + to be read. Files are structured so that each line specifies the value of a + parameter. Each line gives the parameter name, a white space and then the + parameter value. Lines whose first non white-space charachter is # are + ignored. A basic schema for using the Parameters class is to set the default + values using addParameter calls and then calling readParameters to read in + parameters with other values or new parameters. The following example works + as such using the Parameters::dump method to print all the parameters + and their values: + EXAMPLE + Parameters::addParameter("CubeSize", 1.0); + Parameters::addParameter("MinVote", 8); + ifstream params("params"); + Parameters::readParameters(params); + params.close(); + Parameters::dump(cout); + END + With the following parameters file: + EXAMPLE + CubeSize 0.5 + File pdb4hhb.ent + END + The following output should result: + EXAMPLE + CubeSize (Float) 0.5 + File (Str) pdb4hhb.ent + MinVote (Int) 8 + END + +USAGE: ACCESSING PARAMETERS VALUES + using the getInt, getFloat and getstring methods one may access the + parameters values. Note that a value will always be returned even if the + parameter is not stored as the same type. The get methods attempt to + convert the parameter type to the requested return type of the method. + The follwing code should produce 3 1's as its output: + EXAMPLE: + Parameters::addParameter("MaxMix", 1); OK added an integer parameter + cout << Parameters::getInt("MaxMix"); + cout << Parameters::getFloat("MaxMix"); + cout << Parameters::getstring("MaxMix"); + END + Also note that parameters names are case sensitive. + +USAGE: SUBCLASSING AND PERFORMANCE + The Parameters engine keeps the parameters in a sorted list. Although + finding a parameter and its value in this list is considerably fast most + users will not want this overhead of searching for the parameter using + string comparisons inside their main loops, as part of a code which can be + executed a great number of times. + The idea is to subclass the Parameters class and hold the values which + require direct and fast access in seperate static variables. All parameters + are accessed not throguh the getParameter methods but rather through + specialized methods of the subclass. The following is an example of such an + implementation. Notice the readParameters method. + EXAMPLE: + static int min_vote = 8; // Default values + static float cube_size = 1.0; + + class ProgParams : protected Parameters + { + int minVote() { return min_vote }; + + float cubeSize() { return cube_size }; + + // file name is not held in static variable. Don't care about parameter + // access time. + string fileName() { return getstring("FileName"); } + + int readParameters(char* paramsfile) { + addParameter("MinVote", min_vote); + addParameter("CubeSize", cube_size); + + ifstream params(paramsfile); + Parameters::readParameters(params); + params.close(); + + min_vote = getInt("MinVote"); + cube_size = getFloat("CubeSize"); + } + } + END +*/ +class Parameters +{ +public: + //// Used by the paramType method. See below. + enum ParamType { Undef, Int, Float, Str }; + + //// readParameters recieves an input stream and reads parameters off this + // input stream. See the usage section for details of how a parameters + // file may be structured. + static void readParameters(std::istream& paramStream); + + //// + // Returns true if no parameters are defined.
+ static bool empty(); + + // GROUP: Setting parameters + + //// Adds an integer parameter. The integer value added will actually be + // stored as an integer. Subsequent updates to the same parameter using + // updateParameter will all be stored as integers. + static void addParameter(const std::string& paramName, const int value); + + //// Adds a float parameter. The float value added will actually be + // stored as a float. Subsequent updates to the same parameter using + // updateParameter will all be stored as floats. + static void addParameter(const std::string& paramName, const double value); + + //// Adds a string parameter. The string value added will actually be + // stored as a string. Subsequent updates to the same parameter using + // updateParameter will all be stored as strings. + static void addParameter(const std::string& paramName, const std::string& value); + + //// Update the parameter value without changing the parameter type. The + // value parameter is converted to the parameter's type if this parameter + // already exists. If the parameter is not yet listed then updateParameter + // adds a new parameter of string type. + static void updateParameter(const std::string& paramName, + const char* const value); + + // GROUP: Getting parameters values. + + //// Returns the storage type of the given parameter. If a parameter + // of the given name does not exist then Undef is returned. See enum + // ParamType above for possible return values. + static ParamType paramType(const std::string& paramName); + + //// Gets the integer value of a given parameter. If parameter is not of + // integer type then its value is converted to integer. If parameter does + // not exist a 0 is returned. + static int getInt(const std::string& paramName, const int& defaultValue=0); + + //// Gets the float value of a given parameter. If parameter is not of + // float type then its value is converted to float. If parameter does + // not exist a 0 is returned. + static float getFloat(const std::string& paramName, const float& defaultValue=0.0); + + //// Gets the string value of a given parameter. If parameter is not of + // string type then its value is converted to string. If parameter does + // not exist an empty string is returned. + static std::string getString(const std::string& paramName, const std::string& defaultValue=std::string()); + + // GROUP: Other methods + + + + //// Output all listed parameters. Used for debugging. + static void dump(std::ostream& out); + + //// Output all listed parameters. Used for debugging. + static void dump(DebugStream& out, const unsigned int msgLevel); + + //// + // Output all listed parameters.
+ static void dump(FILE* outputFile); + + //// A utility method. nextToken recieves an argument string, finds the first + // white-space delimited token in this string and returns it while cutting + // this token off of the argument string (It it passed by reference). Tokens + // are returned without any spaces. This method may be used repetitively to + // tokenize a string. + static std::string nextToken(std::string& str); + +protected: + //// Constructor is protected since all methods are static. No need to + // actually form an instance of this class. + Parameters(); +}; + +#endif diff --git a/model/CombinatorialAssembler/libs_gamb/Particle.cc b/model/CombinatorialAssembler/libs_gamb/Particle.cc new file mode 100644 index 0000000000000000000000000000000000000000..5dd80d9ee457a29f53a721eab84616ad7d4e3373 --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/Particle.cc @@ -0,0 +1,57 @@ +#include "Particle.h" +#include "PDB.h" + +Particle::Particle(): pos() {}; +Particle::Particle(const Vector3& newPos) : pos(newPos) {}; +Particle::Particle(const char* const PDBrec) : + pos(PDB::atomXCoord(PDBrec), + PDB::atomYCoord(PDBrec), + PDB::atomZCoord(PDBrec)) {} + + +Vector3 Particle::position() const { + return pos; +} + +void Particle::moveto(const Vector3 &v) { + pos=v; +} + +Particle::operator Vector3() const { + return pos; +} + +Particle::operator const Vector3&() const +{ + return pos; +} + +Particle& Particle::operator*=(const Matrix3& lt) { + pos = lt*pos; + return *this; +} + +Particle& Particle::operator*=(const RigidTrans3& rt) { + pos = rt*pos; + return *this; +} + +Particle& Particle::operator+=(const Vector3 &v) { + pos+=v; + return *this; +} + +Particle& Particle::operator-=(const Vector3 &v) { + pos-=v; + return *this; +} + +Particle& Particle::operator*=(const float &m) { + pos*=m; + return *this; +} + +Particle& Particle::operator/=(const float &m) { + pos/=m; + return *this; +} diff --git a/model/CombinatorialAssembler/libs_gamb/Particle.h b/model/CombinatorialAssembler/libs_gamb/Particle.h new file mode 100644 index 0000000000000000000000000000000000000000..cb0a283161e640725eac74fa2a16de1ab4b186d9 --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/Particle.h @@ -0,0 +1,113 @@ +#ifndef _Particle_h + +#define _Particle_h + +#include "RigidTrans3.h" +/* +CLASS + Particle + + A class meant to serve as a basis for particle types such as atoms, molecular + surface points etc. This class's only property is position and operators to + alter the Particle's position are supplied. Users of the class are encouraged + to use the class as an inheritance base in creating their Particle types. + +KEYWORDS + vector, position, algebra, linear, rigid, matrix, distance, match + +AUTHORS + Meir Fuchs. (meirfux@math.tau.ac.il) + + Copyright: SAMBA group, Tel-Aviv Univ. Israel, 1997. + +CHANGES LOG +
    +
  • +
+ +GOALS + Class Particle is supposed to serve as a base-class for particle type classes + such as atoms or surface points. The class may be constructed in the usual + manner supplying the particle's position or it may be constructed using a + PDB format ATOM record. The class hosts a set of operators used to + shift particle positions. + +USAGE + Below is a piece of code used to read in PDB atoms, shift their positions + using a rigid transformation and print their new positions to the standard + output. + EXAMPLE + RigidTrans3 rt = ....; // rigid transformation = whatever + ifstream PDBfile("pdb1tpo.ent"); // open PDB file (e.g. 1TPO) + char line[85]; + while (!PDBfile.eof()) { + PDBfile.getline(line, 85); // Get a PDB file record. + if (PDB::isATOMrec(line)) { // check that the PDB rec is an ATOM record + Particle p(line); // Construct a Particle using ATOM record. + p*= rt; // Shift particle's position using a rigid + cout << p.position << '\n'; // transformation and print new position. + } + } + END +*/ +class Particle +{ +public: + //// Creates a Particle with 0-position. + Particle(); + //// Creates a particle with given position NewPos. + explicit Particle(const Vector3& newPos); + + //// Constructs a Particle using a PDB ATOM record. The constructor assumes + // that the given string is an ATOM record. If this is not so the object + // constructed may hold faulty data. + explicit Particle(const char* const PDBrec); + + //// Returns particle's position. + Vector3 position() const; + + //// Moves particle to a new position. + void moveto(const Vector3 &); + + //// Default Vector3 cast. When vector3 cast is used the particle's position + // is returned. + operator Vector3() const; + + //// Default Vector3 cast. When vector3 cast is used the particle's position + // is returned. + operator const Vector3&() const; + + //// Shift particle's position using a linear transformation. + Particle& operator*=(const Matrix3 &); + + //// Shift particle's position using a rigid transformation. + Particle& operator*=(const RigidTrans3 &); + + //// Shift particle's position using vector addition. + Particle& operator+=(const Vector3 &); + + //// Shift particle's position using vector subtraction. + Particle& operator-=(const Vector3 &); + + //// Shift particle's position using scalar multiplication. + Particle& operator*=(const float &); + + //// Shift particle's position using scalar division. + Particle& operator/=(const float &); + +protected: + // changed to protected by Zipi Fligelman in order to make this information availabnle to descendant + Vector3 pos; +}; + +#endif + + + + + + + + + + diff --git a/model/CombinatorialAssembler/libs_gamb/RigidTrans3.cc b/model/CombinatorialAssembler/libs_gamb/RigidTrans3.cc new file mode 100644 index 0000000000000000000000000000000000000000..ca9fe6d130a3d78ed9f7940d9b7f8b13cd432168 --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/RigidTrans3.cc @@ -0,0 +1,51 @@ +#include +#include "RigidTrans3.h" + +typedef RigidTrans3::real real; + + +RigidTrans3::RigidTrans3(const Vector3 &o, const Vector3 &u, const Vector3 &v) +{ + trans = (o+u+v)/3.0; + Vector3 x = trans - o; + x/= x.norm(); + Vector3 z = (v-o)&(v-u); + z/= z.norm(); + Vector3 y = z & x; + rot = Matrix3(x,y,z); + rot.transpose(); +} + +RigidTrans3::RigidTrans3(const Vector3 &u ,const Vector3 &w ,const Vector3 &base , int) +{ + trans=base; + Vector3 x = u-trans; + Vector3 z = x&(w-trans); + Vector3 y = z&x; + x/= x.norm(); + y/= y.norm(); + z/= z.norm(); + rot = Matrix3(x,y,z); + rot.transpose(); +} + +RigidTrans3::RigidTrans3(int , const Vector3 &u ,const Vector3 &w ,const Vector3 &base) +{ + trans=base; + Vector3 x = u-trans; + Vector3 z = x&(w-trans); + Vector3 y = z&x; + x/= x.norm(); + y/= y.norm(); + z/= z.norm(); + rot = Matrix3(x,y,z); + trans = -1*(rot*trans); +} + +std::istream& operator>>(std::istream& s, RigidTrans3& rt) +{ + Vector3 rotvec; + s >> rotvec >> rt.trans; + rt.rot=Matrix3(rotvec[0], rotvec[1], rotvec[2]); + return s; +} diff --git a/model/CombinatorialAssembler/libs_gamb/RigidTrans3.h b/model/CombinatorialAssembler/libs_gamb/RigidTrans3.h new file mode 100644 index 0000000000000000000000000000000000000000..5192836e606e2bb34d46c4d305f170fecba28d65 --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/RigidTrans3.h @@ -0,0 +1,211 @@ +#ifndef _RigidTrans3_h +#define _RigidTrans3_h + +#include "Matrix3.h" +#include "Rotation3.h" + +#include + +/* +CLASS + RigidTrans3 + + Defines 3-dimensional rigid transformations class. Rigid transformations + are comprised of a rotation followed by a translation of the vector space. + +KEYWORDS + rigid, linear, rotation, algebra, translation, vector, matrix + +AUTHORS + Meir Fuchs (meirfux@math.tau.ac.il). + + Copyright: SAMBA group, Tel-Aviv Univ. Israel, 1997. + +CHANGES LOG +
    +
  • +
+ +GOALS + The RigidTrans3 class was designed to represent a rigid transformation of + 3 dimensional space. A rigid transformation is a rotation and then a + translation of vector space. The class is based upon the Matrix3 class and + the Vector3 class. Although 3-dimensional rotations can be represented + more compactly using 3 rotational angles instead of 9 rotational matrix + elements, the latter was chosen for considerations of speed and performance. + +USAGE + RigidTrans3 is comprised of a rotation and a translation. The basic + constructor requires a rotation specified by either rotational angles + or by a rotational matrix and a translation specified by a vector. + EXAMPLE + RigidTrans3 T(Vector3(pi, pi, pi/2), // rotation + Vector3(0, 0, 1)); // translation + END + A second method of construction is used in reference of base construction. + Given an ordered set of 3 points one can construct an axes-system based + on these points. A constructor that recieves 3 points returns the + RigidTrans3 that transforms the normal axes to the point-based axes system. + + Rigid transformations form a group. We can talk of multiplying or + applying one rigid transformation on top of the other and we can talk + about the inverse of a rigid transformation. These operators are supplied. + EXAMPLE + RigidTrans3 T(Vector3(pi/2, 0, 0), Vector3(1, 0, 0)); + RigidTrans3 S(Vector3(-pi/2, 0, 0), Vector3(-1, 0, 0)); + RigidTrans3 I=T*S // This gives the identity rigid trans since T=!S + RigidTrans3 J=!I // J should equal I (or be very close to it). + END +*/ +class RigidTrans3 +{ + +public: + + // GROUP: real definition. + + //// real is defined to be float by default. By changing real in Vector3 to + // double one may change the whole library to work with double precision. + typedef Vector3::real real; + + // GROUP: Contructors. + + //// Instantiates to a an identity rigid transformation. i.e. no rotation and + // 0 translation. + RigidTrans3() + : rot(1), trans() + {} + + //// Instantiate a rigid transformation using a linear transformation as + // rotation and a vector for translation. The int is a dummy int argument + // used to set this constructor apart from the identically defined public + // constructor. Note that the matrix itself is not used but rather + // its rotational parameters, so if a non-rotational matrix is + // given, a different, rotational Matrix3 will be used in the RigidTrans3 object. + //Warning: This function is deprecated + RigidTrans3(const Matrix3& r, const Vector3& t, int) + : rot(Rotation3(r)), trans(t) + {} + + //// Instantiate a rigid transformation using a linear transformation as + // rotation and a vector for translation. This constructor is used + // to instantiate a RigidTrans3 with a given rotational matrix and + // translational vector without verifying that the matrix is rotational. + RigidTrans3(const Matrix3& r, const Vector3& t) : + rot(r), trans(t) + {} + + //// Instanitiates a rigid transformation using a Rotation3 object and a + // translational vector. + RigidTrans3(const Rotation3& r, const Vector3& t) + : rot(r), trans(t) + {} + + //// Instanitiates a rigid transformation using the 3 angles given in the + // rotational vector and the translational vector. The rotational vector is + // assumes rotations are in the order: around X (Y,Z plane rotation) - + // around Y (X,Z plane rotation) - around Z (X,Y plane roation). + RigidTrans3(const Vector3& r, const Vector3& t) + : rot(r[0], r[1], r[2]) , trans(t) + {} + + //// Returns the rigid transformations which transforms the natural axes to + // the axis system created by u,v,w. The x-axis lies on c-u, (where c is the + // mass center of u,v,w,). The z-axis is perpendicular to the u,v,w + // plane and the y-axis is perpendicular to both x and z. + RigidTrans3(const Vector3& u, const Vector3& v, const Vector3& w); + + + //// Returns the rigid transformations which transforms the natural axes to + // the axis system created by u,w and base (where 'base' is the axis center). + // The x-axis lies on u-base , the z-axis is + // perpendicular to the u-base , w-base plane and the y-axis is perpendicular + // to both x and z.(i.e the first variable is set by default as the local x-axis) + // The int is a dummy int argument used to set this constructor apart from + // the identically defined public constructor. + RigidTrans3(const Vector3& u ,const Vector3& w ,const Vector3& base , int); + + + //// Returns the rigid transformations which transforms the new axis system + // created by u,w and base (where 'base' is the axis center). + // (this is just inverse of RigidTrans3(const Vector3&,const Vector3&,const Vector3&, int)) + // The x-axis lies on u-base , the z-axis is perpendicular to the u-trans , + // w-trans plane and the y-axis is perpendicular to both x and z + // (i.e the first variable is set by default as the local x-axis) + // The int is a dummy int argument used to set this constructor apart from + // the identically defined public constructors. + RigidTrans3(int , const Vector3 &u ,const Vector3 &w ,const Vector3 &base); + + // GROUP: Inspection and propery methods + + //// Returns linear transformation representing rotation of rigid + // tranformation. + const Matrix3& rotation() const { + return rot; + } + + //// Returns vector with axis rotational angles assuming the order of + // rotation is x-y-z. + Vector3 rotationAngles() const { + return rot.rot(); + } + + //// Returns translation vector of rigid transformation. + const Vector3& translation() const { + return trans; + } + + // GROUP: Operators. + + //// Apply the rigid transformation rn ob vector v. Apply a rotation followed + // by a translation + friend Vector3 operator*(const RigidTrans3 &rt, const Vector3 &v) { + return (rt.rot * v) + rt.trans; + } + + //// Apply the rigid transformation rn ob vector v. Apply a rotation followed + // by a translation + friend Vector3& operator*=(Vector3 &v,const RigidTrans3 &rt) { + v=(rt.rot * v) + rt.trans; + return v; + } + + + + //// multiply two rigid transformation such that for any vector v + // (rt1*rt2)*v = rt1*(rt2*v) + friend RigidTrans3 operator*(const RigidTrans3 &rt1, + const RigidTrans3 &rt2) { + return RigidTrans3(rt1.rot * rt2.rot, rt1*(rt2.trans)); + } + + //// Gives the inverse of a rigid transformation such that for any vector v + // (rt*!rt)*v = rt*(!rt*v) = v and rt*!rt = RigidTrans3(). + friend RigidTrans3 operator!(const RigidTrans3 &rt) { + Matrix3 inv = rt.rot.transposeMatrix(); + return RigidTrans3(inv, -(inv*(rt.trans))); + } + + //// prints 3 rotations in X-Y-Z order (see above) and translation vector. + friend std::ostream& operator<<(std::ostream& s, const RigidTrans3 &rt) { + return s << rt.rotationAngles() << ' ' << rt.trans; + } + + //// reads 3 rotations in X-Y-Z order (see above) and translation vector. + friend std::istream& operator>>(std::istream& s, RigidTrans3& rt); + + //// + // Prints the three rotation angles in X-Y-Z order and the translation vector
+ // Author: Oranit Dror (oranit@tau.ac.il) + void output(FILE* outputFile) const { + rotationAngles().output(outputFile); + fprintf(outputFile, " "); + trans.output(outputFile); + } + +private: + Matrix3 rot; // rotation + Vector3 trans; // translation +}; + +#endif diff --git a/model/CombinatorialAssembler/libs_gamb/Rotation3.cc b/model/CombinatorialAssembler/libs_gamb/Rotation3.cc new file mode 100644 index 0000000000000000000000000000000000000000..8e3b28de3f4a180169f08c1ebba03a6fd1b7c46a --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/Rotation3.cc @@ -0,0 +1,315 @@ +#include "Rotation3.h" +#include +#include "macros.h" + +typedef Rotation3::real real; + +real Rotation3::matrix11() const { + return sqr(quat[0]) + sqr(quat[1]) - sqr(quat[2]) - sqr(quat[3]); +} +real Rotation3::matrix12() const { + return 2*(quat[1]*quat[2] - quat[0]*quat[3]); +} +real Rotation3::matrix13() const{ + return 2*(quat[2]*quat[3] + quat[0]*quat[2]); +} +real Rotation3::matrix21() const { + return 2*(quat[1]*quat[2] + quat[0]*quat[3]); +} +real Rotation3::matrix22() const { + return sqr(quat[0]) - sqr(quat[1]) + sqr(quat[2]) - sqr(quat[3]); +} +real Rotation3::matrix23() const { + return 2*(quat[2]*quat[3] - quat[0]*quat[1]); +} +real Rotation3::matrix31() const { + return 2*(quat[1]*quat[3] - quat[0]*quat[2]); +} +real Rotation3::matrix32() const { + return 2*(quat[2]*quat[3] + quat[0]*quat[1]); +} +real Rotation3::matrix33() const { + return sqr(quat[0]) - sqr(quat[1]) - sqr(quat[2]) + sqr(quat[3]); +} + +Rotation3::Rotation3() +{ + quat[0] = 1; // quaternion 1, 0, 0, 0 is the identity rotation. + quat[1] = quat[2] = quat[3] = 0.0; +} + + +Rotation3::Rotation3(const Matrix3& m) +{ + quat[0] = fabs(1+m[0][0]+m[1][1]+m[2][2])/4; + quat[1] = fabs(1+m[0][0]-m[1][1]-m[2][2])/4; + quat[2] = fabs(1-m[0][0]+m[1][1]-m[2][2])/4; + quat[3] = fabs(1-m[0][0]-m[1][1]+m[2][2])/4; + + // make sure quat is normalized. + real sum = quat[0]+quat[1]+quat[2]+quat[3]; + quat[0] = sqrt(quat[0]/sum); + quat[1] = sqrt(quat[1]/sum); + quat[2] = sqrt(quat[2]/sum); + quat[3] = sqrt(quat[3]/sum); + + if (m[2][1]-m[1][2] < 0.0) quat[1] = -quat[1]; + if (m[0][2]-m[2][0] < 0.0) quat[2] = -quat[2]; + if (m[1][0]-m[0][1] < 0.0) quat[3] = -quat[3]; +} + +Rotation3::Rotation3(const real &xr, const real &yr, const real &zr) +{ + real cx = cos(xr); real cy = cos(yr); real cz = cos(zr); + real sx = sin(xr); real sy = sin(yr); real sz = sin(zr); + real m00 = cz*cy; + real m11 = -sy*sx*sz + cx*cz; + real m22 = cy*cx; + + quat[0] = (real)(sqrt(1+m00+m11+m22)/2.0); + quat[1] = (real)(sqrt(1+m00-m11-m22)/2.0); + quat[2] = (real)(sqrt(1-m00+m11-m22)/2.0); + quat[3] = (real)(sqrt(1-m00-m11+m22)/2.0); + + if (cy*sx + sy*cx*sz + sx*cz < 0.0) quat[1] = -quat[1]; + if (sz*sx - sy*cx*cz - sy < 0.0) quat[2] = -quat[2]; + if (sz*cy + sy*sx*cz + sz*cx < 0.0) quat[3] = -quat[3]; + + // vrow[0] = Vector3(cz*cy, -sy*sx*cz - sz*cx, -sy*cx*cz + sz*sx); + // vrow[1] = Vector3(sz*cy, -sy*sx*sz + cx*cz, -sy*cx*sz - sx*cz); + // vrow[2] = Vector3(sy, cy*sx, cy*cx ); +} + +Rotation3::Rotation3(const real a, const real b, const real c, const real d) +{ + real sum = sqrt(a*a + b*b + c*c + d*d); + if (a<0.0) sum = -sum; + quat[0] = a/sum; + quat[1] = b/sum; + quat[2] = c/sum; + quat[3] = d/sum; +} + +Rotation3::Rotation3(const real a, const real b, const real c, const real d, + int) +{ + quat[0] = a; + quat[1] = b; + quat[2] = c; + quat[3] = d; +} + +Rotation3::Rotation3(const real m[3][3]) +{ + quat[0] = fabs(1+m[0][0]+m[1][1]+m[2][2])/4; + quat[1] = fabs(1+m[0][0]-m[1][1]-m[2][2])/4; + quat[2] = fabs(1-m[0][0]+m[1][1]-m[2][2])/4; + quat[3] = fabs(1-m[0][0]-m[1][1]+m[2][2])/4; + + // make sure quat is normalized. + real sum = quat[0]+quat[1]+quat[2]+quat[3]; + quat[0] = sqrt(quat[0]/sum); + quat[1] = sqrt(quat[1]/sum); + quat[2] = sqrt(quat[2]/sum); + quat[3] = sqrt(quat[3]/sum); + + if (m[2][1]-m[1][2] < 0.0) quat[1] = -quat[1]; + if (m[0][2]-m[2][0] < 0.0) quat[2] = -quat[2]; + if (m[1][0]-m[0][1] < 0.0) quat[3] = -quat[3]; +} + +Rotation3::Rotation3(const double m[3][3]) +{ + quat[0] = (real)fabs(1+m[0][0]+m[1][1]+m[2][2])/4; + quat[1] = (real)fabs(1+m[0][0]-m[1][1]-m[2][2])/4; + quat[2] = (real)fabs(1-m[0][0]+m[1][1]-m[2][2])/4; + quat[3] = (real)fabs(1-m[0][0]-m[1][1]+m[2][2])/4; + + // make sure quat is normalized. + real sum = quat[0]+quat[1]+quat[2]+quat[3]; + quat[0] = sqrt(quat[0]/sum); + quat[1] = sqrt(quat[1]/sum); + quat[2] = sqrt(quat[2]/sum); + quat[3] = sqrt(quat[3]/sum); + + if (m[2][1]-m[1][2] < 0.0) quat[1] = -quat[1]; + if (m[0][2]-m[2][0] < 0.0) quat[2] = -quat[2]; + if (m[1][0]-m[0][1] < 0.0) quat[3] = -quat[3]; +} + +Rotation3::Rotation3(const Vector3 &v1, const Vector3 &v2, const Vector3 &v3) +{ + quat[0] = fabs(1+v1[0]+v2[1]+v3[2])/4; + quat[1] = fabs(1+v1[0]-v2[1]-v3[2])/4; + quat[2] = fabs(1-v1[0]+v2[1]-v3[2])/4; + quat[3] = fabs(1-v1[0]-v2[1]+v3[2])/4; + + // make sure quat is normalized. + real sum = quat[0]+quat[1]+quat[2]+quat[3]; + quat[0] = sqrt(quat[0]/sum); + quat[1] = sqrt(quat[1]/sum); + quat[2] = sqrt(quat[2]/sum); + quat[3] = sqrt(quat[3]/sum); + + if (v3[1]-v2[2] < 0.0) quat[1] = -quat[1]; + if (v1[2]-v3[0] < 0.0) quat[2] = -quat[2]; + if (v2[0]-v1[1] < 0.0) quat[3] = -quat[3]; +} + +real Rotation3::trace() const +{ + return sqr(quat[0]) - sqr(quat[1]) - sqr(quat[2]) - sqr(quat[3]); +} + +const real Rotation3::operator[](const unsigned short r) const +{ + return quat[r]; +} + +Vector3 Rotation3::row(const unsigned short r) const +{ + switch (r) { + case 1: return Vector3(matrix11(), matrix12(), matrix13()); + case 2: return Vector3(matrix21(), matrix22(), matrix23()); + case 3: return Vector3(matrix31(), matrix32(), matrix33()); + } + return Vector3(); +} + +Vector3 Rotation3::column(const unsigned short col) const +{ + switch (col) { + case 1: return Vector3(matrix11(), matrix21(), matrix31()); + case 2: return Vector3(matrix12(), matrix22(), matrix32()); + case 3: return Vector3(matrix13(), matrix23(), matrix33()); + } + return Vector3(); +} + +real Rotation3::element(const unsigned short r, + const unsigned short col) const +{ + switch (r) { + case 1: + switch(col) { + case 1: return matrix11(); + case 2: return matrix12(); + case 3: return matrix13(); + } + case 2: + switch(col) { + case 1: return matrix21(); + case 2: return matrix22(); + case 3: return matrix23(); + } + case 3: + switch(col) { + case 1: return matrix31(); + case 2: return matrix32(); + case 3: return matrix33(); + } + } + return 0.0; +} + +real Rotation3::rotX() const +{ + return atan2(matrix32(), matrix33()); +} + +real Rotation3::rotY() const +{ + return atan2(matrix31(), sqrt(sqr(matrix21())+sqr(matrix11()))); +} + +real Rotation3::rotZ() const +{ + return atan2(matrix21(), matrix11()); +} + +Vector3 Rotation3::rotationAngles() const +{ + return Vector3(rotX(), rotY(), rotZ()); +} + +Rotation3::operator Matrix3() const +{ + const real a = quat[0]; + const real b = quat[1]; + const real c = quat[2]; + const real d = quat[3]; + return Matrix3(a*a+b*b-c*c-d*d, 2*(b*c-a*d) , 2*(b*d+a*c), + 2*(b*c+a*d) , a*a-b*b+c*c-d*d, 2*(c*d-a*b), + 2*(b*d-a*c) , 2*(c*d+a*b) , a*a-b*b-c*c+d*d); +} + +real Rotation3::dist2(const Rotation3& r) const +{ + return (sqr(quat[0]-r.quat[0]) + sqr(quat[1]-r.quat[1]) + + sqr(quat[2]-r.quat[2]) + sqr(quat[3]-r.quat[3])); +} + +real Rotation3::dist(const Rotation3& r) const +{ + return sqrt(dist2(r)); +} + +std::pair Rotation3::getAxisAndAngle() const +{ + real a, b, c, d; + a = quat[0]; + b = quat[1]; + c = quat[2]; + d = quat[3]; + if (fabs(a) > .9999) return std::make_pair(Vector3(1, 0, 0), 0.0); + double angle = acos(a) * 2; + double s = sin(angle / 2); + Vector3 axis(b / s, c / s, d / s); + return std::make_pair(axis.getUnitVector(), angle); +} + +Rotation3 operator*(const Rotation3 &q, const Rotation3 &p) +{ + return Rotation3(q[0]*p[0] - q[1]*p[1] - q[2]*p[2] - q[3]*p[3], + q[0]*p[1] + q[1]*p[0] + q[2]*p[3] - q[3]*p[2], + q[0]*p[2] - q[1]*p[3] + q[2]*p[0] + q[3]*p[1], + q[0]*p[3] + q[1]*p[2] - q[2]*p[1] + q[3]*p[0], + 0); +} + + +Vector3 operator*(const Rotation3 &t, const Vector3 &v) +{ + const real a = t.quat[0]; + const real b = t.quat[1]; + const real c = t.quat[2]; + const real d = t.quat[3]; + return Vector3((a*a+b*b-c*c-d*d)*v[0] + 2*(b*c-a*d)*v[1] + 2*(b*d+a*c)*v[2], + 2*(b*c+a*d)*v[0] + (a*a-b*b+c*c-d*d)*v[1] + 2*(c*d-a*b)*v[2], + 2*(b*d-a*c)*v[0] + 2*(c*d+a*b)*v[1] + (a*a-b*b-c*c+d*d)*v[2]); +} + +Rotation3& Rotation3::operator*=(const Rotation3 &p) +{ + real a = quat[0]; + real b = quat[1]; + real c = quat[2]; + real d = quat[3]; + + quat[0] = a*p[0] - b*p[1] - c*p[2] - d*p[3]; + quat[1] = a*p[1] + b*p[0] + c*p[3] - d*p[2]; + quat[2] = a*p[2] - b*p[3] + c*p[0] + d*p[1]; + quat[3] = a*p[3] + b*p[2] - c*p[1] + d*p[0]; + return *this; +} + +Rotation3 operator!(const Rotation3 &t) +{ + return Rotation3(t.quat[0], -t.quat[1], -t.quat[2], -t.quat[3], 0); +} + +std::ostream& operator<<(std::ostream& s, const Rotation3 &t) +{ + return (s << t.quat[0] << ' ' << t.quat[1] << ' ' << t.quat[2] << ' ' + << t.quat[3]); +} diff --git a/model/CombinatorialAssembler/libs_gamb/Rotation3.h b/model/CombinatorialAssembler/libs_gamb/Rotation3.h new file mode 100644 index 0000000000000000000000000000000000000000..0abc2f39c8bfe5f66685205951d9a543df609b6b --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/Rotation3.h @@ -0,0 +1,187 @@ +#ifndef _Rotation3_h + +#define _Rotation3_h + +#include +#include "numerics.h" +#include "Matrix3.h" + +/* +CLASS + Rotation3 + + Defines a rotation of 3-dimensional space. + +KEYWORDS + linear, algebra, matrix, real, vector, rotation, inverse, angle, quaternion + +AUTHORS + Meir Fuchs. (meirfux@math.tau.ac.il) + + Copyright: SAMBA group, Tel-Aviv Univ. Israel, 1997. + +CHANGES LOG +
    +
  • +
+ +GOALS + Rotation3 hold a 3-dimensional rotation compactly. Using quaternions a + rotation may be held compactly without sacrificing much on the performance + side. Rotation3 should support most of the methods implemented in Matrix3. + +USAGE + Rotation3 hosts a set of constructors, operators and inspection methods. + Below are just several examples of how one may use the class. + EXAMPLE + Rotation3 ra(1.0, 1.1, -1.3); // rotation angles. + Rotation3 rb(0.9, -2.3, 0.1); // rotation angles. + + Matrix3 a= ra; + Matrix3 b= rb; + + Rotation3 rab = ra*rb; + Matrix3 ab = a*b; // rab should be = to ab; + + Vector3 v(1.0, 2.0, 3.0); + cout !rab*v; + END +*/ +class Rotation3 { + +public: + // GROUP: define real type + + //// real is defined using the real defined in Vector3 class. By default + // this is of type float. For example , changing Vector3::real's defintion + // to double will turn the gamb library's vector operations to double + // precision. + typedef Vector3::real real; + + // GROUP: Constructors + + //// Default constructor: 0 matrix. + Rotation3(); + + //// Instantiate a rotation using 4 quaternion parameters. + Rotation3(const real a, const real b, const real c, const real d); + + //// Instantiate a Rotation3 using a rotation matrix. If the matrix is not + // a rotation matrix the parameters will be normalized. + explicit Rotation3(const Matrix3& m); + + //// Creates a Rotation3 object. given rotational angles around the + // x axis (xr), around the y axis (yr) and around the z axis (zr) a + // linear transformation that rotates space in the order x-y-z is created. + // Rotation in this order is the default representation for the gamb++ + // library rotational transformations. + Rotation3(const real &xr, const real &yr, const real &zr); + + //// Use a 3x3 real 2D array matrix to initialize a Rotation3. If matrix is + // not a rotation it is "normalized". + explicit Rotation3(const real m[3][3]); + + //// Use a 3x3 double prec. 2D array matrix to initialize a Rotation3. If + // matrix is not a rotation it is "normalized". + explicit Rotation3(const double m[3][3]); + + //// Constructs a Rotation3 using the 3 given matrix row vectors. If matrix + // is not a rotation it is "normalized". + Rotation3(const Vector3 &v1, const Vector3 &v2, const Vector3 &v3); + + // GROUP: Inspection methods and properties. + + //// Returns the ith element of quaternion representing the rotation. index + // may take values from 0 to 3. Otherwise results are undefined. + const real operator[](const unsigned short index) const; + + //// Returns a vector matching the r'th row of the rotation matrix + // representing the rotation object . r must be in the range 1..3 or run + // time errors may occur. + Vector3 row(const unsigned short r) const; + + //// Returns a vector matching the col'th column of the rotation matrix + // representing the rotation object . r must be in the range 1..3 or run + // time errors may occur. + Vector3 column(const unsigned short col) const; + + //// Returns the numerical value at position row, col in the 3X3 matrix + // representing the rotation. + // row, col should be within the range of 1..3 or run time errors may occur. + /// Use the matrix(ij) familiy of methods for faster access. + real element(const unsigned short r, const unsigned short col) const; + + real matrix11() const; + real matrix12() const; + real matrix13() const; + real matrix21() const; + real matrix22() const; + real matrix23() const; + real matrix31() const; + real matrix32() const; + real matrix33() const; + + //// Returns the rotation around the x-axis for a rotational matrix assuming + // rotations are performed in the order of x-y-z. + real rotX() const; + + //// Returns the rotation around the y-axis for a rotational matrix assuming + // rotations are performed in the order of x-y-z. + real rotY() const; + + //// Returns the rotation around the z-axis for a rotational matrix assuming + // rotations are performed in the order of x-y-z. + real rotZ() const; + + //// Returns 3 rotation angles assuming + // rotations are performed in the order of x-y-z. + Vector3 rotationAngles() const; + + //// Returns the trace of the rotation matrix representing the rotation. + real trace() const; + + //// Returns squared distance between 2 rotations. Currently this is + // implemented by taking the euclidean distance between the 4 parameters of + // the quternion. + real dist2(const Rotation3& r) const; + + //// Returns distance between 2 rotations. Currently this is + // implemented by taking the euclidean distance between the 4 parameters of + // the quternion. + real dist(const Rotation3& r) const; + + std::pair getAxisAndAngle() const; + + + // GROUP: Operators + + //// Convert a Rotation3 object to Matrix3 type. + operator Matrix3() const; + + //// In place multiplication with a given rotation object. + Rotation3& operator*=(const Rotation3 &); + + //// Multiplication of two rotations. + friend Rotation3 operator*(const Rotation3&, const Rotation3&); + + //// Inverse rotation. + friend Rotation3 operator!(const Rotation3&); + + //// Rotation by vector multiplication. + friend Vector3 operator*(const Rotation3&, const Vector3&); + + //// Output rotation in quaternion format. + friend std::ostream& operator<<(std::ostream& s, const Rotation3 &t); + +protected: + + //// Instantiate a rotation using 4 quaternion parameters. This version + // differs from the public version. It dows not normalize the quaternion + // and does not make sure the 4 parameters actually represent a rotation. + Rotation3(const real a, const real b, const real c, const real d, int); + +private: + real quat[4]; +}; + +#endif diff --git a/model/CombinatorialAssembler/libs_gamb/Shou.cc b/model/CombinatorialAssembler/libs_gamb/Shou.cc new file mode 100644 index 0000000000000000000000000000000000000000..16b0ad39af511839120c8b03b705127bad9cef85 --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/Shou.cc @@ -0,0 +1,106 @@ +#include +#include +#include "Shou.h" + + +bool Shou::isRec(const char* const shouRec) +{ + return (atoi(shouRec) != 0); +} + +unsigned short Shou::atomIndex(const char *const shouRec, + unsigned int atom) +{ + unsigned short fieldLength=5; + std::string num; + + num.resize(fieldLength + 1); + for(int i=0; i +
  • + + +GOALS + Class Shou is designed to encapsulate a set of methods used for reading the + Shou file format. Methods are static requiring no object instantiation + before their use. + +USAGE + The class was written as a takeoff on the PDB class used to read the PDB + format files. Basically the class encapsulates a set fo methods to extract + data from a single line found in the Shou surface file. The class's methods + are all static, they all recieve a char. string as input and they all return + the requested data as output. +*/ +class Shou +{ +public: + //// Checks that the shouRec string is actually a record. This will rule out + // all REMARKs and # lines. + static bool isRec(const char* const shouRec); + + static const unsigned short xCoordField = 15; + static const unsigned short yCoordField = 23; + static const unsigned short zCoordField = 31; + static const unsigned short surfaceField = 39; + static const unsigned short xNormField = 47; + static const unsigned short yNormField = 54; + static const unsigned short zNormField = 61; + + //// returns the 1 (for caps), 2 (for belts) or 3 (for pits) atoms + // involved in the definition of the surface point. The atom parameter + // may assume values of 0..2. For each such value the method will return + // a different atom index involved in the definition of the critical point. + // For caps and belts which are defined by less then 3 atoms 0s will be + // returned for the upper indices. + static unsigned short atomIndex(const char *const shouRec, + unsigned int atom); + + //// Returns x coordinate of the surface point. + static float xCoord(const char* const shouRec); + + //// Returns y coordinate of the surface point. + static float yCoord(const char* const shouRec); + + //// Returns z coordinate of the surface point. + static float zCoord(const char* const shouRec); + + //// Returns surface area of element represented by vector. + static float surface(const char* const shouRec); + + //// Returns x coordinate of the surface normal. + static float xNorm(const char* const shouRec); + + //// Returns y coordinate of the surface normal. + static float yNorm(const char* const shouRec); + + //// Returns z coordinate of the surface normal. + static float zNorm(const char* const shouRec); + +private: + + // Class should not be constructed since all methods are static. The class is + // used to encapsulate the methods. + Shou(); +}; + +#endif diff --git a/model/CombinatorialAssembler/libs_gamb/Surface.cc b/model/CombinatorialAssembler/libs_gamb/Surface.cc new file mode 100644 index 0000000000000000000000000000000000000000..3c4ce4f5bb5905ad006969e0b8a6c1114167b450 --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/Surface.cc @@ -0,0 +1,164 @@ +#include "Shou.h" +#include "Surface.h" +#include "Atom.h" + +Surface::Surface() : Molecule() +{} + +int Surface::readShouFile(std::istream& shouStream) { + return readSurfaceFile(shouStream); +} + +int Surface::readSurfaceFile(std::istream& inFile) { + if(isBinaryFile(inFile)) { + //cerr << "BINARY" << endl; + return readBinaryFile(inFile); + } else { + //cerr << "ASCII" << endl; + return readASCIIFile(inFile); + } +} + + +int Surface::readSurfaceFile(const std::string fileName) { + std::ifstream inFile(fileName.c_str()); + if (!inFile) { + std::cerr << "Can't open surface file: " << fileName << std::endl; + exit(1); + } + return readSurfaceFile(inFile); +} + +void Surface::output2PDB(const std::string outFileName) { + std::ofstream outFile(outFileName.c_str()); + unsigned int counter = 0; + for(iterator it = begin(); it != end() ; it++, counter++) { + SurfacePoint::SurfaceType nodeType = (*it).surfaceType(); + unsigned int resId = 10; + switch(nodeType) { + case SurfacePoint::Caps: resId = 10; break; + case SurfacePoint::Pits: resId = 20; break; + case SurfacePoint::Belts: resId = 30; break; + default: break; + } + Atom atom(it->position(), ' ', counter, resId, "PRB", 'X'); + outFile << atom << std::endl; + } +} + +int Surface::readBinaryFile(std::istream& inFile) { + int atoms[3]; + float coord[3]; + float area; + float normal[3]; + + while(!inFile.eof()) { + // read 3 atom numbers + for(int i=0; i<3; i++) inFile.read((char *)&atoms[i], sizeof(int)); + + // read point coordinates + for(int i=0; i<3; i++) inFile.read((char *)&coord[i], sizeof(float)); + + // read area + inFile.read((char *)&area, sizeof(float)); + + // read normal + for(int i=0; i<3; i++) inFile.read((char *)&normal[i], sizeof(float)); + + if(inFile.eof()) break; + + Vector3 point(coord); + Vector3 norm(normal); + SurfacePoint sp(point, norm, area, atoms[0]-1, atoms[1]-1, atoms[2]-1); + push_back(sp); + } + std::cerr << "Surface size: " << size() << std::endl; + return size(); +} + +int Surface::readASCIIFile(std::istream& shouStream) { + while (!shouStream.eof()) { + std::string line; + getline(shouStream, line); + if (Shou::isRec(line.c_str())) + add(SurfacePoint(line.c_str())); + } + return size(); +} + +float Surface::area() const { + float area=0; + for (const_iterator it=begin(); it != end(); ++it) { + area+= it->surfaceArea(); + } + return area; +} + +void Surface::outputVRML(std::ostream& VRMLstream, + const float red, + const float green, + const float blue) const +{ + const char header [] = "#VRML V1.0 ascii"; // VRML 1.0 file header + const char separator[] = "Separator {\n"; // VRML spearator + const char ind[] = " "; // indentation + + VRMLstream << header << '\n' << '\n'; + VRMLstream << separator; + VRMLstream << ind << "Material {\n"; + VRMLstream << ind << ind << "diffuseColor " + << red << ' ' << green << ' ' << blue << '\n'; + VRMLstream << ind << "}\n"; + + VRMLstream << ind << "Coordinate3 {\n"; + VRMLstream << ind << ind << "point [\n"; + for (const_iterator it=begin(); it != end(); ++it) { + if (it != begin()) + VRMLstream << ',' << '\n'; + const Vector3 pos = (*it).position(); + const Vector3 tip = pos + (*it).normal(); + VRMLstream << ind << ind << ind + << pos[0] << ' ' << pos[1] << ' ' << pos[2] << ',' << '\n'; + VRMLstream << ind << ind << ind + << tip[0] << ' ' << tip[1] << ' ' << tip[2]; + } + VRMLstream << '\n'; + VRMLstream << ind << ind << ']' << '\n'; + VRMLstream << ind << '}' << '\n'; + VRMLstream << ind << "IndexedLineSet {\n"; + VRMLstream << ind << ind << "coordIndex [\n"; + + for (unsigned i=0; i +#include "SurfacePoint.h" +#include "Molecule.h" + +/* +CLASS + Surface + + Defines a collection of SurfaceParticle's that make up the + surface of a molecule. The class hosts a reader that reads output out of + a Shou surface file. + +KEYWORDS + surface particle, molecule, atom, vector, linear, rigid, matrix + PDB, Connolly, Shou, rotation, translation + +AUTHORS + Zipi Fligelman (zipo@math.tau.ac.il) + + Copyright: SAMBA group, Tel-Aviv Univ. Israel, 1997. + +CHANGES LOG +
      +
    • +12/01/04 - area method added +
    + +GOALS + Holds a list of surface points. Surfcae points are defined using the surface + point and the normal to the surface at the point. The class is descendant + of Molecule and hosts all the methods supported by the + Molecule template. + +USAGE + +*/ + +class Surface + : public Molecule +{ +public: + + //// Default constructor - empty Surface. + Surface(); + + //// We read a Surface file/s and we add them to the list of surface point + // with the appropriate constructor. + int readShouFile(std::istream& shouStream); + + //// Same as readShouFile + int readSurfaceFile(std::istream& inFile); + + //// + int readSurfaceFile(const std::string fileName); + + //// computes surface area by summing the areas of all the points + float area() const; + + void output2PDB(const std::string outFileName); + + //// Output the molecular surface as a VRML 1.0 model. This function may be + // used to display the molecular surfac as a 3d model. + void outputVRML(std::ostream& VRMLstream, + const float red = 1.0, + const float green = 1.0, + const float blue = 1.0) const; + + friend std::ostream& operator<<(std::ostream& s, const Surface& surf); + +private: + int readBinaryFile(std::istream& inFile); + + int readASCIIFile(std::istream& inFile); + + bool isBinaryFile(std::istream& inFile); +}; + +#endif diff --git a/model/CombinatorialAssembler/libs_gamb/SurfacePoint.cc b/model/CombinatorialAssembler/libs_gamb/SurfacePoint.cc new file mode 100644 index 0000000000000000000000000000000000000000..f4dd82daaeb390df734f9b984751362815cabec4 --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/SurfacePoint.cc @@ -0,0 +1,200 @@ +#include "SurfacePoint.h" +#include "Shou.h" + +#include + +typedef SurfacePoint::SurfaceType SurfaceType; + +void sortAtoms(int atoms[3]) +{ + if (atoms[1] > atoms[0]) std::swap(atoms[0], atoms[1]); + if (atoms[2] > atoms[1]) std::swap(atoms[1], atoms[2]); + if (atoms[1] > atoms[0]) std::swap(atoms[0], atoms[1]); +} + +SurfacePoint::SurfacePoint(): Vector3(), norm() +{ + atoms[0] = atoms[1] = atoms[2] = -1; +} + +SurfacePoint::SurfacePoint(const SurfacePoint& t) + : Vector3(t), norm(t.norm), area(t.area) +{ + atoms[0] = t.atoms[0]; + atoms[1] = t.atoms[1]; + atoms[2] = t.atoms[2]; +} + +SurfacePoint::SurfacePoint(const Vector3& newPos, const Vector3& newNorm, + const float surface, + const int atomIndex1, + const int atomIndex2, + const int atomIndex3) + : Vector3(newPos), norm(newNorm), area(surface) +{ + setAtomIndices(atomIndex1, atomIndex2, atomIndex3); +} + +SurfacePoint::SurfacePoint(const char* const shouRec) + : Vector3(Shou::xCoord(shouRec), Shou::yCoord(shouRec), + Shou::zCoord(shouRec)), + norm(Vector3(Shou::xNorm(shouRec), Shou::yNorm(shouRec), + Shou::zNorm(shouRec))), + area(Shou::surface(shouRec)) +{ + for (int i=0; i<3 ; ++i) + atoms[i] = Shou::atomIndex(shouRec, i)-1; + sortAtoms(atoms); +} + +Vector3 SurfacePoint::normal() const +{ + return norm; +} + +float SurfacePoint::surfaceArea() const +{ + return area; +} + +void SurfacePoint::setAtomIndices(const int atomIndex1, + const int atomIndex2, + const int atomIndex3) +{ + atoms[0] = atomIndex1; + atoms[1] = atomIndex2; + atoms[2] = atomIndex3; + sortAtoms(atoms); +} + +int SurfacePoint::atomIndex(const unsigned int indx) const +{ + return atoms[indx]; +} + +bool SurfacePoint::atomIndicesGTE(const unsigned int atomIndex) const +{ + bool result = (atoms[0] >= (int)atomIndex); + if (result && (atoms[1] >= 0)) { + result = (atoms[1] >= (int)atomIndex); + if (result && (atoms[2] >= 0)) + result = (atoms[2] >= (int)atomIndex); + } + return result; +} + +bool SurfacePoint::atomIndicesLT(const unsigned int atomIndex) const +{ + return ((atoms[0] < (int)atomIndex) && (atoms[1] < (int)atomIndex) && + (atoms[2] < (int)atomIndex)); +} + +SurfaceType SurfacePoint::surfaceType() const +{ + if (atoms[0] == -1) + return Undef; + if (atoms[1] == -1) + return Caps; + if (atoms[2] == -1) + return Belts; + return Pits; +} + +void SurfacePoint::orient(const Vector3 &v) +{ + norm= v/v.norm(); +} + +bool SurfacePoint::adjacentTo(const SurfacePoint& sp) const +{ + bool result = false; + switch (surfaceType()) { + case Caps: + result = (atoms[0] == sp.atoms[0] || + atoms[0] == sp.atoms[1] || + atoms[0] == sp.atoms[2]); + break; + case Belts: + switch (sp.surfaceType()) { + case Caps: + result = (atoms[0] == sp.atoms[0] || atoms[1] == sp.atoms[0]); + break; + case Belts: + result = (atoms[0] == sp.atoms[0] && atoms[1] == sp.atoms[1]); + break; + case Pits: + result = ((atoms[0] == sp.atoms[0] && atoms[1] == sp.atoms[1]) || + (atoms[0] == sp.atoms[1] && atoms[1] == sp.atoms[2]) || + (atoms[0] == sp.atoms[0] && atoms[1] == sp.atoms[2])); + break; + case Undef: + break; + } + break; + case Pits: + switch (sp.surfaceType()) { + case Caps: + result = (atoms[0] == sp.atoms[0] || + atoms[1] == sp.atoms[0] || + atoms[2] == sp.atoms[0]); + break; + case Belts: + result = ((atoms[0] == sp.atoms[0] && atoms[1] == sp.atoms[1]) || + (atoms[1] == sp.atoms[0] && atoms[2] == sp.atoms[1]) || + (atoms[0] == sp.atoms[0] && atoms[2] == sp.atoms[1])); + break; + case Pits: + result = (atoms[0] == sp.atoms[0] && + atoms[1] == sp.atoms[1] && + atoms[2] == sp.atoms[2]); + break; + case Undef: + break; + } + case Undef: + break; + } + return result; +} + +SurfacePoint& SurfacePoint::operator*=(const Matrix3& lt) { + (Vector3&)(*this)=(lt*position()); + orient(lt*normal()); + return *this; +} + +SurfacePoint& SurfacePoint::operator*=(const RigidTrans3& rt) { + (Vector3&)(*this)=(rt*position()); + orient(rt.rotation()*normal()); + return *this; +} + +std::ostream& operator<<(std::ostream& s, const SurfacePoint& sp) +{ + s.setf(std::ios::fixed, std::ios::floatfield); + s.setf(std::ios::right, std::ios::adjustfield); + s.precision(3); + + s.width(5); + s << sp.atoms[0]+1; + s.width(5); + s << sp.atoms[1]+1; + s.width(5); + s << sp.atoms[2]+1; + s.width(8); + s << sp.x(); + s.width(8); + s << sp.y(); + s.width(8); + s << sp.z(); + s.width(8); + s << sp.area; + s.width(7); + s << sp.norm.x(); + s.width(7); + s << sp.norm.y(); + s.width(7); + s << sp.norm.z(); + s << std::endl; + return s; +} diff --git a/model/CombinatorialAssembler/libs_gamb/SurfacePoint.h b/model/CombinatorialAssembler/libs_gamb/SurfacePoint.h new file mode 100644 index 0000000000000000000000000000000000000000..8ba67526981fb6bb62c7b7255aa756d64e4f8fdf --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/SurfacePoint.h @@ -0,0 +1,140 @@ +#ifndef _SurfacePoint_h +#define _SurfacePoint_h + +#include +#include "Vector3.h" +#include "Matrix3.h" +#include "RigidTrans3.h" + +/* +CLASS + SurfacePoint + + A descendant of the Vector3 class. Serves as a base class for surface + points. Besides their positions holds geometrical information such as nornal + to the surface and type of surface point (caps, pits and belts). + +KEYWORDS + vector, position, algebra, linear, rigid, matrix, distance, match, vector3, + normal + +AUTHOR + Zipi Fligelman (zipo@math.tau.ac.il) + + Copyright: SAMBA group, Tel-Aviv Univ. Israel, 1997. + +CHANGES LOG +
      +
    • +
    + +GOALS + Defines a class to hold information about surface points. Besides the + position inherited from the Vector3 class this class holds the normal to + surface at the given point and the type of surface point (caps, pits or + belts). More surface properties may be added including chemical ones. + + The class also holds a parameter which designates which fragment or part of + the molecule the surface point belongs to. This is useful for hinge- + flexible molecules. + +USAGE +*/ + +class SurfacePoint : public Vector3 +{ +public: + + enum SurfaceType { Undef, Caps , Belts , Pits }; + + //// Creates a Surface Point with 0-position and a general empty normal + SurfacePoint(); + + //// copy constructor + SurfacePoint(const SurfacePoint &t); + + //// Creates a point with a given position newPos and a given normal and + // a given type + explicit SurfacePoint(const Vector3& newPos, const Vector3& norm, + const float surface=0, + const int atomIndex1 = -1, + const int atomIndex2 = -1, + const int atomIndex3 = -1); + + //// Construcs a surface point using a Shou surface format record. + explicit SurfacePoint(const char* const shouRec); + + //// Returns normal of surface point. + Vector3 normal() const; + + //// Returns area of surface represented by surface point and normal + float surfaceArea() const; + + //// Set the atom indices of the surface point. This method allows the class + // user to set the indices of the atoms from which the surface point was + // calculated. These indices are read in through the shou file but user may + // update them at will. + void setAtomIndices(const int atomIndex1 = -1, + const int atomIndex2 = -1, + const int atomIndex3 = -1); + + //// Returns atom's indices that participated in the creation of the Shou + // surface critical points. Up to 3 atoms may define a surface point + // depeneding on the surface point type (Caps = 1, Belts = 2, Pits = 3) + // indx may take value of 0..2. If less then 3 atoms defined the surface + // point then atomIndex will return a -1. + int atomIndex(const unsigned int indx) const; + + //// Returns true if all atom indices are greater than or equal to the given + // atom index. Disregards undefined atom indices (i.e. for caps and belts). + bool atomIndicesGTE(const unsigned int atomIndex) const; + + //// Returns true if all atom indices are less than the given + // atom index. Disregards undefined atom indices (i.e. for caps and belts). + bool atomIndicesLT(const unsigned int atomIndex) const; + + //// Returns surface type (caps, pits or belts) + SurfaceType surfaceType() const; + + //// Changing a point orientation with a new normal + void orient(const Vector3 &v); + + //// Defines symmetric adjacency relation. This relation stems from a + // natural triangulation of the protein surface resulting directly from + // the defintion of the Shou surface. The triangulation is such that every + // cap point is adjacent to all belts and pits surrounding it, every belt + // is adjacent to its two pits and two caps neighbors and hence every pit + // is adjacent to its three caps and three belts in its immediate + // neighborhood. + bool adjacentTo(const SurfacePoint& sp) const; + + //// Shift SurfacePoint position and normal using a linear tranfromation + SurfacePoint& operator*=(const Matrix3 &); + + //// shift SurfacePoint position and normal using a rigid transformation + SurfacePoint& operator*=(const RigidTrans3 &); + + //// Shift surface point's position using vector addition. normal doesn't + // change + // V.P. SurfacePoint& operator+=(const Vector3 &); + + //// Shift surface point's position using vector subtraction. normal + // doesn't chnage + SurfacePoint& operator-=(const Vector3 &); + + friend std::ostream& operator<<(std::ostream& s, const SurfacePoint& sp); + + +protected: + Vector3 norm; + +private: + // the atoms creating the patch filled according to SurfaceType with 1/2/3 + // atoms + int atoms[3]; + + //The area on which the surface point is based + float area; +}; + +#endif diff --git a/model/CombinatorialAssembler/libs_gamb/Timer.cc b/model/CombinatorialAssembler/libs_gamb/Timer.cc new file mode 100644 index 0000000000000000000000000000000000000000..ea90589b6808a178b2e3added285b0c8f28f2d8f --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/Timer.cc @@ -0,0 +1,86 @@ +#include "Timer.h" +#include +#include +#include + +Timer::Timer() : id(NULL) +{ + reset(); +} + +Timer::Timer(const char* const tmID) +{ + unsigned n=strlen(tmID); + id = new char[n+1]; + strcpy(id,tmID); + reset(); +} + +Timer::~Timer() +{ + if (id) + delete [] id; +} + +void Timer::reset() +{ + struct tms ptms; + times(&ptms); + + begin_clock = ptms.tms_utime; + clock_sum = 0; +} + +void Timer::start() +{ + struct tms ptms; + times(&ptms); + + begin_clock = ptms.tms_utime; +} + +void Timer::stop() +{ + if (begin_clock >= 0) { + struct tms ptms; + times(&ptms); + + clock_sum+= ptms.tms_utime - begin_clock; + begin_clock = -1; + } +} + +float Timer::operator()() const +{ + if (begin_clock >= 0){ + struct tms ptms; + times(&ptms); + + return (clock_sum + (ptms.tms_utime - begin_clock))/(float)sysconf(_SC_CLK_TCK);//CLOCKS_PER_SEC; + }else + return clock_sum/(float)sysconf(_SC_CLK_TCK); //(float)CLOCKS_PER_SEC; +} + +std::ostream& operator<<(std::ostream& out, const Timer& tm) +{ + if (tm.id) + out << tm.id << ": "; + out.setf(std::ios::fixed, std::ios::floatfield); + out.setf(std::ios::right, std::ios::adjustfield); + out.precision(2); + out.width(8); + unsigned int time = (unsigned int)(tm()*100.0); + unsigned int sec100 = time%100; + time /= 100; + unsigned int sec = time%60; + time /= 60; + unsigned int min = time%60; + time /= 60; + out.fill('0'); + out.width(2); out << time << ':'; + out.width(2); out << min << ':'; + out.width(2); out << sec << '.'; + out.width(2); out << sec100; + out.fill(' '); + return out; +} diff --git a/model/CombinatorialAssembler/libs_gamb/Timer.h b/model/CombinatorialAssembler/libs_gamb/Timer.h new file mode 100644 index 0000000000000000000000000000000000000000..2c89192af8b2fad784daecc560a0603d9925602f --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/Timer.h @@ -0,0 +1,126 @@ +#ifndef __Timer_H +#define __Timer_H + +#include +#include +#include + +/* +CLASS + Timer + + This is a general class to compute the user processor time on parts of the program. + +KEYWORD + time, clock, clocks_per_sec + +AUTHORS + Zipi Fligelman (zipo@math.tau.ac.il) + + copyright: SAMBA Group , Tel-Aviv Univ. Israel, 1998. + +CHANGES LOG +
      +
    • +
    + +USAGE + Before an important code part you define a timer thus strating it. + After starting it every time you will output it, it will compute the + proc sec. passed between the construction of the Timer and the curr + time. + You can also stop the timer after the operation you wanted has finished. + Stopping the clock means that every time you will output the class, you + will only recieve the time that was set between start and finish. + You can accumulate the time on different part of the program using the + start and stop facilities, the time will be added together. Thus giving + you exact information on running of the program without for example major + debugging parts + + first example: + EXAMPLE + Timer timer("prog_time"); + ... doing preprocessing + ... some debugging info + cout << timer; ( how much time preprocessing & debugging took.) + .... doing matching + .... some debugging info + cout << timer ; (how much time preprocessing , matching & debugging took.) + .... doing verification + cout << timer; (how much time the whole program took including debugging) + END + + second example: + EXAMPLE + Timer timer("on_off_time"); + .... doing preprocessing + timer.stop(); + .... som debugging info + cout << timer; (how much time preprocessing took.) + .... doing matching + .... some debugging info + cout << timer; (how much time preprocessing took.) + .... doing verification + cout << timer; (how much time preprocessing took.) + END + + third example: + EXAMPLE + Timer timer("on_off_time"); + .... doing preprocessing + timer.stop(); + .... some debugging info + cout << timer; (how much time preprocessing took.) + timer.start(); + .... doing matching + cout << timer; (how much time only preprocessing and matching took.) + timer.stop(); + .... some debugging info + timer.start(); + .... doing verification + cout << timer; (how much time preprocessing took.) + END +*/ +class Timer +{ +public: + + // Group: Constructors + + //// Empty constructor. Resetting the start to zero. Assuming null Id. + Timer(); + //// special possibilty if you want to keep different timers and + // differentiate them by an ID + Timer(const char* const tmID); + + //// destructor + ~Timer(); + + + // Group: Using The class + + //// resetting the time so clock will be current clock and sum of clocks + // will be zero + void reset(); + //// Enable the timer to continue after a stop operation has been performed + void start(); + //// Stopping the timer operation + void stop(); + + // Group: Operators + + //// If the timer is stopped returning the sec of proc time + // accumalated in memory. Otherwise the time accumalated in memory and + // the time form the last start operation until present. + float operator()() const; + + //// printing the i.d and the time (on what time is printed see operator()) + friend std::ostream& operator<<(std::ostream& out, const Timer& tm); + +private: + char *id; + clock_t begin_clock, clock_sum; + +}; + +#endif diff --git a/model/CombinatorialAssembler/libs_gamb/Vector3.cc b/model/CombinatorialAssembler/libs_gamb/Vector3.cc new file mode 100644 index 0000000000000000000000000000000000000000..16fdea9133c7ca1bc98b33c755223e147a1fc866 --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/Vector3.cc @@ -0,0 +1,10 @@ +#include "Vector3.h" + +typedef Vector3::real real; + + + + + + + diff --git a/model/CombinatorialAssembler/libs_gamb/Vector3.h b/model/CombinatorialAssembler/libs_gamb/Vector3.h new file mode 100644 index 0000000000000000000000000000000000000000..5d01258da6409935ddf5a5cff8a1a54383440a71 --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/Vector3.h @@ -0,0 +1,335 @@ +#ifndef _Vector3_h +#define _Vector3_h + +#include +#include +#include +#include + +/* +CLASS + Vector3 + + Defines a 3D vector class with operators. + +KEYWORDS + linear, algebra, vector, angle, distance, norm, real + +AUTHORS + Meir Fuchs. (meirfux@math.tau.ac.il) + + Copyright: SAMBA group, Tel-Aviv Univ. Israel, 1997. + +CHANGES LOG +
      +
    • 23/08/2004 - Oranit Dror:
      +Adding the '==' operator and the 'getPerpendicularVector' and 'getUnitVector' methods +
    • +
    + +GOALS + Since most of the work on protein structure is done in 3D space, a fast, + well designed 3D vector class was in place. Knowing the dimension is 3 + allows for a fast, loopless implemetation of Vector3 and Matrix3 to be + written. + +USAGE + Many 3D vector operations are supplied including vector addition and + subtraction, dot-product mult., multiplication by scalar, distance and + angular operators.

    + + A new Vector3 may be constructed using 3 floats or an array of 3 floats. + EXAMPLE + Vector3 v(1.0, 2.0, 3.0); + float x[3]; + Vector3 v(x); + END + + Coordinates may be accessed using the x(int) method which returns the + vector's given coordinate (1..3) or using the [] operator (0..2). + EXAMPLE + if (v[0] == v.x(1)) cout << "OK\n"; + END + + Addition, subtraction, multiplication and division by scalar, dot-product + distance, angle. + EXAMPLE + w = u+v; // addition + w-= v; // in-place subtraction w==u + if ((w|u) == 0) cout << "OK\n"; // distance operator | + if (w^u == 0) cout << "OK\n"; // angle operator ^ + w=2*w; // multiplicatio by scalar + float f=w*v; // dot-product; + END + +*/ +class Vector3 +{ + +public: + // GROUP: define real type. + + //// Defines the type with which coordinates are defined. Changing this to + // double will casue the Vector3 and Matrix3 classes to work with double + // precision coordinates. float was chosen for considerations of speed. + // double precision is rarely needed since strctural data is inexact. + typedef float real; + + // GROUP: Constructors. + + //// Initialize to the 0 vector. + Vector3() { + c[0] = c[1] = c[2] = 0; + } + + //// Initialize using 3 coordinates x,y,z. + Vector3(const real& nx, const real& ny, const real& nz) { + c[0]=nx; + c[1]=ny; + c[2]=nz; + } + + //// initialize using 3 coordinates in array. + Vector3(const real x[3]) { + c[0] = x[0]; + c[1] = x[1]; + c[2] = x[2]; + } + + //// initialize using 3 double precision coordinates in array. + Vector3(const double x[3]) { + c[0] = (real)x[0]; + c[1] = (real)x[1]; + c[2] = (real)x[2]; + } + + + // GROUP: Inspection. + + //// Returns position (redundant function) + const Vector3& position() const { return *this; } + + //// return the x, y or z coordinates. + real x() const { return c[0]; } + + real y() const { return c[1]; } + + real z() const { return c[2]; } + + //// Returns x=x(1), y=x(2) or z=x(3) coordinate. If other values are given + // result are unexpected. + real x(const unsigned short coord) const { + return c[coord-1]; + } + + //// + // Returns true if the vector is the zero vector
    + // Author: Oranit Dror (oranit@tau.ac.il) + bool isZero() const { + return ((c[0] == 0) && (c[1] == 0) && (c[2] == 0)); + } + + // Group Update + void updateX(const real& x) { + c[0]=x; + } + + void updateY(const real& y) { + c[1]=y; + } + + void updateZ(const real& z) { + c[2]=z; + } + + void update(const Vector3& v) { + c[0]=v[0]; + c[1]=v[1]; + c[2]=v[2]; + } + + //// Return Vector3's distance from origin. + real norm() const { + return sqrt(norm2()); + } + + //// Return Vector3's squared distance ffrom origin. + real norm2() const { + return c[0]*c[0]+c[1]*c[1]+c[2]*c[2]; + } + + //// Returns Vector3's distance from Vector3 p. + real dist(const Vector3& p) const { + return sqrt(dist2(p)); + } + + //// Returns Vector3's distance squared from Vector3 p. + real dist2(const Vector3& p) const{ + real d=c[0]-p.c[0]; + real s=d*d; + d=c[1]-p.c[1]; + s+=d*d; + d=c[2]-p.c[2]; + return s+d*d; + } + + //// Treats vector like a real array. Acceptable indices 0, 1, 2. Other + // indices may cause run-time errors. + const real& operator[](const unsigned short coord) const{ + return c[coord]; + } + + //// Access coordinates + real& operator[](const unsigned short coord){ + return c[coord]; + } + + //// + // Returns a perpendicular vector + // (A zero vector is returned in case of a zero vector).
    + // Author: Oranit Dror (oranit@tau.ac.il) + Vector3 getPerpendicularVector() const { + if (x() != 0) { + return Vector3((-y()-z())/x(),1,1); + } else if (y() != 0) { + return Vector3(1,(-x()-z())/y(),1); + } else if (z() != 0) { + return Vector3(1,1,(-x()-y())/z()); + } else { + return Vector3(0,0,0); + } + } + + //// + // Returns a unit vector in the same direction + // (A zero vector is returned in case of a zero vector).
    + // Author: Oranit Dror (oranit@tau.ac.il) + Vector3 getUnitVector() const { + return ((isZero()) ? *this : (*this/norm())); + } + + // GROUP: Operators. + + //// + // Returns true if the two vectors have the same coordinates + // Author: Oranit Dror (oranit@tau.ac.il) + bool operator==(const Vector3& v) const { + return ((x() == v.x()) && (y() == v.y()) && (z() == v.z())); + } + + //// Returns Vector3 addition of two Vector3s. + friend Vector3 operator+(const Vector3& p1, const Vector3& p2) { + return Vector3(p1.c[0]+p2.c[0], p1.c[1]+p2.c[1], p1.c[2]+p2.c[2]); + } + + //// Returns Vector3 subtraction of two Vector3s. + friend Vector3 operator-(const Vector3& p1, const Vector3& p2) { + return Vector3(p1.c[0]-p2.c[0], p1.c[1]-p2.c[1], p1.c[2]-p2.c[2]); + } + + //// Returns Vector3 negative. + friend Vector3 operator-(const Vector3& p) { + return Vector3(-p.c[0], -p.c[1], -p.c[2]); + } + + //// Returns dot product of two Vector3s. + friend real operator*(const Vector3& p1, const Vector3& p2) { + return p1.c[0]*p2.c[0]+p1.c[1]*p2.c[1]+p1.c[2]*p2.c[2]; + } + + //// Returns vector multiplied by scalar m. + friend Vector3 operator*(const Vector3& p, const real& m) { + return Vector3(p.c[0]*m, p.c[1]*m, p.c[2]*m); + } + + + //// Returns vector multiplied by scalar m. + friend Vector3 operator*(const real& m, const Vector3& p) { + return Vector3(p.c[0]*m, p.c[1]*m, p.c[2]*m); + } + + //// Returns vector divided by scalar m. + friend Vector3 operator/(const Vector3& p, const real& m) { + return Vector3(p.c[0]/m, p.c[1]/m, p.c[2]/m); + } + + //// Returns vector that is vertical to both vectors. + friend Vector3 operator&(const Vector3& p1, const Vector3& p2) { + return Vector3(p1.c[1]*p2.c[2]-p1.c[2]*p2.c[1], + p1.c[2]*p2.c[0]-p1.c[0]*p2.c[2], + p1.c[0]*p2.c[1]-p1.c[1]*p2.c[0]); + } + + //// Returns distance between 2 Vector3s (similar to dist). + friend real operator|(const Vector3& p1, const Vector3& p2) { + return p1.dist(p2); + } + + //// Returns angle between p1 and p2 in radians. + friend real operator^(const Vector3& p1, const Vector3& p2) { + float angle=p1*p2/(p1.norm()*p2.norm()); + if(angle<-1) + angle=-1; + else + if(angle>1) + angle=1; + return acos(angle); + } + + //// Vector3 addition of another Vector3's coordinates. + Vector3& operator+=(const Vector3& p) { + c[0]+=p.c[0]; c[1]+=p.c[1]; c[2]+=p.c[2]; + return *this; + } + + //// Vector3 subtraction of another Vector3's coordinates. + Vector3& operator-=(const Vector3& p) { + c[0]-=p.c[0]; c[1]-=p.c[1]; c[2]-=p.c[2]; + return *this; + } + + Vector3& operator+=(const real& m) { + c[0]+=m; c[1]+=m; c[2]+=m; + return *this; + } + + Vector3& operator-=(const real& m) { + c[0]-=m; c[1]-=m; c[2]-=m; + return *this; + } + + //// Multiplies coordinates by scalar m. + Vector3& operator*=(const real& m) { + c[0]*=m; c[1]*=m; c[2]*=m; + return *this; + } + + //// Divides coordinates by scalar m. + Vector3& operator/=(const real& m) { + c[0]/=m; c[1]/=m; c[2]/=m; + return *this; + } + + //// Outputs coordinates delimited by single space. + friend std::ostream& operator<<(std::ostream& s, const Vector3& v) { + return s << v.c[0] << ' ' << v.c[1] << ' ' << v.c[2]; + } + + + //// Inputs coordinates delimited by single space. + friend std::istream& operator>>(std::istream& s, Vector3& v) { + return s >> v.c[0] >> v.c[1] >> v.c[2]; + } + + //// + // Outputs the vector's coordinates delimited by a single space.
    + // Author: Oranit Dror (oranit@tau.ac.il) + void output(FILE* outputFile) const { + fprintf(outputFile, "%f %f %f", c[0], c[1], c[2]); + } + +private: + std::array c; +}; + +#endif diff --git a/model/CombinatorialAssembler/libs_gamb/macros.h b/model/CombinatorialAssembler/libs_gamb/macros.h new file mode 100644 index 0000000000000000000000000000000000000000..41659547effbf24f3b13709e776d94adad88489d --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/macros.h @@ -0,0 +1,75 @@ +#ifndef _macros_h +#define _macros_h + +#include +#include + + +//// Template function returns x squared. +template +inline T sqr(const T x) +{ + return x*x; +} + +//// Template function returns absolute value of referenced x. +template +inline T tabs(const T& x) { + return (x < 0) ? -x : x; +} + +//// Template function returns a with b's sign +template +inline T sign(const T a, const S b) +{ + return ((b) >= 0.0 ? tabs(a) : -tabs(a)); +} + +//// Template function returns minimum of two numbers +template +inline T min2(const T x, const T y){ + return ((x < y)? x : y); +} + +////Template function returns maximum of two numbers +template +inline T max2(const T x, const T y){ + return ((x > y)? x : y); +} + +////Template function returning maximum of three numbers +template +inline T max3(const T x, const T y, const T z){ + if ( x > y ) + return ((x > z) ? x : z); + else + return ((y > z) ? y : z); +} + +//// Template function returnin minimun of three numbers +template +inline T min3(const T x, const T y, const T z) +{ + if (x < y) + return ((x < z) ? x : z); + else + return ((y < z) ? y : z); +} + +template +inline T max4(const T x, const T y, const T z,const T t){ + if ( x > y ){ + if(x > z) + return ((x > t) ? x : t); + else + return ((z > t) ? z : t); + }else{ + if(y > z) + return ((y > t) ? y : t); + else + return ((z > t) ? z : t); + } +} + + +#endif diff --git a/model/CombinatorialAssembler/libs_gamb/numerics.cc b/model/CombinatorialAssembler/libs_gamb/numerics.cc new file mode 100644 index 0000000000000000000000000000000000000000..d6241dba990cab8f6671385e1168bf58d26004ae --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/numerics.cc @@ -0,0 +1,219 @@ +#include +#include "macros.h" + + +// Part of the C-code is from the book NUMERICAL RECIPES in C +// The routine "svd3d" computes the singular value decomposition of a 3x3 +// matrix a. The result is in the form a = u w v^t. +// The maxtix u replaces a on output. The diagonal matrix w +// of singular values is output as a vector w[0..2]. The matrix v (not +// the transposed v^t) is output as v[0..2][0..2]. +// if an error occurs svd3d will return true. otherwise it'll return false. +// The only error possible is that after 30 iterations the svd calc. have not +// converged. + +double pythag(double a, double b) +{ + double absa, absb; + + absa = fabs(a); + absb = fabs(b); + if(absa > absb) + return (absa * sqrt(1.0 + sqr(absb/absa))); + else + return (absb == 0.0 ? 0.0 : absb * sqrt(1.0+sqr(absa/absb))); +} + +bool svd3d(double a[3][3], double w[3], double v[3][3]) +{ + int flag, i, its, j, jj, k, l, nm=0; + double anorm, c, f, g, h, s, scale, x, y, z; + double rv1[3]; + + g = scale = anorm = 0.0; + + // Householder reduction to bidiagonal form + + for (i = 0; i < 3; ++i) { + l = i + 1; + rv1[i] = scale * g; + g = s = scale = 0.0; + if(i < 3) { + for(k=i; k<3; ++k) scale += fabs(a[k][i]); + if(scale) { + for(k=i; k<3; ++k) { + a[k][i] /= scale; + s += a[k][i] * a[k][i]; + } + f = a[i][i]; + g = -sign(sqrt(s),f); + h = f * g - s; + a[i][i] = f-g; + for ( j= l; j < 3; ++j) { + for(s= 0.0, k= i; k<3; ++k) s+= a[k][i] * a[k][j]; + f = s/h; + for(k=i; k<3;++k) a[k][j] += f * a[k][i]; + } + for(k=i;k<3;++k) a[k][i] *= scale; + } + } + w[i] = scale * g; + g = s = scale = 0.0; + if(i<2) { + for (k = l; k < 3; ++k) scale += fabs(a[i][k]); + if(scale) { + for(k= l; k<3; ++k) { + a[i][k] /= scale; + s += a[i][k] * a[i][k]; + } + f = a[i][l]; + g = -sign(sqrt(s),f); + h= f*g-s; + a[i][l] = f-g; + for(k = l; k < 3; ++k) rv1[k] = a[i][k]/h; + for ( j = l; j < 3; ++j) { + for (s = 0.0, k= l; k < 3; ++k) s += a[j][k] * a[i][k]; + for (k = l; k < 3; ++k) a[j][k] += s * rv1[k]; + } + for(k = l; k < 3; ++k) a[i][k] *= scale; + } + } + anorm = max2(anorm, (fabs(w[i]) + fabs(rv1[i]))); + } + + // Accumulation of right-hand transformations + + for (i = 2; i >= 0; --i) { + if (i < 2) { + if(g) { + for (j = l; j < 3; ++j) + v[j][i] = (a[i][j]/a[i][l])/g; + for(j = l; j < 3; ++j) { + for (s = 0.0, k = l; k < 3; ++k) s += a[i][k] * v[k][j]; + for(k = l; k < 3; ++k) v[k][j] += s * v[k][i]; + } + } + for(j = l; j < 3; ++j) v[i][j] = v[j][i] = 0.0; + } + v[i][i] = 1.0; + g= rv1[i]; + l = i; + } + + // Accumulation of left-hand transformations + + for(i = 2; i >= 0; --i) { + l = i + 1; + g = w[i]; + for(j = l; j < 3; ++j) a[i][j] = 0.0; + if(g) { + g = 1.0/g; + for ( j = l; j < 3; ++j) { + for ( s= 0.0, k = l; k < 3; ++k) s+= a[k][i] * a[k][j]; + f = (s/a[i][i]) * g; + for( k = i; k < 3; ++k) a[k][j] += f * a[k][i]; + } + for(j = i; j < 3; ++j) a[j][i] *= g; + } + else for (j = i; j < 3; ++j) a[j][i] = 0.0; + ++a[i][i]; + } + + // Diagonalization of bidiagonal form + + for(k = 2; k >= 0; --k) { + for(its= 1; its <= 30; ++its) { + flag = 1; + for(l=k; l >= 0; --l) { + nm = l-1; + if((float)(fabs(rv1[l]) + anorm) == (float)anorm) { + flag = 0; + break; + } + if((float)(fabs(w[nm]) + anorm) == (float)anorm) break; + } + if(flag) { + c = 0.0; + s = 1.0; + for(i = l; i <= k; ++i) { + f = s * rv1[i]; + rv1[i] = c * rv1[i]; + if ((float)(fabs(f) + anorm) == (float)anorm) break; + g = w[i]; + h = pythag(f,g); + w[i] = h; + h = 1.0/h; + c = g * h; + s = -f * h; + for( j = 0; j < 3; ++j) { + y = a[j][nm]; + z = a[j][i]; + a[j][nm] = y * c + z * s; + a[j][i] = z * c - y * s; + } + } + } + z = w[k]; + if( l == k) { + if ( z < 0.0) { + w[k] = -z; + for ( j = 0; j < 3; ++j) v[j][k] = (-v[j][k]); + } + break; + } + if(its == 30) return true; + x = w[l]; + nm = k - 1; + y = w[nm]; + g = rv1[nm]; + h = rv1[k]; + f = ((y-z) * (y + z) + (g - h) * (g + h))/(2.0*h*y); + g = pythag(f,1.0); + f= ((x-z) * (x+z) + h * ((y/(f + sign(g,f))) - h))/x; + c=s = 1.0; + for(j = l; j <= nm; ++j) { + i = j + 1; + g = rv1[i]; + y = w[i]; + h = s * g; + g = c * g; + z = pythag(f,h); + rv1[j] = z; + c = f/z; + s = h/z; + f = x * c + g * s; + g = g * c - x * s; + h = y * s; + y *= c; + for ( jj= 0; jj < 3; ++jj) { + x = v[jj][j]; + z = v[jj][i]; + v[jj][j] = x * c + z * s; + v[jj][i] = z * c - x * s; + } + z = pythag(f,h); + w[j] = z; + if(z) { + z = 1.0/z; + c = f * z; + s = h * z; + } + f = (c * g) + (s * y); + x = (c * y) - (s * g); + for(jj = 0; jj < 3; ++jj) { + y = a[jj][j]; + z = a[jj][i]; + a[jj][j] = y * c + z * s; + a[jj][i] = z * c - y * s; + } + } + rv1[l] = 0.0; + rv1[k] = f; + w[k] = x; + } + } + return false; +} + + + diff --git a/model/CombinatorialAssembler/libs_gamb/numerics.h b/model/CombinatorialAssembler/libs_gamb/numerics.h new file mode 100644 index 0000000000000000000000000000000000000000..b3e01b30c53af8ccb421f18d33218f1560db8281 --- /dev/null +++ b/model/CombinatorialAssembler/libs_gamb/numerics.h @@ -0,0 +1,19 @@ +#ifndef _numerics_h +#define _numerics_h + +#define MAX_FLOAT 99999999.0 +#define MIN_FLOAT -99999999.0 + +const float pi = (float)3.14159265358979; +const float sqrt_2 = (float)1.4142136; +const float sqrt_3 = (float)1.7320508; +const float sqrt_6 = (float)2.4494897; + +// calcualtes c where c = sqrt(a^2 + b^2). +float pythag(float a, float b); + +// recieves a 3x3 matrix a and returns its DVDecompisions in a,w and v such +// that original = a*w*vt +bool svd3d(double a[3][3], double w[3], double v[3][3]); + +#endif diff --git a/model/README.md b/model/README.md new file mode 100644 index 0000000000000000000000000000000000000000..e47c83374f999e3bc1c8c82bbd44c67668a3ec36 --- /dev/null +++ b/model/README.md @@ -0,0 +1,15 @@ +# CombFold model implementation + +`CombinatorialAssembler/` contains the upstream C++17 combinatorial assembly +implementation and its `AF2trans` structure-alignment helper. + +Build from the repository root with: + +```bash +cd model/CombinatorialAssembler +make +``` + +On the validated SCNet environment, Boost 1.53 headers and the system +`boost_program_options` library require the old libstdc++ ABI; see the root +README for the corresponding build command. diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..bf079829acab2c18e616fdcda7cd621b469bc56e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,37 @@ +# CombFold / ColabFold extra dependencies for OneScience bio-dcu environment. +# Install with: python -m pip install --no-deps -r requirements.txt + +# ----------------------------------------------------------------------------- +# Provided by onescience[bio-dcu]; retained here for a complete dependency list. +# Do not install these again in the OneScience base environment. +# ----------------------------------------------------------------------------- +# numpy==1.26.3 +# scipy==1.14.1 +# biopython==1.84 +# pandas>=2.2.2 +# matplotlib +# requests +# tqdm>=4.60.0 +# absl-py==2.1.0 +# chex==0.1.90 +# dm-tree==0.1.8 +# ml-collections==0.1.1 +# ml-dtypes==0.4.0 +# dm-haiku==0.0.14 +# py3Dmol==2.5.2 +# tensorflow==2.18.0+das.opt1.dtk25042 +# importlib-metadata +# jax==0.4.34+das.opt1.dtk25042 +# jaxlib==0.4.34+das.opt1.dtk25042 +# jax-rocm60-pjrt==0.4.34+das.opt1.dtk25042 +# jax-rocm60-plugin==0.4.34+das.opt1.dtk25042 + +# ----------------------------------------------------------------------------- +# Extra Python components required by the CombFold AlphaFold-Multimer workflow. +# ----------------------------------------------------------------------------- +colabfold==1.5.5 +alphafold-colabfold==2.3.6 +appdirs==1.4.4 +immutabledict==2.2.5 + + diff --git a/scripts/CombFold.ipynb b/scripts/CombFold.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..ea26ce78e0d8fd2ee8c6d0a971dc9b57d0263dd5 --- /dev/null +++ b/scripts/CombFold.ipynb @@ -0,0 +1,210 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [], + "include_colab_link": true + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "view-in-github", + "colab_type": "text" + }, + "source": [ + "\"Open" + ] + }, + { + "cell_type": "markdown", + "source": [ + "![combfold_logo.png](data:image/png;base64,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CCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKK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zJdeDQJz/vz5JTGpLoFSoC0+Lk0xsa6PMqAyv5OUShIoRQJ8xVwpIAK/PNAmQVhOSEAgfLE2YP69guBh6/C72YzJ/3JyEh8wCF4lRRFQBBQBRUARUAQUAUVAEVAEFAFFwGcEVAkQAcALFy4sQaCvsgT8akWk3noEQ6mIQqAS7gNlEMQlCmY+btzz8v+pKVW8VhAEbvflhl+Cu6W+DOcUwv5e+tpAtNVlEmGTwG4bCAq0kzRAuyMYrlZVBBQBRUARUAQUAUVAEVAEFAFFQBGIcwRUCeDhBJIzvCh5wkvs3LmzjuQMJX1PeVwIJIVPYRQEJYnqXl5Sq4hyIBBfQNKDBXOH50BhEMz/Llf5wVzgcpsfzF0r6dsklcYZbvYlwupRbvn3EGV1Iz/38e99tJFEOo1N/PtI9+7ds2z+dg+nVZtSBBQBRUARUAQUAUVAEVAEFAFFIGEQUCWAz1NJxPZcCOY5JB0GLgRlUQyUkvSD8pNo/BJoUAR/SaWSGyVBycC/JVp/MvlTD+Gzf5T/T03phuLgNAL/PvHtJ8L7SXKpHpQ0QM2bN5cAf/ooAoqAIqAIKAKKgCKgCCgCioAioAgoAopAPCGwffv27LyqnImnSVNaFQFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBLIYAv8Pemu73UV+cNgAAAAASUVORK5CYII=)\n" + ], + "metadata": { + "id": "MbEK2RpZcVCU" + } + }, + { + "cell_type": "markdown", + "source": [ + "This notebook runs the CombFold assembly algorithm. The algorithm assembles large protein complexes, by combining PDB files of possible subcomplexes. \"Possible subcomplexes\" are structure models of different combinations of subunits from the target complex.\n", + "\n", + "By default, the notebook runs on an example ([PDB: 6YBQ](https://www.rcsb.org/structure/6YBQ)), but by uploading your own PDB files to Google Drive (and creating a subunits.json) you can assemble your complex by changing the path in the \"Run\" cell, as instructed in the cell.\n", + "\n", + "\n", + "**The inputs** are\n", + "1. subunits.json - a json describing all the subunits (sequences) in the complex.\n", + "2. pdbs folder - a folder with AlphaFold-Multimer models (PDB files) of different combinations of these subunits.\n", + "\n", + "**The output** consists of several .PDB files which are models of the assembled complex structure.\n", + "more information is available on https://github.com/dina-lab3D/CombFold.\n" + ], + "metadata": { + "id": "ZbAj512vF4eK" + } + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "yPmEy4fH4CT8", + "cellView": "form" + }, + "outputs": [], + "source": [ + "#@title Install CombFold (~2 min)\n", + "\n", + "!pip -q install biopython\n", + "!pip -q install py3Dmol\n", + "!echo Installed python dependencies\n", + "\n", + "!wget -qnc -O CombFold-master.zip wget https://github.com/dina-lab3D/CombFold/archive/refs/heads/master.zip\n", + "!unzip -q CombFold-master.zip\n", + "!echo Downloaded CombFold, Installing\n", + "!cd CombFold-master/CombinatorialAssembler && make --silent\n", + "!echo CombFold Installed!\n", + "\n", + "import py3Dmol\n", + "import os\n", + "\n", + "def view_pdb_color_by_chain(pdb_path: str):\n", + " pdb_content = open(pdb_path, \"r\").read()\n", + " view = py3Dmol.view(width=400, height=300)\n", + " view.addModelsAsFrames(pdb_content)\n", + "\n", + " # Get the list of chains in the protein\n", + " chains = {i[21] for i in pdb_content.split(\"\\n\") if len(i) > 21}\n", + "\n", + " # Assign a color to each chain\n", + " colors = [\"red\", \"blue\", \"green\", \"orange\", \"purple\", \"yellow\", \"pink\", \"brown\", \"black\", \"gray\", \"cyan\", \"magenta\", \"olive\", \"maroon\", \"navy\", \"teal\", \"gold\", \"silver\", \"crimson\"]\n", + " colors = 10 * colors\n", + "\n", + " # Set the style for each chain\n", + " for i, chain in enumerate(chains):\n", + " view.setStyle({'chain': chain}, {'cartoon': {'color': colors[i]}})\n", + "\n", + " view.zoomTo()\n", + " view.show()\n" + ] + }, + { + "cell_type": "code", + "source": [ + "#@title View example elements {run: \"auto\"}\n", + "\n", + "#@markdown Here we demonstrate the input used to create a model of [PDB: 6YBQ](https://www.rcsb.org/structure/6YBQ). This complex have a total of 12 chains, composed of 2 subunits (unique chains in this case) with 6 copies each.\n", + "\n", + "#@markdown - subunits.json - Defines two subunits (A0 and G0) with 6 copies each (implemented by defining 6 chain_names)\n", + "\n", + "#@markdown - pdb files - Structure models predicted by AlphaFold-Multimer of different subsets of 2 or 3 subunits from the complex. Each combination have multiple models, as many different pairwise interactions can be considered during assembly\n", + "\n", + "#@markdown You can view elements in the input by choosing them and running the cell.\n", + "\n", + "element_to_view = 'subunits.json' #@param [\"subunits.json\", 'pdbs/AFM_A0_A0_unrelaxed_rank_1_model_1.pdb', 'pdbs/AFM_A0_A0_unrelaxed_rank_2_model_4.pdb', 'pdbs/AFM_A0_G0_unrelaxed_rank_1_model_2.pdb', 'pdbs/AFM_A0_G0_unrelaxed_rank_2_model_3.pdb', 'pdbs/AFM_G0_G0_unrelaxed_rank_2_model_5.pdb', 'pdbs/AFM_G0_G0_unrelaxed_rank_1_model_4.pdb', 'pdbs/AFM_A0_A0_A0_unrelaxed_rank_1_model_3.pdb', 'pdbs/AFM_A0_A0_A0_unrelaxed_rank_2_model_1.pdb','pdbs/AFM_A0_A0_G0_unrelaxed_rank_2_model_3.pdb', 'pdbs/AFM_A0_A0_G0_unrelaxed_rank_1_model_1.pdb']\n", + "example_path = \"/content/CombFold-master/example/\"\n", + "\n", + "subunits_path = os.path.join(example_path, \"subunits.json\")\n", + "\n", + "if element_to_view == 'subunits.json':\n", + " print(open(subunits_path, \"r\").read())\n", + "else:\n", + " view_pdb_color_by_chain(os.path.join(example_path, element_to_view))\n" + ], + "metadata": { + "id": "vAk-cPl4p2Jg", + "cellView": "form" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "#@title Run (~2 min on example)\n", + "import os\n", + "#@markdown The folder path on drive should have\n", + "#@markdown (1) a file named \"subunits.json\" with a description of the subunits\n", + "#@markdown and (2) a folder in it named \"pdbs\" containing all pdbs created by AFM.\n", + "\n", + "#@markdown The results will be saved to a new folder named \"assembled\", under the path_on_drive .\n", + "\n", + "#@markdown The path should start with /content/drive/MyDrive/ and can be copied by selecting it from the files sidebar with Right-Click->Copy Path.\n", + "path_on_drive = '/content/CombFold-master/example/' #@param {type:\"string\"}\n", + "max_results_number = \"5\" #@param [1, 5, 10, 20]\n", + "create_cif_instead_of_pdb = False #@param {type:\"boolean\"}\n", + "\n", + "subunits_path = os.path.join(path_on_drive, \"subunits.json\")\n", + "crosslinks_path = os.path.join(path_on_drive, \"crosslinks.txt\")\n", + "pdbs_folder = os.path.join(path_on_drive, \"pdbs\")\n", + "assembled_folder = os.path.join(path_on_drive, \"assembled\")\n", + "tmp_assembled_folder = \"/content/tmp_assembled\"\n", + "\n", + "if not os.path.exists(crosslinks_path):\n", + " crosslinks_path = None\n", + "\n", + "if os.path.exists(assembled_folder):\n", + " answer = input(f\"{assembled_folder} already exists, Should delete? (y/n)\")\n", + " if answer in (\"y\", \"Y\"):\n", + " print(\"Deleteing\")\n", + " shutil.rmtree(assembled_folder)\n", + " else:\n", + " print(\"Stopping\")\n", + " exit()\n", + "\n", + "if os.path.exists(tmp_assembled_folder):\n", + " shutil.rmtree(tmp_assembled_folder)\n", + "\n", + "import subprocess\n", + "import sys\n", + "import shutil\n", + "sys.path.append(\"/content/CombFold-master/scripts/\")\n", + "import run_on_pdbs\n", + "run_on_pdbs.run_on_pdbs_folder(subunits_path, pdbs_folder, tmp_assembled_folder,\n", + " crosslinks_path=crosslinks_path,\n", + " output_cif=create_cif_instead_of_pdb,\n", + " max_results_number=int(max_results_number))\n", + "\n", + "shutil.copytree(os.path.join(tmp_assembled_folder, \"assembled_results\"),\n", + " assembled_folder)\n", + "\n", + "print(\"Results saved to\", assembled_folder)" + ], + "metadata": { + "id": "OgnoSp2WNMPr", + "cellView": "form" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "#@title Display Assemled 3D structure models {run: \"auto\"}\n", + "model_num = \"0\" #@param [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n", + "model_num = int(model_num)\n", + "\n", + "output_filenames = [i for i in os.listdir(assembled_folder) if i.endswith(\".pdb\") or i.endswith(\".cif\")]\n", + "assert model_num < len(output_filenames), f\"Only have {len(output_filenames)} models\"\n", + "output_path = os.path.join(assembled_folder, output_filenames[model_num])\n", + "\n", + "view_pdb_color_by_chain(output_path)\n" + ], + "metadata": { + "id": "WwqShmF0_YCY", + "cellView": "form" + }, + "execution_count": null, + "outputs": [] + } + ] +} diff --git a/scripts/CombFold_Crosslinks.ipynb b/scripts/CombFold_Crosslinks.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..1855be918e3df8ac03806a2bf4c529c0406c0ec7 --- /dev/null +++ b/scripts/CombFold_Crosslinks.ipynb @@ -0,0 +1,306 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [], + "include_colab_link": true + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "view-in-github", + "colab_type": "text" + }, + "source": [ + "\"Open" + ] + }, + { + "cell_type": "markdown", + "source": [ + "![combfold_logo.png](data:image/png;base64,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CCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgCKgCCgCioAioAgoAoqAIqAIKAKKgGcIZPOsJW0oUwR27NiR/cyZM/lSUlLyUPDCwYMHi/L/ubNlS52C7OfOncu1b9++MoF/W+d5ihYteqBAgQLJF3goc56/nStTpkyS1M+ePXtyrVq1khV2RUARUAQUAUVAEVAEFAFFQBFQBBQBRcAuAqoEsIuUjXLbtm3Ldvr06YLHjh3Lj1Cf98CBA6X4WXzXrl11jh49Wv7kyZOl+FsZBPi8CPQFeXPTbA5RAvDm4vf5A/+W3s4h+5/iPS3/H3hT0A0c43cnc+XKdbBcuXKrixQpsos+ThYuXHhH+fLl9+fMmfNUoUKFjtepU0cVBDbmLNGKwIM5zp49mws+yQFf5OCnKJjk/4XHrBw5clzgPc97LuQ9C9+chZ9E2aSPIqAIKAKKgCKgCCgCioAioAgkMAKqBIhgcvfs2ZOdt/SpU6eKHTp0qNKGDRs68/+VkpKSyiOoF0CoL0bzheQnbz6Efnki6PHiqiL8i0KA355G2DvKe4B/H8OCYHvJkiXXIdjtqVmz5uJSpUrtx6LgcOXKlc971rk2FDMEdu7cmQt+K3b8+PEiKJYK8rPsiRMnSvOzHPxXCiVAQXgjHz/ziTJKlAKiCIDgbKIAgC/O8SYH3lP8PIFS6Tg/j+TNm/cASqQ9KJV28TMp8B5FwXQ2ZgPWjhUBRUARUAQUAUVAEVAEFAFFwDMEvJNIPSPJ7Ibmzp0rQlaJ/fv319u7d28LzPqbcftfhbc4glRxhPw8CPwxHQTKgHO8YkVwgndX8eLFN+TLl29NwYIF11asWHEpAt3u3LlzJ6EgUKVATGfKXudYkuRB2VQWfqt++PDhOiiZah85cqQ+An85XEoKw3MFeEXJJBYmOUMVTRkpnX71MMGv5OKfKWJ5Au+cpN4JFAKHUR7t5N3Eu11e3FFWwUM7qlatesQe9VpKEVAEFAFFQBFQBBQBRUARUARMQkCVADZmY86cOSURuMpv2rSpK4JYB4SkGtzAVuRWtQQCf6rQ5eUNvw2SHBWBXhH2UrgNPoKiYge3u6uhfT3KgfnNmjWb36RJkwOOGtTCviOAK0nOVatW1du6dWt7hP4m3PI34ra/MnMpPCdCfyrP+cl3oiCQN8A/EpviGMqkPbigLMPaZCnuA4urV6++EmXSPt8B0Q4UAUVAEXCAAIrTPMThSV0r03nsnH0yc4/KqH7w98G68jO0nfOs32dQxIsFX5Z+tm/fLgrsVAu1NEDIv9P+zo6rWrBOej9Df5de+0JCcK5C5yz0d8EyoeSmpSvcv4N1LyoneyyWmqpYj9MvAuvMQpyPxL03lHfkNjCU1zJaE8LxdnrfR3o8mLZ9CSd2AQvPFM5q4lacJR8uzLLxlgxczqbGVwsAIbgG5yjtd5nRXNnFML31K8gb0r+8F7hQO2G3Qb/K2dkI/erb+HbnzZtXdenSpb259e/JIt2Y2/7KCM/5hHA/hS8/gQkKdvSRjFJgL2bf87nhnV+jRo3pCHPrateurRuRnxMQpu3ly5dXQfjvunv37g4I/p2Sk5OrBG75rVhbmKSegP6/UiAFa5I9+fPnX4ZSaWmFChXmt2jRYiaHW+WfGPKPdq0IKAKWtXbt2mJTp059GGV9R9bPwoHDubg0SXyd4CEwrdAn/87o8JYerMEDfmid4O/kcJmeEHCSdXNJt27dXmvQoMHOrDpXs2bNaoZV5UPsaXXAIG9gXs4EfqYVwlMPzIE3FLJQ4Sl0LkLnIPSgn/b/g22FHviDfYX2GRQc0lMOpG0jo3+npSnIM9JPCtalu+vVqzf80ksvnZBVeSJex80lYasFCxbcwXm6boCXQwXNtPwayku/CYMZ8Hd6iqu0CqyMvgH5vcQVO5knT55FvXr1eo04YYfiFeNI6J44ceIQztU3owwpGmhH9oHgWh9cE+RP6e0Hab/9tOtQ2vlIq4BMS7r8XdpI4T1YtmzZ0ddff/2XkYwv0ro5I20g0eovW7asEsJ/N8yuW06ePLkpgn+TwCHCgonifrght8d52YCrcLss7wB8zHeg8Fjy0UcfTatfv/4MhLrNlSpVyvK3FdGa8NmzZzdftGjR9WPHjhXBvw5KpyIi9JvGc8I/KMIEltxsepUJeFkZLWt/bt12obyYP3LkyPEdO3YcweJ2NFrYaT+KgCKgCIQiwL5dgj28G3tbJxOUp0HaRInKobw4a/xwfpdllQAoZypx5ujJXlJBOdeysFjZR9DoJWChSoA4Ywh4uRxrTS8UOdVMuxwUCxPcOvOyHn4ErFlSCcB3VYO1ph1W0AUMY61jTM26WNMU/1KtRwiOHz++8eLFiwePGTOmFQtySzbrkvJBm3SA8GioFzUTGJ8EkKsl75YtWy4hneFmFo/lQ4cOHTlkyJDv/ehX2/wVATTIjdAk3/bjjz/2YxOpxa9Sg0cGBG2jYQpRKGXne6nIgbviL7/80pVbuGtH8HTq1OkrlAHHjB6EEqcIKAIJhwBrE1tb9lRFqkl7uCgBoKdQwAw+4XC3OyCJXyM4mDQ3dmn3o1yAV1OtTPWJLwQkDhPzV1h42TQlgCApWcdMpCtas8z4ZTPIb+BaU1DcyaOFQ0b9xJyAWAMwZcqUxpim3crtfyc26IZ8LHn89rWO9ZjT6z9kkSiCMqApWDRev359m2eeeebKRo0afXPNNdeoMsDDiVuxYkW1adOm3fb9999fDvYNpel4X6iFfvimOFrnnigDWmAZMPCbb775snPnzt+QoSLL+qR5yDbalCKgCNhDQEzxU02WDHzk3BXb6MGxB0XmxtT5iQU6JvNrLPCIpz7FxNtkWSq9uBvxhK8XtJro+p5RbBIvxmu7DZMZ1/YgnBYklV/en3/+uTNC7oDp06d3RRtTTz7ieBfCnOKQWXm5SeHvdbiRrr1y5cqOTz/99I2tWrX6FFPv8Qh04QKZeElKQrVFwL9sM2fOvByz+fsRmMVfNU9CDfD/KzOKYU3SH/eaFnxv3bCyea158+a/JNpYdTyKgCJgJAKyf5m6T8nhL6sLwEHfWCOZJ0ZEmcqvMYIjbroNxv8wlWAjhM0YgqNZ0DIBP0spAYjun5tgNJ0+//zzm7jt7oqAWzlWgn8wNVtwbgIB+yRoUXAjELPwHGnpiwG9QkcN/Glq4LPeauHChXNxmfiUrAIzNGaAs2WN4CSVvvzyy7vwn78BPI3zH3M2Gnul4Z2yBDi8ffTo0Y1RfLyLVcBXpUuXPmWvtpZSBBQBRcA1ArKfmviEBqMykb5o0aSH82ghrf1kdQRMvAmP1pyock2VAJYlkf4xTb6XABGXE523FgqAqHwUEpiDhx/nkxD0j/Am8f/HiMh/ABok7ZoUOIM1wil+J4E7JHKlmIbnIC1hUeITFOSfQdPBPAhUJfh3AcpLqrii1Jfc8Pn4/9x+Kwigtyp9V8GKoo0oAwhi92HdunUXkFVAg8CFWc7GjRvX49tvv30Yv/+eKAAkGnKWecQni8G2XrJkSUWyHjRAGfJ248aNN2QZAHSgioAiEG0E5OAn0eZNfaJy/jB18AG69HD+/ycovcwUhk+fkheCgMnfc1b/zkxVNmaURSKqH1bCWwIgeJTCN7kXaSJuQ6DugALANwFMJH1mLxlBbx//ewDh5wQp+ETY31+yZMk15FjfybsPGg4VK1ZsP8JgSsAi4DxlzhUvXlz+nfrBShALIq/nQRGQK0S4z4k5eVHaLnjw4MHKKARKUa4AZcrw7+opKSlFiSZflvLyewlUIgoEr59s0F8NMqti1t4MM+9JM2bMeJsb3rVed5Qo7Q0bNuwG4k48znw3jYeAf37hDm+WRwlwD1YBdX744YdXevfu/aNffWm7ioAikKURSE2RlaURMHvw6aX8M5tipU4RSB8B03nZdPr85itTlSBGzEvCKgEw/c9L1PU+BF67FiG5OwJYKa8FsIAJ/0mE8m0oGNYWKVJkG4LOfm7G55cpU2YrQngKvzvO706To9NN/vT0bjL2B74YSSdjIVRlY3x59+/fXwAlQP5t27ZVIP1QaVEK8Dak7wYUq8zYS6AcyO3V1yY+AqS3qEuflUhvV4tg8B8xxpnc8O7zqo94b2f79u35STN5BzffD6E4qR7v4/GCfr7D/PBMf3imLG4lzw4cOPBbL9rVNhQBRUARCEFAFPKmKgGMOPzFmFsUg99PgKnCSoxZxfjuTZ+3rP6tmTw/MactIZUACBjVEP7v3rt371UIwdURwDxdRYgnsA+hfxO3+nsLFiz4S7ly5ebWrl17adGiRQ+LiX/16tWjdvigb2Ei8bEO+llvCw4WH/4iWEFI6r8qpP1rQ77MBghhTVBOlEaGz+UFKIy3AHlS++Ee0Hjjxo0/fvHFFyO6d+8+CbpS3Rqy6kPQyRIIuffu2rXrPjAvl1VxSG/cYuWC4qwFPPNPXHTyXXnllV8oPoqAIqAIeIiAKAFMNQOVYcb88Och1tpU5Ahk9eBtkSMYuxbiwZUjq683ps5RzOcloZQA5LjPM3Xq1Gt4ryNNWReEL8/yroofP2vMVn4uqFq16pT69evPx4f/SNOmTYM387FbgjLouUWLFmJ9sDDwfkNQxLKkbmu2c+fOHuDTHgWJpEQsFCnhItRhFSB54oeQ+q4tLgsNli5d+rHJ2EQ65szqo3DJ/9133z1AAMD7wbiUn33Fa9vCM1jSNCB7wF+/+uqrfNddd90H8ToWpVsRUASMQ8D026+YH/5iPGNZffzpwW+yX3mM2UW7jxCBrPy9ZeWxh2WbhFECLFiwoDKB1+4n8N8NCBgVPDL9P4egIv79i8qWLTsTpcKqNm3azEK4TQqLrIEF2rZtuweyJnBLPXnz5s2158+fPwjXgfYoAmqDWRX+FhE/0EaOPHny1ME14VFiMFQkXsCrpIXbZCAUvpLE2O/ACuUeeFAVAJkgLbEu+L5qr169+onPPvvs/E033fSRrxOjjSsCikBWQsBUocp0BUVW4hFTxqqWAKbMRGLSoYJwYs5rxKOKSOiLuHePGsCkuBeR6v+AQNEdwSu/B82KL/46hP+5BPSb1aFDh8ktW7bc6UG7RjRRq1YtMdVfJe9PP/1UjpgClcmecD3/7s5bN1JlAHNQBheB24iIX+v1118f0aNHj28bNGgQl4oTpxNGEMBrVq5ceR8KkbJO62bF8oGgl7VxJbmPYIGbCRb4U1bEQcesCCgCWQYBPZBnmal2NNBgFihHlbSwEQiYqnA0AhwlIl0EjFD8xbUSAH/rbBMmTLiLDAD3InQ1iSRFXiDI3yna2Mg7B7/+UTfffPPERGfebt267WaM8s4jjkJ7bu+HgEVP4ghUl5t9t+MHwwLETeizb9++uuPHj6+KG8LrDRs2POC2vXioBy+2I33iQ4y9TjzQaxiNLbBMeXTNmjVbSTuZ5axHDJsLJUcRUAT8Q0AtAfzDNl5bFoFAlQDxOXuq1DN73nR+MpmfuFUCYNJeeOjQoX8h2N0N3DxHFHgNoTcZ3+2FBBCcVqpUqTncRk6pVKmSyTmGffnkLr300jk0PAe3ii4IY1cSN+ASlAE1wNeVMkCUMlSteuTIkbswkc9LvIBXGjVqJAqHhHu4/S+D5cPd8FI78XfXxzkCZA3ojiLlEVx6HiVdZpb7/pwjpjUUAUUgAwRMP/iZTp8yVvQR0Nvk6GOuPWYNBEwNDBjzbz4ulQDTpk2rQ+C1J1EAXIXAVdAtD8vtv6T3I6r/mCZNmnzQq1evZW7bSqR6gwYNms54po8cObInsQMuO3r06CVgJcoAV9KtuAegCLiTOSs7atSod6+44orZiYSXjIWYFFcdPny4v9eZKBINp8zGg9Iof1JS0iAURssp935WGruOVRFQBLIMAmoJ8Gt2BFWE/H+Wj7kwkGW+Pu8HqnzsPaZetmhylhgvx+mqLVdCnauePKqEgNCR28L3UADc7FYBIML/2bNnD3H7P54b/38MGDDgb6oA+P0EDR48eMoTTzzxIIEQ7yU2wlsoTCSwoKuHuSqKe8C1ZCj4L0HgriJDgSvrAled+1yJuArNN2zYcCsKgJI+d5XwzcNj5VE8XU96S3WpSPjZ1gEqAr4hYPLBXAVgVQCkZXyT+dW3jzRBGo6H7zkr81dWHnvYTyyuLAHwLe89ffr0/yMdXeuwI8ugAObtyUT5n4/wP7Z27dpjunTpstZtW1mlHnncfyTt3QyCL65AQLsZ/Jq6CcDITW8u5q490eALcWteDsH545o1a0rqxbh9SEtZcPjw4bcxgGZxOwiDCBdXCgJVNieGRH/I0m/ToLlRUhQBRcATBOJBaPBkoNqIIwT0xtIRXFrYJgJZXQg2dfxGWP/EjRLgk08+uXPmzJkPcdvawCbjX1RMbv/xOd5RrFixMaT5e6tr164r3bSTVetUrFhRfLTfmzJlyuy1a9desXXr1gHMhSgD8jjFhHqNSKH3MEqdvCgC3o5nRQDCajt82PuQGtGID9rpXJhYHmVR4d27d3clhsQIYkhsN5FGpUkRUASMRsBkQdtk2qI1qYrBxUgLHqoEiBb3eduPqUKmt6OM39Z0fjKZu7hQAqAAuBth8WmEx8pu+BAT4xRuGBfVqVPnCxQAI+rXr7/PTTtax7J69uy5ElP+VaR0+3bZsmV3IqzdgGVAKafYUKcaioAHCKaXgyCPb5G28IjTNmJdfvv27flwbbgWvqwRa1oSqX+xBsDdp9WqVaskZeWniTQ2HYsioAhEBQGTD34qAKs7QNqPQJUAUVkWfOnE5LVGBqzrjWXpJV0GrG+8EuDjjz++DwXAnxAMKrn5fDH/P4wJ+oROnTq9iun/AjdtaJ2LEahQoYIsKqsQ3v/GbT6y/N67RKh3ihMxGSrt37//PhQBedatW/ca7hmHnbYRy/JLly5td/z48S4aDND7WeB7L7tt27Y+ZKmYSMrAvd73oC0qAopAAiNg+sHXdMEhgVnD2KEpTxg7NZkSZvpaE5+oeke1KgAywdLowICiAODG+SlRAEi6OacPwf/WFSpU6O2rr776YVUAOEUvfHlu74/269fvf6VLl/4r6QTH4HJxInyti0vgTlDx4MGDd6EIeAQ3g+JO68ey/OLFiwej/FArAB8mQawBiBvRCkVAfR+a1yYVAUUgsREw/WCe1QU+0+cn2l+H4hFtxL3rz/RvOavzlnPh0TveML4lY5UAuADciwJALAAqulAACNPPwez/708++eSfuWHeb/xMxCmBKAJOPPzww0OvvfbaewoXLvw+lhdJTofCHJfDr/52rAoe4ua3mNP6sSg/e/bsWsSYaOGCN2NBroWCJoX3eOCV+A7GP9Ba7sCBA42MJ1QJVAQUAdMQMPlgntUP5cIrJs+Pabys9JiNgOm8bDp9fs+uqUoAI/YBI90BUADcjgvAk24sABAcTnO7PJ2b/78R/G+u39yl7f+KAGkEdxPI7VlSOJ49evTorcxdCSfYUL48FgG3km5PXAJedVI3FmVXrlzZGT6rG4u+7fbJt3BMYmHgrrC2TJkyq7CK2YvS4jwuDGVw4ahHFH5JwydZDYy0wJAMFCgB6hOEslCVKlXiOouE3TnTcoqAIpAlEMjqQeCyumCSJZg8iwzSCGEuE6yz+rcm4zdVERDzT8Q4JQBm4b1mzZolWQCqOEWHAIDHqTce///nUAAscVpfy0eGAJHc96MIeG7SpEnHjxw5cpcI9k5aROirRCrC+99///1Td95557tO6kaz7K5du3K8/fbb7Rlf4Wj266QvFACbCxYsOKJz587vtm3bdlN6dbFmqDlnzpw7CMJ3NcoBxzEdnNDjpqy4BKAEaICVSBnqqxLADYhaRxHImgjowdzsec/qgkna2VE8zObXzKjTuTN77kyen5jTZpQSYMKECe1nzJjxf5JCzilPiRl6vnz5vrvqqquewvx/j9P6Wt4bBFAEHKSlf7z22muH9+3bdx+CfR0nJvMEC6y5efPmhz/88MNTt99++2feUOVtK7gs1GJMjnnUWyoybg1l2KHixYt/RiyMfxHE8WxGJTt06LCBOAz/N3r06LxYB9zOmApGi0Y7/QjfEGuiCnxUj/Ib7NSJ5zJkm2DI2SzSccZ8Y4hnHE2kHUVWNvaoHLgQ5UFBl409TizWzpYoUSLu5nrPnj25zpw5k5u1+ky5cuVSTMQbmrKkEoBAu7lYM/PCW+dIW3uKfcBU/jJ9fqLN1qbOk2McyBiVnXhcOWV9K1++fFa3eHGMX6JXSEpKysYZNbukbWetysP/55C1qlSpUhmeVSPERHkwEwCNUQJg/l/o3Xff/Uv+/PnbOJlwYSQOzrsxdf7msssu+48qAJyg51/Zhx566PUPPvhgP+kEb+fw2405sh1/gmB7dRFOH/j++++3XHrppTP8o9JdywhrTalZ011t/2uB9YIePXq8l5kCIEgFaTNPgPPoRYsWteNbau0/dc564CBRhkNFU4SoCRxo/doknBFlszR058DlogC0F+YtgVVDOVxlSiMIFkKIKsjPAoE3PxthTqxLUlt+6qmnLjCHZ/gOTiJoneI9wSaZxBq3h7gb+3gPFSlS5Dg/T/Aeq1y58jmbJGkxnxFgznPj1lQSd5vyzHdNftZGwVYWpWgR5jw/3efgzca3dp55Pf3vf//7aIECBfaVLFlyCy47awmyup3D0A4OzzEVrlm386B8E7ehSrx1GFMNGQf8WpCx5BZtFWO48Mwzz5yGN4+h1DjK3r0P2tcxjo1ly5bdC48eYhxxEX/EZ7ZI27yYproW+th/CmIxV405qXXixInSzEspfpbkZ0nmpihriSiaLrB+nHjuuecOMS8HuSA5JC//f4A1Y3elSpU2FStW7AD8Fqs11fX4ozxX0epO8DBSWGEdy46wlh8eKyhrG/+uxH5W/uTJkyVkL+PNLy/rWz5+5n399ddzBC5+LshehkWf7GWneI+z5h0nW9ch4UFe2c8OsE4clX0My8XjKMDjcb1QhVYGXwn7YWGsgmU/rIvyuC57SmXkvOLwUyHefKxVqXuJnHf+9a9/yR5yiP1wP+8BWauKFi26mbVqLT8PsLe4XTOM/K6itbCE68cIJQDm1TmxAniIw0SncASH/l0UADDRhnr16r3TsWPHj/AbFn9yfQxB4I477viKed0wb968v6II6MdmYFsRwEbRguj7f5g/f/621q1bbzFkSKlksLA1YdEqahJNQVrA+QTC//TGjRvvsksfZecsW7ZsLZu4cUoAcM7NgaM+B44ijEesTIx9Nm3aVICNriICVD05oJPdpCquFuISU5xxFIP9i/FTBMFc/JS1V97sHIzku0jrsyYb13nm5BzvWcZ/BhxOSpwH3iO8R3mTqHuQg/96hK6VCF9b2Cj3NWjQ4JCxIIUQxoEgZzpWQk43+iBumdVLi23w0Bb0FQz+PZub2wjmOx97WGXmvzXxbNpx6KktrlCMrbS4DPFT5tviEPy7aeEgZCUnJ5/jYH1y1apVh9jPdqDsWTNs2LBZ1apVm8e3vJmDcXI05hPhMjfCZQ3G0e6jjz5qD8+JxVNFxiD8W1B4FqWcJW/oA39awqcIoac57B1dvnz5Psaxi0P95qFDh85H0TgXZcBWxnIqGuMI9GHywdz2PhjEC+u4ovBYHeamC+tKW+amBvNQmr8XYF7yBnks7fxQzkJ4E52AKJVO8/NUYP3YAa+v/OabbxahRFyMEmoX/BbN9dXk+Ykim/7WVUSKIS8JZl3ORWaeUrIWsJ81+Oqrr2rw7yrwUln4rATrQRF+Ct/lCexlqYrNjNY4oS1w63uB9U7WiRQsVpIlm1SAF2U/E0vePe+8844oEdewn23ESmovl3oHvBybtuU/ApyPC8I31Uj73ZO1qjMXHzXgmZKBM5CsValEcHa5iBgUSGIZIFmhzgcCWct5R84y2+CHZWPGjJmHQmCxKMnZE53sJSZfkjg973g+gTFXAoh/9dixYx/esmXLfRySCtgdoSwqvKIAeIk0de+j1TZ5ou0OK+HK9e3bd8EPP/zwV/zPxQSor11FAAtFdg6WvWbOnHkPgeH+bUpgOBa3bGxUldIR2oyYOzDezmFuphNiwPb0f//73wMswucFdyd1/S4reiM2kYoISrI2RPOQamtoCG5FRfDnwNSew1J7hP5GktFEDkq8ebghtdVOOoVkHrIHlAV5Ahtn0XTKXRABUoQv5n4fQsDmF198cRmb5RwO96sw195TtWrVWN32ZTj24cOHX/btt9/2EMWIjJNX1m+5BZJX/j9VCRJ4gxvlb8J6SMOhAn6wXNqNNRVLXjmsph5YA2/wd8F98PgXX3wx7YYbbhhmZ9LYswqvX7++2YgRIwZx8OkI9tU52BRFmW2nemoZmVdeoakQ9QvxswoHoQ7EVhmMYk6UOj9Mnjx5JFlYVjCPTg4+tmlAwMzHga35119/PRhe6iLj4C1il3cDvCm3f6Lgyk/dsvxsLIe61atXX0cQ1a0c4qazz49jv55Xo0aNI7aJc18wIYRM9s1mzM8lrC2dUC7Vg7/KgW9euzwW4C/hd2FKeSWOjcRYqc1huzvC2JGFCxfKjeyazz//fHrdunUntWrVapV72G3X1Ns521D5XxBFZm54oTzn8SajRo1qz7/bcf6qJgGe4aF8sg/b5bmMqE3VEiD7BfY0WSskILGcpVIfLjAs9tEU0gIf4f8PcRG07Y033pjPPjYToW8Fe9lu3pgLTemMz0SaQsmMSlA8zunFOA91Hzly5KXsh+0hoDLrFWxjbz8MrFVCt+zLeQOv8EhNeLM7bR74+eefd7MnLmLP/QkF0Y9NmjTZaePrMO78Y4PmqBWJuRKAQIC3wTz3ogAo53DU29iwXkXI/FAVAA6Ri3Lx3r17L+Eg+wxB6PKwuPdM5/YvXYo47BTmUHoD1gRyq/16lMlOtzs2SbnZrWACLenRgCC4lxu3dAMBZkYz5lZ7uDESIcO2Ii4aGAivYFJfDNrEEsCIB6E/B8JNA27lenz33XdtUVLUEcGJg1IhuxuehwPJRr8F5KVNWUObICz0xRJhNzey69gwFyBwL+QQ9QuC5Cb+HfPDNwe9XAg13bFskCwiJsWhOI4JouCTqRKAA0l23Ge6MvfXoHzpztzX4MDq2UFLeJ7Dk/B7EzCqR8aU3r/88st41tBvevXqtchD3rHI5tKWQ/+1rLOXMBe14V/PlICBcRSF3qJg1ojvpj9uXj9MmTLli549e872chwZtGXq4Tys6ffSpUur8Q4hbe5VXHbUEsGfx1PIRLBD0SN8VoRb3joc4HuQlvmqL7/88msO199jUeR4H3FAYEIoaRyMN1zRkxSIisVPKCG44ZYhxlFr1rKuWAC0QQEpvFZaLErsKgHDDczu32W9oN/cvKWQB0rB93VYM7qjkLiRf69GAfCz3AajFFhONirblo52+4+gnKnrTHBInu1N6WHE3pEDhXUP9pHr4aGerCsV0rN6iwBfK7BWlaSNkigvG9FPX/aS2SgDvmGtmoRCILMLIr0gzgT8mCoB0HAXwVzkGg481Z0wCBrKI5gXDkUB8G68+Qk7GWcileXwupQF/AVu9stx0G5gd2xsRBXhk3u4RVo3YMCAiXbr+VWOQ3lpNidHWQ/8oiVtu2Idw2Ip/nVHnfbJd8Reu1MOIkYpAWQcHA6KEkxGbhdXOB2Xl+UR+HKy2bVks+uPMmgACpdaInzHQPDPdFgBC4SqFKrKBt0Vug9wwF+PIDkZAWwK7h9L8AWOmc+53Aix5hfmLejAQ8jLqUy3Lb6fgvgiZnpgYv6rcmC+FcX1dTRSy++DcuBQ3IR5rM3a2Z2AqR8T0HMECvCIXD7ghXIoZYdw03wdfNyQQ9vvfRU8RJxxZBdlCQe4u6ZPn96ZDDCft2nT5nMnbksOyTH5YB60dPndkHDJyIv73I0I/0Mwm27JtMiNqe9P4JBdEF5ohxVKPRR1fbDUGYZVwCisihzvJzYIjrkyMhyNYHFWbqQD5eT7CPrBhLoTyZ+D/w61XJL/D7obSZmgNVKo61dQGXSKM+16gulFTbBF+C/HPPckHlBfFHRdWJLLi+Bv0l4WUApAVo7KrM2V4cnuWAnsge7lWK1MQPCbxBpiSsBgk9eboOVbOJZ3/HcuGqpzHrqHebmSOaru8zaSSl/AKqUMCqtBS5YsaUfffeDjYS1btpyIoii9dSUe40w4ngu3FWKmBOAWrSiC3d/40B3FAWBRPomv4dhLLrnkDVUAuJ322NQbOHDgDwQFeROh/o9sNiKk2Hq4AamHyeIjCxYs2MShZJ2tSj4Vwtxb/DCL+tR8RM2KEgAh5iCCiWMBj+A8YlYlafhKRUSED5Xlhh3hwamlkKeUcGPaWsy+OTD157AmN/+50vq0edqhR40FhMjyHGjLI7i2QnlxNTc/kxEy5AA1Fb/LWGnJPbtx9giq1GYkSnFG7ZH6tDO4PYxVSm9R/ti1aPKCPngtH993G/bNyigiG2Id8H63bt2Wu2kbXm7F3vsH1rJ+8EfxaPKxnOgZRwNcKf6ItUpdFBEvtG/fPhrm526g8quOHEp/JzRMmzatDYL3fawx/eCtktE4UKcdoPA0+0dReKMPMXkaoRhuhtXLWy1atPB63zXaEkD2UtbGybjWfQMmJwI4Bc/LoW5HoQqBoFAf+ncRwEIVAMH/lyalnKy/Kewph3DLW+oXwwXb5exVgjhL/bmQuZR1pDO/Lx0LPnM6zoBVkShiKuEaWIk9rD3C3xUoE6fUr19/LIrRX5y26WF5kxUAMkxfLAFYrzpgyf0kyspL5JwRbYW+9MdbjrPhjfB0a3i7KzR9Tnr4tJdFsqfLmdi1b6aHvJK2qZjzTkyUAGghC8M8j2LScQuTaHtiJLoNwUOmDB48+AlMW3f7ODHatE8IXH755e+iOczDAv5HJy4gCDGdMYd9BG3w45g2Bzdln6jMuFkOR+JPGZXbGaeDk4MLQcUOcqh37AOFdcZ+OexIG6Y90JUXE3dRvkT9wey/LBvLzfiiXYUCsjHrVVwI/2mBChyiJAtBY0ws6yNoDMSffezcuXM/adu2rSthMsLJMPImECVAumsLAdQGI7D+EfxapQ2MFyEOtqsH5rAc38JtWAXU4SD/MopVR9ZRuIYM4Kb5UW4eO7NOXBzhzzYlkRUMjKM4ypTrMUwpS8yYf+MyNiuyVn9X22QhUw6kF/E/frTXoth5nDWmRaz4KxTBwIG+AlZEd02dOrU8N35PY7Xh5a2ryfMjUBwlBsckMk596DFfxqw5+KsZSqZ7sKobCBFlTeAzN2AEhL9i3AT3QLHdlv3sMjJRfdulS5dPkAv2u2kzwet4rgRgvbqac5GsV62iqUROb54CfFxHUpKzLwqPvz1o0KBvQ8qKEkCsXG3LmgnODxcNLya3MRyoB/Dh3sTHXNQJ2Ez2z506dZI0gKoAcAKcQWWJ/Hr+nnvueRVB/m2037YDRIkgiAa4Lx/55bEcDv7fZUUXFUsaMusbq4mjbvy+A8KPLJTGPaIoRNtbVnyxo0kcAkoHNpRXCUjzJ+ZcDuepUd7j+QmYWOYUP2NMzB9AAHubiPZ/4MZPAoZF84mVBUJmY5Tc6ofTFiDC/e34Z/8DzFpF+7Yjg0NPftbOXphC/p0sAtfYnTQi/t+MW8jfJGWr3MjbredXOfmuUUb0mTFjxr/41tr51Y+B7YqS9jdtK4qZIfDXM4E1xihyYZN87HlXMD9/QyH6WxA3D4g0T9t88aDOS1o7D8YZ8yZk3yTg6c244byKAuB2vruyJqxjkQIT2MsKsIa05VLpaSz13sJV67JI23VR33RedjGkjKsQz2cIbnF/F4W4SXzEWiW80FMUlriL3BIyAlG6HvcUhARqLKqHasGNYA7FUQBcCvNUcYIj5Rdj5vHnzp07z3VST8uaiQBa23cxQR/FQmJbGGDRr8Lt5a34sTaN1ai4vRJz+ZgfoNMbP4fIc5hyurKSQPg5TZtGHnpks8fkrDgLfFQ0udx+5SFWyYMIJ29w23CNxCSId+E/Lb/IeMSqAWGyPWZ0zzDeN7AKaByl7ypsYLQo0ZG2m6Mo0S66SeLwfAemp38Er/om8YDQwtrZBsHsKYTI68Ph9d57793LrdljrBEtTRpH4FavC9/anzHprB1uHA7/bqrG7hzjTrXW4kB9OwfqP/PvBibNSyjO0JYT0/HBWOL9CXcUidbtxWO64JSdb8XIfd4J+PjPV0aJ+SJr2L/FmpK5jPsxpbeXyR7NJdFgFKMvvPLKK89hbVzCCU5ZoKwnayEWANegSH4GPqpn4nolNIkyFTnhD1iH3C3zyu/Ecjfqsq5Nnor5Ohh1YNB492dD6epEg8RBdQcKgCcRHKfZBFaLGY4AkYf3NW/e/BWEz+lOSIUXOqDRfgjNr6QWi/rDRmqsFQBgiBLAlSCP8CORiR3HEojGBMjCToaAwmDvu/sSh9xS3Iy/IIIx/TY1caPzEvPA+EqhYLmOLByvYGIerZuUmG9+6eB4kPXotyjD4jfLgedebhhqm8oHHHiaoAh4hLm7JCO+gJ9vw9Ljfv7eyEve8aqtALa9iVXwFJcE4m7lxWMif6WOi/EWwAqnOlYc9xGg8XHMaeuayl/BiZDsBFzeXIdrwJ0eWWQZOz+BMUf9bOwF04e2gdVkY9yYXsGK7j7O2xVM57FIxx8YXy3ki/uwCngVpXbDSNvU+v8fAeLhXIL89jS8VMd0XOCFZgRZfeiFF174FEvOS1m/JLC0PukgENWFDq1keXy6B7Hp2Z4QTBdPc2P8IwqAKTqDiYUAaaKWowj4L6OynYpI3AK4ne2FS4n4tUX94dAdldtoNwODtrMoAVyZ9PNNBvOzu+na9zoof/Iw7/YSzrqkhtuDstyq/od0WbfCZzGJQeCSdC+qiYKlO8E3n8Uq4AEO+r5Gi6cvE2MCHA26A2D+3Anf+T/yXTT1Aly/2pCDL3tkS4K3PU7Mgt9ZcsDP16LQ+oME4/OLBi/aZRy5UERdjiLgDi/aM7UNmS/Wsfoosj9AwfR3UQCYSmtauqC1GLx03cqVK1vHC80R0OnJzWkE/UdUVXyjWcNeQHE+CJ7zdd+MiFAfKksgYS4NriOQ6xsIrn196CLLNYmiuQJKlWcQpqNlLegFxvWwIL0ey06JPRdVWdcL4qPVRtSA4YatILcVj6Glk3zEtsbH4eYCN5Q/XXXVVU/YqqCF4g6Bfv36TSIK76dsVhKZ3tZD2Qrw0yAWJkcuJbYaz6QQi4kwrt/CUSRknsWP0VWuYQ4Kpppop+IBfTkw9/PNEgBzyQrcHjyLAkBMq03KXR8JPziuy9pcn1zhT4DFYwTaMZnXHY8tXAVxpyHw7Cl8CisipD2D0NOGOvY2q3CN+/h3ESz5NrrgJvUIiqyiwa4w3+7IGnmfWAvEwy2gxAhC+SRm5608gstUQS4PY63GnBiXiSUz3IWHeBvxfVyHZYlxqWQ94plgM76lVfOYzt81x9rVim/oRb773vHw3fuBh5wXkB+6cFn0PHECrvSjj5A2TbdqiXj4KGf/CqYdI24o+g3kNPwbiDnvRO2Ag5lfO0zgLpNUR3b4QKKUkwpwKvnlnyJNi0b8tANanJbp27fvGyh7RkO+rQ9ClEhE6e/y448/3hLNIXPQljRdJmvVz7Pg2Y6xEIodGl6pZwv/aGL+24ksW7bcaPd9wR6T3CocFP6FAuBa8LO1PsUCg2j1yfdVGRO6+0ePHv0QQplfihfjeE2sPzCdrUOwxDfAoBt4x40SRIJWEvSrNzeAtwqfIPwXxzrgEQmaZfgh6CK2ZhxNscC4A/7zwnfZVCVAtD5lz/thfrKTZrQ/1gAdPG/crAbjUgmAu0Y7zkUvAmX3ePru/Zj6oNKKdfDvZCMb4EcfcdJmRHst6WQHsy9eHidjjScyI5oXrwYaFSUA/tv5CX5zM6aWNewSjsC1l5zwz5PLeqndOlouPhEgrcuhHj16/CslJWWh3RGgTCrCwjSQDa+93TqRlhNzdJQARqYHlLFB23mEFzHrd/xQT8yzTTTRDo4lD0oAzwV01qZCaLkfwWzsKjDwvH3HE2FAhcDhqSKB5O7HnPIO8rn7sU8YJ6Ax7irccr5JNHSJixA3CoAgyyCglccP8vYvv/zyCdI3vYyiVPI3x9042Ad6oAjoYsCnoCSkgwAuZzUQrAbBa5Gul8atASHDjTslwMKFC+uQBvBfrGOds7oCIDiPARwaEnT0L1hHdNIP2jkCKJSvplZWc490DpS7GjFXBPhxuPsdFDBRV9wAujlwA7AwEZ9dt25dzQTgjrHirha5ytdWqVLlK0y4kuwQH/CtbESgkuvwU4xKkEAOp7lFCWDwBhupEiDmC1JGcw/m+SU4oB3ecFKGQ9Mt5F2/VtLLOKmX6GWFx1mvqxJl90Ful6/wYbxGCgAo+koY/H1nOg1CN7EzGmDZ8jzm2jejKI1LnhZlDHzX2wee0yY9QED4DKuTFihQq3rQnMlNGLlGpQeYuGdgBSDBbLubDGgsaBO5AxfS1rhLPb148eKasaAhxn26PteRtaUT5664siaLMdZx173vSgDM+nKhhbsVc+/ydtARNwD8mjd36tTpxQoVKtj2E7fTtpYxG4E+ffq8jwvIROEBO4/ccmEN0BfTxKhoeDGtlVu1vHZoi1EZ1+4AHB7ECsAe8DEYHPQVYDMq6mXXmH13IrbELbTtVURyL8mLeVsBi4D6WHE9QFAgryPLG3nAjlcFQJBZhH72z2ysjTHnnwgIyIlrTnMUvBUiaEOr+ogACqZaKN8j8RE2dq8Jgc3INSq9acWa7TEsfy6L9/XLL5YVRQAK0h4ES3yU4OSRWrCkJTNu+MQpvsRK6glulZzW0/Lxg4DvSgAOkL3Ird7O7uKEoHWeG+GhDRs2nBc/MCqlXiBQtWrV42QLeIcb91UO2quxbt26qzmQ+B5kCSsFOVkbmx0A2sQSIDX/tMvH2INZQAlQxOW4flcNs+8ymLTewZw2t7s2edV3PLUjhye+x47cCPxB0id6SHvCHpw8xCjLNsXNXWMsCNtmWQAMHzhKpiJbtmxpZziZkZAXN+uT+GxjlXGbRMWPZMCJXlcujbD6uxoXt/t8GKvJ/OL6XIdiqZHdSzkfMM0KTbqeG6/A8VUJQCDAbKQtupNYALY0+hzIrVKlSo1t377924ULFzbZP9kr/LWdNAj07t17eu3atT+GF2xZgciNF3zWKRoHRjSiOU1OEQiUsqC4XVQiqRsNPs6DMOpZ1H7xaSUOgPhMR4P2uO6DW7+cHJ4uw5zyGo8GEnf+th6NW5uxiQBKgFJkY2lqs7gWizICojjFWqMOVngVXXbtdp9y2V1iVsNtpgxzcDPnkqhmSopXNNnKSmAJcDPpA3vE6xiiRTduZVW5lG2glyTRQjw2/fiqBCC4z+UIc7a1xZQ9wG3wUITA3bGBQ3s1AQECQg5l8ZljlxaJZk7O5b4EMvPcZzyUBpQAYgVgsiWAkBvJ4cpkxVsu1gdPsgMsWLCgGrfal6MAUDcAmx8Zh6dSmAbeBHZeCWYm35zYREWL+YUAB8/sKP0qkp1CtXR+gRxhu+y7lcgUUNllM6YrnV0OK7rVWI+vRpmtgQAdwM7a0ggX5buyQJpLB6j8viiu3A2IkWPLjTuijrRyTBHwVQlAjk4JTmTroI3mX6wAxjdp0mRKTBGJUudo0XNgapMXn/bUl4U8Hx+cL5GcuVEp8J/nnru1Xfv2oypVrrQ0e47sJxo0bDDpoYcfevzpp5++mf79SgPmCs1GjRrt6dy582soArbYaSBgDdAFIaWJnfJuy8SBEiDSg1UkCgS3sNqtl4PbDk/4FIVRPxQKrVXDbRf6X8vxnbUgB/WdWN54MQ+qBHAGf5YrzXpbY8+ePXoINXfmC3OGSeT5MXqNwg2yHEqYS2EPXy8/zGU/95SxtvTCGuB29y38rqbJvOLqXEgw95rgVFDPSR5yiYFN+aYEIO/2YJjHthUAB/xD5cuXn1SjRo3D6eH09bBhze+9+57/q1+33mz+npInV+7DA/r1//Luu+5+Fp+oZl5iS0qsXNwq59+4cWMhTIdyEwnXs9sI2sr7009TG185aNCwjh07TClWrNgxeXv16jn+4YceepXndoRyTxZ1xpHv9ddfH9y6detpf3/mr/9bv2L15WePJjepWLRs/v3b9vT+4O33//rss89+0LNXzxEjR45sQ+yGiBcy5qLRI488+rcWzVv9xJyc5k3u0rnruLuZuw8/+LCr3XkiM8SM4sWLTxUXETsPwbBqE39iENpL327qoSVXoroDlC1b1tVGYWduPCqTA/wjnlt4pMKGDRsGoJws6hFdWaYZuZ1lberH7VNfDwYd8VrjAQ3ahMEI8L1XxRJAg1IZOkesB3k5M5RwSZ7JCmeXQ4puNeLa9MdNq43drFvRpc7s3jjHFcMS4HKyBdhOW272iLynjovJCnI5q4+vCMR8HfTiRiddhPC5vZnFyVZuSRH0iAEwpk2bNmPTNjZ8+PDGL7/40rPXXHvtJTkL5judv1iR/GUa1Lpw7szZwj8umDP41ORjF0aMHHFdv759J/zjn//8L6bkW9xOGZYLdcZPnHjZ4Kuv6TZt+oxOF86m5C5WsvTmrl06rxhy403bn/zjE/9BWDqMxYKrXOyzZs2qd+eddzzxzTffXl+wcIk8uXPnsapUrw+52ay167d1nb9gcddzZ1POE4SrG2m5XiJDwhK3Y0FDXPSee+7554gRI+6tVqpSjqcuvz9bu9rNrdKFSljli5ax1u/dYi3evLLQl7NHWWtWre81ePDgvk899dSDbCqfFSlSRIR3Rw85WOu9/PIr/xhw6YDL8+bPbhUtXiBX7SalrQvnL1irNizo9/OC6Zd8PvSzO7t06TrusccefXXgwIErMusAZdCJMTxEJe/FDWTYgyC8lpMbylbcTAjP7XBEvM3CogSgqGcKIZvdOikmC0okq3YkdZ3Q6bisCKBeKAE4OHVnY2uBEsAxDX5XkAA8wTeQos/vLh23z7dYdcmSJdejYJxIGldX66DjTmNYIXROhAxT58UORHKgCwZ5Mn0cQh/fe9GjR4+Kublt1zA7OJhYJp7mJgS/XBGkbY354ddEPrBLExdUxTke9eF79uTCyG6/dsuF8rMoKUy7TRZ6cDdqs3DhwsGM6Xm748qgnOkxblx9a1jiehkIOEKIM66e9twUyGrkW38eNuxqXjzsP7UpX07C48aN64/PTWu7xLJIbKtYseLoatWqHQ2t889//vPGa6+++oMchQvkbnLDQKtMw1o5S9etbhWvXilbyvGT1t5VG7LvX7fF2vjT3KrjJ0y4G/OVCigfHm3RosVGu31LOczIK77zzruP9u7T59rTR46UqliiYM4uNUpbBfPmtg4cO1l7/uSxtfccTT73408/de7ZretoBOwXsFo45aQPhPG21157zds7du5p1LJt7xzdLrnGyl+oqFW2XHUrO0vI9q1rrQ1rlliL503OPuq7MdfNnftz82HDhj08aNCgKblz53YknKHBy3nrrbf+SxQAjSvXy/6PwY9abWs2tU6mJFvnL5y3jpw6ZpUtUsoa2KKHdVWbftY3CyYU+Ne3r5//z3P/eZMgjtLXh07G9sEHH/S+avA1759IPlS5zzX1rEZtKlg1G5SyqtUvaZ07c97asGKftXHVgRwLZ2wtM33CtFt37thZnQitd/br129DZv3Ur19/GiZvM7l9vN6O0CZpiwiU05I2fVECsLGJAsA36xknmGdSNpKFxRGfeUSv3WZECRCRuwzWPfk+/PDDnuLfbrfTaJSTTYyxbSWN6i8FChTYgaB9gRu2ihyw69F/DZNuegIBwTpweJI87uOigU+s+uB7PynbQ6FChVbky5dvP/8vLlxVmZvmzFEV0w62GeHEOOTZgHJ3CePYI3TDW5UQsJvCe9UYi5FWGXwT+cDbljthBmOPZC2MCtsxRjF128B3L6/MTXb272qMuzHrVHGTeUxoxYWxQFSA0k4uQgAlQGsUsW1N2huEQNnLWGvW5M+ffxXvHv4trq61k5OTm7DOFDKJn6ElP5ZG3UhF+mXTpk23JzCLOV4H9+3bl+vrr782ev0J8Nou5KNNrJ27sQY+iWKnGK7WVViXarJ+6tpkg6l9UQIQdKsnfduyAhCNIYffqV26dJkQSu8//vGPW//z73+/UrxOtdxNh1xm1er9a0ra82fPWslHjsuVjFWmQW2rQotGVpV2zaxlX47JPnfkpEvvvffeHJiE31CuXLkjNsZvcTNY5fbb7/h49uxZXTvXKp/t+stbW3XLFrcaVyxlFSuU19px4Ki1YucBa9XOgznenbak+eeff94Y5itCSpZ/Va5cOV3XhbT9Tps2rf7NN9/8+u49+5teMvBmq8+lt1l58uW3zjGWlNNyzrSssuWrWZWq1rXadb7UmjTmo+w/jPu83hNPPPECVgd38+e5dsYSLPPll1/2QwFwT5PK9bP/ffAjVovqjaxjyScuakKUASln5T1jDWjWHVuEbNmf/e5/2d54880/4ve7gPlYbqdPhKpeT/3p6TfyFE6pfN0jnaxLrqlv5c2fyzqTcs46eSwltYlq9UpadZqVtdr2qmZVqVki23efLGn90EMPv8KN4o3NmjVLyqifmjVrHhk1atRYFDvdBaJw9PDRl2ZBH4iZ15QKFSrAJN4+zLsoAExWAkRq0u94s/AW4Uxby8ZaEdF6tX379qpsEg1RdEWR7My7gqfE6mYysVDeqVSp0vKSJUvuZzNL4YBXCmVjLbJeDME6Z7BENTaFaAkIxrc7EHoSVgkgShms00YTn+Qz5mU9KWtTFdQoP0qiaGzHvNzO3PXhIGkOM6XDIHwzJziMj8W96tNatWrNYxyHpBhxMYqyTzcnsvhNKAMuh788S7/pFZ9KDBAULkVdtmfyWpY6JHjsIK6AYxo0aPBJmTJlNmBZs5szxQVMlCvAY92Yo3sp1t7l+KNSjdtCtznXI92rojE+I5VjMnCEtBbwT3mTstvwvR5jbx3Lme5N4WfcOfdDY04E7QqkcO4JT9/Hv5uaoggQOlBOtEYGEIW2o4uvaDBfLPtAcVOEb7uEKXOVFgt47RzvXCyGvyKQ/GTWzl3sccfZlwtwGVyR9KU9WD9vY19rEUscbfQd830qokN1egME+HKY8HeyMfjUIhwodzKRkxDafjNB/3r48Ba333Lrf63CBYq0f+gmq2LLxtbZ0wiUaBl/e0TjKEL08bNW4XKlrfYP3WylnEzONn/89H5/ePDBf1HuwXA0sNnWuvvuuz/EDaDTPV2bWHd3a2w1rFDSSj5zzjp77rx16Ngpq1C+3Fbn2hWtHvUqW00ql7Jen7wk59ChQx9C8yTS+1/C9SF/f/mll/62Y8fuVj37D7F697/ZyoUQciblYov7c+fOWudOnbVy5Mpl9eh/k3gIWBPHfNr4pZdefBKB4CaY3FbKPA6p1YcMGfJnus3xj6setZpXbWAlYwGQ2XOGvvs17YpCICXb40P/Vf2TTz6+mfKPhRsbAneFSy7p89rRkwdq3ftEF+uSq+uh1DhnJZ+82Er4LMqGs8dTrIKF81jXP9RKtMX5h7218JLHH3/83/Rxf2b9NG7ceDwbyFV82FeE2/BkweLQ2BErlDa0+WM4+l38PR7MviJZVEy2BMgWqSWACG9sCkb5ACKkzW7ZsuVzl19+ucQ6CX328o+9pOXb8NNPP0mk9BvFB9cFz3peJWAN0EzWz+bNm6/3vIMYN8j6tAkl8rvdu3d/p169ehdZpzFXByDv+2+++WY/h0exTOkXY3Iz7F4OSijYJzCOv+Bqd9E8YWWVRMWprJWr4a8UDn3ivhdxzA2vsSAwVX6v2zShPb775IIFC47t2bPnMygAd4XSxDe1k38PxVpuKymWX2Tfs21VGe2xsS65VQJEm9SE6U9cAUaPHt3KMAuecyivx3br1u0ZUnxvCgFbboI2876PXHAQ+eAF1qXqpkyGxAbAjbQNFwRfoez99UbO+ZNw50Ju0vNx3jLS1USmhzPIaoT+d6+//vrPQ6eL/VpuO9fKC79tWbFixT/Z10xXBDjnOA9reH6riRB6BRtcXbs0Mplzrr322mGh5UcMG/bgmUJ5Cnd48EarbMM61rkUhMpQBUCaxs+dOWNlz5HTan3XNVapejWyEeXuHgTHsJYIf/zjH18UBcDAptWtv1/e3qpZuph19FQKwvA5zOZ/laXO4dN+mn8fP33Gal+zgvXslR2tZlVK5fjs88/vfeWVV64PN04C/V07YeKES2vWbWr1vfx2K2++AqkWABk98rc8efNbvQbcYrXu0M/6YdKkvnNmzxETd1sPB/PmmzZvatilXluraZV6KDTCu/enmtVgGdCrYUesAnrk/H78uIFETg178Pjqq2HXHTmxp9qND7W1OvSpjmLjPKZgGcugogyQv192a2Orbc/quX788cc7sDrINMUQCqIjRYsWnQNP2XK/EDNdblB/NRvx+BF3ALAy9naA4SZsTABZ98E/UneAdmwIxtx4yk0gLlAj01EA/Ma5HTp02MNN4VCEodUes3NEzbFu1yXIolh8uX0iUVa57dNOvSQE5zH9+/d/Ja0CILTylVdeORdF8HR+J0oBIx9uctYjUL6dVgEQSixBY3fXqVNnKOtapjFaYjHAQFyAvCjB3Z5TjOQx2W9RRq7CBPmTtAqAUJxxl5vJ5ci3lL/YjC8Wk5FOnzI/gYw5bigycm5CBmKsYEe2p5pcijQ2yRUAHl0HL3+YRgFwEV9cffXV33KWm2hSsDnBENebdlwQSHAufQIIiJsP82SkOb14UGEhPRuF0xeZTRj8Nh637eGUz/wWNIvPutvNNV3Y+JAKYSbfgz/aZZ6t3PR+HdoYAfFKzZ41u0PBksVyl65XI/Vm/IKNCJViFSAWAU2uH2DlzJv7DAL6A5nN7ZQpUxrMQQFwefMaCPadrLy5cqQK/5k9yWfOWpVLFLb+eXlHK9vpkyW+Hzv2qnD8s2jRQvDIla9lu0us/PkLs2mGj6V1jjJ5UBZ07X2VVbhY2TyvvPLy0yz6tm4BUX405OPNf0Wr3qmm/nYfUXrIglizbDXrwP791ZjHTFP/YG5TeMx33w8uVCR/XgkAWLBIHswbw18kn0MRUKREfmvgjY2soiXynnv9tf9lagkg9GMivQQt8+ZgUKvMxsTBJDcxBNpBv+fm0+oOYJebvC/HvEbkDkBE+wpsbDVMMm+TFJjcyIa1WEFR8Au+3Gvt8L/3yGfYYiHcrtqxLrndQ0wVAha3bdv2g6pVq4ZdPDFDnAY6juLPRGt+hFdwZ1iIO8PMcH1yo7KIw/likw7nQZqhKS/frXEWCuEwzezvMjcokNahfFkYrh0UBd9yiF0Xrlys/h6pdVas6I7nfll3m4J7OZP2Mnh6A1ZSohTN9MHdZRYXNW5v3MM17/jvgQCBVckC1spx5YsrmH455Gh4WPgUhscKmMRjwQHAa8cJ0L4Qa72wAgfub+NMXj8ZU8zPQW4PcOkyFL6FIjjatgJgMtdL8LfQxgjeVWvv/n1Vq7VtZhWuUCbTW/O0RIjLQLVOra1sOXLkGzF8+NWZcT3BC2/AH6hY9VJFrUrFC6fe+Nt5iK9stapa1rq5YyNr3sLFrUit1zazemtWr65ZrEQZq0adptbpMGb5oe2IIqAggQPzoTjYum1raT5KW76n3Kx3Oouyok3NZo4issrBJH/ufCgBqsCW2bJhHlUhs3FJGsP1G9c2rt2orFWnaRnr9Cl7qfykzeRTZ6zGHSpaxcsUzDfm+zG3hcOd26yZBOeaYkcIkkWLQ2MTzKi7hmvX6d8DSgCTF3sZkj1GTn/wYRdVp5h5WT4QmNFVk5hQ1uP2rZqryj5UEl7Gf3IPbj5i9p/pg4CWRFC3XaYJadwC1kfZ5sa001h/YJSNKzGfXxluTuTvKAo2sN5cZMptp140ynDwOU6w3bm8YZUZKAFOwF+bTOMvwYlx5GXv89xtMRpzkFEf8u1jbbIH5V7YuDXt2rVbj3Lel0C3XmAgPt8u24lkn3LZpaNqRu7zWDnm3rNnTwsEaVuXQo5G7LKw8DOuLdu4dc3YxDXQNoLbKmjfZecs55Icx9VYwwsiu9QklpRbXjaSV0KAcLzfsuYW8CIbk+PJCFNB+IZAgPs5N9lSvsNv4oqy22s6Eqk9T5UAEpTN7m0bk3mWRWMWB5CDoYByO5abW+kceQsXtHLlRe7NxA0gvYnImTvVYvhCuNQ1+Nq1O49Zya0dG1rnMIW3+4g5u2QN6NGgqnXiyOFyuD80yqgugnSOxUuWtilesrRVoWJN6zy+93Yfoa1k6YpWkaLFra1bt9ViPLZS0+ETn7o55M/tfI8QS4ACeXDxw+Sd/jJVOnBgzI4QgMFCbqsAvv6OFnWWpFy5c2B5kM06cyalYDhMOMimVK9efSKbh3zQYR/GUYasAl3DFnRYIA6UAJEerCKt7xBRR8Uj2mg5ODVgszcmK0Dg4HQIYSCsmwvC5nnKJTn6xhxB664wa0AVblDquKsdkbLKZZeZVwPfk9ww2Ha74JZWguwZc6sVOjrmZi8mk7bHgqXJQcYflhd9AT6TRhlH7kRTAshwUTZdFGsiM1xRAu4wUUEjNEeimI02LyVCfxKwDeuruia5AoDrGeFRO/gSCHM357iwim87bXlVRi6OwLUmbkfFI2gzovNJBP3aqer4XCcBPyN1v7RDmNMygcsTCai6zU5d9rVTJl6g2KE9WmU8UwIQlVHSbbSxGy0ZBtuMYDcj7UDF6pffXZD88m4OvecCJv0sNJnegAQPFkXy2bpgv4hM1gwrV45UmTwbgnCG0naqrz1+5GTS4VbeOdQ5iHMgi7343dnFApOsJdkQrpdsWekIv9SFkAwCy7autrLnznkaU8VwJoi/zpPkNrdpRREKorgFiH6Hfm1pRjDRnYwgNM8mDmI6XnPDhg3lvPyQ4kQJ4HjBD8EokrpeQp1RW643Wg5O4gpg103J97EIH2PdchDzyF9TaIR54P0j1LFvbhOuQQ/+zjdWjJspt76UJvJaEgpHW1YAIfCFvWn3AGrHTcArhzmY27ZSwHVgN5YyxvmeM47cXCy4iQViIn+lziN7+lluTg/bnVQOvDts7nt2m/SynOs12UsifGrL/u2QTwSkbRb/9RIIrOVNMtOGN0+QCcBWij0EssPQblsBFg1YAwGlqxEg0O150fRvwPFayJqb30QlgPAD+9QxMt7Yyv5GTJULnLP2Gbx+RoPFM+3DuWSaQXOY1pcG6Jp2R8RELu/du/dPactjIremTPGSW3evWW+d2H9YTPvtNmnlwApg5Xc/SCDBM506dZqcWUX6Tw0WccaGH3vadsR//gSBAgMMmeFNEAf8c7Vr1Vx6JOmgtXfPFoIX2h+LlN29c6N16OBeq0GD+svxIbQlLIhgAV3nJCVgdgeKh+yiBCBd4YINy6xK5Stsxt8101t3Fv0jNWvUWbBz82Frx8bDVk5iKth98uTNaU3/fp21d8eRlFYtW4+1U09MzVDsSNRZu0E+GhH0UbIEZLXH8YKf6ABhFVKEtckYKwDBW7RnuAPYyvgh5bk5lFtaWwqzaM0nQ8iBGaWbg5Nj88RojInxHGP9TE2hZ/exq8S0255X5cSqgT3DtqApOepZX4+adFiSw7lYAnAr5UYJ4BWUnreDEuCM3UOsdI7gtFMsJz0nxJsGTReAIhmlUUpXGQhumJX4LopGMigf6p6Q9cNOu5hnn4H/7Z7h7DQZcZlAgMvSSUlJVSNuzNwGHJ0LZW/nNfLbZp86wQ2/bQs8yh42aV8zjUU8UwKQLqknB8JqdjSU+Jocxs/7y/TAID/9vkuvuHzopklzLuxcspKbYkql/ifzJxs35iknTlknDyZhdn8+e/8BAzIVLnv16jU2Z65cp3/4BeHcgbCcg1v2g8dPWV/NW2nlzl/gCBGkM/VNQVjek3I62TqdfJIbAPuCcq5ceaytG1dZhw7ssYi6uglGtqUEePDBB1+vUL7Cuncmf2EdOXnMymmzzxyUW4+iYsa6+VbpUqU3BVIgZgg6yppj99x75ztrluy7MGP8htRyduZeXABOExPg0L6T+O6npPTr13d4uLkN/h2sJ7CB2HIJoE4prD1sK6Xs0qDlYoaALAKu1itcASRTiBvfdd8GK4d6fNvC+gQHCRAlAHWMunUOCGlFsQZwkybM0aHEt4m4uOFT4OzogIoy2ag5keHIgQfeOoGSyfZY2F8OMRbbSoMozUeqEiACd4DwB4doDSSkH76bM8yNbasLFAaSgcL2XMZgSInYpaxPxlkCYLJelTOQaanbTsLPtm5mA+dEW2fZaDIV32RhXGDLuOzT2EwSgfE43mvF3ffXI71ZS6jsbaIEYH8Ln/YsMHgnly0u5z+uq7k6VKc3YkzWK9ldnGCwHZgfZphfunOXzpOLFC68d+HH31gHN26zUv38M2FGuTXPliO7tfjzUdaST0dZFcuVW3nnnXd+l9nM3HfffW8ULFDg4MuTFli7ko5bItzbfbYcPGLtPXLCqlWj5ooWLVpkaj762BN//PehAzvOzvzxW4IcnhEGDtuNjOfc2RRr4c+TrJTTJ87WqFFzWTj3hmCjpNQ7SMrFT1buXHf+uTFvobBIsnLlzPwiJRduB2t2bbLemPiJlTd/vsMPPfTQK4wrrLCNtcV0NLvrvv9subVo+lZSGzIPmeCYPUc2K1eeHNaUb9dan74w50yh/MU3/v0ffx8VFpBAgQEDBsyCx5bZLJ+bQC9t8Fk2bcO0SX5Mitn/CGJCnuWKPpQAVdnMqsaG5Ax7PedECcD3Lwcn424DWfdLHDlyxKmbhZGWACJoIQg7OqDaiekQC77j4HPciYKCcZyEx5JiQWtmfXLoy8mlgZtziqu1wu/xyyGWtShFlDR2+2IuxUIj0ZQAjgUTu3h5VE7WWuMsAVACVOMMZFS2DHjzlKwfDnA3bh/jm8yHEqCkgzHEU1HH35rJ6bCZq9NlypSx/W2yr52GR41T6JnCQG4219/RTmrAopjSNLITrEQ2QW5b1nXt2jXDvMQ33XTTzLvvueefSeu2nl348Uhr94q1qab+8sqNf+qDUiB7zhwk38trnT5+0lo56gdr6VfjLEzwl7/2+usPhgMY389zffr0+Xr9viMX/vztTOs0sQRy015mT95cOa0dh45bjw+bZs3fsv/cZZf2/5gUFPszqzP4yivnd+nc+fvZ08ZY0374OjVFoPj6Z/SIkuAc6Q5/mjTMWrNyodWxQ/upCOUvcjNv+0Om/LvQNXPEvHEXnh/zjnXg6CGrUD4CLdJvULMn1g9y+5+PAIJrd2+y/jbiZevnDYvPXz7wsg+HDBkSNm2Z0E/+6S3/+Mc//nT8sJU07K2F1sJp27CqyIYyICdj/P+slSNndpQLuaxzZ85b08est0a8u+h4gfzF1//rX/8KO09pccKXUiwv7H7QdYhT4cZcORz7mPx323ySziCMPDiH0Bleg5bOoMirXJtfFzFp0sQSgHXQ9sFJlIAmmgRzWCiJEiBRFG1nwNn24UL4ycmNRLT4L2AJcIqx2D5sI2imsH+fNNBsMjs0eXJOiRb+4fqRQ6wTJQAClvhQ2775Cte//t0WAufsXrzYas2DQgSazkVsG1Foe9Cap00ks37Y5k+JieFp7x40JpiePHmyNNnJjFKweDC0YBOOzoXiCmCqO4AoUZ3gEuA3R+N30n68l3WbEuOicRNQoyJatPp2FicOjckEBJwXDrj//ve/b99yyy3lPvnkk6cPbtqRo8WNl1vFqhEtn7SBhcqWtM6cOm0d3rbLOrH3oLV+6s/WmlGTL1StUmXxK6++ev8VV1wRtn3p/80333x8y5at9UbN/blPxaKFrTu7NLTqlS9hJZ85Z50lVoD4/ouFQE4UD7kQamdv3GW9MXmJtXDLvvPXXHPNv2+//fZhzz77bLihWI89/sQ/Vq66tfG4b9+rIYW7XXIdgnJ+6+yZFFIg/boeiquAKAfOYgEwa+o31thv3rUKFcy7lbrPlixZ0lHU5kqVKh2ZMWPG3ffed9+7I+eP63Tk5NHsfZp0sZpVa2hVKVnBKpK/kLXz8F7iIZyxFm9eYb31w+fW0m2rzvfv3//Dv//97y8NGzYs7JiCBe66667v/vznP9d84b8v//2lP04ucNVdLazaTcpYZSsVtsrwniNd4Z7tx6y9O49ZS2Ztt0a+tyi5YP7iO5599p93Y40xy3ZHgYLwztwlS5ZI3IOwPt7cIFVas2ZNU8quddpPFi1v+mHbFX0oKKvYDVgaxXk/L77BdvuTjSwQNNVulaiUYz0vhRJAvkWJ1xHvzzlwdqoESAnc7ho1doRMUQLYHgvlhb9MvG3OETBNNQrfSIiRQ6wTBaAoDExzBQoZv3ESaSRzE1K3wO7duxsuW7asVjpxP0ItmcJZNQk+QQHkorISF4Z4D3sk+4sdmgnWlpfsT2XhBzvFo1lGlKeO9rJoEme3L+KalUGOEdc2R0Im5U3/BhwLwEF3ALvYRbMce7Sj+aF8ahDzaNIYT315ogQgB3cHIpZWRBsYduww135u4ZeELUgBFAB/vfnmmy/8MGHi1T89+3atAuVL5ShRtZJVtHxp6xyB+U7tP2RtW7nWOn/0ZMqlAy4dfv8D97/A7f5yO21LGXJNnl+8ePFdCKMf/+/HuT1W7txnDWnfwKpVppjVsEJJq3ihfNbOg8eslbsOYi5/0Hr7p8XWmj1HzmFu/9zzzz//7ypVqthiRszYl7377rsPPP3UU++MHv5Wle1b1lhNWnS1KlevZ5Ut92vq8v37t1vJJ09Yc6aNtqZPGWkVKpB31z//+c8nL7vssul2xxNarnPnzmsJjHfb61hFfD1y+D2Tf5mZt2ml+lbNclWtwlgFHEk5YSUdTbJmrZyfkr9koV0PP/zw+/fff//7tWrVytSyIT1a/v3vf7/41FNP5fpi6JdDPnxudv0SZfNZlWuWtEqVL2gVLFDQWr18m7Vj86HkpH1ncnTs2Om7++6797Xrr79+rptxsWEuQwmwhbphlQDssYURAFMVL/rYQsCT9cBWT+4KudpsMVkv5q47X2tdcCpw2rG08pXiNI2L0hdFWxHWfqdmlOEOztEcRmhfMie2DuTBSk5M7qM1KFFKQNdpsXaz2yflzzJ227d5dtuNtJwEqIogDZ2r9SJSmm3UF2WTE6FJ5tERX9qgwbMiWNtlJx2lsfS5GShrW/YtW7bcgkthJ+rLTY34VMoBV6zRZP2S3wXf4PwE4wikFTiC8WykrryizD7D2rmLAMzv8/+T7NCIkCoR24vaKRvlMo6Up073vWiMRfZWlCwlBWP6sx3fIIQ2U9caV/DJmstLXHZXxpeu+nRQyfa+Jm0GLqdVCZABwJ4c+klbUhNmseUXyoF8BzfVq+xO+Keffvq3WbNm/e+Vl1/+67EjRysuXLCg9baZC8u3bdkqW/lCRa3arTv8cvOttzyC0DuddBC2N9Zg/wQo3I5QeePjjz/+1sJFi9vM/XRSuYrFC1sVihW0CuXNbR0+mWJtO5BkbU06eb5cxUpL/+//Hv/vrbfe+i0HLEd93X333RNHjRo1+I03/vfUjz9OuHzZohnZy5SvbBUsVDw1TsDxY4et5FMnrT07N57r1q3rmAceePB/V1555U92cUqvXLt27eR27pHPPvts/NAvht598NChiiPnj29FCsDsffv2sU7muLDl3kfu/3rQFYM+FqXBq6++6rq7//DMnTt3xAfvf/D37dt3dpoz5+fK2epUtPJVqmwVuJDraO8uXad36drls969e01B0ZDktiNyc2//y1/+soFFu5WNNrJzS1l13759eUuXLu3FLVeiLyQJFYVb+INDXI73338/v4FmzhfYnGzzk6nabHDNy8HJKFcLG+tCRkUczYk0wo2uLUVwBDQ5rhpQAjiii3FI1G5HdRwT5qJChEoAFz1GpYpYXdg2iQ4ITaYK2Qkl/ITOPnNUjMwULf3iCNo+iuAp1pC2lAAoWwvajbvlF80ZtHvO4V5mm/ejOQ7mowhvhim/o0mLx33ZPmcE+zXVFSBAn9O1MGHXKC/4JGIlAD40ecaMGWMrb6kcTsh5uxITKEdpmDp27CjRcf8gA+bmvvCCBQsGEofgTwjPZREKP7ruuuumRAJGs2bNdm/btm0wGQ7qfvzJp8/vP3iwG7a6+fcRvX7H0e2bG3XsseDuDh2mDOjfb3zjxo33I4S66g43hYX4dQ3+4IMPnlq1auUla9euaVO7Vpk8v6z85XS2cylbK1coue3lF754jtvy2cQ28EJoTaWTGAuTCZD3E1GW82Le1gALgccxKWuCJcToRx555E+vvvKqq/GkrdS2bdsN+KzdRSC2Ru+9994M4hjk5oZpY6s2TYfjPvECpvyeRJ9GoF/B7cOgcCbeogFkzLVXrlxZBVrVJSD8LIc35Qnfhp8lHG9mHJzEX72on0S5bNuRJYAoAZwctFzS5Lia5BLmIFvIcUVDKzjF2ERLAIFWgiE5gVjiBzgRTJ20HWHZ7BFYAkTYtT/VBWcnrj0yN2I67g812mpmCNhxcXWLoAj0tG977cRnXfaxgm7787HeBSfxR0y0BJB5RgFQwKUSIB6ETDfrh6njcjoWU8fh4ydpv+mIlQCYW5fET6m6ncUS86dkbusXk/PdtYDLzb0EyRn64osvNuWW94oNGzb0x/99DjfZtuIAZAQNQreYmKzcvHnzdT/88MNzKDd6YrUwu127R95GeF8wYez31tNP/ck+shmUxApCGPhZ8r2+iKD8DQt7xdp16i5GgP6kVatWs4m271TLZYsmLBdE+yopyeaNGDFiGMqUGghINWbPnt2kQ4cOdiPuh+0L5cJ5LAIeRdmTm/mWNFU/Xn311a94pQAQAmhrDrf7e+G5yuEI4gBZi9SV4hKgSoBwYP1q7mjy43Txt7BSEgslI90BXADtePwu+nBaJQeKNltWYE4bjkV5p0oAJ4ffaI7HaZYD9p0Lr732mnE3dBIUMNECA8IHZ53wjeGWANFk60Tsy3acG86KxVmfxFzdtMepe5eJ+5i4tuXmzG/6Gci0uY8FPUbyTyyA8KJP2wtQRp1xI1sRRUAZO/6qCGQHOWzYdgXIbICXXHLJyxw+12KKWm3hwoV3ctNd1AtAJPgKVgFNWWx3NWjQYJIoALxoN20b3JZX4nBTEUws0iVuHjhw4Ey/FABp+5bMAcWLF1/AWKtPnTr1ThQfxb0YI3yQ48svv/zvpk2burOYHkABMIr4Cc+QttBxnIHM6CEWwxqw22CHZugoDtb17ZTVMqn+iiY/jhVkYkIJr3jC3yYDE0Pa5KbWuEhV0cLDxFstGTv7lyO/yWAdAy+cJUp1xOeUaPGDzX5kbmwfZANWA7bL26TBq2J6y+YVkmHa4bKtFLwggetMe0zlTUc4gW1eZAoTLS0cjSMLFHbKb7pGZcIUEW+uHACLsEnbuglC03aQ/I5bvGDSRo0a7erWrdvz9H2UW/VeI0eO/Cu3wxEvkCgUrqLNXAiPW3E1cBy93u7YuIF/glvKOiw8x3B3mGi3nhfl6tWrtw8XiM/wAz3Ootdm5syZAyNtlznIxhz8HQuKAYzpCGmNxhH872Fu7T1VAAidKDHECsAuH+WF77KKEOh0cbxo2uF7SRFGUowLloHvKWhylCVDBsc3VoIfiZDCzultS6SftN36iaQEkJgAjhRNgajYjurYBdZtOfl2XabhMmocgfEn4gHOkRIggENEa7tbXtJ6viNge14DSoCIrXd9H1H4DmyPOXxTnpbILS4BnraojSkChiMQ8YIieUs5cNjya6LcIV7XrgBpsSQTwPShQ4e+t3bt2iewSBj4448/SmaAT9xi/sUXX1yL//gVHOx2Imh+26RJk51u28qsHvRWJI5CB4kQjLC8GiXAQj/6yazNFi1azIGOqeRQv4IouD1wqxhXs2ZNVwI7PJCdoId/xAJgAGM6ki9fvgXEaXiyWrVqnsQASG8cuJSs3bVrl0SlzfT2OhAXoPTBgwfzEAPBkZ9stOck1v3B98sQIMpAR1leCRIoSsK0isLgBh48nKd3SM/sb0FhNlSoTa986N/FJ3YbJs5LnGLEwUmUACaaUDodiqlKALmpdbqPmDoWp3MiN+4iOMsbsULdcecZV3AUbyLYjFMFiIf0ZrWmEk0J4EZR46ZOIvKJbcUb6etK2HG7jQOQTFUCSER8dQcwn4Gc8o/T8uYj4CGFTg9vv+sav/zyCGJhb+DF7B0rgDWYvotfumcPioAPuc0uRZrCW1asWHHTV199tRcBdEJoBwi7hdHwZWcRRebOewph8ChC5EWMsW7dusIfffTRlQgaxxGGx3CLPcYzItM0hNl8NW6nC/Nu7tGjx9t+9ZNZu/jsXwCXd4YPH14PbCpiDdCD8sNC65A1oSQWEcIjFwoWLJiMC8FR5vAi3FAA5ESh8SiBGq+AD44XKlRo3uDBg//tpwJAaISfJDbESd6wCiiElNKMQ0yWs4ISwPXhirgUH4Cr8L18zzLvQSWA/JR2w70yNWnLBFkqVPgPCk5BXkr7U/4uB+Vg6iXJxX4CHtzs9Fth3mUsppqrO92cnJZ3Cpeb8sH0V27qmlbHMb6GmmqLRYNjdwDTJiOEHjdrmps60YIgkfJWm4xztOYzkn5sKwHYy0w1VU8Upa7EHzHdJdINrzne1wLnODd9RaOOm/FEg6647CMiJQB++NlGjx4dNl97QGg7TVaAbQTgO+ElUiVLlkxBAH2XG78iWAO0RREwkOj3q0mPt2X69OnVUQrcidvAFQir5YgfUIDAdSe5eZ9y2WWXHXjggQde6tWr19rdu3dn//zzz5/kJjQ3QuwvCEOjvaQxtC1uy0twa/40wepyFS1adG6bNm2W+tVXuHbr1KmzC+uHkWvWrLkWM/5OEyZM2Ij5/nqCBjYneOAtuAxcH2yDPMA7oHUmAv8rWBGsJsDjKdKvFRo7duy91B+CAuAY8zt90KBBLxIDIClc35H+HYXEJuZNLA0yVQKI5pyAOjWI81CBsmsi7TeR6/fu3XsH45M3YR4UbaLZj2id8xEMJ5uZ7cOij/Sm27QLS4Bok+hbfwGze9PmxnGqwwBATvjRN0zTNByJkGnieGR4ToUmp+WjNTfaT+QI2FbWsZeFvWyLnJyotGDkd8lZUSwB3KRJNnI8UZnJ2HTiFG+n5aM1qkj2Ns9ojOhwjIl1CQTv6pLnPtwj/sYEitsXrpybv5N3fu8vv/zyogjXfMQ1UALcS/aApEsvvfQhlAPFUTzkIpichYBvESisEGnyroB2CyVB/yeffPKLiRMn7sCioTEHuj24AUxEOHaUwtAJzQj/lQjIVwmt7j6sAF53UtePsjfccMPXL7zwQjl8p3vMnz//8c8++6wkipOupPfLTtwFC6WJmLxapDasMm3atCoI/deT7u8r0ik+A973owjqC12nEMp/QrHyIlYUSX7QmbZNgjYuQeGzHd4LmyEArEtgfVGONiJVAhjx0UYD30TpIxDt1yRT7SC0TjcmIwUBWRsS6PbEMcaBwIBO59Lvz8txbAMhSN0B/J6W39p3YwlgGo9FDawE78iJEsDUHPZOedNp+WixgMS3cSsTmTqmaGGn/cQpAm4ZPnW43LAW4C2BD3jY4fNxHStbtuzGsAVdFkB4341Q/8GcOXP+iqDdF9/+GtxM5yfqvoXpv4UCIlUJgLBriQIA9wGLMuW/++67RxGEZzKG3bQxsn///jNdkmCrGn1ejUBamlSB33Kj7kmmBFsdZ1IIy4gxxFPoinKiOZYKNevXr2+1b9/eQnmSips8xA2wcLuwsBbI/vXXX9+AZUUX6D+Me8UuTP+/69Sp01dYEURFASD0QItsnmHjS4iQIhp0lD+2LFYixVLrm4VAwA3ERCWAAOXk4OBYQI3WTLiM3u5k7NEaitt+TBuLKyWA28FrPccIuOEXN3UcE+aigirGXYAWqBJ0i7PVgii0EyQmgK3xxqBQJEqAGJCbZbt0uhYae3YyYQYjUgKwIOWUtBp2BsJBMTl//vy+3bALDdzgryNCfUGyBDRCOWGR3k/iEFgsnqnRzrEKsMRqAWWEhRAu5S2sAnKgCOiKsuAdbr59SQcYxIe+GtNXPzA7QTrAZXZwi0YZAuxVXrVqVT1wqykWE1WrVrVQoFjEChBFTyoJ0JtqEcBtv4WixZo8eXJFca/ApeJFLAM+iwadoX1geXAc4f44GQ7Cds048qAYkgBxif7IYqeHspBZhkeEQUzFxMlm5ub2MCr8nsUPpk7mMCrzEUEniTSWCGDwvaqbQ6nOje/TEpMObLsSGZyK1Sk/G8vLWXwvi8kH4KJTY/nHxVhiXiWiG7LABxNeCmOYogRgEbO94LlBhhz1N3JL3ZK89xa30xbxAlJvr6XbYP5j+YlgYCHApv5dTN5Jb2d98sknV44fP36Qm37t1lm9evXl3EiX4cb8p+bNm4+wW8/vcmB207hx42oQONGqW7euRQC2VNxCc0YLZqJMEasK3BisihUrWgsWLCgAjjH5IMEwhQCP2+3ktZYMAh7lfzVVmPSbReK2fXg2lx0eicEAnX43Tg9aURuSS0uAqNHnZ0fsgSbOi4k0+TkNGbXt9BuLFo2m0hWt8Ws/vyLg6DsNKLRNxC5R+FktAf4/d+lZ18QvzQeaIlICSLA9BKyweTVFCCcrQBLvER/GkNrkTz/9VOutt966h9vs4q1atbJKly6dKuxn9sjfpRwuANaePXtKkW7wFmIc+JIihJv2OsQtuIab61MI2asQYg/4hYWTdhlzr0mTJvUlSGGOzp07p1pKZIabKALESoAgclIuN4EhfVWcZDYWXDiO2hHwOKhnF1qd4BKnZRNlM/YM/oA7gGftedyQk/lydGD0mM5wzWXlA4OTOQyHY6z/nkhjiTWW4fp3irXT8uH617/HHgFHa7ooAfSm2tdJk3S3EclEvlKnjQcRcLoWOi2fpZCOiOExsxYFgC1LAITLk7xn/UIXhURJfP0r4NdvYSoeVgEQpEMEXm6ULRGAiYrfAqH4Gj9oJJDe9QgkZRGgV6Gk+MKPPty0iStARdwkihHQz8JdI9Vqws4jVhRkCJC4CrWxCKhtp47XZViwnZhIR8TrIbRnZYHH6yn0vT2+74hcnnwn0H4HJm9kWfabMNQSwD5XaclYIGDytxwLPLJqn8IH9g5cFDRYoe2Un52WjyZ/ZNm9LJoga1/mIBCRYCQpNRiK3TZS/Bw2QnZr/L6LizDrVFsqt8kS3JBFNi9tlPSaTlIYViIewFUoQZKJUTC7du3au73uw217CPDtqJtHYgGEs5wI9iF4SdYAqUNqwcq4OTRx23+E9ZwoAbxY3L1oI8IhZ1rd5M3Vz3Fn2HaCafZ1fv3lIjfft6PbPH/J19bTKGvdzGc0QHTzHbupE42xmIpxNMYe1T7YyxRrfxEXfBXjXzE2GQdT10I33BnzsdgV4NMdHLfGEhTQ1k0bgvlxXt8sAbjFbyW3fiKY2klZGDogKS8+7iLcYrJf181MZlaH1Ht34DZRErx2ktpunNftR9LezJkzu8i4JVCiHdN66UvKkRUgFTMJDrhmzZp6kdDgti502D2A6+LuFuT4r5com5ldXjd+xrAgivnGZzxIsSFQ5yU2uGuvWRcB/ebMmns35wU3daI96nig0S9M9BvLBNmIlACY35dFuA3ray0m5vic7+H1LSYAN/mpYewloJ2bR3zd5SEl3q/h8D16CFJYGiuFQWBwrlatWhMJWPiLR0170gzCfOp4g+O326jI38E6WAWcslvPy3LEVzghpNtoM5vBkXVtkK9FFAGjEXBzwEiUjTlRxmE0gyUgcU75xmn5BIQs4Yakc5pwU6oDUgTiC4GIlAAScI3h2mlDzLZFUPRt0cPEfgX0nCfAn22z9uBUiTC7ZcsWUQCcaN269Xwvp3D69OmPIaxWwtpgB5H3x+JLb9sHzEs6MmqrW7duk8R9QsZv141CykkmAVwBJEjgfuIwLI0GrWn7wLViJ8L96XB9C72kOiy6bt26sEEsw7UVB393I5DFwbCyPIm+rZ1ZHlkFIBERMNn6K5G+Zbf7jdt6icarTnBIFL4xdRwmrxmR8L0THgv246ZOJDRq3RghYEeA94o0CdXv28fPDft6rAGOHDhwwMI6wTbNIiDKrTZ+7ZIa7yDuBFttVw5TcMOGDWUxlb8UQTVb1apVp7Zv395TBYMXdHbp0uUHxn9aBPqcOW15dqTiS1BIa9u2bRJUcWe5cuV2eEGL0zYCgQFtKVWYZ4msayuIZSZ0mL4wmk6f0ynW8hcj4Nv6qUD/hkBW/oZM5K9EPZjrJ6cIOOVtE7/PRJtFt+u/zk2icUIWGY99aTkdQBwIYchf2bLbvWl2g/3tt98+kVv8H/C/vyDCqV2BVuIBSHl569Sps7hGjRqb3PSfXh0CAvaFjhJYGGzGUmGKV+162c4tt9wylRv1ddBqkR4xbDwFmUNx78DNQcqfbdKkyaLy5cvv8pImB23pwnsxWG43MAeQx11RxSTupkwJVgQ8QSCRvn2T97pEwtkTxnPQSERncAf9aNGsjUBW/kZNXjtjzpURLUDcnCehCDgTbhSiAcAcuxBvvnBlI/n73Xff/QYC6slx48almreHUwTI30WYnThxogS6O/zYY489j2m7J5H7EarLcrveXyLuly5deiYpCCdHMjY/6953330v7t2799wPP/yQesOfGW7yN8paU6ZMkXJHrrrqqi+xwvAt1kNm4w5YfNha3MTagddO/IBwUJu+oNjCI9wg9e+KgCJgC4GECdjIaE1f22xNSKCQroNO0IpN2aw+RzL+iM7gsZm23/Wa1efRkGnwlAydU0/hNLexiBYg8softakEEB/yQrxhgwhGAtV11103a8iQIakC7dixY1MFfEn9J7f9QSsE+SnCI7fz1saNG60xY8ZY+/btO3fTTTe9cOWVV86NpP/Qutu3b++IdUELMNpERoDRXrXrRzvPPPPMZ127dv0Kl4jzATxS8Ql1q5D/l7SAK1eutEaPHm0dOXLk9AMPPPB09+7dZ/tBk502CUxZivnME66sZBEQJQ/xC5LDlY3zv+vC/fsJTBRMTBbQEgXjOP/8lfwERsDk798N7Lpm/JqGLaIzuBvgfaiTSHOZSGMJTnUijskHNs6aTUa0ACFbiZ+/XZ/sHIFAgr4i/dxzz/0Tgf5fKALOjhw50ho6dKglpu6ksrMQyK0TJ05YEjdg2rRp1qhRo+T/z9x2223/4nnJS8JINXgFgnMOBM/5WAEY6QoQOt533nnnDz169BixbNmy85MnT7aWLFliHTp0KFWJIukAEfqtSZMmWd9///2pXbt2nX7iiSfueuGFF94rW7asu3QMHoCNZUlheMpOIINzBGc8jtuDF5YAHlDuWxO62PsGrS8N63z5Aqs26hIBFTRdAuewmhuc3dRxSJYWjzICdgNrR5ksx90lyj6WKONwPIHpVDAZC5Np8wL7qLZhR4DKjCDbSoCAxtP3yUPQE6XE399+++15L7744l8QxtsQoO80wmw+BEGrQIECEileTNrP4ac/HUH2ZUzaxxcrVsyzTXbp0qVNUTA04jZ9d4sWLb6J6oy67IzMBYcJYngPrgFnp06devWmTZtyIeCnWlBIJoDTp0+fEUUAT8qwYcO6XHvttctcduVZNQfZKcQYINEVAJ7hmmAN+b7mJBheWXk4WZ1XPNsDPWairD4vHsOpzRmCQKJYAhgCp5KRCQK6hip7pItARJYAtCiClS1LgGgpAYKjvPfeeyfg49714Ycfvv/6669/AYF8JfECNvXs2XMdwexWvfbaa0NIGdfjrrvuGuelAkD6R4h+nL6KYj6/hkCDnrkY+M3DKAKSoH0IVhI1evfuPaZTp05HWrZseSYpKel4xYoVJ2JV0QUaipqgAAhgIUosO4ub8KgorBL9sYNFomOQdnyKSVabcR1vIvG827G4rWcq95iqoDEVr3ihK9IzuAnjlG8tUb63RBlHKF+4GZObOibwotLgEIGILAG4iT1Ff7bMwbmJzccbaYo2R8NDABfa3glU+hs32J0wd3+sZMmSp8gksMBRYzYLk52gC4EGWxGH4EiHDh0+jKW5vE2Sf1eM+ADbiadw2/jx418ntkHF//73v5+hTPmQeAtum/Sl3u7du6uiaAm7WKGQOSfxK3whwrxG9bB48ZyE5Y8YTaGpdLmBIyuPRb83NxyjdbI6Aom0ZkQylxGdwSPpWOsqAnGKgK4dHk5cRFpIhOm9CFhJ4eiRoHKYkpc+fPhwkXBl/fx748aNV5LX/mei2ucicOAfFi1aVM3r/ubOnXtXSkpKafzotyB47vW6/Wi0R6DEXPj/P7Zz587GuFCsRZkxNRr9Ou1D4v3ZrCNWAGGzWNhsy+Riujj+fnZMxsQJbU7KmsyjSpu5CJiq0HDL+27rmThDps6NiVjFC00yp3YtaU0eUyJ9ZybjHAltOkeRoOdf3ZjPS0RKAG67k3mP28EHRUARormXtlPWrzL169c/hDvAp7gG7MKKoc6cOXOuReC1K0iGJevnn3/ufvTo0baM9WTbtm3fa968+dqwlQwsgBVAQzInDGJuU3AHGNOqVavNBpIpJNmdu3MexgTQw5ihzJABWTFfZOMLLqVWETAKAf1+jZqOrEMMZwaLFM/nJc1zJu8F/pbeK/VCX/550ZNy9uzZ7fzGyUWRnj38Zz83642bOv6PJLIeEnFMkSGSoLUjMkUqVKjQicKFC++TiPuh6eQywKoAQmXYdG5+49y0adM9pMH7euHChU/u2bNnIC4CObBSeJYo/hFrZHE1uJUgemVRMKwmzsB6v8fiR/sEBCyDO8NfmKtiuDJMateunZGZDWbNmlV+woQJJe1gAG+eRvGTVdwB7ECSlcpEpOj0EahE8qP0ESZtWhFw5W9s8iHWDW2JJgCavv4lE0t6ZtGiRRfz/QVpDQbCDv4Mzknan/LJhv4ueON/0c0/CoAjBLKOWYplD9cVN/zsYfeeNRXJOCKp69kAtCFFwCkCESkBMBU/gU/2ruPHwxsDiCUA7gAVnRLoR/mBAwfO/oqHzAGP7d+//zKEyYPQ9gGCu2uTcawKevz4448dGeexUqVK/UjwwTV+0O5nm2RMKDB8+PCXcAPohivDGjIbfFa6dOlkP/t02zbpC8uhbAlrWSLafFwzkipXrrzNbV9aL64RSKTNOZHGEtdMpcRHDYFIeD6SulEboHZkHgKcG/ZVr159xDXXXPO+edQZSVGifGuJMo5QJkm0MTkdj9Py0fzAYk5bRLdkBN47S1yADcQFCCs8IxwX2rFjR33MzPNFE+GM+urRo8fwOnXqfI4gmQdlwD3cft+Fu4Jrpcjq1auvIfVgqTNnzmzs06fPqyaM0SkNxDO4GQVADxQ7SVhMfIRLwyKnbUSrPNYn5eirqJ3+iAGxG4uVw3bKapmEQyDmi2wmiDqhzUnZaE+iybT5jYXc7pl4S5tIc5JIYwnyYyKNKZHGEpyfcwULFjzg9+LhsH0T1xmHQ9DiMULA6TfqtHyMhqXdRoqAa6E32DG3rPvRmkqWgHCR/3MjjBXCzDw3ZaV8TB9uuM8dOHDgTdLeVeXn4JUrV96B8LsHor5xShjCc2uUCD0Z2zFS6Y2rXbv2bqdtxLo84685atSom1DWZEOx8yNxAL6KNU2Z9Y/bQlNoDasEEEsA+G4bSoAjHozH1AO/B0OzLIJBVgavsjRWiFe+U1kfcvCmVRaGHkbsHkwyq5O2jSDOv5lUQtdJrFPWd+nSZZfDwZq6mbmhy00dh3BpcUXAKATc8rzp5uZGgRwDYtzOa7RIlWxWsgea9Njda02iOd5occOXburEGy7xTq+Jc2QETRErAdCW7pUDOhxSODMu4cZdMgSUTU5OFksALwSyiJkSYfcsKfD+8c033xTCCqArPv2Dp0+fvhhB47dAeG+99daNxA6otGrVqgYEcslOoLyf69Wrt6h8+fJb8JffKURgBXD16dOni2ARsRQLg7gzHyM4YsHPP//8n8QzqISgtQoFwEflypUL7+MR8Qy4bwAXlPIobcIpnix48xy+fVtxBzB6PO6R8K7mzJkzHwavnrQosRZE+E97kPb7EBJcFNMujnxa5w8Qt+NTaHre4YiNWGgzoNkJbU7KOoRIi4cgkAg4ux2D23p+M5CpdPk97nhoP1HnRuJXyR5o0uP3/hutsZrKM6o4jBYHRLcfU/lNUIg5bRErAYgLkMQBXZQAmT6iBMBUvhx+5yJgyI27EU+lSpWS5s+f/wK34CW4ya9IwMDr+feXCPat3n///bueeOKJppj5F0U4St0QSC14BcFi9kh6xDfffPODrl27Tpk9e3Zd/r6/bt26I2rVqrXfiIE5IIIge/cQE6Etc3mgQYMGX6PcmOOgetSLYgWQ/6OPPipms+MT8N5Bm2XjvVhECwoWE9Xh44YmLExpJwK6imP5Ud3FBEWEiYv+/KySSGPxEye3bScSvok0Fp1PtwhoPbcImKYAcDuOaNRLpLUmkcYSydybjIPJtEWCeUzqRqwEQCA+TJaAIykpKWEzBCBklN+9e3dtRvpLTEabQaetW7deyy3onwkQ+C8E/oFffvnloNGjR1fBP75Ex44dLW4gLW7+U8e3devWPOvWrauyZs2aKs8++2xd/v8oEV6zowiZ1759+69NGpcdWpYsWdJu3LhxQxCUs2PVsYA4AF/YqRfLMqQwrAS9tezQgGLnIFkOVtkpa6NMPLgDRLJAynoQSX0bELouIocyN9lFTB2PAOGENidlXYOsFbM0AibymNvbObf1osEAJuIcjXGH9qEYOEfcZEuArDyfpo/ddPqcfwnOamT18WeKVsRKAAT7IwhaO+ilWbh5ER9ulAD1sQYYjeAsaVaMeTp16rTq66+/HrNixYp/QFRlIsNa3PJbVapUsRDwLcaYSisZBCwi/1uY0FuLFy8uTHaBwihCDjRq1GgagQbjKvgc81Dss88++weuDNVIobewc+fO75vuBiBzsGXLliYoAaqFYx6JB4DLwCasPeIyXWO48fnw97M+tKlNeoOAyUKNNyOM31b0kOHv3CUivokypkjGEUldfznu19ZNFrqjMf6s2IdbnnRbL1oYm06fnziYfHaK+bxElB1AZq1Zs2aHERxnkPPUToaAfPjX1yGye0E/Z9xt2wQIzENwtNLcjluiBJBXBElxZRBFgLzy/2IRUKFCBQvfebEMsLAGyE28gGNu+41VPdIa3o9vfXPGlIQrw7dYRMyLFS1O+t22bVtLaC5ms84WFFWHbJYNVyzRDwWmKwFivmCGYxAHf3c6FqflHZCiRQMIJBLGbsbipo7fzBMJTZHU9Xtc2r7ZCMhef95sEpU6jxHQ9cJjQA1pTuc1k4mIWAkgbXPbugWh7IidCUeALk+qQEnvZtSzdOnSUv/5z3/uX79+fV4EYotAchaCfYY0ovSwUH6IEsSaNm1a4ccee+zPCxYsCJu33pRBE/egD8EOr2Mc53EDmI3bw2em0JYZHWvXrs3P38vz2vmwz0h6QOboqEdjiwd3gEiGqoeeSNBzXtcOD4e26rS8c4rc1Uh05VhmqJg6J+5m0sxabjB2U8fM0f9KVaKNx2SshTYT93pT11mTb1pN5zOlzzkCuhY6xyzDGp4oATCZX0aqwF1yax7ukeBeKAEk+JhRzxtvvPGI0EV6v1QLgMwUAEHCpYzECiCbgHXs2LHypBt80KhBZUAM1hhFsQK4l2wA5XEDWNO7d+/n8ZuPC0sGXAEqMqw6NnFOKl68+GqbZe0UEyE5PJPbacmfMpEujqoE8GdevGg10Q5akfKqF5im14apdDkZr9sxuK3nhLZolk2U8Zg+DtPpc8NzqgRwg5rWUQTMQyDRzk6eIuyJEoDAeXsRJlfbUQLgW18ac+4Wno7Cg8aOHj1aivR42Ro2bGjhI2+7RTIeWPicW8Q4yPftt9/2xT3AtNyyvxvLvHnz7iQbQDPcGw6TDWBE06ZNl9kecIwLkhmgIyTUsEMGvLaN+Vxop6ydMiiwxDQkkQVlo8eGtZFRcUTs8IyHZRLxoO0hPNpUgiKQqAc4/Z7NZ1jTFP6m0RM6g4nCz4kyDvO/LvcUOp0jp+XdUxaHNT1RAhAU7ygZAhaiBAjrU8xBPj8CaAOEZaPiAkyfPr2DZAEQE3/x/bf7iOIDKwhxibAklSDWAW4imNvtLuJyKAB6E/xwiGRzKFWq1CQyGnwScaNRbICAjM3prpidLhHaV2PhsMtOWTtl4kQJEMmCZ/IhQ+JxhF1f7MyjQWWczFWiCkMGTUdCkeKEtxJq4HEwGDdz46ZOHEBhLIlG74WGoaa8adiEKDlxgYARZzpPlAACd8mSJVdy83rIjjUA+ejr4IMvwpwxD5Hya4owL4oAO2MIEi5lCxcubBUoUMA6ePBgRUzsI8644BcojLEIcQvuwXWhAsqOn/r16/efeHEDEEx+/vnnyvyw6wpwAmXONlw7jnuFJ/ydYvhtdKQHl0jrewX179oJBOgMG3zUNwJi37ARG4YXMJB61dSxuDnMuqnjBYx+tGHqWNzS5baeH9hqm/GHgGn7oWn0xN+MKsWJgIDTdd1p+UTAyPYYPFMCIDyvQ0DabqdnblQrLF++vDduAb/m3TPgwaR/JTf51qFDh1IzANh9pCyuBBYZDyz8z7fmy5fPvi+B3U48KkcwwNtQBLRFON6LBcCbpDTc5lHTUWlmw4YNrQhk2MhmZ7vr1av3k82ytoqhBBAhNCsLorZw8quQ4QoYv4YdbNf+ouQ3Jb9v/wLrisn0RR8R7dErBNzyldt6XtGt7SgCWQkB/d7Mne2sPjdZffyZcqZnSoBatWptI8r8Yju36BzmCyQnJ9dF6C5hyneDO8MBCfQn8QCcKAEkXSAm6hbpBQ+3adNmEVYBRioBsADosWjRojsY4zkCOU6oVq3aYlOwt0MHCoBcKI26w19l7JSnzC+lS5deY7OsrWLMtSoBbCHlT6EA/v40HptWnW5OTstHbVTERHF6S2XsWKIGmnkdmTonbulyWy8aM+OENlMtZ6KBU6z6MDEwYKyw8LpfJ7zvdd+ZtsfZ374vcFQp085CEHDDP27qZAnQPVMCIFSe4RZ8KULaETvIcavaYsmSJe3slI1GmZtuuukTYhWkkCLQIq2c7S5xbbBIWyfWAPlRJKzFz9644GVYN+TDCuAuFC9lsHiYOmDAgOdLlCgRV/7V69atq4cCo60oXWw8p7B2WNu1a9cdNsraLhJwBzBSyWN7EJkXNHqhTDAlgFOsnZb3iCXCN5PFD06mzoupdIVnKC2RHgI6n8oXpiKQKLx5gTOGced3Uydd6UoMBGxJVHaHSrq8eQhKtgQvDo5VEOwGkfKtsN32/Sx39dVXD0NAXkbqPGvz5s22FAGiLNi+fbu1evXqA9yELXz44Yff9pNGt23PmjXrnt27d3dAQXOAbABfV61adb/btmJVb+PGjX1QAtSz0z8KgJ2tW7ceZaeskzIBd4AUJ3WiXFZuLyLZkD1dDzwe+3m+NzcKGKc31B6TnWlzTubK5NtAkzGO5nxqX/4g4OQ7CaXAbT1/RhFZq6aOJVFvzE0cl66zkX1D4WqbOOfhaPbr7ybzmtO10Gl5vzA1sl1PD/1t27ZdTIC82XZcAkRYQVjrMHPmzM4mIEMAuXN/+MMfXkpKSkoZN26chel5qiIgPdcA+R0CoYVgbVH2OO4AeW6//fb/4me/z4SxhNKwePHijpISkDk5h7XGd7htzDCNxnD0kM2gHDEXhE/yhSsrf2esq7BK2WKnrJMygewAJisBnAznorJ79uwxWcgUWs+LJYbrAcZ/RZM3MjcmlCaPxym3JNJYnI7d7/KJiK3TtTYRMfCbbyJt30QhyESagjg74VEnZSOdRyf11RLACVpa1gsEYv4teKoEIOL8hcqVK09DSLblEgCCFRG2Je+7Ec9jjz329XXXXfcaQa5Svv/+e4vbZ0tS6UnKQBH65ZX/l5cI19bYsWMPi7Kgf//+r9x5552TjBhECBG4KOTFsuEJhOKKZAGYjRvAf7DW8CxafrTGSyyD3oyljU1XgEMobxb27Nlzr9f0Mf/iQuHmNtprUjJrL5JFJZK6vo4RHr4A/o6xZy1KpIOTcfMTyNrgVAlg3Dh8Zd74aVznxdy50rmJ/twYtXcY7HaVKLx5wWCMo8/95vbolN+cljd35D5QZt/53WbnWAL8QlHJElDERpU8RHuvh099RSLV23IjsNFmREW++OKLP95yyy0po0ePvvmTTz6piIuARZR5i2B6qe1KEEDJBkC6ugO5c+c+cOWVV372/PPPv8Itu2MBJSJCbVSePHnyE9zwipLlEC4AEytWrGhXOWOj9egUwQqg5Pjx4/sgAJa00yMuA/M7der0tZ2yTssksiUAWDi9nXIKX6Tlz7lRAiSQj5/JG5lTJYDwgsnjiZRXtb63CGR1XjF5bXYrKJtueu12XN5yfkhrgfOHb+1HsWGTv2fj5j2K8xLalck4OOUfp+VjBHlsuvXUEkCGcOmll67Inz//FP7XVoANhLbWs2fPHhCb4affK8L/M2+++eYQ3BvGQt/a6dOnH3v//fetoUOHproJYGK/o127dhNffvnle0eOHPmfGjVqJJtEv9ACjV15b0RwOlGzZs2vWrRo8Z1pNNqhByVAd1w0Otq0AkjGCmAxCpnNdtp2Woa2z6IpPuW0XpTLu13wpJ7n64GHYz9LEM4TTtszWAng9GDvdl6dQua4fBZP3egYryhVMJZfojR+k7tx+u3LWHQ+ozujxikpRAlg09U2ukj92lvc8yfYnjf4vBCLOdU+swACnlsCCGZNmjQZzk35ZfxvNRsYlt21a1f/X375ZVzDhg3FgsCI5/rrr5++atWqBQQJrMnbdMaMGf0RQjoj8Gc7cuTIitdee+3Gn37yNA29Z+MmG0AhLBoe4XBeCoXM9L59+/4HV42jnnUQpYbgiRK4ZfRH+K5op0usSnbjjjILqxJJ5ef5kydPHvFJP+l5w+Y0aPJGfgbLG8euLAEXDnMQTjxKxArAlsI3Dobu9vbDbb04gERJ9AEBN+usmzo+kP67Jo0TlqMx6Fj0IUoAzjix6Dpcn6byZji60/5dlABuzo6JuP6bPCan/OZG6eqUd+K2vC83f3369PkZv3lbeeglyB5+963IY9/HNBTr169/En//5Q888MBnw4cPv6ZVq1afQGOeChUqFEPJYUx6w7S4TZw48c+4LbRGs7mfgIdj4lEBIGMi60IHB1YAF+CjhczZUr/4iGCDp+HXkwZr42XoThfIIFxGL5RgnuxGCSCa/fSCe/rFIw7bdTJXps6P+FE6TTfqZNwOIdXiESBg8sHPzbBMHY/Tb9lpeTdYua1jKsZuxxOsZ5xyw6WAGikOftQ3df0/l0AY+zFvprTplH+cljdlnFGhwxclgFBepEiRRfywdXPHAb8MkfZ7LVu2rHJURu2yk969e7935syZDcQEaDRt2rSniWVQ3WVTvlVDmdKd1IuXczA/gUD8Ubdu3b70rTMfG16zZk0R3kHcvtvCWNICEvxwUseOHXf7RRYKlWQ2CeNcP0LG6/qwCH6yFhi7WLJGnEIJ4MYdwKmA6hf7pNeuE7ydlI3mGOT2JCtnbYgm1lmxr0QUMn07d2VFBskqYzY4JoCpe5Mj1uCMcRarU5PPd47GE2Fhk9ddp/zmtHyE0MVXdd82ozZt2nyDP/1yO3CIv/fJkyc7oQS4xE75WJVBqN7cuXPn9zHJSobeLnPnzn348OHDBWJFT9p+casoQTrAh6GvFEqApf369XuxZMmScbmorVy5sj1pATvYvcVNTk6e3bp162hkaHBjLhYtFskuQfTddIYSQOr5th64oSlNneMohI44bUd9/Jwi5ri83J4kihLAzcHHTR3HILuoYCpdLobiuoqpGLhRuJp6kDUVY9dME6honCWAC4urSDHIavXPoQRwfNEQwi8m4+X0O3VaPppjd7oWOi0fzbHEHGffDv1EaF9XokSJHxBKbFkDcJAsu2HDhuvws68bzRlw2hdKgI/IFvAlgvY5UggO/uGHH/6IIsBW/nqnfTktD3ZPkg2gLQLdLuIyjCxWrJjJt6AZDg8+KLJ06dIbuPmtaQcDpmIPsQDECsD3DBNsxMZlgQjBKAdY5LKDWdoy1JP4IK4UCG76c1qHdeSES0sAk5U2TjYn11YeTrF2WF4tARwCFoXibg8WbutFYUiOuzBOiAsZQaIpAdzyjdt6jpkhESokkLLVyb4XtakTJQtKADcxn0znYzf0uakTrblyyj9Oy0drHEb045sSQEZHdP2vsQaQlIFhH7nxRRBpjR/4lbgG5A1bIUYFSIF4dsCAAX9GESBjK7h8+fIhmOBfFSNyfut24cKFXQhg2A9h6SiZAN4dNGjQsFjT5LZ/LEK6gm0Xu1YAuGjMa9SoUVSiNAY2CTcp0dzC4aReDhRArpQAcWAJcJIgl44zM+TNm1eUkCZGU0qIjUkyAxAw1fG8OGHqKJZ1c/AxWdiMInS+duVmXnwlKMLGnZ67TFUACgxu58ZtvQiht13duO8aS7jjBscjMn0+w068BBF2qQQI23YcFjB5Pp2enZyWj9Z0GYGx083IETjt27dfU6ZMmfEIGLa0a3yABXbs2HH1nDlzujjqKMqFixcvfgxf+7/wcz6H4NJkDnicGAFXRpmM37oDs+Lz58+/D5P4EmwSi7p06fJurGiJtF/cGaosWbLkLm59bWUEQFmwCyuA77t27bol0r7t1Cc4YBLljLSwgBdzMv+uMn4EYgK4qmsHt0jLMC5RAji2wpCDE32bag1g6uZke7rgOcna4NaE0nY/WjAqCBhxKEkzUrc0GSfEhYzL0bmLtS8RlQBRYehE6oR19liCjMe4fU+UK6IE4E0U17ZQVnG7hprKbk75x+T1M+YYO9qM3FBLRP1h3PDbyhQg7fMRNiSw3c34hFdw01+06lSsWHE/gQIfYWwifFZftGjR7fjkF49W/6H9oDS5b/v27eI/vw3Fyxe4YZgq9ISFByXAjYyjc9iCgQIIr3Nq1ao12W75SMuhBDgMfaZuFGIJkNvNGFGmiAWBq7pu+nNah006hVt9xzfOKAHk4BS334NTnKJdXtxjsASw5fIVQpvTTTxaw3JzWHJTJ1rjyer9mDo34nZl+xsw3ErLZGVLpPxvFP+wlx1lHzTRqi1SnI2oLwoAl0oAo/gkHTDdfKMmj8n22hnAwnc51wgGdkmE7+AglK4nRd1YhAxbeerxe8p+/PjxbjNnzrz8wIEDvtPnErfUao0bN17BDfRLLMyHDh482HLq1KlPRTs+AO4IjfGhv4KDQjJuAO/hqjAmkjHFsu6IESMuIbXhNbBAQTt0wFN7UcaMQxmzzU55L8pwG32QdkwNtpgT1whX8SlOnz4tLjhGuuGIll6EeYRNx8K8KG0Yl2MLAi94JSu0IVlI+CYE46z6mHhYcnPok/kzcSyR8JWp4xFlrW2rK9a/7HxnTg++keDmpK6pGDsZQ3pljRuXKAEg1NQLCCd4G8fLcsbA0uIIGLu1ajOOX0ImxA1tbvcQJ3zgtqwjudCti6xb4hzWi/m34AhMh4P7rXjLli2HYeo/x64/E2XLbtq06UpS8DVw22e06mHp8EXDhg0/g+azq1atun7cuHHPRKtvLA+KoSx55tixY5VYvBY1a9ZsZLT69rofhP8cuAHcIpYgdtvmXLSwatWq0+yW96JcoUKF9tOO05tPL7oO2wZ45EpJSbGlQEnbGK4kefidKwVCWMI8KEAsjoN8Y46VAGzscnBybEHgAclhm5DDfdhC/7+Am43cQfPOi8p6jsLusMyN89pG1nCLsdt6RoKQIEQZe4jlu8nlUAkgB0Una0U0p/A8+47yfxQQZy+TmAAmKrSzBVxW7KIQc8EnPULB9wCvkWcFu8B6WM7kb9oR/7jNmOUhlkY3FZWNpV27dlsbNGjwAbeNW+2igelvO6LdP0Su+LJ268SiHHEBzlxyySX/RBj9js0wL24M106aNOnmaNACPo9t27atEzdxG8nG8GmVKlUcp1CLBp12+pg8efKNCHnd7JSVMrhhbCf94Tf9+/ffYreOF+XoU9wBTBV6cvGNFXIzTupJqksjlQCSv7dgwYL7y5Yt6zggYyAwoFvtvhso7dbJ5kQQsNtotMuhtBMlgC0rrzS0OdrIoz0uB/2ZfFhyMIzUook0FpPHk9PJ7ZThmVsusCcmGt84/W6iUj5wS22iEiB7IKaQXRyiInfYJSZ1ofjV2nAPrxslgOn874Y+Y5WoTJcj/nGy1jrhmUQp6wjMSAbdvHnzCQjM49jQbAVVE4H61KlTl06ZMuUGboldRTyPhF4ndRlXCoEC/1mhQoXpaBJLEiTwcfz0+zhpw2lZYhC04eb8SvHJJVPBF7169RrvtA1Tyg8dOnQwVhQPM5YydmhiwRZz/B9q1KgR9TETb0FMn/fYoTPaZcAvO+4AhQ4dOuT4u+ZbKwauRioBWMRPY4Gxzw2eCKgnwMVEc3W5PbE9T3LIcnjb4gYux3XA9ggY2wr86rjx6Fdwc1iKPpXaY7wj4EgJEFAW2l4rogyOY8VslOlLmO5QaB+T7E8GDkjcW2zzp4nK74ASYC9Bpt0oAQyckshIwsJPYk8YuR86DX6tlgCZ84LtDzcylrKsmjVrnkQR8D7MtdxuW9wMlyby/XVz587tZLdOrMpVq1ZtD1H5/4zf8iborv7zzz//EauAOn7Qg3VEFdwAnkbhUBwrgMUoAUb70U802pwwYUI7xvMwN4pN7PQXiOK6kJSA71166aV77dTxsgxBCA/RnrgEGPmcOHGiJIoAx0qzo0ePlmZA+U0clGQXcasEKFy4sFjHyJyZ9jiyBJDgYCYqAVjPj6IEcBMjQy0BTONIQw99LmEy8gAbGEtu4tnYVrji4lVAMr+4xMHvaibjHMnYjVuf2AMPsg+YaO0pSi0Jdmnr4SLQ8fnEVsORFTrLedpUC8/IRvZrbUffKfv6WV5TFXw5icHmhN9ymxtSxX6AWC+YIL02oqYEkM65rV6K6djXbIBJdgfEh9mM4Hd/mD59ej27dWJVDpeH1QinH3JY30+AwGa4Bfzfzp07Pc8YQNyBv+3du7ctm8J6AhP+j363x2rMkfRLTIP8WDQ8yAcqmQ3sNrUHJcvoa665Zr7dCl6XE7Mx2jQuSi+LtnXkyJGygUj/joadlJQkbjdGKgHk9gNh3pUlAG46Er/BuNRKYrXBYch2NoaAJUBU12s7DITy7ggYO7UEMDllj6PDkh2MtIwiEIoA33Je3K9sx24JBHs1VQlgqqCQcEzHHpjEXmj77BxFAHLDo072MuOUAHLRgKXFAZeYmWw672pInE+MtQSQDFh2LU+wis2GEtW2wtUVWHFeKeqHytatWw/l9noqk2gLOjkss2H2nDFjxpPcrFe2VSmGhS677LI3iNL/OgLZMRQBHfB1f9JLchYvXtwUxm4HLqck60Lnzp1/8rL9aLWFciT/+PHjH2Dz6CfCq83nAmWnomj52mZ5X4oVK1Zsk+St96XxCBoVRQrfSglM+8W/39GDEqA8YzI1O8B+XG5c3+ajNDLx9iSH3PDZnaTATYttTZnddiMpJ7EaUAK4NU81aiyR4JBAdVUB4vNkyhrNt5zHybcfUAIYJzgJVHz/joO1+gyxl80btUZxbj4F3iYqAfISWNj2BYKJ7gAwzVHw3e0l88RzW+IOwFplT0iL8kDFHYALFFuWAHJuClhSRZnK+OnOtvTl1ZDatm27q379+m+ymK23my0AhizARA7gZv1e0gaaqhH/DaIrrrjiZQR0MdHPsXHjxquHDRvmiSJg6dKldX744Yf/gkcRTHB/JkXhN17NSzTbQQGQFzeA27ds2XIvPFDEQd/bWZimDBw4MKaWD1izbJBNwwHdUSsKPqWJoeFYWYbyoGTUiHTQkawRaOgPEhjQNd7coOykHaMEHFFusqbZPjhh3SE3LbY2PgfwRlSUdehUkSJFdrpsxKgDtssxmFwtq+Nr7PglOwDrbWG7zCNWA5L5xW75aJZzmVc9miQmTF9YxSYjqO4zbCuz4M08KAFs8zP027YaiMbkCZ648R7knLAjGv3FQx/s7WdECWAgr4kSNV8gpXVYKEVZwDnLVbDssI17UyDm+1TUlQCCG0LyVA71nzgxbYIpS3Czfu1XX331EP4gMaHbyZzfcMMNfyhfvvwUxlh8/fr118+fP7+Nk/rplSVI4kPHjx+vj1C0rmfPns8TY2F9pG3Gov6sWbOu2Lx58z3MaVUHbgCHwfIbLElGxYLm0D65ld7Fv40MDsihrNz27dvbOsEIl4zqLJbVHcyFk+YjKsuCfwFBc0Mgyr+rtqgvSiPTLDdSNdQobGytZdwG5mV+jFICSFDAUqVKiULM6WOyO4DTsSRSeaMUZSHAuj0oua3n65zKTShpfUvY7YQ9v6QIWnbLR6tcIJia23XVyLlJw3NG0VipUqXznJv3wD+m3dDmO3nypG23V9MCEAsf58uXbyfnBNfWhtH65qLVjygB6Ms0PksdPhciBVEC2DLxl/hYWMYWN/Fsy1CMWF9sHUD9YDyC6L2Pb/UoPkDb5mQiNOILfws36/egCDACwIywQat4oWPHjn/j75th2Jo//vjj/3GTX90tlgRHbErQt2bUP0LbU9u0abPUbVuxrPfNN98MWLZs2aOYaNd38GEmwydjSTX5Wt++fZNiSb/0TRaI3dCzNtZ0pNc/30ghrE+6IVza1ravXr26J0qAhiaOB5wPk5HhFw5ArqP2IqiKssyoDAHiAsPhvgSKAFvzFDAfNuo2kDFsA9ttLvnG6PXbwZgSZRwOhhzVopHgG0ld3wYp0aoRmmxbwBG0tQJ7pa11wjei02/4QiBtXZS7zbrdFS1adJtproiioMKdsJydWeGCohA6DKNuZgNKgG2BIMJ2hpFeGVMVqK7Gw2XS6YAiwFV9PyuhBChsV+kksSpQuJZx4HLsJ+lGth0zJUCrVq32N2zY8FU+wNlOkIE5G2JOfgfB8a50Ui8WZfFd39i+fftnYcDdMGIL0gbetW3bNsebOVYEokR4BkEtP5rgn1AufBqL8UTa5+zZsxvNmzfvcUyvWjppiw95Pm4kL6AA2Oqknl9l69Spc5QxrKP9FL/6cNuuKFZYIFuSneJyO22sWLGiLBvzAL5D2zdTdtr1qgwHhh0oAVZF0h4WOVsY38ZI2vC6rswTa0IpO2Ztu3fvzsacljDtNpB1ba0oxFxiE7O9xyW98VTNraVFIh1kjVQABJmIPc2REsBQ5jsbiYWWoWMymixcEbeyJyYZRmRurHRtKQEoVxr6bVsNRGOcnA2SOVdvrl69+gmX/SXSupkKAa4nJ9jf3WT9cQmhvWpybuJCpAQBsKvaqcFFS1HWWiPPtnboj0aZmB7ELr/88uUsah+yKIhAZfvhFrkZ/uR34B/f2nalGBXs16/fcEzYn4Z5D5HucBDB8G7aunVrfhQZ2XFt6NSyZctv6tatK8H95IM7RbT/sbfeeusrn376ac8gyQsXLrwDIaAp/95yySWXPN+sWTMjhGEnkP7yyy9VCO74Fw4NjtI9whs7me9J4LjCSX9+lyWf7HxocxWx3m/aUJRVgmcehdf6ZdYXc1Jm4sSJf4e3ejqwyvCb/Ivah67tcvCJpNOmTZuK3/rmSNrwum5AWVOeW76wGxRlimAFVN6kOeLgdBwTyjUR5FU2UUhzQ5ObOl6zk1ftJdph1si5ke8YH+oK69atKxNu4sigk5NAwNVMvMmSwKBYAkj2FTePkXPjZiDRrMNeuF3ORNHsM1xfopxGKKsYrpz8ff/+/fW4yS1vp2y0ykD/YYI9G2nZGS0M0vbDd32Mc6Tbb9s3soPBr1Em2bKqxnK8MWtnMd8ISoCGY6oEEPweeuihoaS6+5iFzXZ6DmEEtDs9uFn/O37yjm6VYzFnAwYMGF67du3/IcxeQBHwp+HDh7917bXX/njddddN3bRpU29cG7pWq1YtT5UqVfLiMtD/448//sPtt9/+3QMPPPDZqFGjbuPG8DLoPoUp2BQUADENiucGP8nqMGbMmH8jbF4mAdHstoHlwyFu3L9GMfKR3TrRKsdcreIQtCVa/Tnth4WvDZYXL//vf/97lW+k26pVq8rAa4U2bNhQeMGCBTVQQN2Ga8b7bN43UtaWf5VTGiItz/yfY3NexcEn4vy9aPrFEsAozTa4V8J1o104nNjIKmJuWdckQYDveDOpAWeFoz2Dv6sA4BI4rWYbAbfWELY7cFtQzi/4qdZhPQ57iUHsHHHTquC2L5/rpaDUNy79qodjNm6dwm/9AJiLS4CHw4y8KeipynmjfriWuLzrghKgqCkKbcGRYIurK1asuDIc7WH+btaERDgYLAFOct44bhqfBYaVG2VSYztD5LzbH2WG7XSsdtpMtDJGRNrv3bv3B6NHj66KIuBGALYVMRsGlTQRvWfOnJkNIecfBMqba/Lk3Hzzza+///77VTDtvQ+XgJtZDC183C3cIgqiHLBKly4tig1rz549FoeD7AhtBXB5EAFtSJkyZQ6ULVv2A8b4+aOPPmryMH9HG7fN1Zjbf3PoGcRYbLtCsPgcY7P7FneK17t3725cED6UALvh1y0sMB1NnRD4qg6xAWpzmzQIWgXDFDZfOdgUhu7KvPLTVPIlCuxBvotlNWvWjFh458Z6Lsqo7Yy3likDZn6KEo/hetJ+zibIZ7qWCsxddhRoV/I9OImh4fsQoecXFJuRHJyMO2C7BM1YYdPleLSazwjIEowgVJP16HqsAebwHaWr5JS4R0OHDr2HNcvWrZfPZP+ueTGjJktRxAraaNMdz/3Vq1fvyBtvvLGUc+Ig+MKYzRuebozb6u1YuD6Fi1i6bpJLliypSVaorqYoAIQPOGOcRQmwgPPcpnjmC68VFCgBJB2lcZYAMk5hey5GWk2fPr07seWmZjR2LMUHIh/2NugzMZLFbN/K+kk95roHOnTo8DyC4jT6OWu3L4TKHHzEvdFA/ue77767HO2QbSHTbh9elhP/ZoTivBzsrU6dOll9+vSxuOW00O7KzYAcDCwEfgvGtrAUsPA9t4jcno3DwmniJ3zFBpDkJT1+t8Wc9B87duyrjO0q5spJdOOzmEv+hJuEKAAiMgX3a4z4j6UQSGaNmEX71YcX7YJ7Nhb0SihUWvF2wMyrPW9DBNDCJm3G6Y0V2ldxoFjiBQ4NGjRYyvcXidDqBRkXtREwbetOvI+/LV++/HcmktwCFiAt6mMoDW9hvmz7EHtO6O8bPIllxQpinhxx2ZfcmiTUzYlLHEyrpnMSpRkRqx7M/PtwgfHnNWvW/M4lCJfBQrhqPYkS90rWCaMCgoZAdJSzy94oQabdBBAgSO58zopuY7H4hWMxTLSv47z3N/at3wX+w8K1Brz+GmfBliadOyReF2fweVyyRRIJPx7WTUc0ckZMRnh2nZbZLyaTdgPuVI2xBH+It3navlCQ5eZMNQAFwHOUdZwu20/a02nb0bz4QZsRlgAyMG65N3/00UevrV27tgjCSnuZazsD5iPOjkVAV5ihBNqhWmya/+P2MOKbQzt9OynDuLIPGTKkL5pSC4WHxe1manVov6gZ+bcoBDhkWxyyLQ4CFhYBuUhL14eCRvnFZzb+ESNGDJEggAgvTZyaMWMRsZg5/Hzw4MFGj5c5nIF2ewhjrOuEF7RseAT4Do6yOc8AY0809I0bNz7w0ksvLcT9oRcbQ4HwFESnBBut+FPewHde8ZNPPpmD+8MafncGF6AKWAC0RrHZC5eYktGhxl4vKFM2g+cke6W1lCKgCKSHgFgCcWC9FcGpxmeffTYPd7/1HL7xmjtZHuu53pxnekoZU9FjHdiPEsCorCumYuUlXVi1rUBpvIxzUkWTBGpoKYe760PsWw2+/PLL+VySbGYfz0tMm7rsbz2Ja9Pc6VnQS9zStiWZFolrM5cLtp896CfmwpwHY/itCeQPCX59yFB3ALEGyEbQv0uwBijEuWkma+cm6D3N76qydjbC4rqb8KNJ34eX8+NlW8YoAWRQt9122w/4w+dBYP4Lm2EruwOVieZjboSJ/R++/fbb4pjSv1u/fv0tdutHo9zrr79+O77YnYlyb7HopHaZWbpXsQogVoJ16aWXWvhvl0ZBcifa1FewmrBtKRGNcaXtg02gMB/mDdD6KB9lTacfIeNeRjT4twYOHPjd448/Hosh2O6TeVzGOOWmWpUAtlELXzCw8fyCid5k/M49U+ixJkwkOOUVWEa0CE9F9EqwoUne8B74/fdgTRDLkvNiOSOvaaZsktIVwWQmVkyLI0TIxEOTKJ7d0OWmToTwafVEQID9sShKwIFYBfQNpH6TW4F8fPf5xE3Q5Ad6D3L4th3LyeSxxBNtxIXa+8EHH/zEjbsoiZxYWPo+TPasAiiuL0OBNZC97CT8nVMCB8o+ZpICIADEYZRYszhTR+puavr675i+cuXKnXv77bcP+c4wEXQgvI/CtBsuVZ1k7ZRzI7xWUC6Ghd+cyh4RkBLXVY1wBwhFkMj432MG/zJpINY4RZbDfUXSnd1NVPR/YHZfw2l9P8ujmGiJdu0UZsmpPi2ZKQCCdIhVAP5KVufOnbOh9S383HPPScwEYx+sHAqB/aOM9c+M0bECAEw2osz5mBSII2rVqmW0skMmAXeFo7gFTGKe1C/SQ66ED1LYnH8GX09cAYKk4Xu7io1juYnabTkgoTSzUH4W5C3M/xunABAcmZtVrM8jPZjuSMwvPeg+oZtwfOgzGA1bFoEZ0B8XOAS+/VycX4rwFucVJYDBU/Iraayl+1DSnnJJqOkuQUbzDoHsJAbWZhP3MuFd9q9s7GMF4OU8sq+ZpgAQ3ODfxVwMTHbJv2mrGc0vEOuYPvm+TeSvUOADa2dO+KywnJv4mV2Up3GkAHA8Lx7x62/NGKcEEMrwiR+Jb/w73Ao7joQPAxRDsz6YACTPkQO9jteAuW2PKJUV8DsqxC3371wAMmtTFAHiGsBCmoyQXcVt/37XA+vquAC8IOZgbAIVnH6ELDab+YjfJQbAx23atDnpN71etY/LxjTmyFNh1Sva4ridjXwr04mB4TZvb7pDr1GjximycExlvkzzp4yLqRJtO+vQVAKazoyQYNMFgAiHp9UNQEB5zMdJQBl4irPMeh+70KYzQQDlyzJcx6bZuUxSINNF4ABWLOO6deu22iN8Yi7MZTIOV7QR9HMXQvZpj/DRZtJHwNXceAmmkUoAblfPkhf+XXyC33JzYIdx82Neeznp9T4hkupfMRexlcPUS2BD28IfqgTCcVMEmxwsPLasAIL1ZZGXYIFouC7gTvC7IBh+0eykXXwarxg5cuTHmDTeKeaNThUA9LUD4eJ9FuR3yQZgZDCSjPAgu8NWAvVMEB92J5hp2fQRgN9P4Es4uWXLlm7Tz2UKLdYAM5gro2NNmMobKGUXwu+f46aRbvRnh3SbaAng5tY55pt4BribSpdDNtHiJiIg8QA4zyw3kTaPaDL6+8FK7nipUqV+Yh72eTTeLNUMF1XTCc493KNBG80rjNGVQhRZZTOXqkdNtwbwaA6zbDNGKgFkNjisJ6MIeJ2AeG8G0ps5miTxReLQ2pbAOw9iov6X9evXxzrXrhx6XR18A0K1pE2/OIqgI0S8L4zvVx4CGj08d+7cF1goOkOnY36i3lYWmnewAHibgIlxKUhjHj2OqVnkPcJZr0V4YTbWFR9jBeBLwClcTbZxg/Iz8+WplUGizxR47WEtHs2a7IXVixxKXK2FhuJs+iHQLmyJMo7geBOJx+zOYVTKsW9vQQmwNoLO4oHXjKYRZexcrCfnqZDmjAvlzIk7xXDS8u50VjPD0q6EbI/6ttOMrIOOeZm4ABu4nNtipwMt4xoBx/PiuqcMKjoW2rwmILP2EK5Ocuh8lUO7W0WA+N+XPHDgwHW4B/wbX/WY5AjHbO4I71roSMFCwZF/lPi8SEaB06dPn0RQ/j6a+GfWl6Tm+Pzzz99EufIMGNdwcfsvFhHbxAKgR48eb6AASDJlbE7pgP61WK98iaC0y2ldLf//EYAfVpMS8LO+ffsu9RMXItt/y0FgoZ99JFLbEgyQdwpBTb/yaFymKgESxRLA9EOpEzZyMyfSfiJh4ASvqJRlPdiBheL+qHSmnaSLQNeuXbcxB2PVGsA+g8C3EvX+W85s39mvZatkzIW5TKh0RRtKgD1YIK9QJZOt+Y/bQkYrAQRVzJ5OoAh4BUXA6yx2roQshNRCCODXc2s9nIiXL6xevboqkbjdHi4cTzYmtGe5RdtBJMtjBPhzpASQoCobN25EB3A6mSim8x137nEFUjDmGzZs2P24AHwIhrehpPhdjmM7XWKlsZoF5g0W4zdxAXCbb9xOV1Epg5/0d/hQiUWA+lC5QJyNZiduAKIA8CLoXKYU0MdyblG+gAe9uglwMeL4qSJWLmTC+AgrCi9jKbg6mBiImmsLLwPHYuqcuN2rTR2PgVNvnySxokL4XIa11jH7teKuZFzwjgRR5tzxHftnXNAbYy44Q/9TCbT9DjEVvHBpCw7HdOxdWQJg6XOOQN0rGaSeaWPMuH52b7wSIKgI6N+//6sI0q8iRG9wAwg31bkwnWq6a9eu+0m3Nxwf9j/iKlDYTVtu6txxxx0fbdu2LTcB9FJjAtiJlipRVlEcWIsXL94P7VuwjHAcKNENrenVIe9mgZkzZ3Yh/+t7ZF74B8qJpmDq5nB2njlczFy+1Lt377dQACR5RWMs20FIOtCiRYs3mbOfdT92NhN8Dwfgp68ICPoelgBR2XAIPvkNh6fvOdAan4XCGZrelhZFSenSpYdx2JzhYcum3tK6Wc9MHYubg6mpY/GQ9bSpSBFgTdjBWcSXmC2R0pbV6nPuOMz7IeOO+QWR6dhz7lzEpeI7nDPW+UCrm/XWBzLSbdL1uk4g5bmcaWMmd0QLoBj2E3O+iQslgEwQm86pAQMG/I8I3//HrfhCt5pPhO98pN1rtWnTpke5zX5+/vz5rbESyOs3E/zxj3+cSTqSMbNmzdq9fPlyK5DTMsNuJc2FKACmTJly7uDBg7mvvvrqD2+77baY3JhDc7vhw4e/hEvFh9AyhEXB1e0/Yz7L3M0i4ONLl1122acIYgnll41SYxka5nfErN1vfkqU9oMKANxBXmrdunXU8tI2adLkEG4BH7EezFelTfrchILkGDcB3xJA6SuCX3odjyTmm5+H35BpvueJhG0ijcVDlotNU6yVcjs4F/e3VRFS4EbhFmGXjqrHDd9deuml8zHd/pS52etohFmosFwecrP96fXXXz/Jp2GbzC+u9yfiJszjAnKanpF84RojeCZulAAyBeSOT7799ts/Q/P5d4Tk8RxSXaeS4+ax9ObNm+/45ptv3hs9evQ/ueXuSnR7X5UBDz744AsM49QPP/yQJIoAMfVPm0NVLAQwk7dOnTplTZs27RxB904SJHHk448/Ps0XNsyk0a1bt5YCnwcnT578Dm4Ad0FrDTsWDOk1KenFMByYgpD8PMqcYTVr1kzIG1j89L5DyfERvKna0zAMiwLgYMAC4D/Eu9gTbf6GDxeUL19+KHR4aeYe7WH40p+kAGNuxmOp8z8OAn5EoDZdCLCLq6nxDdwcMFzfGNkFy0U5tzQlCn+5gMy/KiJMcZs6gkuZpAh70fmJEMDQ6j179vyc9fprfpfsYbMJ0RQ8uwt34k+5pPEqpk284eJ2DbXEJYAgimMZsCtX7HgDKivSG1dKgOAEDRkyZBx+vfcTbO8zPnDXmxGKhJx58+Ztgs/9Q6TxexuB978//vhjn6NHj+bwgxnuvffeFX/6058e4Db8BBkLDn399dcWwQot+rPQrlty+y//j3LCGjNmzHEUAIdQAHzx2muvvUDE9KiYScu4sY6oz83/3ShH3lq6dOnfEeAbQ5vrTZv6SQgV3zGGp+65557xjMm1ZtKPefGyTcynTiNcvg1vSjBLVQRkAC7Y7OXQ8glpIZ/FPC9mQjhm7sNYA76V1IRe8kE8t8X3moyy7wcsM55FOaO5wMNPphuBO3yr7ku4pcdtPfeUhq/p+gBL0673rPBkuS/B93UMk3pZ8+JKES6xAHDj+x6l4Ez3o/+tppw9jZyfAIWR8J0H8Dhrgguy4wRufZPz8ARqJuz5yhkqfGBnz+4tUqTIRygA3kF55aclrYlrZxCuiGjjfDYRawAJSh6XfGW4FUPM18CcTj8qU8pLfnYE6GcwUU/mBn8IQmpJt7QhjOSmbl2E75o7duzouW7duhmY4f+AdvVbt21mVO8vf/nLhKFDh17y+uuv/w1h+9K1a9eeIR9nIRYqWbCsEydOpPDuxBIg+YYbbnjx/vvv/47buKiYSS9YsKAhFgqDuPnvTRaDhmBaROISRPIgXO2knVEEY3kZPDdH0la81MXK4cSWLVte/e67787iPnEfAlX1eKE9GnRymNyGtcvnCJivIoQfiEafGfUhPpU//fTTy1jdlGKzGIi1iq/WQLEcq52+wUCUIT82a9bs3xJA0U4dl2VMVEC72ZClTkSHLJf4abXwCLiZz/CtRlBCFABiXos5/TfE1rmavbEjzUUtNpFb0gPul/NQ5A9D4PQila+J339aeIzjn8zmr1evXusI2vzSsmXL8rO/9qZsXNHvljczqsc5Yy9xfz7jouEN3P9ies7wemwO24tI4Va5cuXTXI6+wzmpCfJAW4d9x6y4WB9LFhO+hWzIVlUgROQ8056Yf6NxqwSQmcTH/uDKlSv/LbfqPL2Z7Hb82rXUijIgJ0xTb/v27fUwf+/LDfzVLB6jiFq+Cu33Cq+4B0sGibh59aOPPnoT/TSaN29eXRQPnfjdOTbYH4k0vwTXgfeInH/wiy++8KrbdNshHkJOLA76oPzoiPDf/fjx4yL85+OgEmm/Z1mEl3LL+s2gQYPeY66iosiIlGiv6uP2cJrUjq98++23p5jje1EENPSq7Thu5xyL8XIxzUMB8Bnf1GETxsIhYRNKgD9PnTpVNst+vPlNoCsGNCSx/k3AAuAF/EyX+Nx/zDe/dMbnVjBJFCWA2/H7ySqR8Ekkdf0a066CBQtOv+666z5FSbyZvf8855ZudFbArw69aJe9fCXK7Q/69OmzyIv2aCMiwcQjGjJrxkTeCTvsa6+9djZWnP/CgvMC59lekZyHw3ZmcAGxwkQB8CkXT29wYeh3rATTeUVkoohoRBZZ+u67776FnFCWs2xVg6c+lTTOMbuI/TaeWFPPEQy+HZebf0OuqWkg3THfc+NaCSAT2qBBA9Hw/R+32F+hrfobt+iDYFLXh3gJeC9++mx6lffv319ZYgXQ3uaXXnppMcL5KMy9lxCE5aAXzPTyyy9/lrad9evXW/JyQPCiiwzbwP2hBa4HTT799NNuCP6dMCOrxDizy9g9eI7zEU4jiOO7t95669hnnnnGgybjrwki3Yv51FvvvPNOEovnnQEtala9aT7OxjyH7+cdDpJj8DPzOtBcRAxCLIcNM2bMeBILoBOsAZfSWPGIGoy/yrv4/L+75JJLXsLyaFMUyI/55pfOGN0KJiYqAdwc+kycEznAuqXLDQa+sj7f2AHWvlRB+vLLL5+BS2BeDqjimdidX5l4UyVnoW3ETvmQvdxLn2q335qv8xPSuPCOcfxjZ/AEkZ6FIuCfKAJOcp4TpXZWOnOclcDMXDR8jFvmB7ieRiONpel8IuuK68vRIM/dfffdn7/yyiuluXB9lHWsvB1ejEUZZI8NKFpHXnHFFX/lUvXMqFGjyvA7kdlUCZDOhMS9EiA4JrR9G0il9xRB9w6hCLiOg3wpdxnsfm1R6oopPMJxGf5ZJjk5uRU35f1ZVDd/9dVXU7jZHoeVwNJYMLnbPjFRL87bfOHChYN+/vlnMUOszMdRJDhWt+2mqbeR9iaigXsDP6w1HrUZ180QB+FLlC5ruPV5mA1KzPSEp7LME/D/H8fN/+tkhVhm6sBxWdmEKeWTzNVW1pBrobOWyzSYpg7xd3Tx/UuskV/YNL9mbt73IOBX3Iw9HULdHOZE0ReXvpLpjD/iGyMfJl/OKBEfYH2gy1WTHJ6TqlSp8puS7ZprrvlhxIgRKaw7Z/lbb9YboxQBcqNKHID3+vXr966rAWdcSZUAHgMa2hyKgJ8nTZr0Z84ce7G+uwq+cu0u6yOZnjbNXnaEb2gemWw+uOuuu0Zgaetp+5k0ZrpLmCc3ezJ+rHpfRXGZE3fhB8G6QrQAttmP7MNrWK8+fvjhh18kI1tqNdYwmR+j1tWQ8bhVcNuEJHyxhFECyFARMnaQz/5hmHQzt9yDU1JSmolVQKTKAGmbNnLATFUI6ldlzZo1rYlHcO3zzz+/htvuhRygNxJ0ZCYm4H6bHYWf0TQlFi1a1AjlSE+UGJU/+uijxoyjNkUqBotFgk1oV2AjC/Bs/Ic+x897PMKEF36DjsdragVuvxcT/+FhbppvwD3gKvBqKekqTaXXC7oY43H4aznuNN9hcv9Fy5YtjY8wi2JPvuG/ffzxx0vIjjGEW7BujKG4V9+JF7h60UYgWM5uFJ2T8U/+7MYbb/wxuGl60X6YNky8OQ+S7JS2uAogFmZeYn4gSYc+ocmNEsC4eZFvjjX/MOvgttBxXnXVVdOw/Du5ZMkS8WHtz1pjhGsA6/dGAty+h2vQm1hwnfJhXXCjdPOBjHSbjFtLgOBosOpas2LFiqexkF0jcbMkwDP8F7GfZ7QmwG4/jOsMZbfgeioXcx/179/fM9ddmzSYzMcyBFECeEIjCsxznGNfJXD5GRQBd9BuPRPORnLWlHTPWCx9gcXCR2nmrRD/dpXW3Ob8R1LMk3mJhICEUgIIEJjqy+b/Kn7u49GC3gyjDkBwbwSDeAl2fvHxPnnypATSu0xMTTAl3oqpzEoUAQuxFtiKZcIUypwpVaqU00Olq/kkAF02BJYc+PiX55b/GsZcHl+Y+hwuqvGRlqPRgn58rHx8KbS9jgX4h6ZNm37MAvyLqwFkgUooRiQuwv9YQOcRy+JG+Kcv/xY3DFO1lK5mBZ6QNEXrCxUqNInvYSS3EvNcNRTDSpi+fkcgnCWYVF515MiRy7hNacg8FfXjG4rmMEUQkdSMCP9LSWU5EtP/US1atIiq8hI3mQuslVFZF6OArdw+mDgWNzS5qeM3xG73bRPnJRmF6Mb0AMM1YD7C2tNY6e3kzHIda03ZWK01kh6U/peh0H/3zjvv/IRbNb/m2O3c+kVP2nZN/B4cjZ1AjklUeH3kyJELyYJ1NRZu/dkDqjK/cX/2ZxznxMqQ+FUzcbH5kjhDExBSRSEQ7cc4hWMaADyda86xp7kgeQWLyd24ud7HPLSDn9woaiOeJwlYyrMV//8J0PUllgqz0mm0GL8rEnFn/jQQ8zXGU+bwByN3rZIuZR01/zx9+vRRvE8Sbb8bwnkJrzdW2svFK8Eyyh4+fLgNwvhV9Lt39uzZW/jdPvzBl6MI2IqAfgGLgQMIRWtIB3gcofkMfxe/6NQFhDbOlS5d+ndmpXv27MkZpBlmz8448iGQ5JZ4Bdzul+NjbMihoeybb75ZjgW+NIf7Urwi+OePNLJ/ZsiLMCEfH8FXprAAj8IKYxYuEkfczVbWqjVw4MD5WJKswkpj4t69e/slJSVJUMjqzJcRN0BuZ4MN+TTvJoT/mRwgRxJgbo5kSnDbXqzrYb2wFRpeJPDoRAJ39uP77hVQBpSK1abnFhM5MPHsIwjZLwj/k7BgmhRjhZ2J6dHkAOnUtF/WcKPiWwT2FDc0ydilnknnApkTNwd7GUvU0ura+S7Zk3fx3U3PqCzBtzaSMeCvs2bNWkVQ2dtYS5uxJ0TNn1v2dNYICVw4hXX7UywUptoZl8syprvRyPpkFP+4xDm12uDBg+egBFhCRqoJBL4egKVsN+a7GvwVkaVsJDS5rct3cYZ3N2fPuZh+T2rcuPEElNkxSzMcWG9N3M+CEHv+raFsEbnlK7JR7MQy4HbcpvuK7OF2Tt3U4yx2EDnqZ+SPEcz/SJQAJzNoR6xfTNrTgmTKXhtzvjERGDf8kGEdclwuxCrgkdWrVw9AayWa0EYchEt6rQwQAgK+9WJ6UogPIjUIBZv5VSy6wpznWHRPYjGwX/LtchA/Ax3nRY0lhxzKH/3yyy83BUybzktb3ECWIJ1gIQQOKZONv+XBlFz8cAoEbiULB36KIsLig/AUu4wag34x9Z/HgeYzLB4mkmYtK6dfcYU5CpPjVByHcDkdk73m8GYvFDtdmeO68IKYn5tomvu7sQZulg/DE5s4PC5B+P8Ok7y5bMyeBM90Ba7HlfCHFeuWX0hHOprvuY3ME4cosS6SG5Wipt6qyFrCvCRxYNrGgWlV2bJlp2DWOwdhY4PHELlpTvhfFESmKL5kM5Z1zakSQATUaASfcoKx0JPRgSizdkToEQxMCYop+95+XuEVp4/Mp2QfkfnxzCfWKREh5WU+lvL9Lc6sDfZSGesHY8eOXUaA4GvY7/sTNLCan2bcAeF/P/0s4nA/AneFCSj1/RaqZF5MVRDL4Xxf4FuIYMrNqsp5TVw6JnEWnsPbgguITuxlXbhYqiMCnPCYH+diL1AQwZ+9TC6ZNpNSewnWZBMJ+jaXiwa/+dQO+cLLsm6a5iMvtMsa6mZfszNui2wUMwievgmL6CW4Yg+mUkt4yTeXk8BadRheXY3V93dYH3+D+3G4QMZicSR7mylnjSC2Mi8xXwMTXgkgaGMVsIMf7+AeMIGNtReCl8QLaIkQ7rufCMyaizdoiiKHq1R/fCLy/+4jY8MP/i5V6JcFOe2iHC1BP70VgIX4GNq3tSzCPyLEfoWvoLFB3mytYAYUInqtMMIMAjbOQqNac/PmzR2x/uiMlUcDsejgFRP0mJhaZQKP3Cof5d3CwXF9mTJlZiNgzsO3fC2m5UkGwOoLCX379l1Lw2txEfgOC5wabHpN8bWULBu1+car8JbmLSDWQb4QEL5RiYx8ivcw3+lGBP81+MgtxMJoOcGSNqGwMyZNJzcHGzh8zoC/xVVJ+DtoFhc0rQyacwd/hprNhZYNXTODh55gG8FycggI+panDUgWDIZ3QqJKBw4L4ZEOlJCDKd/qfOZcXGDkkBEMEiV9izAh7lJySJT/T6tgCPodh/4M7Tu9ccrfZQzBN1g3dc+QPtjbdjD/juNvUG83Y5nFnJSlHTkbBG+QgmMJWgqkxTWIaagfdaipd6i5bOj/px13aJ0cHPiOQctCcV+xPSGBgqJwZzzLGI8INxIULa3peSiP/GaRl6af9HguyGOhvBY6jqDyVuoG8ROFxD7MlqfbtZYjsvmCDRs2LGetmcCecAnrTBewqMtYCnslqIFRMt/hPtpdzvo9qV69euNJqRbuQO10KtItz5wmMTeL6VsUVsJr6c1B2rUgdD5C5zPtdyR9Bv8e/JnW7Da4JgT/HjpXR8BmCd+2KKAS7mGeBfNp8nJB9hF7WRMUAq3gsebMiexjZZkXCRid1ytecwpiwNT/pOxlvCL4r2Ifmw+fLkVRtYGsYG4Ug07JsFU+wMsLWXPlvC4CcOh6GcrDoWtGeutgZv2Frldp16XgvhLc44IZVSDtfBLzuQI8fbNq6dSpk8hXr6K4nEO8tKtwSe4pl6C8nrkgB2SPA7S5Vs6bKLQmYsm40M4EyUUIZ42V/BQljWAle3Xonhxce4JrQuganvbsEJzDtHtd6J6YXjtpSZVzwU54RrCL6WO6T5Yv4GCGXRmBawCmd5fTQRPcBEr70lGCNMrHs5cD1VJMrxZwiPmeIIirAsJrgozQrGEQyLEoCqFqxHRojaDZFncP2ZgrB7T1kv4y2sq7M7KJSPBH3h1i8s+GvIhb/zkIlxvbtGnzm/bKLCT9p4Y1pACbXinmqwYHqUa4AzUQwYPDgChvijFvxfgpioHcvMFI56ECnF0ig0KFCBRiVSRzIoHEjslBiW9UNsgDHJY2YGX0C4el1SgAdqEpN+2WOnW8BEHLLe5LaRQmmQn3QZzs7FlpD/xSN3TTDj2MBdsVq6xT3Co4Fjhx/ZJsHwUDfYQqLUIFwbSHvrQ0pRVagjSm5Y/QculhkU1uzfg+d2Ee6ciqAYEzD8ppsUISJVbaQ2d6c5NW+E1vDEGlSEZ8ntF8yu/P8f0cJzWvK+UVaYMLEBtHXAAzU8qFzktGWKf9fdpDeEYKhqBgKT/PogQ4yi2748M4lxZ5UAQ0QCnQXfYDDo7VUb5KLJlCsq4E+C7cOiI0p8Abouw6wHqxFbfE9Shup2KdMB++3x6uAa//jttD1QD9oTil9+2m13VaXstIoZTuN5KmwdDvNKjoOkaGoySvx2xqeygCsqEEKMk+VpG3DntZM9wUa6AkKi57WMDiVFwHRDEglqehAqedNTm9octedjawlyXL+YL/PwRvHuRbOch5cy172XL2sZXsZ3s4e8b81jSj+SMOV1XoLxJYN4MuYqFK1ND1PHQtzWg9zwzTjL6R0LZS1zWe81x2HsViwvG+5oZXuWTNz4VWU9arzlhAt2etqgPvlOMtyGuXT4Tu03K+gRf2gOv6wEXGXM6bv8APOzh72t7bWDfzs26KNZXwrPBcqAIgdJ/LaP9KK9SnnbP05jD0vJHeXprKI8SI2U3cDlFKxOyxOykxI9DPjgnOVobUPJfz9kMR0C7aPi1+js2Ltvn49iHQzOWgMIFbxHEsynv5f9Fg6RMlBFjA8mGyV4oFtQ5vMzbqmiyMQQFT3FpE4CzC+iaZBiK1GJDFSjZlWYDFjHwXryzCBwi8spf5X4kWdiWHRgl8eThKEMRVN2jC8yJI5Sd+QBHeMrwVsBSogCa6FG8xcelhQGJ2Ka8c4EVAyREQVIIa/FQBKDAXwZtkEfqTZWPk9yexCDqI+4X47+5mXnaxSe4nkvcRNhRjD0pxNZFKrCJgIAKcVYqiHK62bdu2FuwLtVlrygUUjqKEyidCGj9Tb9ZlvZC1IqgsRHFwgPhE63jXsI6vx6R6Dwp9P6L+G4ickuQUAcIWFWTvKsgeVoy3HG95LiTKEdC4BJdC4vYqe5jwW3Avk/1MlN3yU3gweKMa3MtE+JL9LCW4l1H2BGeLfVit7S5SpMh2hP4d8OeBYsWKHcViwY1Lk9NhanmfEMBFoDRn1oZYtjZGsVQP3qnMWVIsmUSZlHoGClmrTsmFhrz87ggKyt2cbbYi9C/mvCnnzn0I/nYVhD6NKDGbzdJKgOCUor0qgibvWpQCA1AGNII5RXOVUBHbHbDvaYTM7WJKScCN6Zj6jGrWrFnMTVYc0J/QRdGw5mUxLYAyQITM8vysys+qxI+ohNY11YRPNmJe0danbsZpBEzBRzbl1Nt9forAL4dF+X8RMk+ghd/HRrwWwXI9P/fwJqGxPM6CrItwBNzF3OVAEZCHG3C5Bc/NN5abby0Xb07mTn7mkJeNMhsKybOsRWc4uP/2otE/Q9DFZHkJ3BXzgDIRQKFVFQFFwAMEsGgsIApHhLVCrCf5sHzIz+1tLtaNFBSFJ3iPs56f5lCdjLLwCEp8XTc8wD2rN0GQwZwoA1L3MX7mgedkLwu+uQL7WU7x4Q7uYexnKexrZ/iZDF+mcKZIRvg/CU/avtXN6rjH6/hRXOZkjSoqCiUuFsVCqwA/8/MzL/xxGoH/MOeaJH4egyeOctZJ5rypfBGFCVclQAjIKAMKcZMnPtltNm3a1IPFSnJg+h43IArzHK4LMYXdyyK+kjGvxd9qLL7ds1icjfG7CjcA/Ts5+davl4VVhMtcchAUIZOfeeRwKJu0/FsETOb4NAdDEfblkHiaBfe0bMr8PCv/xudOF19lKEVAEVAEFAFFQBFQBBQBRSBBEVAlQAYTiylUGXwkB+Lf0l2CtFGsJgoBMblOpEeiU+5mXIsItDEef8Uf5OYA8xs1EUykWdaxKAKKgCKgCCgCioAioAgoAoqAIhBAQJUAYViBgBI5UATUJ4jNdZg91aO4BACpxCsBv1J9nvhpPEOJWRbvWWg9yrsfd4dfMPefye3venJszkQJYGQAMeOBVQIVAUVAEVAEFAFFQBFQBBQBRUARiCMEzJdeDQJz/vz5JTGpLoFSoC0+Lk0xsa6PMqAyv5OUShIoRQJ8xVwpIAK/PNAmQVhOSEAgfLE2YP69guBh6/C72YzJ/3JyEh8wCF4lRRFQBBQBRUARUAQUAUVAEVAEFAFFwGcEVAkQAcALFy4sQaCvsgT8akWk3noEQ6mIQqAS7gNlEMQlCmY+btzz8v+pKVW8VhAEbvflhl+Cu6W+DOcUwv5e+tpAtNVlEmGTwG4bCAq0kzRAuyMYrlZVBBQBRUARUAQUAUVAEVAEFAFFQBGIcwRUCeDhBJIzvCh5wkvs3LmzjuQMJX1PeVwIJIVPYRQEJYnqXl5Sq4hyIBBfQNKDBXOH50BhEMz/Llf5wVzgcpsfzF0r6dsklcYZbvYlwupRbvn3EGV1Iz/38e99tJFEOo1N/PtI9+7ds2z+dg+nVZtSBBQBRUARUAQUAUVAEVAEFAFFIGEQUCWAz1NJxPZcCOY5JB0GLgRlUQyUkvSD8pNo/BJoUAR/SaWSGyVBycC/JVp/MvlTD+Gzf5T/T03phuLgNAL/PvHtJ8L7SXKpHpQ0QM2bN5cAf/ooAoqAIqAIKAKKgCKgCCgCioAioAgoAopAPCGwffv27LyqnImnSVNaFQFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBBQBRUARUAQUAUVAEVAEFAFFQBFQBLIYAv8Pemu73UV+cNgAAAAASUVORK5CYII=)\n" + ], + "metadata": { + "id": "MbEK2RpZcVCU" + } + }, + { + "cell_type": "markdown", + "source": [ + "This notebook runs the CombFold assembly algorithm, with optional crosslinks between subunits. The algorithm assembles large protein complexes, by combining PDB files of possible subcomplexes. \"Possible subcomplexes\" are structure models of different combinations of subunits from the target complex.\n", + "\n", + "By default, the notebook runs on an example ([PDB: 7ZKQ](https://www.rcsb.org/structure/7ZKQ)), but by uploading your own PDB files to Google Drive (and creating a subunits.json) you can assemble your complex by changing the path in the \"Run\" cell, as instructed in the cell.\n", + "\n", + "\n", + "**The inputs** are\n", + "1. subunits.json - a json describing all the subunits (sequences) in the complex.\n", + "2. pdbs folder - a folder with AlphaFold-Multimer models (PDB files) of different combinations of these subunits.\n", + "3. crosslinks.txt - a txt file that can be provided optionally defininig crosslinks between subunits.\n", + "\n", + "**The output** consists of several .PDB files which are models of the assembled complex structure.\n", + "more information is available on https://github.com/dina-lab3D/CombFold.\n" + ], + "metadata": { + "id": "ZbAj512vF4eK" + } + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "yPmEy4fH4CT8", + "cellView": "form" + }, + "outputs": [], + "source": [ + "#@title Install CombFold (~2 min)\n", + "\n", + "!pip -q install biopython\n", + "!pip -q install py3Dmol\n", + "!echo Installed python dependencies\n", + "\n", + "!wget -qnc -O CombFold-master.zip wget https://github.com/dina-lab3D/CombFold/archive/refs/heads/master.zip\n", + "!unzip -q CombFold-master.zip\n", + "!echo Downloaded CombFold, Installing\n", + "!cd CombFold-master/CombinatorialAssembler && make --silent\n", + "!echo CombFold Installed!\n", + "\n", + "import py3Dmol\n", + "import os\n", + "\n", + "def view_pdb_color_by_chain(pdb_path: str):\n", + " pdb_content = open(pdb_path, \"r\").read()\n", + " view = py3Dmol.view(width=400, height=300)\n", + " view.addModelsAsFrames(pdb_content)\n", + "\n", + " # Get the list of chains in the protein\n", + " chains = {i[21] for i in pdb_content.split(\"\\n\") if len(i) > 21}\n", + "\n", + " # Assign a color to each chain\n", + " colors = [\"red\", \"blue\", \"green\", \"orange\", \"purple\", \"yellow\", \"pink\", \"brown\", \"black\", \"gray\", \"cyan\", \"magenta\", \"olive\", \"maroon\", \"navy\", \"teal\", \"gold\", \"silver\", \"crimson\"]\n", + " colors = 10 * colors\n", + "\n", + " # Set the style for each chain\n", + " for i, chain in enumerate(chains):\n", + " view.setStyle({'chain': chain}, {'cartoon': {'color': colors[i]}})\n", + "\n", + " view.zoomTo()\n", + " view.show()\n" + ] + }, + { + "cell_type": "code", + "source": [ + "#@title View example elements {run: \"auto\"}\n", + "\n", + "#@markdown Here we demonstrate the input used to create a model of [PDB: 7ZKQ](https://www.rcsb.org/structure/7ZKQ). This complex have a total of 5 chains, each represents a single subuit.\n", + "\n", + "#@markdown - subunits.json - Defines five subunits (named: 2,A,C,T,b)\n", + "\n", + "#@markdown - pdb files - Structure models predicted by AlphaFold-Multimer of different pairings of the subunits. Each pair have multiple models, as many different pairwise interactions can be considered during assembly\n", + "\n", + "#@markdown - crosslinks.txt - Defines crosslinks, each line represents a single crosslink. the format of each line is
    \\ \\ \\ \\ \\ \\ \\\n", + "\n", + "#@markdown You can view elements in the input by choosing them and running the cell.\n", + "\n", + "element_to_view = 'subunits.json' #@param [\"subunits.json\", \"crosslinks.txt\", \"pdbs/AFM_2_A_rank_001.pdb\", \"pdbs/AFM_2_A_rank_002.pdb\", \"pdbs/AFM_2_C_rank_001.pdb\", \"pdbs/AFM_2_C_rank_002.pdb\", \"pdbs/AFM_2_T_rank_001.pdb\", \"pdbs/AFM_2_T_rank_002.pdb\", \"pdbs/AFM_2_b_rank_001.pdb\", \"pdbs/AFM_2_b_rank_002.pdb\", \"pdbs/AFM_A_C_rank_001.pdb\", \"pdbs/AFM_A_C_rank_002.pdb\", \"pdbs/AFM_A_T_rank_001.pdb\", \"pdbs/AFM_A_T_rank_002.pdb\", \"pdbs/AFM_A_b_rank_001.pdb\", \"pdbs/AFM_A_b_rank_002.pdb\", \"pdbs/AFM_C_T_rank_001.pdb\", \"pdbs/AFM_C_T_rank_002.pdb\", \"pdbs/AFM_C_b_rank_001.pdb\", \"pdbs/AFM_C_b_rank_002.pdb\", \"pdbs/AFM_T_b_rank_001.pdb\", \"pdbs/AFM_T_b_rank_002.pdb\"]\n", + "example_path = \"/content/CombFold-master/example/example_xlinks\"\n", + "\n", + "subunits_path = os.path.join(example_path, \"subunits.json\")\n", + "crosslinks_path = os.path.join(example_path, \"crosslinks.txt\")\n", + "\n", + "if element_to_view == 'subunits.json':\n", + " print(open(subunits_path, \"r\").read())\n", + "elif element_to_view == 'crosslinks.txt':\n", + " print(open(crosslinks_path, \"r\").read())\n", + "else:\n", + " view_pdb_color_by_chain(os.path.join(example_path, element_to_view))\n" + ], + "metadata": { + "id": "vAk-cPl4p2Jg", + "cellView": "form" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "#@title Run (~2 min on example)\n", + "import os\n", + "#@markdown The folder path on drive should have\n", + "#@markdown (1) a file named \"subunits.json\" with a description of the subunits\n", + "#@markdown and (2) a folder in it named \"pdbs\" containing all pdbs created by AFM.\n", + "#@markdown If you wish to include crosslinks - tick the enable_crosslinks box and include a crosslinks.txt\n", + "\n", + "#@markdown The results will be saved to a new folder named \"assembled\", under the path_on_drive .\n", + "\n", + "#@markdown The path should start with /content/drive/MyDrive/ and can be copied by selecting it from the files sidebar with Right-Click->Copy Path.\n", + "path_on_drive = '/content/CombFold-master/example/example_xlinks' #@param {type:\"string\"}\n", + "max_results_number = \"5\" #@param [1, 5, 10, 20]\n", + "enable_crosslinks = True #@param {type:\"boolean\"}\n", + "create_cif_instead_of_pdb = False #@param {type:\"boolean\"}\n", + "\n", + "subunits_path = os.path.join(path_on_drive, \"subunits.json\")\n", + "crosslinks_path = os.path.join(path_on_drive, \"crosslinks.txt\")\n", + "pdbs_folder = os.path.join(path_on_drive, \"pdbs\")\n", + "assembled_folder = os.path.join(path_on_drive, \"assembled\")\n", + "tmp_assembled_folder = \"/content/tmp_assembled\"\n", + "\n", + "if not (enable_crosslinks and os.path.exists(crosslinks_path)):\n", + " crosslinks_path = None\n", + "\n", + "if os.path.exists(assembled_folder):\n", + " answer = input(f\"{assembled_folder} already exists, Should delete? (y/n)\")\n", + " if answer in (\"y\", \"Y\"):\n", + " print(\"Deleteing\")\n", + " shutil.rmtree(assembled_folder)\n", + " else:\n", + " print(\"Stopping\")\n", + " exit()\n", + "\n", + "if os.path.exists(tmp_assembled_folder):\n", + " shutil.rmtree(tmp_assembled_folder)\n", + "\n", + "import subprocess\n", + "import sys\n", + "import shutil\n", + "sys.path.append(\"/content/CombFold-master/scripts/\")\n", + "import run_on_pdbs\n", + "run_on_pdbs.run_on_pdbs_folder(subunits_path, pdbs_folder, tmp_assembled_folder,\n", + " crosslinks_path=crosslinks_path,\n", + " output_cif=create_cif_instead_of_pdb,\n", + " max_results_number=int(max_results_number))\n", + "\n", + "shutil.copytree(os.path.join(tmp_assembled_folder, \"assembled_results\"),\n", + " assembled_folder)\n", + "\n", + "print(\"Results saved to\", assembled_folder)" + ], + "metadata": { + "id": "OgnoSp2WNMPr", + "cellView": "form" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "#@title Display Assemled 3D structure models {run: \"auto\"}\n", + "model_num = \"0\" #@param [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n", + "model_num = int(model_num)\n", + "\n", + "output_filenames = [i for i in os.listdir(assembled_folder) if i.endswith(\".pdb\") or i.endswith(\".cif\")]\n", + "output_filenames.sort()\n", + "assert model_num < len(output_filenames), f\"Only have {len(output_filenames)} models\"\n", + "output_path = os.path.join(assembled_folder, output_filenames[model_num])\n", + "\n", + "view_pdb_color_by_chain(output_path)\n" + ], + "metadata": { + "id": "WwqShmF0_YCY", + "cellView": "form" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "#@title Download results\n", + "from google.colab import files\n", + "\n", + "results_zip_path = \"/content/tmp_assembled/results.zip\"\n", + "os.system(f\"zip -r {results_zip_path} {assembled_folder}\")\n", + "files.download(results_zip_path)\n" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 17 + }, + "cellView": "form", + "id": "ZRNIIGP9FOsa", + "outputId": "ed721f78-a9d4-4719-f608-583ac202ffae" + }, + "execution_count": 16, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "application/javascript": [ + "\n", + " async function download(id, filename, size) {\n", + " if (!google.colab.kernel.accessAllowed) {\n", + " return;\n", + " }\n", + " const div = document.createElement('div');\n", + " const label = document.createElement('label');\n", + " label.textContent = `Downloading \"${filename}\": `;\n", + " div.appendChild(label);\n", + " const progress = document.createElement('progress');\n", + " progress.max = size;\n", + " div.appendChild(progress);\n", + " document.body.appendChild(div);\n", + "\n", + " const buffers = [];\n", + " let downloaded = 0;\n", + "\n", + " const channel = await google.colab.kernel.comms.open(id);\n", + " // Send a message to notify the kernel that we're ready.\n", + " channel.send({})\n", + "\n", + " for await (const message of channel.messages) {\n", + " // Send a message to notify the kernel that we're ready.\n", + " channel.send({})\n", + " if (message.buffers) {\n", + " for (const buffer of message.buffers) {\n", + " buffers.push(buffer);\n", + " downloaded += buffer.byteLength;\n", + " progress.value = downloaded;\n", + " }\n", + " }\n", + " }\n", + " const blob = new Blob(buffers, {type: 'application/binary'});\n", + " const a = document.createElement('a');\n", + " a.href = window.URL.createObjectURL(blob);\n", + " a.download = filename;\n", + " div.appendChild(a);\n", + " a.click();\n", + " div.remove();\n", + " }\n", + " " + ] + }, + "metadata": {} + }, + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "application/javascript": [ + "download(\"download_20623bbe-19a7-462a-9778-96d4ed3cd0a5\", \"results.zip\", 1491275)" + ] + }, + "metadata": {} + } + ] + } + ] +} diff --git a/scripts/automatic_pipeline/configurable.py b/scripts/automatic_pipeline/configurable.py new file mode 100644 index 0000000000000000000000000000000000000000..4d075ff4036cb82aebd8fd19f053c42ce46d8d22 --- /dev/null +++ b/scripts/automatic_pipeline/configurable.py @@ -0,0 +1,132 @@ +import re +from functools import lru_cache +import subprocess +from typing import List, Optional, Dict, Tuple +import os + +from libs.utils_classes import AlphaFoldJobInfo, RunAlphaFoldResult + +# Constants +MAX_AF_SIZE = 3000 +REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) +ASSEMBLER_DIR = os.path.join(REPO_ROOT, "model", "CombinatorialAssembler") +AF2TRANS_BIN_PATH = os.path.join(ASSEMBLER_DIR, "AF2trans.out") +COMB_ASSEMBLY_BIN_PATH = os.path.join(ASSEMBLER_DIR, "CombinatorialAssembler.out") + + +# slurm utils +@lru_cache(1) +def get_squeue_output(): + return subprocess.check_output(["squeue"]) + + +def get_slurm_ids_from_log(slurms_log: str) -> List[str]: + if not os.path.exists(slurms_log): + return [] + return re.findall(r"Submitted batch job (\d+)", open(slurms_log, "r").read()) + + +def check_for_active_slurm(slurms_log: str) -> bool: + slurm_ids = get_slurm_ids_from_log(slurms_log) + if len(slurm_ids) == 0: + return False + squeue = get_squeue_output() + for slurm_id in slurm_ids: + for line in squeue.split(b"\n"): + if slurm_id.encode() in line: + return True + return False + + +def get_last_slurm_output(output_folder: str, slurms_log: str) -> Optional[str]: + slurm_ids = get_slurm_ids_from_log(slurms_log) + if len(slurm_ids) == 0: + return None + if not os.path.exists(os.path.join(output_folder, f"slurm-{slurm_ids[-1]}.out")): + return None + return open(os.path.join(output_folder, f"slurm-{slurm_ids[-1]}.out"), "r").read() + + +def is_bash_running(bash_path: str): + output_path = os.path.dirname(bash_path) + slurm_ids_log_path = os.path.join(output_path, f"slurm_logs_{os.path.basename(bash_path)}.log") + if check_for_active_slurm(slurm_ids_log_path): + return True + return False + + +def run_bash_file(bash_path: str): + output_path = os.path.dirname(bash_path) + slurm_ids_log_path = os.path.join(output_path, f"slurm_logs_{os.path.basename(bash_path)}.log") + subprocess.run(f"sbatch -c2 --mem=10G --time=3:0:0 {bash_path} >> {slurm_ids_log_path}", shell=True) + + +# alphafold run +def get_scores_from_path(af_pdb_path: str): + return af_pdb_path.replace("unrelaxed", "scores").replace("pdb", "json") + + +def run_alphafold_on_fasta(fasta_path: str, output_folder: str) -> Tuple[RunAlphaFoldResult, List[str]]: + """ + This function gets a path to a fasta and an output folder and runs alphafold on the fasta. + The function return a tuple of the status of the alphafold job(running/completed/failed) and if completed, + the second part of the returned tuple should be a list of paths to the alphafold predicted pdbs. + """ + af_jobname = [i for i in open(fasta_path, "r").read().split("\n") if i.startswith(">")][0][1:] + + os.chdir(output_folder) + cluster_run_path = os.path.join(output_folder, f"cluster_run_{af_jobname}.sh") + slurm_ids_log_path = os.path.join(output_folder, f"slurm_logs_{af_jobname}.sh") + + # check if done + result_files = [os.path.join(output_folder, filename) for filename in os.listdir(output_folder) + if filename.endswith(".pdb") and filename.startswith(af_jobname + "_unrelaxed") + and os.path.exists(get_scores_from_path(os.path.join(output_folder, filename)))] + if result_files and not check_for_active_slurm(slurm_ids_log_path): + return RunAlphaFoldResult.SUCCESS, result_files + + # for phoenix + # running_line = f"sbatch --gres=gpu:1,vmem:20g --exclude=gsm-03 --time=24:0:0 --mem=20G " \ + # f"--killable {cluster_run_path} >> {slurm_ids_log_path}" + # for moriah + running_line = f"sbatch --gres=gpu:a100-1-10 --time=24:0:0 --mem=30G {cluster_run_path} >> {slurm_ids_log_path}" + # running_line = f"sbatch --gres=gpu:a30 --time=24:0:0 --mem=30G {cluster_run_path} >> {slurm_ids_log_path}" + + if os.path.exists(cluster_run_path): + # check if running + if check_for_active_slurm(slurm_ids_log_path): + return RunAlphaFoldResult.RUNNING, [] + + # check if failed + last_output = get_last_slurm_output(output_folder, slurm_ids_log_path) + if last_output is None: + print("Some error with cluster run, no log of canceled run, rerunning") + elif "DUE TO PREEMPTION" in last_output or "CANCELLED AT" in last_output: + print(f"rerunning due to preemption {os.path.basename(fasta_path)}") + elif ("[Errno 110] Connection timed out" in last_output or "Connection reset by peer" in last_output + or "MMseqs2 API is giving errors" in last_output): + print(f"rerunning due to connection timeout/reset {os.path.basename(fasta_path)}") + elif "CUDA_ERROR_ILLEGAL_ADDRESS" in last_output: + print(f"rerunning due to CUDA error {os.path.basename(fasta_path)}") + elif "Out of memory while trying to allocate" in last_output: + running_line = f"sbatch --gres=gpu:a30 --time=24:0:0 --mem=30G {cluster_run_path} >> {slurm_ids_log_path}" + if running_line in open(cluster_run_path, "r").read(): + print(f"skipping because failed {af_jobname} on memory len: {len(open(fasta_path, 'r').read())} ") + return RunAlphaFoldResult.FAILED, [] + print(f"rerunning due to out of memory {os.path.basename(fasta_path)}") + else: + print(f"skipping because failed {af_jobname} len: {len(open(fasta_path, 'r').read())} ") + return RunAlphaFoldResult.FAILED, [] + + logname = "log_" + os.path.basename(fasta_path).split(".")[0] + ".txt" + with open(cluster_run_path, "w") as f: + f.write("#!/bin/bash\n") + f.write('export PATH="/sci/labs/dina/bshor/projects/colabfold/phoenix/202302/colabfold_batch/bin:$PATH"\n') + f.write("module load cuda/11.1\n") + f.write("module load cudnn/8.0.5\n") + f.write(f"colabfold_batch {fasta_path} {output_folder} --logname {logname} --num-models 5" + f" --num-recycle 3\n") + + subprocess.run(running_line, shell=True) + print("Started Alphafold", af_jobname) + return RunAlphaFoldResult.RUNNING, [] diff --git a/scripts/automatic_pipeline/libs/get_alphafold_jobs.py b/scripts/automatic_pipeline/libs/get_alphafold_jobs.py new file mode 100644 index 0000000000000000000000000000000000000000..0137a4eeb8d180d66023476c88d5fb9859f24c1d --- /dev/null +++ b/scripts/automatic_pipeline/libs/get_alphafold_jobs.py @@ -0,0 +1,136 @@ +from collections import defaultdict +from typing import List, Optional, Dict + +from automatic_pipeline.configurable import MAX_AF_SIZE +from automatic_pipeline.libs.utils_classes import SubunitsInfo, AlphaFoldJobInfo, SubunitInfo, AFResultScoredPair, \ + SubunitName + + +def get_residue_diff(subunit1: SubunitInfo, subunit2: SubunitInfo) -> int: + return (subunit2.start_res - 1) - subunit1.get_end_res() + + +def get_merged_info(subunits_info: SubunitsInfo, subunit_names: List[str]) -> List[bool]: + ret_merged = [] + residues_less_than_max = MAX_AF_SIZE - sum([len(subunits_info[s].sequence) for s in subunit_names]) + + for i in range(len(subunit_names) - 1): + subunit1 = subunits_info[subunit_names[i]] + subunit2 = subunits_info[subunit_names[i + 1]] + + residue_diff = get_residue_diff(subunit1, subunit2) + if subunit1.chain_names[0] == subunit2.chain_names[0] \ + and 0 <= residue_diff <= min(residues_less_than_max, 100) and subunit1.name != subunit2.name: + residues_less_than_max -= residue_diff + ret_merged.append(True) + else: + ret_merged.append(False) + return ret_merged + + +def get_alphafold_job(subunits_info: SubunitsInfo, subunit_names: List[str]) -> Optional[AlphaFoldJobInfo]: + assert tuple(subunit_names) == tuple(sorted(subunit_names)), f"subunit names must be sorted {subunit_names}" + sequences_length = sum([len(subunits_info[subunit_name].sequence) for subunit_name in subunit_names]) + if sequences_length > MAX_AF_SIZE: + print(f"Skipping because too long", subunit_names) + return None + + merged_subunits = get_merged_info(subunits_info, subunit_names) + + sequences = [] + next_sequence = subunits_info[subunit_names[0]].sequence + + for i in range(1, len(subunit_names)): + if merged_subunits[i - 1]: + residue_diff = get_residue_diff(subunits_info[subunit_names[i - 1]], subunits_info[subunit_names[i]]) + next_sequence += "X" * residue_diff + next_sequence += subunits_info[subunit_names[i]].sequence + else: + sequences.append(next_sequence) + next_sequence = subunits_info[subunit_names[i]].sequence + + if next_sequence: + sequences.append(next_sequence) + + return AlphaFoldJobInfo(subunit_names=subunit_names, + merged_subunits=get_merged_info(subunits_info, subunit_names), + sequences=sequences) + + +def get_af_jobs_for_pairs(subunits_info: SubunitsInfo) -> List[AlphaFoldJobInfo]: + returned_jobs = [] + all_subunits_names = sorted(list(subunits_info.keys())) + for i in range(len(all_subunits_names)): + start_from = i if len(subunits_info[all_subunits_names[i]].chain_names) > 1 else i + 1 + for j in range(start_from, len(all_subunits_names)): + af_job = get_alphafold_job(subunits_info, [all_subunits_names[i], all_subunits_names[j]]) + if af_job is not None: + returned_jobs.append(af_job) + + return returned_jobs + + +def _score_result_transform(result: AFResultScoredPair) -> float: + full_pae_avg: float = (result.interaction_scores.pae_avg + result.subunit1_scores.self_pae_avg + + result.subunit2_scores.self_pae_avg) / 3 + + # when full_pae_avg=20, we will return 1. has quadric properties + pae_based_score = max([1, 100 - (1/4) * (full_pae_avg ** 2)]) + iplddt_score = (result.subunit1_scores.plddt_interface_avg + result.subunit2_scores.plddt_interface_avg) / 2 + return pae_based_score + iplddt_score/100 + + +def get_job_length(subunit_names, subunits_info): + return sum([len(subunits_info[subunit_name].sequence) for subunit_name in subunit_names]) + + +def get_af_jobs_for_groups(subunits_info: SubunitsInfo, parsed_pairs: List[AFResultScoredPair])\ + -> List[AlphaFoldJobInfo]: + groups_jobs = set() + + best_for_subunit: Dict[SubunitName, Dict[SubunitName, float]] = defaultdict(dict) + for pair in parsed_pairs: + subunit1, subunit2 = pair.subunits_names + score = _score_result_transform(pair) + + best_for_subunit[subunit1][subunit2] = max(best_for_subunit[subunit1].get(subunit2, 0), score) + best_for_subunit[subunit2][subunit1] = max(best_for_subunit[subunit2].get(subunit1, 0), score) + best_for_subunit = dict(best_for_subunit) + + for subunit_name, subunit_info in subunits_info.items(): + sorted_best_for_subunit = sorted(best_for_subunit[subunit_name].keys(), + key=lambda x: best_for_subunit[subunit_name][x], reverse=True) + + print("best for", subunit_name, best_for_subunit[subunit_name], sorted_best_for_subunit) + # with repetitions + job = [subunit_name, sorted_best_for_subunit[0]] + for i in range(3, 6): + for subunit_to_add in sorted_best_for_subunit: + if job.count(subunit_to_add) < len(subunits_info[subunit_to_add].chain_names) \ + and get_job_length(job + [subunit_to_add], subunits_info) <= MAX_AF_SIZE: + job.append(subunit_to_add) + break + if len(job) != i: + break + print("not adding yet, with repetitions", job) + groups_jobs.add(tuple(sorted(job))) + # if len(job) > 2: + # print("adding with repetitions", job) + # groups_jobs.add(tuple(sorted(job))) + + # without repetitions + job = [subunit_name, sorted_best_for_subunit[0]] + for subunit_to_add in sorted_best_for_subunit[1:]: + job.append(subunit_to_add) + if get_job_length(job, subunits_info) > MAX_AF_SIZE: + break + if len(job) > 5: + break + print("not adding yet, without repetitions", job) + groups_jobs.add(tuple(sorted(job))) + # job = job[:-1] + # if len(job) > 2: + # print("adding without repetitions", job) + # groups_jobs.add(tuple(sorted(job))) + + return [get_alphafold_job(subunits_info, list(job)) for job in groups_jobs] diff --git a/scripts/automatic_pipeline/libs/parse_alphafold_jobs.py b/scripts/automatic_pipeline/libs/parse_alphafold_jobs.py new file mode 100644 index 0000000000000000000000000000000000000000..7a7405073a768474b3352d28f8aa3d6a936cb7bc --- /dev/null +++ b/scripts/automatic_pipeline/libs/parse_alphafold_jobs.py @@ -0,0 +1,144 @@ +import pickle +from functools import lru_cache +import os +from typing import Dict, List +import numpy as np + +from automatic_pipeline.configurable import get_scores_from_path +from automatic_pipeline.libs.get_alphafold_jobs import get_residue_diff +from automatic_pipeline.libs.utils_classes import PdbPath, AlphaFoldJobInfo, AFResultScoredPair, SubunitsInfo, \ + SubunitPdbInfo, AFSubunitScores, AFInteractionScores +from automatic_pipeline.libs.utils_pdb import get_pdb_model_readonly, are_subunits_close_in_pdb, get_interface_res_ids + +try: + import ujson as json +except ModuleNotFoundError: + print("using slow json!") + import json + + +@lru_cache(5) +def get_alphafold_scores(pdb_path: str): + return json.load(open(get_scores_from_path(pdb_path), "r")) + + +def parse_results_to_scored_pairs(alphafold_results: Dict[AlphaFoldJobInfo, List[PdbPath]], + subunits_info: SubunitsInfo) -> List[AFResultScoredPair]: + all_pairs = [] + for job_count, (af_job_info, pdb_paths) in enumerate(alphafold_results.items()): + if job_count % 5 == 0: + print("finding pairs", job_count, "out of", len(alphafold_results), "found", len(all_pairs)) + for pdb_path in pdb_paths: + pdb_model = get_pdb_model_readonly(pdb_path) + chain_names = sorted([c.id for c in pdb_model.get_chains()]) + + subunits_per_chain = [[af_job_info.subunit_names[0]]] + for subunit_name, should_merge in zip(af_job_info.subunit_names[1:], af_job_info.merged_subunits): + if should_merge: + subunits_per_chain[-1].append(subunit_name) + else: + subunits_per_chain.append([subunit_name]) + assert len(chain_names) == len(subunits_per_chain), f"Mismatch subunits and chains lengths " \ + f"{len(chain_names)} {len(subunits_per_chain)}" + + subunits_pdb_infos = [] + pdb_start_res = 0 + for chain_name, subunit_list in zip(chain_names, subunits_per_chain): + chain_start_res = 1 + + for subunit_i in range(len(subunit_list)): + subunit_name = subunit_list[subunit_i] + subunit = subunits_info[subunit_name] + if subunit_i > 0: + prev_subunit = subunits_info[subunit_list[subunit_i - 1]] + chain_start_res += get_residue_diff(prev_subunit, subunit) + subunits_pdb_infos.append(SubunitPdbInfo(chain_id=chain_name, chain_residue_id=chain_start_res, + pdb_residue_id=pdb_start_res, + length=len(subunit.sequence))) + chain_start_res += len(subunit.sequence) + pdb_start_res += len(subunit.sequence) + + for i in range(len(subunits_pdb_infos)): + for j in range(i + 1, len(subunits_pdb_infos)): + if are_subunits_close_in_pdb(pdb_path, subunits_pdb_infos[i], subunits_pdb_infos[j]): + all_pairs.append(AFResultScoredPair(pdb_path=pdb_path, + subunits_names=(af_job_info.subunit_names[i], + af_job_info.subunit_names[j]), + subunit1_pdb_info=subunits_pdb_infos[i], + subunit2_pdb_info=subunits_pdb_infos[j])) + + return all_pairs + + +def _get_subunit_scores(plddt: np.ndarray, pae: np.ndarray, subunit_indexes: List[int], interface_indexes: List[int]) \ + -> AFSubunitScores: + subunit_plddt = plddt[subunit_indexes] + return AFSubunitScores( + plddt_avg=float(np.mean(subunit_plddt)), + plddt_percentile=list(np.percentile(subunit_plddt, np.arange(0, 101, 10))), + plddt_interface_avg=float(np.mean(plddt[interface_indexes])), + plddt_interface_percentile=list(np.percentile(plddt[interface_indexes], np.arange(0, 101, 10))), + self_pae_avg=float(np.mean(pae[subunit_indexes][:, subunit_indexes])), + self_pae_percentile=list(np.percentile(pae[subunit_indexes][:, subunit_indexes], np.arange(0, 101, 10))) + ) + + +def _get_interaction_scores(pae: np.ndarray, subunit_indexes1: List[int], subunit_indexes2: List[int], + interface_indexes1: List[int], interface_indexes2: List[int]) -> AFInteractionScores: + all_pae = np.concatenate([pae[subunit_indexes1][:, subunit_indexes2].flatten(), + pae[subunit_indexes2][:, subunit_indexes1].flatten()]) + pae_joined_interface = np.concatenate([pae[interface_indexes1][:, interface_indexes2].flatten(), + pae[interface_indexes2][:, interface_indexes1].flatten()]) + return AFInteractionScores( + pae_avg=float(np.mean(all_pae)), + pae_percentile=list(np.percentile(all_pae, np.arange(0, 101, 10))), + pae_joined_interface_avg=float(np.mean(pae_joined_interface)), + pae_joined_interface_percentile=list(np.percentile(pae_joined_interface, np.arange(0, 101, 10))), + interface1_size=len(interface_indexes1), + interface2_size=len(interface_indexes2), + ) + + +def score_af_results_as_pairs(alphafold_results: Dict[AlphaFoldJobInfo, List[PdbPath]], + output_path: str, subunits_info: SubunitsInfo) -> List[AFResultScoredPair]: + if os.path.exists(output_path): + with open(output_path, "rb") as f: + all_pairs = pickle.load(f) + else: + all_pairs = parse_results_to_scored_pairs(alphafold_results, subunits_info) + pickle.dump(all_pairs, open(output_path, "wb")) + + not_scored_results: List[AFResultScoredPair] = [i for i in all_pairs if i.subunit1_scores is None] + + # enrich with scores + for count, pair in enumerate(not_scored_results): + # pdb_info = AlphaFoldJobInfo.from_result_path(pair.pdb_path) + + job_scores = get_alphafold_scores(pair.pdb_path) + plddt = np.array(job_scores["plddt"]) + pae = np.array(job_scores["pae"]) + + # index are 0-based + subunit1_indexes = [i for i in range(pair.subunit1_pdb_info.length) + if subunits_info[pair.subunits_names[0]].sequence[i] != "X"] + subunit1_indexes = np.array(subunit1_indexes) + pair.subunit1_pdb_info.pdb_residue_id + + subunit2_indexes = [i for i in range(pair.subunit2_pdb_info.length) + if subunits_info[pair.subunits_names[1]].sequence[i] != "X"] + subunit2_indexes = np.array(subunit2_indexes) + pair.subunit2_pdb_info.pdb_residue_id + + subunit1_interface, subunit2_interface = get_interface_res_ids(pair.pdb_path, + pair.subunit1_pdb_info, + pair.subunit2_pdb_info) + subunit1_interface_indexes = np.array(list(subunit1_interface)) + pair.subunit1_pdb_info.pdb_residue_id + subunit2_interface_indexes = np.array(list(subunit2_interface)) + pair.subunit2_pdb_info.pdb_residue_id + + pair.subunit1_scores = _get_subunit_scores(plddt, pae, subunit1_indexes, subunit1_interface_indexes) + pair.subunit2_scores = _get_subunit_scores(plddt, pae, subunit2_indexes, subunit2_interface_indexes) + pair.interaction_scores = _get_interaction_scores(pae, subunit1_indexes, subunit2_indexes, + subunit1_interface_indexes, subunit2_interface_indexes) + if count % 100 == 0 or count == len(not_scored_results) - 1: + print(f"Processed {count + 1}/{len(not_scored_results)} total pairs: {len(all_pairs)}") + pickle.dump(all_pairs, open(output_path, "wb")) + + return all_pairs diff --git a/scripts/automatic_pipeline/libs/prepare_complex.py b/scripts/automatic_pipeline/libs/prepare_complex.py new file mode 100644 index 0000000000000000000000000000000000000000..8b0c9f00fc4575c6257f3be5ac63b71d5457854b --- /dev/null +++ b/scripts/automatic_pipeline/libs/prepare_complex.py @@ -0,0 +1,154 @@ +import os +import shutil +import sys + +import numpy as np +from typing import List, Optional +from math import cos, sin + +import Bio +import Bio.PDB, Bio.SeqIO, Bio.SeqUtils +import Bio.PDB.Residue + + +def read_model_path(pdb_path: str): + if pdb_path.endswith(".cif"): + return Bio.PDB.MMCIFParser().get_structure("s_cif", pdb_path) + return Bio.PDB.PDBParser(QUIET=True).get_structure("s_pdb", pdb_path) + + +def _merge_models(model_path1: str, model_path2: str, output_path: str, output_cif: bool = False): + # print(f"merging {model_path1} and {model_path2} to {output_path}") + model_struct1 = read_model_path(model_path1) + model_struct2 = read_model_path(model_path2) + + model_1 = next(iter(model_struct1)) + model_2 = next(iter(model_struct2)) + + chains_1 = {i.get_id(): i for i in model_1.get_chains()} + chains_2 = {i.get_id(): i for i in model_2.get_chains()} + + for chain in chains_2.values(): + if chain.id in chains_1: + res_to_move = [i for i in chain.get_residues()] + chains_1[chain.id].child_list += res_to_move + chains_1[chain.id].child_list.sort(key=lambda x: x.id[1]) + for res in res_to_move: + chain.detach_child(res.id) + res.detach_parent() + res.parent = chains_1[chain.id] + + chains_1[chain.id].child_list += chain.child_list + else: + model_1.child_list.append(chain) + chain.parent = model_1 + # save the new structure to output_pdb + if output_cif: + io = Bio.PDB.MMCIFIO() + else: + io = Bio.PDB.PDBIO() + io.set_structure(model_struct1) + io.save(output_path) + + +def _rotate_atom(coord, euler_rotation_tuple): + # based on ** gamb::Matrix3, gamb::RigidTrans ** + x, y, z = euler_rotation_tuple + cx, cy, cz = cos(x), cos(y), cos(z) + sx, sy, sz = sin(x), sin(y), sin(z) + v0 = np.array([cz*cy, -sy*sx*cz - sz*cx, -sy*cx*cz + sz*sx]) + v1 = np.array([sz*cy, -sy*sx*sz + cx*cz, -sy*cx*sz - sx*cz]) + v2 = np.array([sy, cy*sx, cy*cx]) + + return np.array([ + v0[0] * coord[0] + v0[1] * coord[1] + v0[2] * coord[2], + v1[0] * coord[0] + v1[1] * coord[1] + v1[2] * coord[2], + v2[0] * coord[0] + v2[1] * coord[1] + v2[2] * coord[2], + ]) + + +def apply_transform(pdb_path: str, output_path: str, transform_numbers: List[float]): + assert len(transform_numbers) == 6 + + pdb_parser = Bio.PDB.PDBParser(QUIET=True) + pdb_struct = pdb_parser.get_structure("original_pdb", pdb_path) + assert len(list(pdb_struct)) == 1, f"Too many models! {pdb_path}" + + rotation_tuple, translation_tuple = transform_numbers[:3], transform_numbers[3:] + for atom in next(iter(pdb_struct)).get_atoms(): + coord = atom.get_coord() + new_coord = _rotate_atom(coord, rotation_tuple) + new_coord += np.array(translation_tuple) + atom.set_coord(new_coord) + + io = Bio.PDB.PDBIO() + io.set_structure(pdb_struct) + io.save(output_path) + + +def create_transformation_pdb(assembly_path: str, transforms_str: str, output_path: str, output_cif: bool = False): + # example: transforms_str = "0(-0.00760431 -1.00941 -2.38469 21.6924 -87.7727 -10.4274),1(0 0 0 0 0 0), + # 2(-2.96419 0.240678 -0.964002 65.6247 -63.7248 -42.0541)" + tmp_pdb_path = os.path.join(assembly_path, "tmp_comb_trans.pdb") + chains_path = os.path.join(assembly_path, "chain.list") + if not os.path.exists(chains_path): + chains_path = os.path.join(assembly_path, "chains.txt") + subunit_filenames = [i.split(" ")[0] for i in open(chains_path, "r").read().split("\n") if len(i) > 0] + + if os.path.exists(output_path): + os.remove(output_path) + for transform_str in transforms_str.split(","): + chain_ind = int(transform_str.split("(")[0]) + subunit_path = os.path.join(assembly_path, subunit_filenames[chain_ind]) + transform_numbers_as_str = transform_str.split("(")[1][:-1] + transform_numbers = list(map(float, transform_numbers_as_str.split(" "))) + + apply_transform(subunit_path, tmp_pdb_path, transform_numbers) + if os.path.exists(output_path): + _merge_models(output_path, tmp_pdb_path, output_path, output_cif=output_cif) + else: + if output_cif: + io = Bio.PDB.MMCIFIO() + io.set_structure(read_model_path(tmp_pdb_path)) + io.save(output_path) + else: + shutil.copy(tmp_pdb_path, output_path) + if output_cif: + # fix _atom_site in cif + cif_lines = open(output_path, "r").read().split("\n") + cif_lines = [i + " " if i.startswith("_atom_site") else i for i in cif_lines] + open(output_path, "w").write("\n".join(cif_lines) + "\n") + os.remove(tmp_pdb_path) + + +def create_complexes(result_path: str, first_result: Optional[int] = None, last_result: Optional[int] = None, + output_folder: Optional[str] = None, output_cif: bool = False) \ + -> List[str]: + output_folder = output_folder or os.path.dirname(result_path) + os.makedirs(output_folder, exist_ok=True) + assembly_path = os.path.dirname(result_path) + transforms_strs = [i[i.index("[") + 1:i.index("]")] for i in open(result_path, "r").read().split("\n") if i] + + if first_result is None: + first_result = 0 + if last_result is None or last_result > len(transforms_strs): + last_result = len(transforms_strs) + + output_files = [] + for i in range(first_result, last_result): + file_ext = ".cif" if output_cif else ".pdb" + output_path = os.path.join(output_folder, + os.path.basename(result_path).split(".")[0] + "_" + str(i) + file_ext) + create_transformation_pdb(assembly_path, transforms_strs[i], output_path=output_path, output_cif=output_cif) + output_files.append(output_path) + return output_files + + +if __name__ == '__main__': + if len(sys.argv) != 4: + raise Exception("Usage: