From 46f6ceab3eb9ef50bd0056b3885ef79ac7c70d5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8E=E6=B5=A9?= Date: Thu, 30 Jul 2026 11:59:00 +0800 Subject: [PATCH 01/11] feat: expand Xponge compatibility surface Vendor the origin RESP, QM, bundled-I/O, and Lipid17/Lipid21 compatibility modules and packaged lipid manifests. Extend frcmod, legacy runtime, CLI, PSF, and force-field parsing behavior while aligning the native RESP grid orientation. Add regression coverage for PSF residue grouping and constrained RESP compatibility. --- cpp/assign/resp.cpp | 15 +- cpp/io/psf.cpp | 47 +- src/Xponge/__init__.py | 4 + src/Xponge/assign/resp.py | 3 + .../forcefield/amber/_forcefield_family.py | 3 + src/Xponge/forcefield/amber/_lipid_common.py | 3 + src/Xponge/forcefield/amber/_lipid_ext.py | 3 + src/Xponge/forcefield/amber/lipid17.py | 3 + src/Xponge/forcefield/amber/lipid21.mol2 | 1330 ++++++ src/Xponge/forcefield/amber/lipid21.py | 3 + .../forcefield/amber/lipid21_manifest.json | 899 ++++ src/Xponge/forcefield/amber/lipid_ext.mol2 | 3735 +++++++++++++++++ .../forcefield/amber/lipid_ext_manifest.json | 1988 +++++++++ src/Xponge/io_bundle/bundle_builder.py | 3 + src/Xponge/io_bundle/contracts.py | 3 + src/Xponge/qm/__init__.py | 7 + src/XpongeCPP/__init__.py | 48 +- src/XpongeCPP/__main__.py | 27 + src/XpongeCPP/_compat/frcmod.py | 286 ++ src/XpongeCPP/_compat/runtime.py | 3 + src/XpongeCPP/assign/resp.py | 12 + .../forcefield/amber/_lipid_common.py | 17 +- src/XpongeCPP/forcefield/base/lj_base.py | 3 + src/XpongeCPP/legacy_types.py | 67 + tests/test_psf_io.py | 42 + tests/test_resp_linear_constraints.py | 30 + 26 files changed, 8557 insertions(+), 27 deletions(-) create mode 100644 src/Xponge/assign/resp.py create mode 100644 src/Xponge/forcefield/amber/_forcefield_family.py create mode 100644 src/Xponge/forcefield/amber/_lipid_common.py create mode 100644 src/Xponge/forcefield/amber/_lipid_ext.py create mode 100644 src/Xponge/forcefield/amber/lipid17.py create mode 100644 src/Xponge/forcefield/amber/lipid21.mol2 create mode 100644 src/Xponge/forcefield/amber/lipid21.py create mode 100644 src/Xponge/forcefield/amber/lipid21_manifest.json create mode 100644 src/Xponge/forcefield/amber/lipid_ext.mol2 create mode 100644 src/Xponge/forcefield/amber/lipid_ext_manifest.json create mode 100644 src/Xponge/io_bundle/bundle_builder.py create mode 100644 src/Xponge/io_bundle/contracts.py create mode 100644 src/Xponge/qm/__init__.py create mode 100644 src/XpongeCPP/_compat/frcmod.py diff --git a/cpp/assign/resp.cpp b/cpp/assign/resp.cpp index 0326f0d..4ddb9bb 100644 --- a/cpp/assign/resp.cpp +++ b/cpp/assign/resp.cpp @@ -26,15 +26,16 @@ std::vector> fibonacci_grid(int npoints, const std::array< return out; } out.reserve(static_cast(npoints)); - const double golden_angle = kPi * (3.0 - std::sqrt(5.0)); + const double golden_angle = kPi * (std::sqrt(5.0) - 1.0); for (int i = 0; i < npoints; ++i) { - const double y = 1.0 - (2.0 * i + 1.0) / static_cast(npoints); - const double r = std::sqrt(std::max(1.0 - y * y, 0.0)); - const double theta = golden_angle * static_cast(i); + const double index = static_cast(i + 1); + const double z = (2.0 * index - 1.0) / static_cast(npoints) - 1.0; + const double planar_radius = std::sqrt(std::max(1.0 - z * z, 0.0)); + const double theta = golden_angle * index; out.push_back({ - center[0] + radius * std::cos(theta) * r, - center[1] + radius * y, - center[2] + radius * std::sin(theta) * r, + center[0] + radius * planar_radius * std::cos(theta), + center[1] + radius * planar_radius * std::sin(theta), + center[2] + radius * z, }); } return out; diff --git a/cpp/io/psf.cpp b/cpp/io/psf.cpp index 054a4ef..c891e98 100644 --- a/cpp/io/psf.cpp +++ b/cpp/io/psf.cpp @@ -129,16 +129,16 @@ void update_psf_residue_type(Molecule& molecule, Residue& residue, const std::st signatures.push_back({residue.type_name, std::move(new_signature)}); } -void append_psf_atom(Molecule& molecule, std::unordered_map& residue_by_key, +void append_psf_atom(Molecule& molecule, std::string& current_residue_key, + ResidueId& current_residue_id, const std::string& segid, const std::string& resnr, const std::string& resname, const std::string& atom_name, const std::string& atom_type, double charge, double mass, std::unordered_map>>& signatures_by_resname) { const auto key = residue_key(segid, resnr, resname); - auto it = residue_by_key.find(key); - if (it == residue_by_key.end()) { - const ResidueId residue_id = static_cast(molecule.residues.size()); - it = residue_by_key.emplace(key, residue_id).first; + if (key != current_residue_key) { + current_residue_key = key; + current_residue_id = static_cast(molecule.residues.size()); Residue residue; residue.name = resname; residue.type_name = resname; @@ -148,12 +148,12 @@ void append_psf_atom(Molecule& molecule, std::unordered_map(molecule.atoms.size()); molecule.residues.push_back(std::move(residue)); } - auto& residue = molecule.residues[it->second]; + auto& residue = molecule.residues[current_residue_id]; update_psf_residue_type(molecule, residue, atom_name, atom_type, charge, signatures_by_resname); Atom atom; atom.name = atom_name; atom.type = atom_type; - atom.residue = it->second; + atom.residue = current_residue_id; atom.charge = charge; atom.mass = mass; atom.element = atom.mass > 0.0 ? guess_element_from_mass(atom.mass) : guess_element(atom_name, ""); @@ -202,6 +202,20 @@ void split_residues_by_connectivity(Molecule& molecule) { push_whole_residue(); continue; } + bool has_internal_bond = false; + for (std::uint32_t local = 0; local < residue.atom_count && !has_internal_bond; ++local) { + const AtomId atom = residue.atom_begin + local; + for (const AtomId next : graph[atom]) { + if (molecule.atoms[next].residue == molecule.atoms[atom].residue) { + has_internal_bond = true; + break; + } + } + } + if (!has_internal_bond) { + push_whole_residue(); + continue; + } std::vector seen(residue.atom_count, false); std::vector> components; for (std::uint32_t local = 0; local < residue.atom_count; ++local) { @@ -296,24 +310,23 @@ std::unordered_map split_molecules_by_connectivity(const std::unordered_map out; std::vector> atom_maps(static_cast(component_count + 1)); - for (int comp = 1; comp <= component_count; ++comp) { - out.emplace(molecule.name + "_" + std::to_string(comp), Molecule(molecule.name + "_" + std::to_string(comp))); - } for (const auto& residue : molecule.residues) { if (residue.atom_count == 0) { continue; } const int comp = component[residue.atom_begin]; - auto& target = out.at(molecule.name + "_" + std::to_string(comp)); + const std::string component_name = molecule.name + "_" + std::to_string(comp); + auto target_it = out.find(component_name); + if (target_it == out.end()) { + target_it = out.emplace(component_name, Molecule(component_name)).first; + } + auto& target = target_it->second; Residue new_residue = residue; new_residue.atom_begin = static_cast(target.atoms.size()); new_residue.atom_count = 0; const ResidueId new_residue_id = static_cast(target.residues.size()); for (std::uint32_t local = 0; local < residue.atom_count; ++local) { const AtomId old_atom = residue.atom_begin + local; - if (component[old_atom] != comp) { - continue; - } Atom atom = molecule.atoms[old_atom]; atom.residue = new_residue_id; atom_maps[comp][old_atom] = static_cast(target.atoms.size()); @@ -342,7 +355,8 @@ PsfData load_molpsf_text(const std::string& text, const std::string& split_by) { } Molecule molecule("psf"); std::unordered_map atom_by_psf_index; - std::unordered_map residue_by_key; + std::string current_residue_key; + ResidueId current_residue_id = std::numeric_limits::max(); std::unordered_map>> signatures_by_resname; while (std::getline(input, line)) { @@ -365,7 +379,8 @@ PsfData load_molpsf_text(const std::string& text, const std::string& split_by) { if (atom_words.size() < 8) { throw std::runtime_error("bad PSF atom line"); } - append_psf_atom(molecule, residue_by_key, atom_words[1], atom_words[2], atom_words[3], + append_psf_atom(molecule, current_residue_key, current_residue_id, + atom_words[1], atom_words[2], atom_words[3], atom_words[4], atom_words[5], std::stod(atom_words[6]), std::stod(atom_words[7]), signatures_by_resname); atom_by_psf_index[std::stoi(atom_words[0])] = static_cast(molecule.atoms.size() - 1); diff --git a/src/Xponge/__init__.py b/src/Xponge/__init__.py index 2f0d145..bd2a528 100644 --- a/src/Xponge/__init__.py +++ b/src/Xponge/__init__.py @@ -6,6 +6,10 @@ from XpongeCPP import * # noqa: F401,F403 +__version__ = _XpongeCPP.__version__ +__mokda_backend__ = "xpongecpp" +__implementation_version__ = _XpongeCPP.__version__ + _SHIM_SUBPACKAGES = { "analysis", "assign", diff --git a/src/Xponge/assign/resp.py b/src/Xponge/assign/resp.py new file mode 100644 index 0000000..3cd726b --- /dev/null +++ b/src/Xponge/assign/resp.py @@ -0,0 +1,3 @@ +"""Legacy RESP module backed by the XpongeCPP implementation.""" + +from XpongeCPP.assign.resp import * # noqa: F401,F403 diff --git a/src/Xponge/forcefield/amber/_forcefield_family.py b/src/Xponge/forcefield/amber/_forcefield_family.py new file mode 100644 index 0000000..d4e3bfc --- /dev/null +++ b/src/Xponge/forcefield/amber/_forcefield_family.py @@ -0,0 +1,3 @@ +"""Share Amber force-field family state with XpongeCPP.""" + +from XpongeCPP.forcefield.amber._forcefield_family import * # noqa: F401,F403 diff --git a/src/Xponge/forcefield/amber/_lipid_common.py b/src/Xponge/forcefield/amber/_lipid_common.py new file mode 100644 index 0000000..76c58a3 --- /dev/null +++ b/src/Xponge/forcefield/amber/_lipid_common.py @@ -0,0 +1,3 @@ +"""Legacy lipid helpers backed by XpongeCPP.""" + +from XpongeCPP.forcefield.amber._lipid_common import * # noqa: F401,F403 diff --git a/src/Xponge/forcefield/amber/_lipid_ext.py b/src/Xponge/forcefield/amber/_lipid_ext.py new file mode 100644 index 0000000..3bc9cb5 --- /dev/null +++ b/src/Xponge/forcefield/amber/_lipid_ext.py @@ -0,0 +1,3 @@ +"""Legacy lipid extension helpers backed by XpongeCPP.""" + +from XpongeCPP.forcefield.amber._lipid_ext import * # noqa: F401,F403 diff --git a/src/Xponge/forcefield/amber/lipid17.py b/src/Xponge/forcefield/amber/lipid17.py new file mode 100644 index 0000000..d2d420d --- /dev/null +++ b/src/Xponge/forcefield/amber/lipid17.py @@ -0,0 +1,3 @@ +"""Legacy Lipid17 loader backed by XpongeCPP.""" + +from XpongeCPP.forcefield.amber.lipid17 import * # noqa: F401,F403 diff --git a/src/Xponge/forcefield/amber/lipid21.mol2 b/src/Xponge/forcefield/amber/lipid21.mol2 new file mode 100644 index 0000000..c3db9a1 --- /dev/null +++ b/src/Xponge/forcefield/amber/lipid21.mol2 @@ -0,0 +1,1330 @@ +@MOLECULE +LIPID21 + 659 647 16 0 1 +SMALL +USER_CHARGES +@ATOM + 1 C116 -3.542000 1.263000 -0.507000 cD 1 AR 0.039100 + 2 H16R -4.373000 1.525000 0.147000 hL 1 AR 0.028848 + 3 H16S -2.920000 0.574000 0.053000 hL 1 AR 0.028848 + 4 C115 -4.106000 0.611000 -1.743000 cB 1 AR -0.244149 + 5 H15R -4.793000 1.231000 -2.299000 hB 1 AR 0.132793 + 6 C114 -3.871000 -0.600000 -2.218000 cB 1 AR -0.224769 + 7 H14R -4.393000 -0.888000 -3.118000 hB 1 AR 0.129616 + 8 C113 -2.969000 -1.670000 -1.649000 cD 1 AR 0.058197 + 9 H13R -2.270000 -1.252000 -0.937000 hL 1 AR 0.061350 + 10 H13S -2.374000 -2.080000 -2.465000 hL 1 AR 0.061350 + 11 C112 -3.766000 -2.788000 -1.011000 cB 1 AR -0.220164 + 12 H12R -4.712000 -3.005000 -1.481000 hB 1 AR 0.131986 + 13 C111 -3.426000 -3.504000 0.046000 cB 1 AR -0.228704 + 14 H11R -4.122000 -4.252000 0.390000 hB 1 AR 0.131341 + 15 C110 -2.146000 -3.387000 0.847000 cD 1 AR 0.095406 + 16 H10R -1.326000 -3.099000 0.203000 hL 1 AR 0.051140 + 17 H10S -1.897000 -4.375000 1.231000 hL 1 AR 0.051140 + 18 C19 -2.272000 -2.426000 2.011000 cB 1 AR -0.228694 + 19 H9R -3.242000 -2.408000 2.482000 hB 1 AR 0.132167 + 20 C18 -1.340000 -1.630000 2.505000 cB 1 AR -0.228527 + 21 H8R -1.611000 -1.007000 3.344000 hB 1 AR 0.134735 + 22 C17 0.097000 -1.490000 2.057000 cD 1 AR 0.055784 + 23 H7R 0.316000 -2.162000 1.240000 hL 1 AR 0.062775 + 24 H7S 0.737000 -1.797000 2.884000 hL 1 AR 0.062775 + 25 C16 0.442000 -0.063000 1.702000 cB 1 AR -0.206786 + 26 H6R 0.227000 0.652000 2.481000 hB 1 AR 0.129858 + 27 C15 0.966000 0.404000 0.581000 cB 1 AR -0.267890 + 28 H5R 1.145000 1.467000 0.528000 hB 1 AR 0.143577 + 29 C14 1.363000 -0.350000 -0.663000 cD 1 AR 0.031382 + 30 H4R 0.783000 0.037000 -1.499000 hL 1 AR 0.038708 + 31 H4S 1.119000 -1.403000 -0.583000 hL 1 AR 0.038708 + 32 C13 2.852000 -0.196000 -1.008000 cD 1 AR 0.025626 + 33 H3R 3.101000 0.857000 -1.091000 hL 1 AR 0.025117 + 34 H3S 3.031000 -0.640000 -1.984000 hL 1 AR 0.025117 + 35 C12 3.775000 -0.860000 0.024000 cD 1 AR -0.123935 + 36 H2R 3.551000 -1.915000 0.111000 hL 1 AR 0.024535 + 37 H2S 3.634000 -0.397000 0.994000 hL 1 AR 0.024535 + 38 C117 -2.747000 2.535000 -0.828000 cD 1 AR -0.028398 + 39 H17R -1.894000 2.273000 -1.450000 hL 1 AR 0.019581 + 40 H17S -3.364000 3.205000 -1.425000 hL 1 AR 0.019581 + 41 C118 -2.265000 3.270000 0.423000 cD 1 AR -0.019772 + 42 H18R -3.125000 3.537000 1.037000 hL 1 AR 0.014218 + 43 H18S -1.659000 2.595000 1.024000 hL 1 AR 0.014218 + 44 C119 -1.459000 4.532000 0.111000 cD 1 AR 0.024957 + 45 H19R -0.599000 4.267000 -0.501000 hL 1 AR 0.005426 + 46 H19S -2.064000 5.207000 -0.492000 hL 1 AR 0.005426 + 47 C120 -0.981000 5.263000 1.365000 cD 1 AR -0.109302 + 48 H20R -0.412000 6.152000 1.111000 hL 1 AR 0.023723 + 49 H20S -0.345000 4.625000 1.972000 hL 1 AR 0.023723 + 50 H20T -1.821000 5.573000 1.982000 hL 1 AR 0.023723 + 51 C1 4.697000 -1.469000 -0.739000 cA 2 CHL -0.247736 + 52 H11 4.428000 -1.214000 -1.762000 hA 2 CHL 0.048772 + 53 H12 4.303000 -2.462000 -0.556000 hA 2 CHL 0.048772 + 54 C2 6.224000 -1.517000 -0.642000 cA 2 CHL -0.061498 + 55 H21 6.547000 -1.874000 0.331000 hA 2 CHL 0.048128 + 56 H22 6.617000 -2.214000 -1.376000 hA 2 CHL 0.048128 + 57 C3 6.830000 -0.145000 -0.877000 cA 2 CHL 0.400362 + 58 H31 6.590000 0.176000 -1.891000 hE 2 CHL -0.008510 + 59 C4 6.246000 0.863000 0.111000 cA 2 CHL -0.237134 + 60 H41 6.601000 0.589000 1.102000 hA 2 CHL 0.100619 + 61 H42 6.636000 1.856000 -0.095000 hA 2 CHL 0.100619 + 62 C5 4.731000 0.895000 0.070000 cB 2 CHL -0.257515 + 63 C6 4.091000 2.041000 -0.088000 cB 2 CHL -0.243730 + 64 H61 4.668000 2.946000 -0.204000 hB 2 CHL 0.148607 + 65 C7 2.599000 2.206000 -0.119000 cA 2 CHL -0.075453 + 66 H71 2.323000 3.070000 0.480000 hA 2 CHL 0.051297 + 67 H72 2.290000 2.443000 -1.137000 hA 2 CHL 0.051297 + 68 C8 1.853000 0.964000 0.375000 cA 2 CHL 0.017835 + 69 H81 1.922000 0.946000 1.460000 hA 2 CHL 0.028120 + 70 C9 2.514000 -0.306000 -0.204000 cA 2 CHL -0.029839 + 71 H91 2.518000 -0.165000 -1.285000 hA 2 CHL -0.003361 + 72 C10 4.012000 -0.452000 0.214000 cA 2 CHL 0.546470 + 73 C11 1.671000 -1.566000 0.076000 cA 2 CHL -0.127715 + 74 H111 2.085000 -2.410000 -0.467000 hA 2 CHL 0.040257 + 75 H112 1.730000 -1.829000 1.125000 hA 2 CHL 0.040257 + 76 C12 0.190000 -1.429000 -0.312000 cA 2 CHL -0.158054 + 77 H121 0.108000 -1.322000 -1.392000 hA 2 CHL 0.007687 + 78 H122 -0.312000 -2.355000 -0.056000 hA 2 CHL 0.007687 + 79 C13 -0.469000 -0.212000 0.358000 cA 2 CHL 0.583692 + 80 C14 0.381000 1.015000 -0.041000 cA 2 CHL 0.013753 + 81 H141 0.381000 1.020000 -1.132000 hA 2 CHL 0.004469 + 82 C15 -0.462000 2.215000 0.391000 cA 2 CHL -0.185047 + 83 H151 -0.304000 2.443000 1.442000 hA 2 CHL 0.041228 + 84 H152 -0.217000 3.115000 -0.163000 hA 2 CHL 0.041228 + 85 C16 -1.913000 1.751000 0.131000 cA 2 CHL -0.082919 + 86 H161 -2.537000 1.930000 1.001000 hA 2 CHL 0.027639 + 87 H162 -2.357000 2.309000 -0.684000 hA 2 CHL 0.027639 + 88 C17 -1.859000 0.228000 -0.203000 cA 2 CHL -0.051767 + 89 H171 -1.794000 0.127000 -1.286000 hA 2 CHL -0.030436 + 90 C18 -0.546000 -0.407000 1.886000 cA 2 CHL -0.536004 + 91 H181 -1.062000 0.412000 2.374000 hA 2 CHL 0.103623 + 92 H182 0.431000 -0.485000 2.344000 hA 2 CHL 0.103623 + 93 H183 -1.086000 -1.316000 2.130000 hA 2 CHL 0.103623 + 94 C19 4.156000 -0.937000 1.675000 cA 2 CHL -0.352218 + 95 H191 3.579000 -0.319000 2.354000 hA 2 CHL 0.072469 + 96 H192 5.185000 -0.898000 2.008000 hA 2 CHL 0.072469 + 97 H193 3.826000 -1.963000 1.788000 hA 2 CHL 0.072469 + 98 C20 -3.141000 -0.519000 0.237000 cD 2 CHL 0.178752 + 99 H201 -3.267000 -0.372000 1.309000 hL 2 CHL -0.016015 + 100 C21 -3.080000 -2.029000 -0.029000 cD 2 CHL -0.427968 + 101 H211 -2.330000 -2.520000 0.577000 hL 2 CHL 0.099219 + 102 H212 -2.851000 -2.232000 -1.072000 hL 2 CHL 0.099219 + 103 H213 -4.025000 -2.508000 0.198000 hL 2 CHL 0.099219 + 104 C22 -4.375000 0.093000 -0.461000 cD 2 CHL -0.050256 + 105 H221 -4.324000 1.175000 -0.403000 hL 2 CHL 0.009445 + 106 H222 -4.337000 -0.153000 -1.522000 hL 2 CHL 0.009445 + 107 C23 -5.732000 -0.335000 0.111000 cD 2 CHL 0.078665 + 108 H231 -5.862000 -1.406000 0.010000 hL 2 CHL -0.007957 + 109 H232 -5.752000 -0.121000 1.179000 hL 2 CHL -0.007957 + 110 C24 -6.898000 0.389000 -0.569000 cD 2 CHL -0.282133 + 111 H241 -6.718000 1.461000 -0.516000 hL 2 CHL 0.062972 + 112 H242 -6.910000 0.137000 -1.629000 hL 2 CHL 0.062972 + 113 C25 -8.286000 0.104000 0.028000 cD 2 CHL 0.450601 + 114 H251 -8.244000 0.333000 1.092000 hL 2 CHL -0.049128 + 115 C26 -8.705000 -1.362000 -0.122000 cD 2 CHL -0.449300 + 116 H261 -8.034000 -2.035000 0.400000 hL 2 CHL 0.099869 + 117 H262 -8.723000 -1.655000 -1.169000 hL 2 CHL 0.099869 + 118 H263 -9.702000 -1.521000 0.280000 hL 2 CHL 0.099869 + 119 C27 -9.336000 1.023000 -0.605000 cD 2 CHL -0.449300 + 120 H271 -9.079000 2.069000 -0.470000 hL 2 CHL 0.099869 + 121 H272 -10.316000 0.862000 -0.166000 hL 2 CHL 0.099869 + 122 H273 -9.420000 0.839000 -1.674000 hL 2 CHL 0.099869 + 123 O1 8.224000 -0.262000 -0.734000 oH 2 CHL -0.766581 + 124 HO1 8.633000 0.570000 -0.924000 hO 2 CHL 0.442970 + 125 C116 6.186000 0.815000 -0.603000 cB 3 DHA -0.233897 + 126 H16R 5.772000 1.811000 -0.619000 hB 3 DHA 0.133812 + 127 C115 5.152000 -0.282000 -0.706000 cD 3 DHA 0.069215 + 128 H15R 5.627000 -1.257000 -0.734000 hL 3 DHA 0.058341 + 129 H15S 4.618000 -0.174000 -1.644000 hL 3 DHA 0.058341 + 130 C114 4.194000 -0.242000 0.462000 cB 3 DHA -0.240419 + 131 H14R 4.661000 -0.419000 1.419000 hB 3 DHA 0.138337 + 132 C113 2.893000 -0.010000 0.455000 cB 3 DHA -0.218668 + 133 H13R 2.380000 -0.028000 1.404000 hB 3 DHA 0.131575 + 134 C112 1.993000 0.270000 -0.727000 cD 3 DHA 0.085245 + 135 H12R 2.572000 0.350000 -1.640000 hL 3 DHA 0.053422 + 136 H12S 1.515000 1.232000 -0.581000 hL 3 DHA 0.053422 + 137 C111 0.961000 -0.820000 -0.898000 cB 3 DHA -0.241205 + 138 H11R 1.380000 -1.795000 -1.094000 hB 3 DHA 0.136308 + 139 C110 -0.357000 -0.729000 -0.828000 cB 3 DHA -0.221433 + 140 H10R -0.925000 -1.634000 -0.972000 hB 3 DHA 0.133628 + 141 C19 -1.204000 0.493000 -0.557000 cD 3 DHA 0.089043 + 142 H9R -1.860000 0.659000 -1.404000 hL 3 DHA 0.054255 + 143 H9S -0.584000 1.379000 -0.470000 hL 3 DHA 0.054255 + 144 C18 -2.001000 0.333000 0.716000 cB 3 DHA -0.226254 + 145 H8R -1.397000 0.236000 1.605000 hB 3 DHA 0.128147 + 146 C17 -3.314000 0.284000 0.867000 cB 3 DHA -0.220895 + 147 H7R -3.695000 0.166000 1.869000 hB 3 DHA 0.139863 + 148 C16 -4.387000 0.387000 -0.193000 cD 3 DHA 0.060337 + 149 H6R -4.997000 -0.509000 -0.162000 hL 3 DHA 0.063515 + 150 H6S -3.948000 0.424000 -1.183000 hL 3 DHA 0.063515 + 151 C15 -5.236000 1.620000 0.008000 cB 3 DHA -0.214845 + 152 H5R -4.695000 2.548000 -0.092000 hB 3 DHA 0.132504 + 153 C14 -6.524000 1.695000 0.297000 cB 3 DHA -0.287059 + 154 H4R -6.951000 2.681000 0.401000 hB 3 DHA 0.154038 + 155 C13 -7.505000 0.570000 0.499000 cD 3 DHA 0.120999 + 156 H3R -7.983000 0.686000 1.467000 hL 3 DHA 0.026097 + 157 H3S -7.007000 -0.392000 0.505000 hL 3 DHA 0.026097 + 158 C12 -8.592000 0.571000 -0.593000 cD 3 DHA -0.142937 + 159 H2R -8.148000 0.420000 -1.567000 hL 3 DHA 0.037544 + 160 H2S -9.110000 1.525000 -0.592000 hL 3 DHA 0.037544 + 161 C117 7.499000 0.703000 -0.489000 cB 3 DHA -0.216499 + 162 H17R 8.069000 1.616000 -0.419000 hB 3 DHA 0.132121 + 163 C118 8.338000 -0.552000 -0.428000 cD 3 DHA 0.092718 + 164 H18R 7.719000 -1.435000 -0.548000 hL 3 DHA 0.053227 + 165 H18S 9.032000 -0.549000 -1.261000 hL 3 DHA 0.053227 + 166 C119 9.079000 -0.651000 0.885000 cB 3 DHA -0.246833 + 167 H19R 8.435000 -0.701000 1.750000 hB 3 DHA 0.136323 + 168 C120 10.384000 -0.673000 1.094000 cB 3 DHA -0.240216 + 169 H20R 10.715000 -0.743000 2.120000 hB 3 DHA 0.133048 + 170 C121 11.504000 -0.603000 0.088000 cD 3 DHA 0.122022 + 171 H21R 12.102000 -1.509000 0.169000 hL 3 DHA 0.010042 + 172 H21S 11.121000 -0.579000 -0.925000 hL 3 DHA 0.010042 + 173 C122 12.411000 0.611000 0.320000 cD 3 DHA -0.108913 + 174 H22R 13.235000 0.621000 -0.387000 hL 3 DHA 0.025968 + 175 H22S 12.834000 0.599000 1.320000 hL 3 DHA 0.025968 + 176 H22T 11.857000 1.537000 0.206000 hL 3 DHA 0.025968 + 177 H12T 72.456000 10.844000 19.346000 hL 4 LAL 0.025809 + 178 C112 71.871000 10.231000 18.815000 cD 4 LAL -0.118534 + 179 H12R 71.178000 9.870000 19.578000 hL 4 LAL 0.025809 + 180 H12S 72.578000 9.424000 18.604000 hL 4 LAL 0.025809 + 181 C111 71.063000 10.566000 17.542000 cD 4 LAL 0.023320 + 182 H11R 71.736000 10.898000 16.748000 hL 4 LAL 0.008276 + 183 H11S 70.381000 11.389000 17.765000 hL 4 LAL 0.008276 + 184 C110 70.248000 9.346000 17.062000 cD 4 LAL -0.019575 + 185 H10R 69.657000 8.960000 17.896000 hL 4 LAL 0.015163 + 186 H10S 70.930000 8.552000 16.748000 hL 4 LAL 0.015163 + 187 C19 69.295000 9.709000 15.900000 cD 4 LAL -0.021975 + 188 H9R 69.867000 10.082000 15.047000 hL 4 LAL 0.005869 + 189 H9S 68.620000 10.502000 16.225000 hL 4 LAL 0.005869 + 190 C18 68.458000 8.485000 15.479000 cD 4 LAL -0.020879 + 191 H8R 67.960000 8.078000 16.362000 hL 4 LAL 0.010385 + 192 H8S 69.123000 7.713000 15.091000 hL 4 LAL 0.010385 + 193 C17 67.396000 8.833000 14.413000 cD 4 LAL -0.019279 + 194 H7R 67.887000 9.155000 13.492000 hL 4 LAL 0.011207 + 195 H7S 66.788000 9.669000 14.765000 hL 4 LAL 0.011207 + 196 C16 66.494000 7.608000 14.129000 cD 4 LAL -0.019631 + 197 H6R 66.260000 7.108000 15.071000 hL 4 LAL 0.010236 + 198 H6S 67.042000 6.887000 13.518000 hL 4 LAL 0.010236 + 199 C15 65.152000 7.963000 13.447000 cD 4 LAL -0.015735 + 200 H5R 64.552000 8.556000 14.137000 hL 4 LAL 0.009551 + 201 H5S 64.602000 7.038000 13.264000 hL 4 LAL 0.009551 + 202 C14 65.314000 8.740000 12.120000 cD 4 LAL -0.024078 + 203 H4R 66.094000 8.276000 11.515000 hL 4 LAL 0.020580 + 204 H4S 65.638000 9.759000 12.335000 hL 4 LAL 0.020580 + 205 C13 64.012000 8.783000 11.285000 cD 4 LAL 0.001510 + 206 H3R 63.755000 7.770000 10.968000 hL 4 LAL 0.019108 + 207 H3S 64.207000 9.367000 10.384000 hL 4 LAL 0.019108 + 208 C12 62.815000 9.404000 12.036000 cD 4 LAL -0.172159 + 209 H2R 63.101000 10.354000 12.488000 hL 4 LAL 0.054419 + 210 H2S 62.489000 8.734000 12.830000 hL 4 LAL 0.054419 + 211 H14T 72.992000 10.982000 21.559000 hL 5 MY 0.025511 + 212 C114 73.387000 11.208000 20.669000 cD 5 MY -0.111712 + 213 H14R 74.081000 10.373000 20.543000 hL 5 MY 0.025511 + 214 H14S 73.988000 12.092000 20.900000 hL 5 MY 0.025511 + 215 C113 72.635000 11.469000 19.341000 cD 5 MY 0.017463 + 216 H13R 73.359000 11.785000 18.585000 hL 5 MY 0.007531 + 217 H13S 71.940000 12.299000 19.482000 hL 5 MY 0.007531 + 218 C112 71.871000 10.231000 18.815000 cD 5 MY -0.028094 + 219 H12R 71.178000 9.870000 19.578000 hL 5 MY 0.017208 + 220 H12S 72.578000 9.424000 18.604000 hL 5 MY 0.017208 + 221 C111 71.063000 10.566000 17.542000 cD 5 MY -0.031787 + 222 H11R 71.736000 10.898000 16.748000 hL 5 MY 0.012677 + 223 H11S 70.381000 11.389000 17.765000 hL 5 MY 0.012677 + 224 C110 70.248000 9.346000 17.062000 cD 5 MY -0.021025 + 225 H10R 69.657000 8.960000 17.896000 hL 5 MY 0.010037 + 226 H10S 70.930000 8.552000 16.748000 hL 5 MY 0.010037 + 227 C19 69.295000 9.709000 15.900000 cD 5 MY -0.014653 + 228 H9R 69.867000 10.082000 15.047000 hL 5 MY 0.010172 + 229 H9S 68.620000 10.502000 16.225000 hL 5 MY 0.010172 + 230 C18 68.458000 8.485000 15.479000 cD 5 MY -0.026451 + 231 H8R 67.960000 8.078000 16.362000 hL 5 MY 0.011241 + 232 H8S 69.123000 7.713000 15.091000 hL 5 MY 0.011241 + 233 C17 67.396000 8.833000 14.413000 cD 5 MY -0.004386 + 234 H7R 67.887000 9.155000 13.492000 hL 5 MY 0.006833 + 235 H7S 66.788000 9.669000 14.765000 hL 5 MY 0.006833 + 236 C16 66.494000 7.608000 14.129000 cD 5 MY -0.027812 + 237 H6R 66.260000 7.108000 15.071000 hL 5 MY 0.011511 + 238 H6S 67.042000 6.887000 13.518000 hL 5 MY 0.011511 + 239 C15 65.152000 7.963000 13.447000 cD 5 MY -0.016521 + 240 H5R 64.552000 8.556000 14.137000 hL 5 MY 0.010573 + 241 H5S 64.602000 7.038000 13.264000 hL 5 MY 0.010573 + 242 C14 65.314000 8.740000 12.120000 cD 5 MY -0.026954 + 243 H4R 66.094000 8.276000 11.515000 hL 5 MY 0.020431 + 244 H4S 65.638000 9.759000 12.335000 hL 5 MY 0.020431 + 245 C13 64.012000 8.783000 11.285000 cD 5 MY -0.002435 + 246 H3R 63.755000 7.770000 10.968000 hL 5 MY 0.018859 + 247 H3S 64.207000 9.367000 10.384000 hL 5 MY 0.018859 + 248 C12 62.815000 9.404000 12.036000 cD 5 MY -0.164200 + 249 H2R 63.101000 10.354000 12.488000 hL 5 MY 0.053944 + 250 H2S 62.489000 8.734000 12.830000 hL 5 MY 0.053944 + 251 C12 6.166000 -0.064000 0.159000 cD 6 OL -0.143418 + 252 H2R 5.945000 -0.958000 0.733000 hL 6 OL 0.042861 + 253 H2S 5.869000 0.773000 0.786000 hL 6 OL 0.042861 + 254 C13 5.422000 -0.043000 -1.175000 cD 6 OL 0.001619 + 255 H3R 5.748000 0.824000 -1.739000 hL 6 OL 0.019529 + 256 H3S 5.713000 -0.909000 -1.761000 hL 6 OL 0.019529 + 257 C14 3.897000 -0.002000 -1.029000 cD 6 OL -0.022960 + 258 H4R 3.618000 0.876000 -0.452000 hL 6 OL 0.018134 + 259 H4S 3.467000 0.136000 -2.018000 hL 6 OL 0.018134 + 260 C15 3.285000 -1.260000 -0.401000 cD 6 OL -0.016692 + 261 H5R 3.676000 -2.132000 -0.922000 hL 6 OL 0.008690 + 262 H5S 3.604000 -1.356000 0.634000 hL 6 OL 0.008690 + 263 C16 1.753000 -1.313000 -0.454000 cD 6 OL -0.023789 + 264 H6R 1.427000 -1.233000 -1.489000 hL 6 OL 0.009266 + 265 H6S 1.431000 -2.294000 -0.110000 hL 6 OL 0.009266 + 266 C17 1.047000 -0.244000 0.385000 cD 6 OL -0.017495 + 267 H7R 1.408000 -0.296000 1.411000 hL 6 OL 0.019598 + 268 H7S 1.297000 0.749000 0.022000 hL 6 OL 0.019598 + 269 C18 -0.480000 -0.400000 0.376000 cD 6 OL 0.031775 + 270 H8R -0.842000 -0.344000 -0.644000 hL 6 OL 0.032685 + 271 H8S -0.733000 -1.396000 0.737000 hL 6 OL 0.032685 + 272 C19 -1.164000 0.618000 1.252000 cB 6 OL -0.244228 + 273 H9R -0.917000 0.539000 2.300000 hB 6 OL 0.131512 + 274 C110 -2.000000 1.581000 0.901000 cB 6 OL -0.239284 + 275 H10R -2.380000 2.214000 1.690000 hB 6 OL 0.126878 + 276 C111 -2.510000 1.937000 -0.472000 cD 6 OL 0.029059 + 277 H11R -2.194000 2.954000 -0.698000 hL 6 OL 0.033531 + 278 H11S -2.066000 1.305000 -1.233000 hL 6 OL 0.033531 + 279 C112 -4.042000 1.873000 -0.585000 cD 6 OL -0.026353 + 280 H12R -4.484000 2.503000 0.186000 hL 6 OL 0.019717 + 281 H12S -4.337000 2.308000 -1.538000 hL 6 OL 0.019717 + 282 C113 -4.615000 0.459000 -0.479000 cD 6 OL -0.018257 + 283 H13R -4.172000 -0.164000 -1.256000 hL 6 OL 0.013800 + 284 H13S -4.321000 0.017000 0.469000 hL 6 OL 0.013800 + 285 C114 -6.139000 0.419000 -0.609000 cD 6 OL -0.024849 + 286 H14R -6.575000 1.054000 0.159000 hL 6 OL 0.008572 + 287 H14S -6.433000 0.851000 -1.564000 hL 6 OL 0.008572 + 288 C115 -6.708000 -0.999000 -0.500000 cD 6 OL -0.023247 + 289 H15R -6.207000 -1.632000 -1.230000 hL 6 OL 0.010403 + 290 H15S -6.458000 -1.413000 0.473000 hL 6 OL 0.010403 + 291 C116 -8.220000 -1.104000 -0.743000 cD 6 OL -0.012488 + 292 H16R -8.435000 -0.751000 -1.750000 hL 6 OL 0.013659 + 293 H16S -8.499000 -2.156000 -0.726000 hL 6 OL 0.013659 + 294 C117 -9.120000 -0.346000 0.242000 cD 6 OL 0.008684 + 295 H17R -8.919000 0.721000 0.187000 hL 6 OL 0.010026 + 296 H17S -10.151000 -0.470000 -0.082000 hL 6 OL 0.010026 + 297 C118 -9.002000 -0.812000 1.694000 cD 6 OL -0.117290 + 298 H18R -9.724000 -0.300000 2.323000 hL 6 OL 0.026627 + 299 H18S -8.017000 -0.616000 2.104000 hL 6 OL 0.026627 + 300 H18T -9.188000 -1.879000 1.778000 hL 6 OL 0.026627 + 301 C116 -10.262000 -0.302000 -0.549000 cD 7 PA -0.125447 + 302 H16R -10.644000 -0.891000 -1.377000 hL 7 PA 0.029047 + 303 H16S -10.859000 0.603000 -0.483000 hL 7 PA 0.029047 + 304 H16T -10.424000 -0.870000 0.363000 hL 7 PA 0.029047 + 305 C115 -8.781000 0.025000 -0.737000 cD 7 PA 0.013975 + 306 H15R -8.648000 0.570000 -1.670000 hL 7 PA 0.009292 + 307 H15S -8.224000 -0.902000 -0.843000 hL 7 PA 0.009292 + 308 C114 -8.209000 0.854000 0.416000 cD 7 PA -0.020086 + 309 H14R -8.302000 0.290000 1.344000 hL 7 PA 0.015929 + 310 H14S -8.824000 1.743000 0.541000 hL 7 PA 0.015929 + 311 C113 -6.751000 1.293000 0.235000 cD 7 PA -0.033096 + 312 H13R -6.500000 1.986000 1.035000 hL 7 PA 0.014621 + 313 H13S -6.660000 1.857000 -0.692000 hL 7 PA 0.014621 + 314 C112 -5.730000 0.153000 0.237000 cD 7 PA -0.027633 + 315 H12R -5.920000 -0.519000 -0.596000 hL 7 PA 0.011368 + 316 H12S -5.856000 -0.439000 1.142000 hL 7 PA 0.011368 + 317 C111 -4.284000 0.648000 0.155000 cD 7 PA -0.025206 + 318 H11R -4.084000 1.312000 0.994000 hL 7 PA 0.014334 + 319 H11S -4.160000 1.248000 -0.744000 hL 7 PA 0.014334 + 320 C110 -3.256000 -0.485000 0.151000 cD 7 PA -0.028831 + 321 H10R -3.455000 -1.148000 -0.689000 hL 7 PA 0.014691 + 322 H10S -3.381000 -1.087000 1.050000 hL 7 PA 0.014691 + 323 C19 -1.811000 0.010000 0.072000 cD 7 PA -0.030472 + 324 H9R -1.612000 0.673000 0.913000 hL 7 PA 0.013897 + 325 H9S -1.685000 0.612000 -0.826000 hL 7 PA 0.013897 + 326 C18 -0.782000 -1.123000 0.068000 cD 7 PA -0.015793 + 327 H8R -0.983000 -1.787000 -0.770000 hL 7 PA 0.009067 + 328 H8S -0.906000 -1.724000 0.968000 hL 7 PA 0.009067 + 329 C17 0.663000 -0.628000 -0.013000 cD 7 PA -0.019630 + 330 H7R 0.854000 0.045000 0.820000 hL 7 PA 0.011041 + 331 H7S 0.790000 -0.036000 -0.919000 hL 7 PA 0.011041 + 332 C16 1.685000 -1.769000 -0.010000 cD 7 PA -0.024427 + 333 H6R 1.433000 -2.463000 -0.809000 hL 7 PA 0.013334 + 334 H6S 1.595000 -2.331000 0.918000 hL 7 PA 0.013334 + 335 C15 3.142000 -1.328000 -0.195000 cD 7 PA -0.029387 + 336 H5R 3.227000 -0.783000 -1.132000 hL 7 PA 0.016763 + 337 H5S 3.759000 -2.218000 -0.304000 hL 7 PA 0.016763 + 338 C14 3.698000 -0.485000 0.958000 cD 7 PA -0.030099 + 339 H4R 3.490000 -0.997000 1.896000 hL 7 PA 0.020877 + 340 H4S 3.179000 0.468000 1.010000 hL 7 PA 0.020877 + 341 C13 5.207000 -0.229000 0.884000 cD 7 PA -0.004882 + 342 H3R 5.739000 -1.174000 0.848000 hL 7 PA 0.019007 + 343 H3S 5.519000 0.273000 1.796000 hL 7 PA 0.019007 + 344 C12 5.627000 0.634000 -0.318000 cD 7 PA -0.149259 + 345 H2R 5.083000 1.570000 -0.321000 hL 7 PA 0.047345 + 346 H2S 5.426000 0.112000 -1.246000 hL 7 PA 0.047345 + 347 C11 4.694000 -2.109000 0.405000 cC 8 PC 0.910403 + 348 O12 4.576000 -2.896000 -0.477000 oC 8 PC -0.671566 + 349 O11 3.902000 -1.060000 0.566000 oS 8 PC -0.575489 + 350 C1 2.854000 -0.874000 -0.377000 cA 8 PC 0.196939 + 351 HR 2.157000 -1.696000 -0.314000 hE 8 PC 0.070469 + 352 HS 3.266000 -0.836000 -1.375000 hE 8 PC 0.070469 + 353 C2 2.165000 0.425000 -0.026000 cA 8 PC 0.281242 + 354 HX 1.911000 0.438000 1.021000 hE 8 PC 0.063253 + 355 C3 0.919000 0.667000 -0.863000 cA 8 PC 0.017459 + 356 HA 1.151000 0.638000 -1.919000 hE 8 PC 0.095591 + 357 HB 0.516000 1.643000 -0.623000 hE 8 PC 0.095591 + 358 O31 -0.006000 -0.339000 -0.535000 oT 8 PC -0.509480 + 359 P31 -1.430000 -0.403000 -1.277000 pA 8 PC 1.339721 + 360 O32 -2.179000 0.795000 -0.393000 oT 8 PC -0.508176 + 361 C31 -3.461000 1.133000 -0.789000 cA 8 PC 0.166801 + 362 H1A -3.812000 0.531000 -1.617000 hE 8 PC 0.078534 + 363 H1B -3.484000 2.165000 -1.127000 hE 8 PC 0.078534 + 364 C32 -4.434000 1.074000 0.381000 cA 8 PC -0.170824 + 365 H2A -5.396000 1.448000 0.054000 hX 8 PC 0.136415 + 366 H2B -4.072000 1.701000 1.183000 hX 8 PC 0.136415 + 367 N31 -4.708000 -0.280000 1.003000 nA 8 PC 0.245262 + 368 C33 -5.092000 -1.293000 -0.033000 cA 8 PC -0.338973 + 369 H3A -5.889000 -0.883000 -0.638000 hX 8 PC 0.172230 + 370 H3B -5.442000 -2.177000 0.479000 hX 8 PC 0.172230 + 371 H3C -4.222000 -1.537000 -0.624000 hX 8 PC 0.172230 + 372 C34 -3.521000 -0.799000 1.770000 cA 8 PC -0.338973 + 373 H4A -3.178000 -0.023000 2.437000 hX 8 PC 0.172230 + 374 H4B -2.753000 -1.088000 1.075000 hX 8 PC 0.172230 + 375 H4C -3.853000 -1.656000 2.338000 hX 8 PC 0.172230 + 376 C35 -5.839000 -0.102000 1.952000 cA 8 PC -0.338973 + 377 H5A -5.570000 0.638000 2.691000 hX 8 PC 0.172230 + 378 H5B -6.044000 -1.043000 2.440000 hX 8 PC 0.172230 + 379 H5C -6.716000 0.223000 1.411000 hX 8 PC 0.172230 + 380 O33 -2.106000 -1.653000 -0.869000 oP 8 PC -0.875812 + 381 O34 -1.344000 0.050000 -2.666000 oP 8 PC -0.875812 + 382 O21 3.096000 1.472000 -0.298000 oS 8 PC -0.552948 + 383 C21 3.110000 2.550000 0.475000 cC 8 PC 0.897613 + 384 O22 2.361000 2.734000 1.379000 oC 8 PC -0.673755 + 385 C11 2.180000 2.916000 -0.064000 cC 9 PE 0.888359 + 386 O12 1.200000 3.445000 0.353000 oC 9 PE -0.650802 + 387 O11 2.300000 1.607000 -0.216000 oS 9 PE -0.593826 + 388 C1 1.186000 0.802000 0.148000 cA 9 PE 0.273776 + 389 HR 0.303000 1.147000 -0.363000 hE 9 PE 0.044574 + 390 HS 1.024000 0.875000 1.214000 hE 9 PE 0.044574 + 391 C2 1.505000 -0.620000 -0.256000 cA 9 PE 0.339004 + 392 HX 1.807000 -0.650000 -1.291000 hE 9 PE 0.032843 + 393 C3 0.330000 -1.562000 -0.031000 cA 9 PE 0.040967 + 394 HA 0.041000 -1.559000 1.011000 hE 9 PE 0.090994 + 395 HB 0.613000 -2.568000 -0.305000 hE 9 PE 0.090994 + 396 O31 -0.732000 -1.142000 -0.860000 oT 9 PE -0.546623 + 397 P31 -2.233000 -1.108000 -0.332000 pA 9 PE 1.373656 + 398 O32 -2.136000 0.266000 0.585000 oT 9 PE -0.514542 + 399 C31 -3.161000 0.573000 1.479000 cA 9 PE 0.075159 + 400 H1A -2.877000 1.497000 1.967000 hE 9 PE 0.091516 + 401 H1B -3.275000 -0.198000 2.230000 hE 9 PE 0.091516 + 402 C32 -4.503000 0.755000 0.756000 cA 9 PE 0.077319 + 403 H2A -4.980000 -0.196000 0.574000 hX 9 PE 0.091630 + 404 H2B -5.171000 1.387000 1.325000 hX 9 PE 0.091630 + 405 N31 -4.264000 1.356000 -0.588000 nA 9 PE -0.368132 + 406 HN1A -3.660000 2.160000 -0.543000 hN 9 PE 0.348549 + 407 HN1B -3.765000 0.574000 -1.162000 hN 9 PE 0.348549 + 408 HN1C -5.121000 1.620000 -1.043000 hN 9 PE 0.348549 + 409 O33 -2.580000 -2.225000 0.542000 oP 9 PE -0.877447 + 410 O34 -3.100000 -0.702000 -1.484000 oP 9 PE -0.877447 + 411 O21 2.581000 -1.061000 0.567000 oS 9 PE -0.575480 + 412 C21 3.515000 -1.854000 0.049000 cC 9 PE 0.869340 + 413 O22 3.494000 -2.265000 -1.064000 oC 9 PE -0.649199 + 414 C11 -4.477000 -1.887000 -0.726000 cC 10 PGR 0.877006 + 415 O12 -4.371000 -2.868000 -0.063000 oC 10 PGR -0.649265 + 416 O11 -3.639000 -0.870000 -0.694000 oS 10 PGR -0.585165 + 417 C1 -2.503000 -0.948000 0.169000 cA 10 PGR 0.295119 + 418 HR -1.828000 -1.711000 -0.185000 hE 10 PGR 0.033797 + 419 HS -2.829000 -1.195000 1.169000 hE 10 PGR 0.033797 + 420 C2 -1.828000 0.404000 0.138000 cA 10 PGR 0.353807 + 421 HX -1.630000 0.695000 -0.880000 hE 10 PGR 0.037488 + 422 C3 -0.548000 0.426000 0.959000 cA 10 PGR 0.043713 + 423 HA -0.757000 0.182000 1.993000 hE 10 PGR 0.091041 + 424 HB -0.133000 1.429000 0.931000 hE 10 PGR 0.091041 + 425 O31 0.324000 -0.498000 0.392000 oT 10 PGR -0.546098 + 426 P31 1.792000 -0.746000 1.103000 pA 10 PGR 1.380464 + 427 O32 2.555000 0.619000 0.624000 oT 10 PGR -0.534308 + 428 C31 2.986000 0.770000 -0.700000 cA 10 PGR -0.056706 + 429 H1A 2.623000 1.721000 -1.076000 hE 10 PGR 0.093354 + 430 H1B 2.604000 -0.014000 -1.340000 hE 10 PGR 0.093354 + 431 C32 4.505000 0.786000 -0.761000 cA 10 PGR 0.336816 + 432 H2A 4.873000 1.541000 -0.068000 hE 10 PGR 0.030042 + 433 O35 4.862000 1.134000 -2.080000 oH 10 PGR -0.726192 + 434 HO5A 5.785000 0.936000 -2.177000 hO 10 PGR 0.439523 + 435 C33 5.129000 -0.545000 -0.390000 cA 10 PGR 0.193597 + 436 H3A 4.915000 -0.793000 0.640000 hE 10 PGR 0.034715 + 437 H3B 4.728000 -1.331000 -1.018000 hE 10 PGR 0.034715 + 438 O33 2.390000 -1.892000 0.403000 oP 10 PGR -0.873444 + 439 O34 1.622000 -0.640000 2.557000 oP 10 PGR -0.873444 + 440 O21 -2.727000 1.350000 0.732000 oS 10 PGR -0.599131 + 441 C21 -3.217000 2.354000 0.028000 cC 10 PGR 0.883669 + 442 O22 -2.963000 2.587000 -1.110000 oC 10 PGR -0.655762 + 443 O36 6.529000 -0.405000 -0.601000 oH 10 PGR -0.700871 + 444 HO6A 6.945000 -1.239000 -0.440000 hO 10 PGR 0.423328 + 445 C11 -4.694000 -1.417000 0.240000 cC 11 PGS 0.877006 + 446 O12 -4.509000 -2.326000 0.983000 oC 11 PGS -0.649265 + 447 O11 -3.752000 -0.596000 -0.184000 oS 11 PGS -0.585165 + 448 C1 -2.409000 -0.821000 0.246000 cA 11 PGS 0.295119 + 449 HR -2.038000 -1.739000 -0.181000 hE 11 PGS 0.033797 + 450 HS -2.383000 -0.893000 1.323000 hE 11 PGS 0.033797 + 451 C2 -1.588000 0.353000 -0.240000 cA 11 PGS 0.353807 + 452 HX -1.724000 0.488000 -1.299000 hE 11 PGS 0.037488 + 453 C3 -0.111000 0.199000 0.091000 cA 11 PGS 0.043713 + 454 HA 0.027000 0.102000 1.161000 hE 11 PGS 0.091041 + 455 HB 0.417000 1.085000 -0.244000 hE 11 PGS 0.091041 + 456 O31 0.348000 -0.934000 -0.576000 oT 11 PGS -0.546098 + 457 P31 1.837000 -1.519000 -0.273000 pA 11 PGS 1.380464 + 458 O32 2.681000 -0.306000 -0.969000 oT 11 PGS -0.534308 + 459 C31 4.081000 -0.360000 -0.920000 cA 11 PGS -0.056706 + 460 H1A 4.460000 -0.364000 -1.935000 hE 11 PGS 0.093354 + 461 H1B 4.422000 -1.266000 -0.434000 hE 11 PGS 0.093354 + 462 C32 4.680000 0.829000 -0.187000 cA 11 PGS 0.336816 + 463 H2A 5.745000 0.618000 -0.068000 hE 11 PGS 0.030042 + 464 O35 4.513000 1.978000 -0.993000 oH 11 PGS -0.726192 + 465 HO5A 4.845000 2.731000 -0.522000 hO 11 PGS 0.439523 + 466 C33 4.080000 1.063000 1.197000 cA 11 PGS 0.193597 + 467 H3A 4.575000 1.928000 1.641000 hE 11 PGS 0.034715 + 468 H3B 3.031000 1.312000 1.083000 hE 11 PGS 0.034715 + 469 O33 2.005000 -2.765000 -1.022000 oP 11 PGS -0.873444 + 470 O34 2.084000 -1.434000 1.191000 oP 11 PGS -0.873444 + 471 O21 -2.046000 1.522000 0.450000 oS 11 PGS -0.599131 + 472 C21 -2.640000 2.506000 -0.204000 cC 11 PGS 0.883669 + 473 O22 -2.822000 2.540000 -1.377000 oC 11 PGS -0.655762 + 474 O36 4.267000 -0.017000 2.049000 oH 11 PGS -0.700871 + 475 HO6A 3.542000 -0.633000 1.892000 hO 11 PGS 0.423328 + 476 C11 -3.044000 -2.013000 0.046000 cC 12 PH- 0.888207 + 477 O12 -2.728000 -2.874000 0.802000 oC 12 PH- -0.671142 + 478 O11 -2.283000 -0.988000 -0.281000 oS 12 PH- -0.547824 + 479 C1 -0.971000 -0.903000 0.280000 cA 12 PH- 0.185299 + 480 HR -0.353000 -1.692000 -0.118000 hE 12 PH- 0.067676 + 481 HS -1.034000 -1.005000 1.353000 hE 12 PH- 0.067676 + 482 C2 -0.409000 0.446000 -0.105000 cA 12 PH- 0.283546 + 483 HX -0.481000 0.586000 -1.171000 hE 12 PH- 0.063862 + 484 C3 1.029000 0.623000 0.360000 cA 12 PH- 0.081528 + 485 HA 1.091000 0.533000 1.438000 hE 12 PH- 0.083099 + 486 HB 1.360000 1.622000 0.090000 hE 12 PH- 0.083099 + 487 O31 1.789000 -0.352000 -0.272000 oT 12 PH- -0.594197 + 488 P31 3.414000 -0.414000 0.038000 pA 12 PH- 1.456663 + 489 O32 3.909000 0.868000 -0.850000 oH 12 PH- -0.706197 + 490 HO2A 4.131000 0.544000 -1.712000 hO 12 PH- 0.415628 + 491 O33 3.901000 -1.629000 -0.630000 oP 12 PH- -0.914158 + 492 O34 3.601000 -0.081000 1.456000 oP 12 PH- -0.914158 + 493 O21 -1.197000 1.447000 0.555000 oS 12 PH- -0.554487 + 494 C21 -1.896000 2.327000 -0.137000 cC 12 PH- 0.897502 + 495 O22 -1.933000 2.399000 -1.324000 oC 12 PH- -0.671622 + 496 C11 -4.858000 -1.850000 -0.410000 cC 13 PS 0.887549 + 497 O12 -4.698000 -2.763000 0.333000 oC 13 PS -0.663331 + 498 O11 -4.019000 -0.838000 -0.535000 oS 13 PS -0.579073 + 499 C1 -2.831000 -0.844000 0.255000 cA 13 PS 0.271173 + 500 HR -2.200000 -1.666000 -0.042000 hE 13 PS 0.037454 + 501 HS -3.092000 -0.954000 1.297000 hE 13 PS 0.037454 + 502 C2 -2.131000 0.474000 0.008000 cA 13 PS 0.440650 + 503 HX -1.992000 0.627000 -1.049000 hE 13 PS 0.002473 + 504 C3 -0.799000 0.566000 0.736000 cA 13 PS 0.012366 + 505 HA -0.935000 0.434000 1.801000 hE 13 PS 0.097347 + 506 HB -0.371000 1.548000 0.565000 hE 13 PS 0.097347 + 507 O31 0.031000 -0.431000 0.214000 oT 13 PS -0.556992 + 508 P31 1.459000 -0.736000 0.918000 pA 13 PS 1.380208 + 509 O32 2.273000 0.544000 0.314000 oT 13 PS -0.520128 + 510 C31 3.667000 0.633000 0.494000 cA 13 PS 0.060375 + 511 H1A 3.999000 -0.001000 1.309000 hE 13 PS 0.093484 + 512 H1B 3.918000 1.652000 0.742000 hE 13 PS 0.093484 + 513 C32 4.399000 0.283000 -0.793000 cA 13 PS 0.039448 + 514 H2A 4.021000 0.911000 -1.586000 hX 13 PS 0.090549 + 515 N31 4.144000 -1.146000 -1.167000 nA 13 PS -0.414327 + 516 HN1A 5.037000 -1.599000 -0.965000 hN 13 PS 0.353866 + 517 HN1B 3.345000 -1.571000 -0.652000 hN 13 PS 0.353866 + 518 HN1C 3.974000 -1.245000 -2.151000 hN 13 PS 0.353866 + 519 C33 5.947000 0.464000 -0.664000 cC 13 PS 0.809867 + 520 O33 2.019000 -1.944000 0.253000 oP 13 PS -0.877922 + 521 O34 1.341000 -0.631000 2.375000 oP 13 PS -0.877922 + 522 O21 -2.961000 1.514000 0.532000 oS 13 PS -0.634381 + 523 C21 -3.475000 2.435000 -0.269000 cC 13 PS 0.891897 + 524 O22 -3.284000 2.507000 -1.439000 oC 13 PS -0.650291 + 525 O35 6.589000 -0.587000 -0.792000 oO 13 PS -0.815178 + 526 O36 6.300000 1.609000 -0.448000 oO 13 PS -0.815178 + 527 C116 9.152000 -1.077000 0.697000 cD 14 SA -0.199999 + 528 H16R 10.204000 -1.219000 0.564000 hL 14 SA 0.049471 + 529 H16S 8.914000 -0.040000 0.579000 hL 14 SA 0.049471 + 530 H16T 8.620000 -1.650000 -0.033000 hL 14 SA 0.049471 + 531 C115 8.749000 -1.537000 2.100000 cD 14 SA -0.046781 + 532 H15R 8.998000 -2.571000 2.214000 hL 14 SA 0.026484 + 533 H15S 7.696000 -1.389000 2.217000 hL 14 SA 0.026484 + 534 C114 9.476000 -0.727000 3.175000 cD 14 SA 0.044648 + 535 H14R 9.150000 0.290000 3.104000 hL 14 SA 0.010970 + 536 H14S 10.523000 -0.848000 2.988000 hL 14 SA 0.010970 + 537 C113 9.235000 -1.205000 4.608000 cD 14 SA -0.119225 + 538 H13R 9.328000 -2.270000 4.640000 hL 14 SA 0.040330 + 539 H13S 8.253000 -0.901000 4.904000 hL 14 SA 0.040330 + 540 C112 10.244000 -0.588000 5.579000 cD 14 SA 0.001048 + 541 H12R 10.085000 0.465000 5.680000 hL 14 SA 0.016935 + 542 H12S 11.258000 -0.696000 5.255000 hL 14 SA 0.016935 + 543 C111 9.953000 -1.315000 6.894000 cD 14 SA -0.079417 + 544 H11R 10.536000 -2.210000 6.950000 hL 14 SA 0.023502 + 545 H11S 8.917000 -1.578000 6.950000 hL 14 SA 0.023502 + 546 C110 10.325000 -0.382000 8.047000 cD 14 SA -0.066372 + 547 H10R 9.732000 0.507000 7.999000 hL 14 SA 0.021723 + 548 H10S 11.358000 -0.109000 7.982000 hL 14 SA 0.021723 + 549 C19 10.054000 -1.114000 9.363000 cD 14 SA 0.053352 + 550 H9R 10.960000 -1.558000 9.719000 hL 14 SA 0.020200 + 551 H9S 9.324000 -1.880000 9.205000 hL 14 SA 0.020200 + 552 C18 9.534000 -0.113000 10.397000 cD 14 SA -0.187889 + 553 H8R 8.612000 0.297000 10.041000 hL 14 SA 0.046909 + 554 H8S 10.285000 0.639000 10.520000 hL 14 SA 0.046909 + 555 C17 9.289000 -0.743000 11.770000 cD 14 SA 0.049288 + 556 H7R 8.953000 -1.749000 11.629000 hL 14 SA 0.006045 + 557 H7S 8.557000 -0.145000 12.271000 hL 14 SA 0.006045 + 558 C16 10.542000 -0.765000 12.648000 cD 14 SA -0.012997 + 559 H6R 10.872000 0.244000 12.784000 hL 14 SA 0.003223 + 560 H6S 11.259000 -1.375000 12.139000 hL 14 SA 0.003223 + 561 C15 10.340000 -1.392000 14.029000 cD 14 SA 0.093171 + 562 H5R 9.875000 -0.666000 14.663000 hL 14 SA -0.011690 + 563 H5S 11.308000 -1.686000 14.378000 hL 14 SA -0.011690 + 564 C14 9.472000 -2.651000 14.023000 cD 14 SA -0.258138 + 565 H4R 10.007000 -3.387000 13.461000 hL 14 SA 0.076843 + 566 H4S 8.542000 -2.295000 13.631000 hL 14 SA 0.076843 + 567 C13 9.061000 -3.309000 15.307000 cB 14 SA 0.028290 + 568 H3R 8.616000 -4.240000 15.290000 hB 14 SA 0.116281 + 569 C12 9.271000 -2.689000 16.476000 cB 14 SA -0.341889 + 570 H2R 9.747000 -1.773000 16.480000 hB 14 SA 0.145550 + 571 C2 -41.583000 54.249000 135.723000 cA 15 SPM 0.012739 + 572 HX -42.533000 53.708000 135.697000 hE 15 SPM 0.087455 + 573 C3 -40.999000 53.884000 137.051000 cA 15 SPM 0.176625 + 574 HA -40.811000 52.812000 137.148000 hE 15 SPM 0.051622 + 575 HB -41.752000 54.112000 137.810000 hE 15 SPM 0.051622 + 576 O31 -39.815000 54.716000 137.276000 oT 15 SPM -0.533356 + 577 P31 -39.293000 55.046000 138.706000 pA 15 SPM 1.324762 + 578 O32 -38.408000 53.823000 139.126000 oT 15 SPM -0.459308 + 579 C31 -39.007000 52.753000 139.780000 cA 15 SPM 0.282447 + 580 H1A -39.703000 52.245000 139.107000 hE 15 SPM 0.049886 + 581 H1B -39.513000 53.055000 140.700000 hE 15 SPM 0.049886 + 582 C32 -37.924000 51.780000 140.007000 cA 15 SPM -0.493665 + 583 H2A -37.321000 51.694000 139.101000 hX 15 SPM 0.233840 + 584 H2B -38.386000 50.810000 140.197000 hX 15 SPM 0.233840 + 585 N31 -36.964000 52.047000 141.081000 nA 15 SPM 0.245275 + 586 C33 -36.033000 53.154000 140.832000 cA 15 SPM -0.386611 + 587 H3A -35.716000 52.950000 139.809000 hX 15 SPM 0.191569 + 588 H3B -36.582000 54.084000 140.986000 hX 15 SPM 0.191569 + 589 H3C -35.184000 53.174000 141.517000 hX 15 SPM 0.191569 + 590 C34 -37.627000 52.236000 142.409000 cA 15 SPM -0.386611 + 591 H4A -36.897000 52.122000 143.212000 hX 15 SPM 0.191569 + 592 H4B -37.859000 53.302000 142.403000 hX 15 SPM 0.191569 + 593 H4C -38.452000 51.526000 142.332000 hX 15 SPM 0.191569 + 594 C35 -36.268000 50.708000 141.208000 cA 15 SPM -0.386611 + 595 H5A -35.199000 50.867000 141.358000 hX 15 SPM 0.191569 + 596 H5B -36.826000 50.065000 141.889000 hX 15 SPM 0.191569 + 597 H5C -36.280000 50.262000 140.212000 hX 15 SPM 0.191569 + 598 O33 -40.354000 55.264000 139.710000 oP 15 SPM -0.884789 + 599 O34 -38.275000 56.136000 138.639000 oP 15 SPM -0.884789 + 600 N11 -40.692000 54.026000 134.611000 nN 15 SPM -0.388410 + 601 H11 -39.968000 54.718000 134.492000 hN 15 SPM 0.273592 + 602 C11 -40.660000 52.864000 133.849000 cC 15 SPM 0.617231 + 603 O12 -41.427000 51.930000 133.938000 oC 15 SPM -0.645303 + 604 C1 -42.202000 55.632000 135.867000 cA 15 SPM 0.323092 + 605 HR -41.432000 56.273000 136.304000 hE 15 SPM 0.069208 + 606 O22 -43.465000 55.672000 136.573000 oH 15 SPM -0.723033 + 607 H22 -44.064000 56.048000 135.903000 hO 15 SPM 0.434964 + 608 C118 12.346000 0.172000 0.239000 cD 16 ST -0.120069 + 609 H18R 13.222000 0.143000 -0.403000 hL 16 ST 0.027938 + 610 H18S 12.424000 -0.652000 0.943000 hL 16 ST 0.027938 + 611 H18T 12.385000 1.096000 0.809000 hL 16 ST 0.027938 + 612 C117 11.057000 0.081000 -0.577000 cD 16 ST 0.020665 + 613 H17R 11.064000 -0.836000 -1.161000 hL 16 ST 0.008483 + 614 H17S 11.026000 0.899000 -1.294000 hL 16 ST 0.008483 + 615 C116 9.797000 0.120000 0.290000 cD 16 ST -0.027654 + 616 H16R 9.792000 1.038000 0.875000 hL 16 ST 0.015627 + 617 H16S 9.831000 -0.698000 1.008000 hL 16 ST 0.015627 + 618 C115 8.502000 0.029000 -0.520000 cD 16 ST -0.032444 + 619 H15R 8.507000 -0.889000 -1.105000 hL 16 ST 0.010253 + 620 H15S 8.469000 0.847000 -1.238000 hL 16 ST 0.010253 + 621 C114 7.242000 0.068000 0.348000 cD 16 ST -0.013489 + 622 H14R 7.236000 0.986000 0.933000 hL 16 ST 0.010073 + 623 H14S 7.275000 -0.750000 1.066000 hL 16 ST 0.010073 + 624 C113 5.947000 -0.023000 -0.462000 cD 16 ST -0.011590 + 625 H13R 5.952000 -0.941000 -1.047000 hL 16 ST 0.006022 + 626 H13S 5.914000 0.795000 -1.180000 hL 16 ST 0.006022 + 627 C112 4.687000 0.016000 0.405000 cD 16 ST -0.019153 + 628 H12R 4.681000 0.934000 0.990000 hL 16 ST 0.008198 + 629 H12S 4.720000 -0.802000 1.123000 hL 16 ST 0.008198 + 630 C111 3.392000 -0.075000 -0.404000 cD 16 ST -0.017093 + 631 H11R 3.397000 -0.993000 -0.989000 hL 16 ST 0.010206 + 632 H11S 3.359000 0.743000 -1.122000 hL 16 ST 0.010206 + 633 C110 2.132000 -0.036000 0.463000 cD 16 ST -0.026418 + 634 H10R 2.126000 0.882000 1.048000 hL 16 ST 0.010825 + 635 H10S 2.165000 -0.854000 1.181000 hL 16 ST 0.010825 + 636 C19 0.837000 -0.127000 -0.347000 cD 16 ST -0.017997 + 637 H9R 0.842000 -1.045000 -0.932000 hL 16 ST 0.009595 + 638 H9S 0.804000 0.691000 -1.065000 hL 16 ST 0.009595 + 639 C18 -0.423000 -0.089000 0.521000 cD 16 ST -0.014623 + 640 H8R -0.429000 0.830000 1.106000 hL 16 ST 0.007326 + 641 H8S -0.390000 -0.906000 1.239000 hL 16 ST 0.007326 + 642 C17 -1.718000 -0.180000 -0.289000 cD 16 ST -0.019469 + 643 H7R -1.712000 -1.098000 -0.874000 hL 16 ST 0.009292 + 644 H7S -1.751000 0.638000 -1.007000 hL 16 ST 0.009292 + 645 C16 -2.977000 -0.141000 0.578000 cD 16 ST -0.037627 + 646 H6R -2.984000 0.777000 1.163000 hL 16 ST 0.017740 + 647 H6S -2.944000 -0.959000 1.297000 hL 16 ST 0.017740 + 648 C15 -4.272000 -0.234000 -0.232000 cD 16 ST -0.013443 + 649 H5R -4.267000 -1.152000 -0.815000 hL 16 ST 0.008191 + 650 H5S -4.306000 0.584000 -0.950000 hL 16 ST 0.008191 + 651 C14 -5.531000 -0.196000 0.636000 cD 16 ST -0.059792 + 652 H4R -5.539000 0.724000 1.220000 hL 16 ST 0.024241 + 653 H4S -5.498000 -1.012000 1.355000 hL 16 ST 0.024241 + 654 C13 -6.822000 -0.294000 -0.177000 cD 16 ST -0.000128 + 655 H3R -6.828000 -1.215000 -0.752000 hL 16 ST 0.013886 + 656 H3S -6.861000 0.522000 -0.896000 hL 16 ST 0.013886 + 657 C12 -8.079000 -0.265000 0.706000 cD 16 ST -0.117020 + 658 H2R -8.110000 0.652000 1.284000 hL 16 ST 0.051807 + 659 H2S -8.050000 -1.094000 1.403000 hL 16 ST 0.051807 +@BOND + 1 1 2 1 + 2 1 3 1 + 3 1 4 1 + 4 1 38 1 + 5 4 5 1 + 6 4 6 2 + 7 6 7 1 + 8 6 8 1 + 9 8 9 1 + 10 8 10 1 + 11 8 11 1 + 12 11 12 1 + 13 11 13 2 + 14 13 14 1 + 15 13 15 1 + 16 15 16 1 + 17 15 17 1 + 18 15 18 1 + 19 18 19 1 + 20 18 20 2 + 21 20 21 1 + 22 20 22 1 + 23 22 23 1 + 24 22 24 1 + 25 22 25 1 + 26 25 26 1 + 27 25 27 2 + 28 27 28 1 + 29 27 29 1 + 30 29 30 1 + 31 29 31 1 + 32 29 32 1 + 33 32 33 1 + 34 32 34 1 + 35 32 35 1 + 36 35 36 1 + 37 35 37 1 + 38 38 39 1 + 39 38 40 1 + 40 38 41 1 + 41 41 42 1 + 42 41 43 1 + 43 41 44 1 + 44 44 45 1 + 45 44 46 1 + 46 44 47 1 + 47 47 48 1 + 48 47 49 1 + 49 47 50 1 + 50 51 52 1 + 51 51 53 1 + 52 51 54 1 + 53 51 72 1 + 54 54 55 1 + 55 54 56 1 + 56 54 57 1 + 57 57 58 1 + 58 57 59 1 + 59 57 123 1 + 60 59 60 1 + 61 59 61 1 + 62 59 62 1 + 63 62 63 2 + 64 62 72 1 + 65 63 64 1 + 66 63 65 1 + 67 65 66 1 + 68 65 67 1 + 69 65 68 1 + 70 68 69 1 + 71 68 70 1 + 72 68 80 1 + 73 70 71 1 + 74 70 72 1 + 75 70 73 1 + 76 72 94 1 + 77 73 74 1 + 78 73 75 1 + 79 73 76 1 + 80 76 77 1 + 81 76 78 1 + 82 76 79 1 + 83 79 80 1 + 84 79 88 1 + 85 79 90 1 + 86 80 81 1 + 87 80 82 1 + 88 82 83 1 + 89 82 84 1 + 90 82 85 1 + 91 85 86 1 + 92 85 87 1 + 93 85 88 1 + 94 88 89 1 + 95 88 98 1 + 96 90 91 1 + 97 90 92 1 + 98 90 93 1 + 99 94 95 1 + 100 94 96 1 + 101 94 97 1 + 102 98 99 1 + 103 98 100 1 + 104 98 104 1 + 105 100 101 1 + 106 100 102 1 + 107 100 103 1 + 108 104 105 1 + 109 104 106 1 + 110 104 107 1 + 111 107 108 1 + 112 107 109 1 + 113 107 110 1 + 114 110 111 1 + 115 110 112 1 + 116 110 113 1 + 117 113 114 1 + 118 113 115 1 + 119 113 119 1 + 120 115 116 1 + 121 115 117 1 + 122 115 118 1 + 123 119 120 1 + 124 119 121 1 + 125 119 122 1 + 126 123 124 1 + 127 125 126 1 + 128 125 127 1 + 129 125 161 2 + 130 127 128 1 + 131 127 129 1 + 132 127 130 1 + 133 130 131 1 + 134 130 132 2 + 135 132 133 1 + 136 132 134 1 + 137 134 135 1 + 138 134 136 1 + 139 134 137 1 + 140 137 138 1 + 141 137 139 2 + 142 139 140 1 + 143 139 141 1 + 144 141 142 1 + 145 141 143 1 + 146 141 144 1 + 147 144 145 1 + 148 144 146 2 + 149 146 147 1 + 150 146 148 1 + 151 148 149 1 + 152 148 150 1 + 153 148 151 1 + 154 151 152 1 + 155 151 153 2 + 156 153 154 1 + 157 153 155 1 + 158 155 156 1 + 159 155 157 1 + 160 155 158 1 + 161 158 159 1 + 162 158 160 1 + 163 161 162 1 + 164 161 163 1 + 165 163 164 1 + 166 163 165 1 + 167 163 166 1 + 168 166 167 1 + 169 166 168 2 + 170 168 169 1 + 171 168 170 1 + 172 170 171 1 + 173 170 172 1 + 174 170 173 1 + 175 173 174 1 + 176 173 175 1 + 177 173 176 1 + 178 177 178 1 + 179 178 179 1 + 180 178 180 1 + 181 178 181 1 + 182 181 184 1 + 183 181 183 1 + 184 181 182 1 + 185 184 187 1 + 186 184 186 1 + 187 184 185 1 + 188 187 190 1 + 189 187 189 1 + 190 187 188 1 + 191 190 193 1 + 192 190 192 1 + 193 190 191 1 + 194 193 196 1 + 195 193 195 1 + 196 193 194 1 + 197 196 199 1 + 198 196 198 1 + 199 196 197 1 + 200 199 202 1 + 201 199 201 1 + 202 199 200 1 + 203 202 205 1 + 204 202 204 1 + 205 202 203 1 + 206 205 208 1 + 207 205 206 1 + 208 205 207 1 + 209 208 210 1 + 210 208 209 1 + 211 211 212 1 + 212 212 215 1 + 213 212 213 1 + 214 212 214 1 + 215 215 218 1 + 216 215 216 1 + 217 215 217 1 + 218 218 221 1 + 219 218 220 1 + 220 218 219 1 + 221 221 224 1 + 222 221 222 1 + 223 221 223 1 + 224 224 227 1 + 225 224 226 1 + 226 224 225 1 + 227 227 230 1 + 228 227 228 1 + 229 227 229 1 + 230 230 233 1 + 231 230 232 1 + 232 230 231 1 + 233 233 236 1 + 234 233 234 1 + 235 233 235 1 + 236 236 239 1 + 237 236 237 1 + 238 236 238 1 + 239 239 242 1 + 240 239 240 1 + 241 239 241 1 + 242 242 245 1 + 243 242 244 1 + 244 242 243 1 + 245 245 248 1 + 246 245 247 1 + 247 245 246 1 + 248 248 250 1 + 249 248 249 1 + 250 251 252 1 + 251 251 253 1 + 252 251 254 1 + 253 254 255 1 + 254 254 256 1 + 255 254 257 1 + 256 257 258 1 + 257 257 259 1 + 258 257 260 1 + 259 260 261 1 + 260 260 262 1 + 261 260 263 1 + 262 263 264 1 + 263 263 265 1 + 264 263 266 1 + 265 266 267 1 + 266 266 268 1 + 267 266 269 1 + 268 269 270 1 + 269 269 271 1 + 270 269 272 1 + 271 272 273 1 + 272 272 274 2 + 273 274 275 1 + 274 274 276 1 + 275 276 277 1 + 276 276 278 1 + 277 276 279 1 + 278 279 280 1 + 279 279 281 1 + 280 279 282 1 + 281 282 283 1 + 282 282 284 1 + 283 282 285 1 + 284 285 286 1 + 285 285 287 1 + 286 285 288 1 + 287 288 289 1 + 288 288 290 1 + 289 288 291 1 + 290 291 292 1 + 291 291 293 1 + 292 291 294 1 + 293 294 295 1 + 294 294 296 1 + 295 294 297 1 + 296 297 298 1 + 297 297 299 1 + 298 297 300 1 + 299 301 302 1 + 300 301 303 1 + 301 301 304 1 + 302 301 305 1 + 303 305 306 1 + 304 305 307 1 + 305 305 308 1 + 306 308 309 1 + 307 308 310 1 + 308 308 311 1 + 309 311 312 1 + 310 311 313 1 + 311 311 314 1 + 312 314 315 1 + 313 314 316 1 + 314 314 317 1 + 315 317 318 1 + 316 317 319 1 + 317 317 320 1 + 318 320 321 1 + 319 320 322 1 + 320 320 323 1 + 321 323 324 1 + 322 323 325 1 + 323 323 326 1 + 324 326 327 1 + 325 326 328 1 + 326 326 329 1 + 327 329 330 1 + 328 329 331 1 + 329 329 332 1 + 330 332 333 1 + 331 332 334 1 + 332 332 335 1 + 333 335 336 1 + 334 335 337 1 + 335 335 338 1 + 336 338 339 1 + 337 338 340 1 + 338 338 341 1 + 339 341 342 1 + 340 341 343 1 + 341 341 344 1 + 342 344 345 1 + 343 344 346 1 + 344 347 348 2 + 345 347 349 1 + 346 349 350 1 + 347 350 351 1 + 348 350 352 1 + 349 350 353 1 + 350 353 354 1 + 351 353 355 1 + 352 353 382 1 + 353 355 356 1 + 354 355 357 1 + 355 355 358 1 + 356 358 359 1 + 357 359 360 1 + 358 359 380 1 + 359 359 381 1 + 360 360 361 1 + 361 361 362 1 + 362 361 363 1 + 363 361 364 1 + 364 364 365 1 + 365 364 366 1 + 366 364 367 1 + 367 367 368 1 + 368 367 372 1 + 369 367 376 1 + 370 368 369 1 + 371 368 370 1 + 372 368 371 1 + 373 372 373 1 + 374 372 374 1 + 375 372 375 1 + 376 376 377 1 + 377 376 378 1 + 378 376 379 1 + 379 382 383 1 + 380 383 384 2 + 381 385 386 2 + 382 385 387 1 + 383 387 388 1 + 384 388 389 1 + 385 388 390 1 + 386 388 391 1 + 387 391 392 1 + 388 391 393 1 + 389 391 411 1 + 390 393 394 1 + 391 393 395 1 + 392 393 396 1 + 393 396 397 1 + 394 397 398 1 + 395 397 409 1 + 396 397 410 1 + 397 398 399 1 + 398 399 400 1 + 399 399 401 1 + 400 399 402 1 + 401 402 403 1 + 402 402 404 1 + 403 402 405 1 + 404 405 406 1 + 405 405 407 1 + 406 405 408 1 + 407 411 412 1 + 408 412 413 2 + 409 414 415 2 + 410 414 416 1 + 411 416 417 1 + 412 417 418 1 + 413 417 419 1 + 414 417 420 1 + 415 420 421 1 + 416 420 422 1 + 417 420 440 1 + 418 422 423 1 + 419 422 424 1 + 420 422 425 1 + 421 425 426 1 + 422 426 427 1 + 423 426 438 1 + 424 426 439 1 + 425 427 428 1 + 426 428 429 1 + 427 428 430 1 + 428 428 431 1 + 429 431 432 1 + 430 431 433 1 + 431 431 435 1 + 432 433 434 1 + 433 435 436 1 + 434 435 437 1 + 435 435 443 1 + 436 440 441 1 + 437 441 442 2 + 438 443 444 1 + 439 445 446 2 + 440 445 447 1 + 441 447 448 1 + 442 448 449 1 + 443 448 450 1 + 444 448 451 1 + 445 451 452 1 + 446 451 453 1 + 447 451 471 1 + 448 453 454 1 + 449 453 455 1 + 450 453 456 1 + 451 456 457 1 + 452 457 458 1 + 453 457 469 1 + 454 457 470 1 + 455 458 459 1 + 456 459 460 1 + 457 459 461 1 + 458 459 462 1 + 459 462 463 1 + 460 462 464 1 + 461 462 466 1 + 462 464 465 1 + 463 466 467 1 + 464 466 468 1 + 465 466 474 1 + 466 471 472 1 + 467 472 473 2 + 468 474 475 1 + 469 476 477 2 + 470 476 478 1 + 471 478 479 1 + 472 479 480 1 + 473 479 481 1 + 474 479 482 1 + 475 482 483 1 + 476 482 484 1 + 477 482 493 1 + 478 484 485 1 + 479 484 486 1 + 480 484 487 1 + 481 487 488 1 + 482 488 489 1 + 483 488 491 1 + 484 488 492 1 + 485 489 490 1 + 486 493 494 1 + 487 494 495 2 + 488 496 497 2 + 489 496 498 1 + 490 498 499 1 + 491 499 500 1 + 492 499 501 1 + 493 499 502 1 + 494 502 503 1 + 495 502 504 1 + 496 502 522 1 + 497 504 505 1 + 498 504 506 1 + 499 504 507 1 + 500 507 508 1 + 501 508 509 1 + 502 508 520 1 + 503 508 521 1 + 504 509 510 1 + 505 510 511 1 + 506 510 512 1 + 507 510 513 1 + 508 513 514 1 + 509 513 515 1 + 510 513 519 1 + 511 515 516 1 + 512 515 517 1 + 513 515 518 1 + 514 519 525 1 + 515 519 526 1 + 516 522 523 1 + 517 523 524 2 + 518 527 531 1 + 519 527 529 1 + 520 527 528 1 + 521 527 530 1 + 522 531 534 1 + 523 531 533 1 + 524 531 532 1 + 525 534 537 1 + 526 534 535 1 + 527 534 536 1 + 528 537 540 1 + 529 537 539 1 + 530 537 538 1 + 531 540 543 1 + 532 540 542 1 + 533 540 541 1 + 534 543 546 1 + 535 543 545 1 + 536 543 544 1 + 537 546 549 1 + 538 546 547 1 + 539 546 548 1 + 540 549 552 1 + 541 549 550 1 + 542 549 551 1 + 543 552 555 1 + 544 552 554 1 + 545 552 553 1 + 546 555 558 1 + 547 555 557 1 + 548 555 556 1 + 549 558 561 1 + 550 558 559 1 + 551 558 560 1 + 552 561 564 1 + 553 561 563 1 + 554 561 562 1 + 555 564 567 1 + 556 564 565 1 + 557 564 566 1 + 558 567 569 1 + 559 567 568 1 + 560 569 570 1 + 561 571 600 1 + 562 571 573 1 + 563 571 572 1 + 564 571 604 1 + 565 573 576 1 + 566 573 575 1 + 567 573 574 1 + 568 576 577 1 + 569 577 578 1 + 570 577 598 1 + 571 577 599 1 + 572 578 579 1 + 573 579 582 1 + 574 579 581 1 + 575 579 580 1 + 576 582 585 1 + 577 582 583 1 + 578 582 584 1 + 579 585 590 1 + 580 585 594 1 + 581 585 586 1 + 582 586 587 1 + 583 586 588 1 + 584 586 589 1 + 585 590 593 1 + 586 590 592 1 + 587 590 591 1 + 588 594 597 1 + 589 594 595 1 + 590 594 596 1 + 591 600 602 1 + 592 600 601 1 + 593 602 603 1 + 594 604 605 1 + 595 604 606 1 + 596 606 607 1 + 597 608 612 1 + 598 608 611 1 + 599 608 610 1 + 600 608 609 1 + 601 612 615 1 + 602 612 614 1 + 603 612 613 1 + 604 615 618 1 + 605 615 616 1 + 606 615 617 1 + 607 618 621 1 + 608 618 620 1 + 609 618 619 1 + 610 621 624 1 + 611 621 622 1 + 612 621 623 1 + 613 624 627 1 + 614 624 626 1 + 615 624 625 1 + 616 627 630 1 + 617 627 628 1 + 618 627 629 1 + 619 630 633 1 + 620 630 632 1 + 621 630 631 1 + 622 633 636 1 + 623 633 634 1 + 624 633 635 1 + 625 636 639 1 + 626 636 638 1 + 627 636 637 1 + 628 639 642 1 + 629 639 640 1 + 630 639 641 1 + 631 642 645 1 + 632 642 644 1 + 633 642 643 1 + 634 645 648 1 + 635 645 646 1 + 636 645 647 1 + 637 648 651 1 + 638 648 650 1 + 639 648 649 1 + 640 651 654 1 + 641 651 652 1 + 642 651 653 1 + 643 654 657 1 + 644 654 656 1 + 645 654 655 1 + 646 657 658 1 + 647 657 659 1 +@SUBSTRUCTURE + 1 AR 1 **** 0 **** **** + 2 CHL 51 **** 0 **** **** + 3 DHA 125 **** 0 **** **** + 4 LAL 177 **** 0 **** **** + 5 MY 211 **** 0 **** **** + 6 OL 251 **** 0 **** **** + 7 PA 301 **** 0 **** **** + 8 PC 347 **** 0 **** **** + 9 PE 385 **** 0 **** **** + 10 PGR 414 **** 0 **** **** + 11 PGS 445 **** 0 **** **** + 12 PH- 476 **** 0 **** **** + 13 PS 496 **** 0 **** **** + 14 SA 527 **** 0 **** **** + 15 SPM 571 **** 0 **** **** + 16 ST 608 **** 0 **** **** diff --git a/src/Xponge/forcefield/amber/lipid21.py b/src/Xponge/forcefield/amber/lipid21.py new file mode 100644 index 0000000..a0fe76c --- /dev/null +++ b/src/Xponge/forcefield/amber/lipid21.py @@ -0,0 +1,3 @@ +"""Legacy Lipid21 loader backed by XpongeCPP.""" + +from XpongeCPP.forcefield.amber.lipid21 import * # noqa: F401,F403 diff --git a/src/Xponge/forcefield/amber/lipid21_manifest.json b/src/Xponge/forcefield/amber/lipid21_manifest.json new file mode 100644 index 0000000..0fa8952 --- /dev/null +++ b/src/Xponge/forcefield/amber/lipid21_manifest.json @@ -0,0 +1,899 @@ +{ + "format_version": 2, + "source": "lipid21.lib", + "source_sha256": "03fab780a94ff7f80f912cf08e80967ef63bae3ae5d9b204bb88bee51f1fae3d", + "source_license": "Public Domain (AmberTools dat/leap)", + "template_count": 16, + "templates": [ + { + "template": "AR", + "source_unit_name": "AR", + "atom_count": 50, + "bond_count": 49, + "total_charge": 0.0, + "expected_integer_charge": 0, + "head_atom": "C12", + "tail_atom": "C12", + "source_connect_indices": [ + 35, + 35 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "C13", + "head_reference_atom": "H2S", + "head_link_conditions": [ + { + "atoms": [ + "H2R", + "C12" + ], + "parameter_degrees": 109.5 + }, + { + "atoms": [ + "H2S", + "H2R", + "C12" + ], + "parameter_degrees": -120.0 + } + ], + "head_rule_source": "standard_chain", + "tail_next_atom": "C13", + "tail_reference_atom": "H2S", + "tail_link_conditions": [ + { + "atoms": [ + "H2R", + "C12" + ], + "parameter_degrees": 109.5 + }, + { + "atoms": [ + "H2S", + "H2R", + "C12" + ], + "parameter_degrees": 120.0 + } + ], + "tail_rule_source": "standard_chain" + }, + { + "template": "CHL", + "source_unit_name": "CHL", + "atom_count": 74, + "bond_count": 77, + "total_charge": 0.0, + "expected_integer_charge": 0, + "head_atom": null, + "tail_atom": null, + "source_connect_indices": [ + 0, + 0 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": null, + "head_reference_atom": null, + "head_link_conditions": [], + "head_rule_source": "none", + "tail_next_atom": null, + "tail_reference_atom": null, + "tail_link_conditions": [], + "tail_rule_source": "none" + }, + { + "template": "DHA", + "source_unit_name": "DHA", + "atom_count": 52, + "bond_count": 51, + "total_charge": 0.0, + "expected_integer_charge": 0, + "head_atom": "C12", + "tail_atom": "C12", + "source_connect_indices": [ + 34, + 34 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "C13", + "head_reference_atom": "H2S", + "head_link_conditions": [ + { + "atoms": [ + "H2R", + "C12" + ], + "parameter_degrees": 109.5 + }, + { + "atoms": [ + "H2S", + "H2R", + "C12" + ], + "parameter_degrees": -120.0 + } + ], + "head_rule_source": "standard_chain", + "tail_next_atom": "C13", + "tail_reference_atom": "H2S", + "tail_link_conditions": [ + { + "atoms": [ + "H2R", + "C12" + ], + "parameter_degrees": 109.5 + }, + { + "atoms": [ + "H2S", + "H2R", + "C12" + ], + "parameter_degrees": 120.0 + } + ], + "tail_rule_source": "standard_chain" + }, + { + "template": "LAL", + "source_unit_name": "LAL", + "atom_count": 34, + "bond_count": 33, + "total_charge": 0.0, + "expected_integer_charge": 0, + "head_atom": "C12", + "tail_atom": "C12", + "source_connect_indices": [ + 32, + 32 + ], + "source_connectivity_flags": [ + 1 + ], + "head_next_atom": "C13", + "head_reference_atom": "H2S", + "head_link_conditions": [ + { + "atoms": [ + "H2R", + "C12" + ], + "parameter_degrees": 109.5 + }, + { + "atoms": [ + "H2S", + "H2R", + "C12" + ], + "parameter_degrees": -120.0 + } + ], + "head_rule_source": "standard_chain", + "tail_next_atom": "C13", + "tail_reference_atom": "H2S", + "tail_link_conditions": [ + { + "atoms": [ + "H2R", + "C12" + ], + "parameter_degrees": 109.5 + }, + { + "atoms": [ + "H2S", + "H2R", + "C12" + ], + "parameter_degrees": 120.0 + } + ], + "tail_rule_source": "standard_chain" + }, + { + "template": "MY", + "source_unit_name": "MY", + "atom_count": 40, + "bond_count": 39, + "total_charge": 0.0, + "expected_integer_charge": 0, + "head_atom": "C12", + "tail_atom": "C12", + "source_connect_indices": [ + 38, + 38 + ], + "source_connectivity_flags": [ + 1 + ], + "head_next_atom": "C13", + "head_reference_atom": "H2S", + "head_link_conditions": [ + { + "atoms": [ + "H2R", + "C12" + ], + "parameter_degrees": 109.5 + }, + { + "atoms": [ + "H2S", + "H2R", + "C12" + ], + "parameter_degrees": -120.0 + } + ], + "head_rule_source": "standard_chain", + "tail_next_atom": "C13", + "tail_reference_atom": "H2S", + "tail_link_conditions": [ + { + "atoms": [ + "H2R", + "C12" + ], + "parameter_degrees": 109.5 + }, + { + "atoms": [ + "H2S", + "H2R", + "C12" + ], + "parameter_degrees": 120.0 + } + ], + "tail_rule_source": "standard_chain" + }, + { + "template": "OL", + "source_unit_name": "OL", + "atom_count": 50, + "bond_count": 49, + "total_charge": 0.0, + "expected_integer_charge": 0, + "head_atom": "C12", + "tail_atom": "C12", + "source_connect_indices": [ + 1, + 1 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "C13", + "head_reference_atom": "H2S", + "head_link_conditions": [ + { + "atoms": [ + "H2R", + "C12" + ], + "parameter_degrees": 109.5 + }, + { + "atoms": [ + "H2S", + "H2R", + "C12" + ], + "parameter_degrees": -120.0 + } + ], + "head_rule_source": "standard_chain", + "tail_next_atom": "C13", + "tail_reference_atom": "H2S", + "tail_link_conditions": [ + { + "atoms": [ + "H2R", + "C12" + ], + "parameter_degrees": 109.5 + }, + { + "atoms": [ + "H2S", + "H2R", + "C12" + ], + "parameter_degrees": 120.0 + } + ], + "tail_rule_source": "standard_chain" + }, + { + "template": "PA", + "source_unit_name": "PA", + "atom_count": 46, + "bond_count": 45, + "total_charge": 0.0, + "expected_integer_charge": 0, + "head_atom": "C12", + "tail_atom": "C12", + "source_connect_indices": [ + 44, + 44 + ], + "source_connectivity_flags": [ + 1 + ], + "head_next_atom": "C13", + "head_reference_atom": "H2S", + "head_link_conditions": [ + { + "atoms": [ + "H2R", + "C12" + ], + "parameter_degrees": 109.5 + }, + { + "atoms": [ + "H2S", + "H2R", + "C12" + ], + "parameter_degrees": -120.0 + } + ], + "head_rule_source": "standard_chain", + "tail_next_atom": "C13", + "tail_reference_atom": "H2S", + "tail_link_conditions": [ + { + "atoms": [ + "H2R", + "C12" + ], + "parameter_degrees": 109.5 + }, + { + "atoms": [ + "H2S", + "H2R", + "C12" + ], + "parameter_degrees": 120.0 + } + ], + "tail_rule_source": "standard_chain" + }, + { + "template": "PC", + "source_unit_name": "PC", + "atom_count": 38, + "bond_count": 37, + "total_charge": 0.0, + "expected_integer_charge": 0, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 37 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "PE", + "source_unit_name": "PE", + "atom_count": 29, + "bond_count": 28, + "total_charge": 0.0, + "expected_integer_charge": 0, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 28 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "PGR", + "source_unit_name": "PGR", + "atom_count": 31, + "bond_count": 30, + "total_charge": -1.0, + "expected_integer_charge": -1, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 28 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "PGS", + "source_unit_name": "PGS", + "atom_count": 31, + "bond_count": 30, + "total_charge": -1.0, + "expected_integer_charge": -1, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 28 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "PH-", + "source_unit_name": "PH-", + "atom_count": 20, + "bond_count": 19, + "total_charge": -1.0, + "expected_integer_charge": -1, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 19 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "PS", + "source_unit_name": "PS", + "atom_count": 31, + "bond_count": 30, + "total_charge": -1.0, + "expected_integer_charge": -1, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 28 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "SA", + "source_unit_name": "SA", + "atom_count": 44, + "bond_count": 43, + "total_charge": -0.069718, + "expected_integer_charge": 0, + "head_atom": "C12", + "tail_atom": "C12", + "source_connect_indices": [ + 43, + 43 + ], + "source_connectivity_flags": [ + 1 + ], + "head_next_atom": "C13", + "head_reference_atom": "H2R", + "head_link_conditions": [ + { + "atoms": [ + "H2R", + "C12" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "C13", + "H2R", + "C12" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "explicit_sa_alkene", + "tail_next_atom": "C13", + "tail_reference_atom": "H2R", + "tail_link_conditions": [ + { + "atoms": [ + "H2R", + "C12" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "C13", + "H2R", + "C12" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "explicit_sa_alkene" + }, + { + "template": "SPM", + "source_unit_name": "SPM", + "atom_count": 37, + "bond_count": 36, + "total_charge": 0.069721, + "expected_integer_charge": 0, + "head_atom": "C11", + "tail_atom": "C1", + "source_connect_indices": [ + 32, + 34 + ], + "source_connectivity_flags": [ + 1 + ], + "head_next_atom": "N11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "N11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "N11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "explicit_spm_amide", + "tail_next_atom": "C2", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "C2", + "C1" + ], + "parameter_degrees": 109.5 + }, + { + "atoms": [ + "O22", + "C2", + "C1" + ], + "parameter_degrees": -120.0 + } + ], + "tail_rule_source": "explicit_spm_sphingosine" + }, + { + "template": "ST", + "source_unit_name": "ST", + "atom_count": 52, + "bond_count": 51, + "total_charge": 0.0, + "expected_integer_charge": 0, + "head_atom": "C12", + "tail_atom": "C12", + "source_connect_indices": [ + 50, + 50 + ], + "source_connectivity_flags": [ + 1 + ], + "head_next_atom": "C13", + "head_reference_atom": "H2S", + "head_link_conditions": [ + { + "atoms": [ + "H2R", + "C12" + ], + "parameter_degrees": 109.5 + }, + { + "atoms": [ + "H2S", + "H2R", + "C12" + ], + "parameter_degrees": -120.0 + } + ], + "head_rule_source": "standard_chain", + "tail_next_atom": "C13", + "tail_reference_atom": "H2S", + "tail_link_conditions": [ + { + "atoms": [ + "H2R", + "C12" + ], + "parameter_degrees": 109.5 + }, + { + "atoms": [ + "H2S", + "H2R", + "C12" + ], + "parameter_degrees": 120.0 + } + ], + "tail_rule_source": "standard_chain" + } + ] +} diff --git a/src/Xponge/forcefield/amber/lipid_ext.mol2 b/src/Xponge/forcefield/amber/lipid_ext.mol2 new file mode 100644 index 0000000..21472f8 --- /dev/null +++ b/src/Xponge/forcefield/amber/lipid_ext.mol2 @@ -0,0 +1,3735 @@ +@MOLECULE +LIPID_EXT + 1842 1847 38 0 1 +SMALL +USER_CHARGES +@ATOM + 1 C11 -3.575000 3.389000 -1.076000 cC 1 2-A 0.756909 + 2 O12 -4.376000 4.166000 -0.626000 oC 1 2-A -0.710418 + 3 O11 -3.661000 2.086000 -0.861000 oS 1 2-A -0.422409 + 4 C1 -4.454000 1.634000 0.212000 cA 1 2-A 0.326256 + 5 HR -5.506000 1.755000 -0.026000 hE 1 2-A -0.052762 + 6 HS -4.230000 2.224000 1.091000 hE 1 2-A -0.052762 + 7 C2 -4.140000 0.180000 0.497000 cA 1 2-A 0.688729 + 8 HX -4.674000 -0.105000 1.392000 hE 1 2-A -0.102106 + 9 C3 -2.655000 -0.116000 0.678000 cA 1 2-A -0.137488 + 10 HA -2.205000 0.689000 1.247000 hE 1 2-A 0.079052 + 11 HB -2.158000 -0.142000 -0.278000 hE 1 2-A 0.079052 + 12 O31 -2.506000 -1.311000 1.393000 Os 1 2-A -0.397242 + 13 P31 -1.858000 -2.691000 0.766000 P 1 2-A 1.127927 + 14 O32 -0.661000 -2.133000 -0.136000 Os 1 2-A -0.510071 + 15 O33 -2.864000 -3.279000 -0.166000 O2 1 2-A -0.828835 + 16 O34 -1.405000 -3.472000 1.937000 O2 1 2-A -0.828835 + 17 O21 -4.631000 -0.628000 -0.587000 oS 1 2-A -0.532309 + 18 C21 -5.608000 -1.480000 -0.426000 cC 1 2-A 0.695200 + 19 O22 -6.448000 -1.421000 0.431000 oC 1 2-A -0.673764 + 20 C31 0.619000 -1.640000 0.272000 Cp 1 2-A 0.727620 + 21 H1A 0.819000 -1.993000 1.277000 H1 1 2-A -0.045238 + 22 C32 0.661000 -0.107000 0.232000 Cg 1 2-A -0.574564 + 23 H2A -0.006000 0.265000 0.997000 H1 1 2-A 0.272118 + 24 C33 2.049000 0.518000 0.565000 Cp 1 2-A 0.420177 + 25 H3A 2.250000 0.323000 1.617000 H1 1 2-A -0.026325 + 26 C34 3.229000 -0.115000 -0.243000 Cp 1 2-A 0.151615 + 27 H4A 3.195000 0.254000 -1.262000 H1 1 2-A 0.090332 + 28 C35 3.063000 -1.654000 -0.254000 Cg 1 2-A 0.193480 + 29 H5A 3.238000 -1.986000 0.773000 H1 1 2-A 0.024519 + 30 C36 1.680000 -2.167000 -0.666000 Cg 1 2-A -0.036389 + 31 H6A 1.453000 -1.820000 -1.669000 H1 1 2-A 0.106594 + 32 O35 0.213000 0.319000 -1.027000 Oh 1 2-A -0.648348 + 33 HO5A 0.026000 1.262000 -0.891000 Ho 1 2-A 0.478738 + 34 O36 1.985000 1.901000 0.348000 Os 1 2-A -0.590654 + 35 O37 4.452000 0.210000 0.361000 Os 1 2-A -0.583038 + 36 O38 3.952000 -2.322000 -1.110000 Oh 1 2-A -0.681488 + 37 HO8A 4.883000 -2.050000 -0.865000 Ho 1 2-A 0.417336 + 38 O39 1.727000 -3.586000 -0.669000 Oh 1 2-A -0.641153 + 39 HO9A 2.628000 -3.754000 -0.927000 Ho 1 2-A 0.354696 + 40 P36 0.800000 2.950000 0.955000 P 1 2-A 1.588284 + 41 O40 -0.372000 2.743000 -0.037000 O2 1 2-A -1.002944 + 42 O41 1.409000 4.317000 0.845000 O2 1 2-A -1.002944 + 43 O42 0.461000 2.464000 2.340000 O2 1 2-A -1.002944 + 44 P37 6.019000 0.050000 -0.296000 P 1 2-A 1.414391 + 45 O45 6.898000 0.705000 0.735000 O2 1 2-A -0.969332 + 46 O44 6.226000 -1.478000 -0.359000 O2 1 2-A -0.969332 + 47 O43 5.992000 0.681000 -1.662000 O2 1 2-A -0.969332 + 48 C11 4.664000 1.827000 -0.629000 cC 2 2-B 0.894683 + 49 O12 5.757000 2.328000 -0.694000 oC 2 2-B -0.793957 + 50 O11 4.583000 0.610000 -0.112000 oS 2 2-B -0.510163 + 51 C1 3.340000 -0.059000 0.171000 cA 2 2-B -0.052197 + 52 HR 2.647000 -0.009000 -0.649000 hE 2 2-B 0.127514 + 53 HS 2.886000 0.400000 1.036000 hE 2 2-B 0.127514 + 54 C2 3.705000 -1.507000 0.425000 cA 2 2-B 0.704616 + 55 HX 4.078000 -1.962000 -0.475000 hE 2 2-B -0.086048 + 56 C3 2.567000 -2.320000 1.050000 cA 2 2-B 0.235229 + 57 HA 2.536000 -2.066000 2.106000 hE 2 2-B -0.008563 + 58 HB 2.804000 -3.379000 0.969000 hE 2 2-B -0.008563 + 59 O31 1.329000 -2.032000 0.523000 Os 2 2-B -0.599287 + 60 P31 0.902000 -2.646000 -0.957000 P 2 2-B 1.371619 + 61 O32 -0.471000 -1.867000 -1.181000 Os 2 2-B -0.591001 + 62 O33 0.673000 -4.101000 -0.785000 O2 2 2-B -0.892155 + 63 O34 1.951000 -2.218000 -1.919000 O2 2 2-B -0.892155 + 64 O21 4.775000 -1.507000 1.423000 oS 2 2-B -0.587306 + 65 C21 6.025000 -1.686000 1.093000 cC 2 2-B 0.747308 + 66 O22 6.440000 -2.335000 0.180000 oC 2 2-B -0.689617 + 67 C31 -1.188000 -0.875000 -0.402000 Cp 2 2-B 0.309534 + 68 H1A -0.923000 -1.008000 0.638000 H1 2 2-B -0.039157 + 69 C32 -2.690000 -1.130000 -0.612000 Cg 2 2-B 0.350864 + 70 H2A -2.957000 -2.117000 -0.258000 H1 2 2-B -0.050751 + 71 C33 -3.576000 -0.120000 0.160000 Cp 2 2-B 0.251710 + 72 H3A -3.359000 -0.258000 1.219000 H1 2 2-B -0.039278 + 73 C34 -3.172000 1.325000 -0.216000 Cg 2 2-B 0.350031 + 74 H4A -3.477000 1.496000 -1.245000 H1 2 2-B -0.037727 + 75 C35 -1.658000 1.570000 -0.117000 Cp 2 2-B 0.251710 + 76 H5A -1.391000 1.529000 0.938000 H1 2 2-B -0.039278 + 77 C36 -0.809000 0.533000 -0.855000 Cg 2 2-B 0.350864 + 78 H6A -1.022000 0.611000 -1.917000 H1 2 2-B -0.050751 + 79 O35 -2.952000 -1.056000 -2.007000 Oh 2 2-B -0.722166 + 80 HO5A -2.348000 -1.672000 -2.400000 Ho 2 2-B 0.419095 + 81 O36 -4.920000 -0.330000 -0.084000 Os 2 2-B -0.657855 + 82 O37 -3.808000 2.267000 0.620000 Oh 2 2-B -0.687828 + 83 HO7A -3.188000 2.978000 0.768000 Ho 2 2-B 0.375863 + 84 O38 -1.364000 2.843000 -0.635000 Os 2 2-B -0.657855 + 85 O39 0.552000 0.785000 -0.638000 Oh 2 2-B -0.722166 + 86 HO9A 0.648000 1.664000 -0.234000 Ho 2 2-B 0.419095 + 87 P36 -5.879000 -1.506000 0.759000 P 2 2-B 1.447362 + 88 O40 -5.361000 -2.847000 0.282000 O2 2 2-B -0.961025 + 89 O41 -7.271000 -1.162000 0.273000 O2 2 2-B -0.961025 + 90 O42 -5.622000 -1.234000 2.225000 O2 2 2-B -0.961025 + 91 P38 -0.455000 3.982000 0.234000 P 2 2-B 1.447362 + 92 O46 -0.260000 5.103000 -0.750000 O2 2 2-B -0.961025 + 93 O47 -1.301000 4.296000 1.442000 O2 2 2-B -0.961025 + 94 O48 0.830000 3.207000 0.563000 O2 2 2-B -0.961025 + 95 C11 -3.719000 -2.117000 -0.381000 cC 3 2-C 0.830947 + 96 O12 -4.254000 -2.257000 0.683000 oC 3 2-C -0.757575 + 97 O11 -3.342000 -0.943000 -0.866000 oS 3 2-C -0.533136 + 98 C1 -2.497000 -0.070000 -0.101000 cA 3 2-C 0.212874 + 99 HR -1.955000 -0.660000 0.620000 hE 3 2-C 0.013743 + 100 HS -1.790000 0.323000 -0.812000 hE 3 2-C 0.013743 + 101 C2 -3.268000 1.071000 0.526000 cA 3 2-C 0.886634 + 102 HX -4.053000 1.424000 -0.123000 hE 3 2-C -0.148061 + 103 C3 -2.347000 2.227000 0.906000 cA 3 2-C 0.158232 + 104 HA -2.881000 2.895000 1.582000 hE 3 2-C -0.004206 + 105 HB -1.479000 1.852000 1.431000 hE 3 2-C -0.004206 + 106 O31 -2.003000 2.899000 -0.262000 Os 3 2-C -0.611068 + 107 P31 -0.488000 3.551000 -0.412000 P 3 2-C 1.253137 + 108 O32 0.301000 2.149000 -0.524000 Os 3 2-C -0.455451 + 109 O33 -0.502000 4.305000 -1.684000 O2 3 2-C -0.860713 + 110 O34 -0.187000 4.248000 0.862000 O2 3 2-C -0.860713 + 111 O21 -3.875000 0.602000 1.762000 oS 3 2-C -0.683923 + 112 C21 -5.153000 0.428000 1.907000 cC 3 2-C 0.775566 + 113 O22 -6.026000 0.878000 1.217000 oC 3 2-C -0.701626 + 114 C31 1.673000 1.978000 -0.902000 Cp 3 2-C 0.414469 + 115 H1A 2.093000 2.952000 -1.111000 H1 3 2-C -0.066520 + 116 C32 1.779000 1.113000 -2.147000 Cg 3 2-C 0.420038 + 117 H2A 2.842000 1.076000 -2.361000 H1 3 2-C -0.132114 + 118 C33 1.279000 -0.333000 -1.926000 Cg 3 2-C 0.324039 + 119 H3A 1.876000 -0.987000 -2.561000 H1 3 2-C -0.047108 + 120 C34 1.486000 -0.852000 -0.478000 Cp 3 2-C 0.030668 + 121 H4A 0.667000 -0.490000 0.136000 H1 3 2-C 0.062593 + 122 C35 2.813000 -0.206000 0.041000 Cp 3 2-C 0.343054 + 123 H5A 3.574000 -0.339000 -0.725000 H1 3 2-C -0.074266 + 124 C36 2.546000 1.332000 0.206000 Cg 3 2-C 0.622951 + 125 H6A 2.031000 1.456000 1.156000 H1 3 2-C -0.214566 + 126 O35 1.177000 1.681000 -3.287000 Oh 3 2-C -0.770849 + 127 HO5A 0.261000 1.439000 -3.214000 Ho 3 2-C 0.374038 + 128 O36 -0.057000 -0.388000 -2.354000 Oh 3 2-C -0.739050 + 129 HO6A -0.310000 -1.340000 -2.323000 Ho 3 2-C 0.380038 + 130 O37 1.476000 -2.252000 -0.481000 Os 3 2-C -0.466452 + 131 O38 3.294000 -0.725000 1.239000 Os 3 2-C -0.587516 + 132 O39 3.745000 2.058000 0.189000 Oh 3 2-C -0.954095 + 133 HO9A 4.362000 1.632000 0.832000 Ho 3 2-C 0.506810 + 134 P37 0.094000 -3.200000 -0.655000 P 3 2-C 1.285907 + 135 O43 -0.940000 -2.657000 0.313000 O2 3 2-C -0.916989 + 136 O44 0.563000 -4.604000 -0.416000 O2 3 2-C -0.916989 + 137 O45 -0.370000 -2.961000 -2.109000 O2 3 2-C -0.916989 + 138 P38 4.956000 -0.697000 1.672000 P 3 2-C 1.376591 + 139 O46 5.235000 0.794000 1.913000 O2 3 2-C -0.953964 + 140 O47 5.005000 -1.516000 2.933000 O2 3 2-C -0.953964 + 141 O48 5.712000 -1.242000 0.486000 O2 3 2-C -0.953964 + 142 C1 4.621000 -0.885000 -1.316000 cA 4 CAM -0.053421 + 143 H11 4.198000 -0.326000 -2.148000 hA 4 CAM 0.022613 + 144 H12 4.360000 -1.925000 -1.480000 hA 4 CAM 0.022613 + 145 C2 6.144000 -0.740000 -1.365000 cA 4 CAM -0.224512 + 146 H21 6.625000 -1.365000 -0.619000 hA 4 CAM 0.065295 + 147 H22 6.505000 -1.081000 -2.334000 hA 4 CAM 0.065295 + 148 C3 6.566000 0.705000 -1.132000 cA 4 CAM 0.469740 + 149 H31 6.165000 1.325000 -1.934000 hE 4 CAM -0.016690 + 150 C4 6.013000 1.205000 0.194000 cA 4 CAM -0.418389 + 151 H41 6.524000 0.662000 0.986000 hA 4 CAM 0.131060 + 152 H42 6.271000 2.250000 0.320000 hA 4 CAM 0.131060 + 153 C5 4.511000 1.021000 0.295000 cB 4 CAM 0.009022 + 154 C6 3.737000 2.041000 0.624000 cB 4 CAM -0.278205 + 155 H61 4.194000 3.003000 0.797000 hB 4 CAM 0.143219 + 156 C7 2.246000 1.984000 0.784000 cA 4 CAM -0.027352 + 157 H71 1.961000 2.531000 1.680000 hA 4 CAM 0.039581 + 158 H72 1.779000 2.516000 -0.046000 hA 4 CAM 0.039581 + 159 C8 1.708000 0.552000 0.850000 cA 4 CAM -0.010968 + 160 H81 1.923000 0.164000 1.842000 hA 4 CAM 0.047880 + 161 C9 2.417000 -0.320000 -0.209000 cA 4 CAM -0.177806 + 162 H91 2.263000 0.193000 -1.159000 hA 4 CAM 0.056181 + 163 C10 3.964000 -0.381000 -0.002000 cA 4 CAM 0.263540 + 164 C11 1.759000 -1.708000 -0.342000 cA 4 CAM -0.030334 + 165 H111 2.184000 -2.233000 -1.192000 hA 4 CAM 0.032108 + 166 H112 1.988000 -2.314000 0.526000 hA 4 CAM 0.032108 + 167 C12 0.233000 -1.665000 -0.522000 cA 4 CAM -0.193086 + 168 H121 -0.005000 -1.200000 -1.476000 hA 4 CAM 0.026882 + 169 H122 -0.130000 -2.685000 -0.577000 hA 4 CAM 0.026882 + 170 C13 -0.453000 -0.873000 0.603000 cA 4 CAM 0.408453 + 171 C14 0.197000 0.528000 0.610000 cA 4 CAM 0.032190 + 172 H141 0.052000 0.914000 -0.401000 hA 4 CAM 0.011682 + 173 C15 -0.706000 1.363000 1.519000 cA 4 CAM -0.138414 + 174 H151 -0.423000 1.247000 2.562000 hA 4 CAM 0.032497 + 175 H152 -0.648000 2.423000 1.297000 hA 4 CAM 0.032497 + 176 C16 -2.118000 0.790000 1.259000 cA 4 CAM -0.100670 + 177 H161 -2.603000 0.529000 2.195000 hA 4 CAM 0.034016 + 178 H162 -2.758000 1.521000 0.778000 hA 4 CAM 0.034016 + 179 C17 -1.943000 -0.472000 0.364000 cA 4 CAM -0.007233 + 180 H171 -2.000000 -0.153000 -0.675000 hA 4 CAM -0.031847 + 181 C18 -0.303000 -1.606000 1.951000 cA 4 CAM -0.445030 + 182 H181 -0.829000 -1.099000 2.751000 hA 4 CAM 0.092728 + 183 H182 0.730000 -1.700000 2.258000 hA 4 CAM 0.092728 + 184 H183 -0.708000 -2.611000 1.887000 hA 4 CAM 0.092728 + 185 C19 4.352000 -1.322000 1.162000 cA 4 CAM -0.260635 + 186 H191 5.405000 -1.246000 1.401000 hA 4 CAM 0.065558 + 187 H192 4.156000 -2.359000 0.917000 hA 4 CAM 0.065558 + 188 H193 3.803000 -1.077000 2.064000 hA 4 CAM 0.065558 + 189 C20 -3.061000 -1.514000 0.589000 cD 4 CAM 0.200662 + 190 H201 -3.009000 -1.830000 1.629000 hL 4 CAM -0.040076 + 191 C21 -2.909000 -2.777000 -0.272000 cD 4 CAM -0.329365 + 192 H211 -3.805000 -3.388000 -0.213000 hL 4 CAM 0.073634 + 193 H212 -2.083000 -3.393000 0.062000 hL 4 CAM 0.073634 + 194 H213 -2.739000 -2.543000 -1.317000 hL 4 CAM 0.073634 + 195 C22 -4.480000 -0.914000 0.422000 cD 4 CAM -0.025832 + 196 H221 -5.183000 -1.694000 0.699000 hL 4 CAM 0.001234 + 197 H222 -4.616000 -0.124000 1.149000 hL 4 CAM 0.001234 + 198 C23 -4.825000 -0.405000 -0.986000 cD 4 CAM -0.080198 + 199 H231 -4.136000 0.383000 -1.271000 hL 4 CAM 0.016898 + 200 H232 -4.649000 -1.214000 -1.690000 hL 4 CAM 0.016898 + 201 C24 -6.273000 0.084000 -1.241000 cD 4 CAM 0.107119 + 202 C25 -6.625000 1.528000 -0.795000 cD 4 CAM 0.343199 + 203 H251 -7.665000 1.664000 -1.091000 hL 4 CAM -0.060375 + 204 C26 -5.815000 2.583000 -1.558000 cD 4 CAM -0.335745 + 205 H261 -4.767000 2.574000 -1.274000 hL 4 CAM 0.069883 + 206 H262 -6.195000 3.579000 -1.350000 hL 4 CAM 0.069883 + 207 H263 -5.870000 2.427000 -2.632000 hL 4 CAM 0.069883 + 208 C27 -6.559000 1.811000 0.711000 cD 4 CAM -0.335745 + 209 H271 -5.536000 1.858000 1.068000 hL 4 CAM 0.069883 + 210 H272 -7.085000 1.065000 1.297000 hL 4 CAM 0.069883 + 211 H273 -7.017000 2.773000 0.925000 hL 4 CAM 0.069883 + 212 O1 7.965000 0.845000 -1.079000 oH 4 CAM -0.645437 + 213 HO1 8.341000 0.579000 -1.907000 hO 4 CAM 0.360586 + 214 C28 -7.321000 -0.930000 -0.763000 cD 4 CAM -0.266484 + 215 H241 -6.365000 0.111000 -2.325000 hL 4 CAM -0.021943 + 216 H281 -7.374000 -1.003000 0.317000 hL 4 CAM 0.060964 + 217 H282 -7.100000 -1.923000 -1.146000 hL 4 CAM 0.060964 + 218 H283 -8.309000 -0.654000 -1.121000 hL 4 CAM 0.060964 + 219 C11A -7.763000 -0.132000 -1.263000 cC 5 CLI 0.751826 + 220 O12A -8.882000 -0.324000 -0.902000 oC 5 CLI -0.625807 + 221 O11A -7.036000 0.778000 -0.626000 oS 5 CLI -0.425552 + 222 C1A -5.768000 1.228000 -1.071000 cA 5 CLI 0.059653 + 223 HRA -5.937000 2.046000 -1.759000 hE 5 CLI 0.062481 + 224 HSA -5.213000 0.441000 -1.552000 hE 5 CLI 0.062481 + 225 C2A -4.950000 1.726000 0.135000 cA 5 CLI 0.384033 + 226 HXA -4.425000 2.627000 -0.159000 hE 5 CLI 0.022742 + 227 C3A -3.916000 0.723000 0.619000 cA 5 CLI 0.195132 + 228 HAA -3.471000 1.100000 1.534000 hE 5 CLI 0.001849 + 229 HBA -3.138000 0.671000 -0.132000 hE 5 CLI 0.001849 + 230 O31A -4.470000 -0.530000 0.866000 oT 5 CLI -0.526260 + 231 P31A -3.971000 -1.824000 0.005000 pA 5 CLI 1.303462 + 232 O32A -2.406000 -1.785000 0.451000 oT 5 CLI -0.519148 + 233 C31A -1.533000 -2.746000 -0.091000 cA 5 CLI 0.235650 + 234 H1AA -1.770000 -2.901000 -1.140000 hE 5 CLI -0.021290 + 235 H1BA -1.659000 -3.686000 0.433000 hE 5 CLI -0.021290 + 236 CC -0.080000 -2.311000 0.019000 cA 5 CLI 0.496492 + 237 HC 0.142966 -2.265557 1.087900 hE 5 CLI -0.112424 + 238 OC 0.732863 -3.264348 -0.597304 oH 5 CLI -0.728180 + 239 C31B 0.136000 -0.903000 -0.533000 cA 5 CLI 0.235650 + 240 O33A -4.046000 -1.464000 -1.437000 oP 5 CLI -0.799997 + 241 O34A -4.649000 -3.014000 0.530000 oP 5 CLI -0.799997 + 242 O21A -5.792000 2.049000 1.237000 oS 5 CLI -0.481795 + 243 C21A -6.623000 3.063000 1.134000 cC 5 CLI 0.738671 + 244 O22A -6.603000 3.871000 0.256000 oC 5 CLI -0.628388 + 245 O12B 9.476000 2.658000 -1.346000 oC 5 CLI -0.625807 + 246 C11B 9.486000 1.636000 -0.735000 cC 5 CLI 0.751826 + 247 HRB 7.251000 1.450000 -2.108000 hE 5 CLI 0.062481 + 248 O11B 8.431000 0.879000 -0.530000 oS 5 CLI -0.425552 + 249 C1B 7.172000 1.313000 -1.037000 cA 5 CLI 0.059653 + 250 O34B 2.711000 -0.952000 1.205000 oP 5 CLI -0.799997 + 251 HSB 6.916000 2.260000 -0.584000 hE 5 CLI 0.062481 + 252 O31B 3.873000 -0.399000 -0.975000 oT 5 CLI -0.526260 + 253 P31B 2.595000 -0.084000 0.004000 pA 5 CLI 1.303462 + 254 C2B 6.142000 0.256000 -0.701000 cA 5 CLI 0.384033 + 255 HXB 6.481000 -0.711000 -1.038000 hE 5 CLI 0.022742 + 256 C3B 4.787000 0.585000 -1.320000 cA 5 CLI 0.195132 + 257 O32B 1.470000 -0.708000 -0.970000 oT 5 CLI -0.519148 + 258 O21B 5.977000 0.224000 0.704000 oS 5 CLI -0.481795 + 259 HAB 4.896000 0.601000 -2.403000 hE 5 CLI 0.001849 + 260 O33B 2.452000 1.376000 0.135000 oP 5 CLI -0.799997 + 261 C21B 6.206000 -0.901000 1.379000 cC 5 CLI 0.738671 + 262 HBB 4.448000 1.557000 -0.992000 hE 5 CLI 0.001849 + 263 O22B 6.812000 -1.827000 0.940000 oC 5 CLI -0.628388 + 264 H1BB -0.141000 -0.170000 0.214000 hE 5 CLI -0.021290 + 265 H1AB -0.489000 -0.749000 -1.403000 hE 5 CLI -0.021290 + 266 HOC 0.417952 -4.138179 -0.355706 hO 5 CLI 0.403503 + 267 C1 4.962000 -1.432000 -0.579000 cA 6 ERG -0.081575 + 268 H11 4.782000 -1.269000 -1.640000 hA 6 ERG 0.017152 + 269 H12 4.620000 -2.438000 -0.361000 hA 6 ERG 0.017152 + 270 C2 6.472000 -1.354000 -0.343000 cA 6 ERG -0.067913 + 271 H21 6.729000 -1.615000 0.679000 hA 6 ERG 0.025347 + 272 H22 6.982000 -2.065000 -0.985000 hA 6 ERG 0.025347 + 273 C3 6.989000 0.044000 -0.630000 cA 6 ERG 0.402468 + 274 H31 6.786000 0.288000 -1.673000 hE 6 ERG -0.000875 + 275 C4 6.279000 1.052000 0.269000 cA 6 ERG -0.307461 + 276 H41 6.603000 0.849000 1.288000 hA 6 ERG 0.103527 + 277 H42 6.605000 2.063000 0.036000 hA 6 ERG 0.103527 + 278 C5 4.770000 0.975000 0.171000 cB 6 ERG -0.042856 + 279 C6 4.026000 2.071000 0.184000 cE 6 ERG -0.211774 + 280 H61 4.496000 3.040000 0.218000 hB 6 ERG 0.149787 + 281 C7 2.561000 2.033000 0.147000 cE 6 ERG -0.118285 + 282 C8 1.905000 0.912000 -0.116000 cB 6 ERG -0.188495 + 283 C9 2.678000 -0.374000 -0.397000 cA 6 ERG -0.003546 + 284 H91 2.816000 -0.368000 -1.481000 hA 6 ERG 0.049541 + 285 C10 4.117000 -0.411000 0.226000 cA 6 ERG 0.219962 + 286 C11 1.859000 -1.646000 -0.069000 cA 6 ERG -0.039219 + 287 H111 2.318000 -2.498000 -0.559000 hA 6 ERG 0.031400 + 288 H112 1.904000 -1.851000 0.992000 hA 6 ERG 0.031400 + 289 C12 0.377000 -1.593000 -0.471000 cA 6 ERG -0.174576 + 290 H121 0.288000 -1.569000 -1.555000 hA 6 ERG 0.023903 + 291 H122 -0.089000 -2.515000 -0.144000 hA 6 ERG 0.023903 + 292 C13 -0.325000 -0.357000 0.111000 cA 6 ERG 0.390222 + 293 C14 0.435000 0.867000 -0.444000 cA 6 ERG 0.064254 + 294 H141 0.400000 0.743000 -1.530000 hA 6 ERG 0.035094 + 295 C15 -0.460000 2.063000 -0.124000 cA 6 ERG -0.166853 + 296 H151 -0.280000 2.423000 0.883000 hA 6 ERG 0.054918 + 297 H152 -0.284000 2.897000 -0.795000 hA 6 ERG 0.054918 + 298 C16 -1.890000 1.490000 -0.262000 cA 6 ERG -0.100674 + 299 H161 -2.486000 1.727000 0.613000 hA 6 ERG 0.037844 + 300 H162 -2.413000 1.921000 -1.107000 hA 6 ERG 0.037844 + 301 C17 -1.754000 -0.053000 -0.429000 cA 6 ERG -0.044635 + 302 H171 -1.732000 -0.274000 -1.496000 hA 6 ERG -0.003299 + 303 C18 -0.313000 -0.382000 1.651000 cA 6 ERG -0.445797 + 304 H181 -0.710000 -1.319000 2.029000 hA 6 ERG 0.102795 + 305 H182 -0.915000 0.415000 2.071000 hA 6 ERG 0.102795 + 306 H183 0.687000 -0.266000 2.050000 hA 6 ERG 0.102795 + 307 C19 4.095000 -0.810000 1.722000 cA 6 ERG -0.277311 + 308 H191 5.067000 -0.663000 2.176000 hA 6 ERG 0.074538 + 309 H192 3.834000 -1.853000 1.863000 hA 6 ERG 0.074538 + 310 H193 3.386000 -0.200000 2.271000 hA 6 ERG 0.074538 + 311 C20 -2.971000 -0.805000 0.158000 cD 6 ERG 0.230774 + 312 H201 -3.043000 -0.576000 1.218000 hL 6 ERG -0.021041 + 313 C21 -2.894000 -2.333000 0.007000 cD 6 ERG -0.311021 + 314 H211 -2.093000 -2.761000 0.595000 hL 6 ERG 0.072874 + 315 H212 -2.738000 -2.620000 -1.030000 hL 6 ERG 0.072874 + 316 H213 -3.822000 -2.787000 0.338000 hL 6 ERG 0.072874 + 317 C22 -4.240000 -0.317000 -0.506000 cB 6 ERG -0.222886 + 318 C23 -5.274000 0.227000 0.111000 cB 6 ERG -0.227366 + 319 C24 -6.536000 0.716000 -0.563000 cD 6 ERG 0.116726 + 320 C25 -7.799000 -0.100000 -0.183000 cD 6 ERG 0.340891 + 321 H251 -8.635000 0.394000 -0.676000 hL 6 ERG -0.058856 + 322 C26 -8.106000 -0.122000 1.320000 cD 6 ERG -0.326183 + 323 H261 -7.341000 -0.655000 1.875000 hL 6 ERG 0.069369 + 324 H262 -9.049000 -0.628000 1.502000 hL 6 ERG 0.069369 + 325 H263 -8.189000 0.876000 1.737000 hL 6 ERG 0.069369 + 326 C27 -7.731000 -1.529000 -0.730000 cD 6 ERG -0.326183 + 327 H271 -6.924000 -2.091000 -0.270000 hL 6 ERG 0.069369 + 328 H272 -7.567000 -1.533000 -1.804000 hL 6 ERG 0.069369 + 329 H273 -8.658000 -2.061000 -0.536000 hL 6 ERG 0.069369 + 330 O1 8.377000 0.043000 -0.405000 oH 6 ERG -0.652907 + 331 HO1 8.734000 0.895000 -0.619000 hO 6 ERG 0.366836 + 332 H71 2.036000 2.965000 0.261000 hB 6 ERG 0.117272 + 333 H221 -4.284000 -0.454000 -1.578000 hB 6 ERG 0.120810 + 334 H231 -5.223000 0.369000 1.181000 hB 6 ERG 0.134174 + 335 C28 -6.720000 2.219000 -0.304000 cD 6 ERG -0.270112 + 336 H241 -6.404000 0.590000 -1.635000 hL 6 ERG -0.000617 + 337 H281 -6.778000 2.445000 0.756000 hL 6 ERG 0.066164 + 338 H282 -7.628000 2.588000 -0.773000 hL 6 ERG 0.066164 + 339 H283 -5.882000 2.778000 -0.710000 hL 6 ERG 0.066164 + 340 C11 7.355000 1.664000 -0.214000 cC 7 H2A 0.762061 + 341 O12 8.107000 1.213000 -1.022000 oC 7 H2A -0.651078 + 342 O11 6.260000 1.080000 0.207000 oS 7 H2A -0.445956 + 343 C1 5.852000 -0.152000 -0.402000 cA 7 H2A 0.194712 + 344 HR 6.656000 -0.866000 -0.322000 hE 7 H2A 0.023328 + 345 HS 5.624000 0.027000 -1.441000 hE 7 H2A 0.023328 + 346 C2 4.604000 -0.641000 0.300000 cA 7 H2A 0.550570 + 347 HX 4.298000 -1.569000 -0.156000 hE 7 H2A 0.003879 + 348 C3 3.443000 0.348000 0.256000 cA 7 H2A 0.207087 + 349 HA 3.720000 1.252000 0.783000 hE 7 H2A 0.020165 + 350 HB 2.591000 -0.087000 0.759000 hE 7 H2A 0.020165 + 351 O31 3.152000 0.686000 -1.063000 Os 7 H2A -0.629179 + 352 P31 2.015000 -0.183000 -1.870000 P 7 H2A 1.306995 + 353 O32 0.760000 0.243000 -0.952000 Os 7 H2A -0.495977 + 354 O33 2.319000 -1.620000 -1.616000 O2 7 H2A -0.809627 + 355 O34 1.927000 0.360000 -3.231000 O2 7 H2A -0.809627 + 356 O21 4.856000 -0.855000 1.703000 oS 7 H2A -0.638201 + 357 C21 5.492000 -1.925000 2.116000 cC 7 H2A 0.760014 + 358 O22 6.029000 -2.725000 1.414000 oC 7 H2A -0.637626 + 359 C31 -0.598000 -0.084000 -1.190000 Cp 7 H2A 0.144445 + 360 H1A -0.777000 -0.081000 -2.260000 H1 7 H2A 0.048090 + 361 C32 -1.464000 0.985000 -0.531000 Cg 7 H2A 0.305989 + 362 H2A -1.223000 1.944000 -0.983000 H1 7 H2A -0.009978 + 363 C33 -2.948000 0.690000 -0.806000 Cp 7 H2A 0.284594 + 364 H3A -3.048000 0.559000 -1.879000 H1 7 H2A 0.001579 + 365 C34 -3.402000 -0.647000 -0.181000 Cp 7 H2A 0.066970 + 366 H4A -3.338000 -0.628000 0.897000 H1 7 H2A 0.101395 + 367 C35 -2.465000 -1.727000 -0.756000 Cg 7 H2A 0.175272 + 368 H5A -2.684000 -1.782000 -1.823000 H1 7 H2A 0.032149 + 369 C36 -0.965000 -1.447000 -0.617000 Cg 7 H2A 0.188734 + 370 H6A -0.700000 -1.456000 0.434000 H1 7 H2A 0.051149 + 371 O35 -1.149000 1.000000 0.833000 Oh 7 H2A -0.676563 + 372 HO5A -1.626000 1.717000 1.238000 Ho 7 H2A 0.399788 + 373 O36 -3.749000 1.827000 -0.564000 Os 7 H2A -0.567002 + 374 O37 -4.698000 -0.925000 -0.608000 Os 7 H2A -0.502226 + 375 O38 -2.731000 -2.970000 -0.170000 Oh 7 H2A -0.645221 + 376 HO8A -2.085000 -3.564000 -0.527000 Ho 7 H2A 0.399462 + 377 O39 -0.323000 -2.507000 -1.290000 Oh 7 H2A -0.685723 + 378 HO9A 0.625000 -2.359000 -1.318000 Ho 7 H2A 0.416422 + 379 P36 -4.214000 2.666000 0.731000 P 7 H2A 1.324171 + 380 O40 -2.753000 3.220000 1.302000 oH 7 H2A -0.721399 + 381 O41 -4.743000 1.787000 1.788000 O2 7 H2A -0.818967 + 382 O42 -4.911000 3.862000 0.222000 O2 7 H2A -0.818967 + 383 P37 -5.932000 -1.460000 0.364000 P 7 H2A 1.359860 + 384 O43 -6.664000 -0.058000 0.706000 oH 7 H2A -0.759790 + 385 O44 -6.842000 -2.231000 -0.507000 O2 7 H2A -0.845321 + 386 O45 -5.349000 -2.023000 1.600000 O2 7 H2A -0.845321 + 387 HOP6 -2.530000 3.988000 0.793000 hO 7 H2A 0.396559 + 388 HOP7 -6.075000 0.526000 1.188000 hO 7 H2A 0.444819 + 389 C11 5.117000 1.598000 -0.769000 cC 8 H2B 0.745850 + 390 O12 6.314000 1.634000 -0.759000 oC 8 H2B -0.648756 + 391 O11 4.433000 0.481000 -0.589000 oS 8 H2B -0.469375 + 392 C1 5.125000 -0.714000 -0.299000 cA 8 H2B 0.289479 + 393 HR 5.792000 -0.555000 0.538000 hE 8 H2B -0.019218 + 394 HS 5.718000 -1.011000 -1.156000 hE 8 H2B -0.019218 + 395 C2 4.102000 -1.783000 0.036000 cA 8 H2B 0.593490 + 396 HX 4.623000 -2.703000 0.264000 hE 8 H2B -0.132437 + 397 C3 3.095000 -2.012000 -1.081000 cA 8 H2B 0.281008 + 398 HA 3.625000 -2.327000 -1.979000 hE 8 H2B -0.053053 + 399 HB 2.590000 -1.085000 -1.300000 hE 8 H2B -0.053053 + 400 O31 2.199000 -3.002000 -0.682000 Os 8 H2B -0.529853 + 401 P31 0.635000 -2.837000 -1.154000 P 8 H2B 1.170094 + 402 O32 0.277000 -1.576000 -0.190000 Os 8 H2B -0.558200 + 403 O33 -0.097000 -4.023000 -0.665000 O2 8 H2B -0.759801 + 404 O34 0.616000 -2.415000 -2.566000 O2 8 H2B -0.759801 + 405 O21 3.392000 -1.355000 1.182000 oS 8 H2B -0.508422 + 406 C21 3.193000 -2.178000 2.200000 cC 8 H2B 0.680241 + 407 O22 3.871000 -3.134000 2.430000 oC 8 H2B -0.603818 + 408 C31 -0.739000 -0.635000 -0.458000 Cp 8 H2B 0.337572 + 409 H1A -0.895000 -0.579000 -1.530000 H1 8 H2B -0.039689 + 410 C32 -0.281000 0.731000 0.037000 Cg 8 H2B 0.194288 + 411 H2A 0.633000 0.987000 -0.484000 H1 8 H2B 0.077355 + 412 C33 -1.338000 1.801000 -0.270000 Cp 8 H2B 0.108412 + 413 H3A -1.418000 1.923000 -1.344000 H1 8 H2B 0.029228 + 414 C34 -2.713000 1.426000 0.296000 Cg 8 H2B 0.489731 + 415 H4A -2.652000 1.508000 1.377000 H1 8 H2B -0.046081 + 416 C35 -3.124000 -0.014000 -0.036000 Cp 8 H2B 0.108412 + 417 H5A -3.386000 -0.057000 -1.088000 H1 8 H2B 0.029228 + 418 C36 -2.040000 -1.056000 0.216000 Cg 8 H2B 0.194288 + 419 H6A -1.871000 -1.140000 1.284000 H1 8 H2B 0.077355 + 420 O35 -0.033000 0.667000 1.421000 Oh 8 H2B -0.624630 + 421 HO5A 0.411000 1.477000 1.646000 Ho 8 H2B 0.389674 + 422 O36 -0.982000 3.043000 0.310000 Os 8 H2B -0.578798 + 423 O37 -3.665000 2.322000 -0.205000 Oh 8 H2B -0.694473 + 424 HO7A -4.513000 1.882000 -0.300000 Ho 8 H2B 0.400954 + 425 O38 -4.241000 -0.352000 0.758000 Os 8 H2B -0.578798 + 426 O39 -2.516000 -2.278000 -0.294000 Oh 8 H2B -0.624630 + 427 HO9A -1.837000 -2.951000 -0.247000 Ho 8 H2B 0.389674 + 428 P36 0.348000 3.904000 -0.005000 P 8 H2B 1.348962 + 429 O40 1.352000 3.304000 1.185000 oH 8 H2B -0.717514 + 430 O41 0.098000 5.302000 0.392000 O2 8 H2B -0.832452 + 431 O42 0.931000 3.509000 -1.300000 O2 8 H2B -0.832452 + 432 P38 -5.735000 -0.523000 0.119000 P 8 H2B 1.348962 + 433 O46 -5.437000 -1.740000 -0.923000 oH 8 H2B -0.717514 + 434 O47 -6.627000 -0.971000 1.203000 O2 8 H2B -0.832452 + 435 O48 -6.034000 0.699000 -0.671000 O2 8 H2B -0.832452 + 436 HOP8 -4.633000 -2.194000 -0.686000 hO 8 H2B 0.391342 + 437 HOP6 1.270000 3.906000 1.912000 hO 8 H2B 0.391342 + 438 C11 -3.889000 1.445000 1.669000 cC 9 H2C 0.722526 + 439 O12 -4.860000 2.139000 1.797000 oC 9 H2C -0.630666 + 440 O11 -3.931000 0.151000 1.439000 oS 9 H2C -0.381066 + 441 C1 -5.150000 -0.510000 1.191000 cA 9 H2C -0.051403 + 442 HR -5.942000 -0.035000 1.753000 hE 9 H2C 0.088559 + 443 HS -4.994000 -1.523000 1.525000 hE 9 H2C 0.088559 + 444 C2 -5.515000 -0.538000 -0.289000 cA 9 H2C 0.504488 + 445 HX -6.529000 -0.910000 -0.364000 hE 9 H2C -0.026001 + 446 C3 -4.636000 -1.415000 -1.171000 cA 9 H2C 0.152138 + 447 HA -4.945000 -1.249000 -2.201000 hE 9 H2C 0.002847 + 448 HB -4.862000 -2.443000 -0.915000 hE 9 H2C 0.002847 + 449 O31 -3.286000 -1.145000 -1.052000 Os 9 H2C -0.500293 + 450 P31 -2.305000 -2.322000 -0.433000 P 9 H2C 1.172112 + 451 O32 -1.146000 -1.374000 0.172000 Os 9 H2C -0.390615 + 452 O33 -1.781000 -3.131000 -1.549000 O2 9 H2C -0.780357 + 453 O34 -3.048000 -2.942000 0.688000 O2 9 H2C -0.780357 + 454 O21 -5.494000 0.793000 -0.815000 oS 9 H2C -0.439175 + 455 C21 -6.564000 1.551000 -0.733000 cC 9 H2C 0.663588 + 456 O22 -7.642000 1.180000 -0.373000 oC 9 H2C -0.622461 + 457 C31 0.156000 -1.249000 -0.379000 Cp 9 H2C -0.032698 + 458 H1A 0.105000 -1.320000 -1.457000 H1 9 H2C 0.129056 + 459 C32 1.022000 -2.387000 0.185000 Cg 9 H2C 0.271556 + 460 H2A 0.653000 -3.324000 -0.219000 H1 9 H2C 0.032172 + 461 C33 2.468000 -2.208000 -0.244000 Cg 9 H2C 0.108646 + 462 H3A 2.467000 -2.219000 -1.336000 H1 9 H2C 0.047765 + 463 C34 3.070000 -0.869000 0.174000 Cp 9 H2C 0.078396 + 464 H4A 3.154000 -0.810000 1.251000 H1 9 H2C 0.137563 + 465 C35 2.203000 0.278000 -0.336000 Cp 9 H2C 0.164795 + 466 H5A 2.305000 0.331000 -1.414000 H1 9 H2C 0.056283 + 467 C36 0.724000 0.104000 0.034000 Cg 9 H2C 0.201170 + 468 H6A 0.649000 0.178000 1.115000 H1 9 H2C 0.079130 + 469 O35 0.925000 -2.407000 1.589000 Oh 9 H2C -0.699246 + 470 HO5A 0.001000 -2.322000 1.791000 Ho 9 H2C 0.426292 + 471 O36 3.253000 -3.271000 0.223000 Oh 9 H2C -0.598187 + 472 HO6A 4.161000 -2.988000 0.148000 Ho 9 H2C 0.360649 + 473 O37 4.334000 -0.853000 -0.431000 Os 9 H2C -0.524462 + 474 O38 2.567000 1.495000 0.249000 Os 9 H2C -0.531288 + 475 O39 -0.017000 1.140000 -0.562000 Oh 9 H2C -0.653343 + 476 HO9A 0.503000 1.935000 -0.474000 Ho 9 H2C 0.401494 + 477 P37 5.762000 -0.370000 0.219000 P 9 H2C 1.335481 + 478 O44 6.593000 -1.575000 0.427000 O2 9 H2C -0.815044 + 479 O43 6.313000 0.450000 -1.044000 oH 9 H2C -0.762271 + 480 O45 5.476000 0.553000 1.352000 O2 9 H2C -0.815044 + 481 P38 3.395000 2.729000 -0.449000 P 9 H2C 1.398822 + 482 O47 2.446000 3.843000 -0.659000 O2 9 H2C -0.842687 + 483 O48 4.175000 2.182000 -1.594000 O2 9 H2C -0.842687 + 484 O46 4.366000 3.068000 0.780000 oH 9 H2C -0.749404 + 485 HOP7 5.648000 1.099000 -1.318000 hO 9 H2C 0.437834 + 486 HOP8 4.818000 2.258000 1.061000 hO 9 H2C 0.403988 + 487 C11 5.795000 -1.195000 1.390000 cC 10 P2A 0.812147 + 488 O12 6.735000 -1.567000 0.753000 oC 10 P2A -0.702541 + 489 O11 5.043000 -0.165000 1.052000 oS 10 P2A -0.431899 + 490 C1 5.044000 0.264000 -0.299000 cA 10 P2A -0.055051 + 491 HR 5.009000 -0.590000 -0.955000 hE 10 P2A 0.079611 + 492 HS 5.939000 0.841000 -0.501000 hE 10 P2A 0.079611 + 493 C2 3.806000 1.126000 -0.481000 cA 10 P2A 0.528520 + 494 HX 3.818000 1.915000 0.260000 hE 10 P2A -0.007800 + 495 C3 2.504000 0.358000 -0.309000 cA 10 P2A 0.128278 + 496 HA 1.680000 1.004000 -0.572000 hE 10 P2A 0.012840 + 497 HB 2.415000 0.123000 0.742000 hE 10 P2A 0.012840 + 498 O31 2.464000 -0.798000 -1.096000 Os 10 P2A -0.511109 + 499 P31 2.081000 -2.248000 -0.425000 P 10 P2A 1.256118 + 500 O32 0.712000 -1.899000 0.359000 Os 10 P2A -0.405218 + 501 O33 3.091000 -2.558000 0.616000 O2 10 P2A -0.824901 + 502 O34 1.809000 -3.167000 -1.546000 O2 10 P2A -0.824901 + 503 O21 3.969000 1.686000 -1.782000 oS 10 P2A -0.465132 + 504 C21 3.529000 2.884000 -2.158000 cC 10 P2A 0.700521 + 505 O22 3.840000 3.279000 -3.242000 oC 10 P2A -0.647372 + 506 C31 -0.463000 -1.319000 -0.235000 Cp 10 P2A 0.119208 + 507 H1A -0.227000 -0.983000 -1.238000 H1 10 P2A 0.045113 + 508 C32 -0.827000 -0.126000 0.623000 Cg 10 P2A 0.065307 + 509 H2A -0.013000 0.573000 0.584000 H1 10 P2A 0.014445 + 510 C33 -2.081000 0.579000 0.114000 Cp 10 P2A 0.898544 + 511 H3A -1.884000 0.876000 -0.911000 H1 10 P2A -0.167481 + 512 C34 -3.303000 -0.371000 0.101000 Cp 10 P2A 0.333334 + 513 H4A -3.565000 -0.634000 1.120000 H1 10 P2A -0.064227 + 514 C35 -2.924000 -1.673000 -0.685000 Cg 10 P2A 0.327056 + 515 H5A -2.834000 -1.371000 -1.733000 H1 10 P2A -0.054116 + 516 C36 -1.590000 -2.333000 -0.298000 Cg 10 P2A 0.092467 + 517 H6A -1.711000 -2.773000 0.688000 H1 10 P2A 0.030424 + 518 O35 -0.994000 -0.530000 1.977000 Oh 10 P2A -0.764615 + 519 HO5A -0.263000 -1.104000 2.167000 Ho 10 P2A 0.427594 + 520 O36 -2.301000 1.758000 0.873000 Os 10 P2A -0.703320 + 521 O37 -4.359000 0.253000 -0.538000 Os 10 P2A -0.678037 + 522 O38 -3.888000 -2.660000 -0.542000 Oh 10 P2A -0.731915 + 523 HO8A -4.783000 -2.267000 -0.706000 Ho 10 P2A 0.419801 + 524 O39 -1.275000 -3.348000 -1.229000 Oh 10 P2A -0.622424 + 525 HO9A -0.329000 -3.443000 -1.283000 Ho 10 P2A 0.364099 + 526 P36 -1.186000 2.914000 1.109000 P 10 P2A 1.408463 + 527 O41 -0.132000 2.830000 0.051000 O2 10 P2A -0.886444 + 528 O40 -0.537000 2.351000 2.513000 oH 10 P2A -0.769430 + 529 O42 -1.826000 4.209000 1.396000 O2 10 P2A -0.886444 + 530 P37 -6.024000 -0.126000 -0.247000 P 10 P2A 1.290619 + 531 O43 -6.169000 -1.487000 -0.935000 O2 10 P2A -0.893617 + 532 O44 -6.153000 -0.196000 1.248000 O2 10 P2A -0.893617 + 533 O45 -6.741000 0.987000 -0.951000 O2 10 P2A -0.893617 + 534 HOP6 -0.797000 1.443000 2.633000 hO 10 P2A 0.438268 + 535 C11 7.060000 2.352000 -1.144000 cC 11 P2B 0.730545 + 536 O12 6.605000 3.225000 -1.819000 oC 11 P2B -0.660901 + 537 O11 6.414000 1.302000 -0.716000 oS 11 P2B -0.441474 + 538 C1 5.016000 1.147000 -1.017000 cA 11 P2B 0.192044 + 539 HR 4.877000 1.254000 -2.083000 hE 11 P2B 0.042436 + 540 HS 4.465000 1.924000 -0.508000 hE 11 P2B 0.042436 + 541 C2 4.593000 -0.227000 -0.546000 cA 11 P2B 0.410087 + 542 HX 5.244000 -0.976000 -0.970000 hE 11 P2B -0.010511 + 543 C3 3.137000 -0.529000 -0.891000 cA 11 P2B 0.353759 + 544 HA 2.993000 -0.362000 -1.959000 hE 11 P2B -0.054166 + 545 HB 2.490000 0.149000 -0.357000 hE 11 P2B -0.054166 + 546 O31 2.879000 -1.855000 -0.593000 Os 11 P2B -0.567863 + 547 P31 1.596000 -2.285000 0.384000 P 11 P2B 1.155359 + 548 O32 0.426000 -1.516000 -0.412000 Os 11 P2B -0.421547 + 549 O33 1.823000 -1.659000 1.711000 O2 11 P2B -0.796243 + 550 O34 1.522000 -3.746000 0.241000 O2 11 P2B -0.796243 + 551 O21 4.724000 -0.246000 0.868000 oS 11 P2B -0.512011 + 552 C21 5.304000 -1.265000 1.484000 cC 11 P2B 0.674225 + 553 O22 6.159000 -1.944000 0.997000 oC 11 P2B -0.615485 + 554 C31 -0.657000 -0.780000 0.152000 Cp 11 P2B 0.201980 + 555 H1A -0.618000 -0.884000 1.229000 H1 11 P2B 0.075741 + 556 C32 -2.014000 -1.288000 -0.375000 Cg 11 P2B 0.098853 + 557 H2A -2.128000 -2.319000 -0.069000 H1 11 P2B 0.127580 + 558 C33 -3.231000 -0.512000 0.228000 Cp 11 P2B 0.086707 + 559 H3A -3.253000 -0.711000 1.302000 H1 11 P2B 0.001942 + 560 C34 -3.059000 1.016000 0.040000 Cp 11 P2B 0.147422 + 561 H4A -3.241000 1.260000 -0.996000 H1 11 P2B 0.092536 + 562 C35 -1.662000 1.456000 0.470000 Cg 11 P2B 0.184854 + 563 H5A -1.583000 1.287000 1.544000 H1 11 P2B 0.009556 + 564 C36 -0.533000 0.691000 -0.204000 Cg 11 P2B 0.167961 + 565 H6A -0.610000 0.795000 -1.280000 H1 11 P2B 0.039453 + 566 O35 -2.033000 -1.196000 -1.767000 Oh 11 P2B -0.598891 + 567 HO5A -2.789000 -1.771000 -2.015000 Ho 11 P2B 0.355304 + 568 O36 -4.421000 -0.875000 -0.363000 Os 11 P2B -0.454710 + 569 O37 -3.979000 1.737000 0.872000 Os 11 P2B -0.428157 + 570 O38 -1.402000 2.826000 0.216000 Oh 11 P2B -0.659802 + 571 HO8A -2.113000 3.338000 0.596000 Ho 11 P2B 0.391211 + 572 O39 0.693000 1.255000 0.233000 Oh 11 P2B -0.639403 + 573 HO9A 0.515000 2.184000 0.307000 Ho 11 P2B 0.363947 + 574 P36 -4.909000 -2.512000 -0.633000 P 11 P2B 1.272799 + 575 O40 -4.088000 -2.873000 -1.870000 O2 11 P2B -0.908360 + 576 O41 -6.383000 -2.396000 -0.882000 O2 11 P2B -0.908360 + 577 O42 -4.489000 -3.233000 0.620000 O2 11 P2B -0.908360 + 578 P37 -4.845000 3.048000 0.542000 P 11 P2B 1.291905 + 579 O45 -5.166000 3.200000 -0.894000 O2 11 P2B -0.846456 + 580 O44 -5.833000 3.229000 1.619000 O2 11 P2B -0.846456 + 581 O43 -3.683000 4.261000 0.765000 oH 11 P2B -0.774356 + 582 HOP7 -3.620000 4.649000 -0.098000 hO 11 P2B 0.393279 + 583 C11 -4.138000 2.492000 -0.367000 cC 12 P2C 0.795714 + 584 O12 -4.745000 2.452000 -1.396000 oC 12 P2C -0.679312 + 585 O11 -3.819000 1.419000 0.335000 oS 12 P2C -0.554668 + 586 C1 -3.870000 0.135000 -0.275000 cA 12 P2C 0.220587 + 587 HR -3.076000 0.037000 -1.000000 hE 12 P2C 0.015668 + 588 HS -4.820000 -0.011000 -0.766000 hE 12 P2C 0.015668 + 589 C2 -3.649000 -0.869000 0.834000 cA 12 P2C 0.769565 + 590 HX -2.847000 -0.519000 1.461000 hE 12 P2C -0.069719 + 591 C3 -3.268000 -2.270000 0.351000 cA 12 P2C 0.114940 + 592 HA -3.787000 -2.528000 -0.562000 hE 12 P2C -0.014072 + 593 HB -3.557000 -2.991000 1.112000 hE 12 P2C -0.014072 + 594 O31 -1.890000 -2.287000 0.200000 Os 12 P2C -0.524902 + 595 P31 -1.237000 -2.657000 -1.281000 P 12 P2C 1.280648 + 596 O32 0.297000 -2.475000 -0.924000 Os 12 P2C -0.628041 + 597 O33 -1.713000 -1.602000 -2.219000 O2 12 P2C -0.815348 + 598 O34 -1.532000 -4.073000 -1.571000 O2 12 P2C -0.815348 + 599 O21 -4.777000 -0.873000 1.761000 oS 12 P2C -0.564226 + 600 C21 -5.959000 -1.329000 1.457000 cC 12 P2C 0.700137 + 601 O22 -6.285000 -1.857000 0.438000 oC 12 P2C -0.669656 + 602 C31 0.998000 -1.386000 -0.308000 Cp 12 P2C 0.303269 + 603 H1A 0.727000 -1.379000 0.742000 H1 12 P2C -0.091106 + 604 C32 0.628000 -0.027000 -0.936000 Cg 12 P2C 0.367127 + 605 H2A -0.398000 0.213000 -0.676000 H1 12 P2C -0.020342 + 606 C33 1.494000 1.098000 -0.354000 Cp 12 P2C 0.428831 + 607 H3A 1.288000 1.138000 0.712000 H1 12 P2C -0.108613 + 608 C34 2.987000 0.829000 -0.536000 Cg 12 P2C 0.633258 + 609 H4A 3.213000 0.849000 -1.598000 H1 12 P2C -0.095924 + 610 C35 3.366000 -0.532000 0.039000 Cp 12 P2C -0.244410 + 611 H5A 3.247000 -0.461000 1.117000 H1 12 P2C 0.079517 + 612 C36 2.493000 -1.699000 -0.424000 Cg 12 P2C 0.668691 + 613 H6A 2.703000 -1.892000 -1.473000 H1 12 P2C -0.118479 + 614 O35 0.747000 -0.080000 -2.331000 Oh 12 P2C -0.784413 + 615 HO5A -0.023000 -0.556000 -2.632000 Ho 12 P2C 0.433468 + 616 O36 1.144000 2.328000 -0.952000 Os 12 P2C -0.705596 + 617 O37 3.731000 1.831000 0.118000 Oh 12 P2C -0.809675 + 618 O38 4.689000 -0.818000 -0.287000 Os 12 P2C -0.591180 + 619 O39 2.807000 -2.830000 0.344000 Oh 12 P2C -0.805470 + 620 HO9A 3.644000 -2.667000 0.802000 Ho 12 P2C 0.502977 + 621 P36 0.637000 3.635000 -0.124000 P 12 P2C 1.495808 + 622 O40 1.889000 3.849000 0.886000 oH 12 P2C -0.839409 + 623 O41 -0.559000 3.290000 0.697000 O2 12 P2C -0.891525 + 624 O42 0.550000 4.757000 -1.086000 O2 12 P2C -0.891525 + 625 P38 5.786000 -0.922000 1.013000 P 12 P2C 1.429626 + 626 O46 5.641000 0.455000 1.643000 O2 12 P2C -0.946131 + 627 O47 5.194000 -2.054000 1.834000 O2 12 P2C -0.946131 + 628 O48 7.106000 -1.205000 0.359000 O2 12 P2C -0.946131 + 629 HO7A 4.455000 1.418000 0.629000 Ho 12 P2C 0.470030 + 630 HOP6 2.628000 3.279000 0.658000 hO 12 P2C 0.459895 + 631 C11 6.537000 0.428000 1.920000 cC 13 P2D 0.689854 + 632 O12 7.417000 0.801000 1.200000 oC 13 P2D -0.656744 + 633 O11 5.267000 0.387000 1.634000 oS 13 P2D -0.337689 + 634 C1 4.769000 0.766000 0.336000 cA 13 P2D -0.041179 + 635 HR 5.440000 1.489000 -0.099000 hE 13 P2D 0.117964 + 636 HS 3.794000 1.192000 0.498000 hE 13 P2D 0.117964 + 637 C2 4.611000 -0.445000 -0.557000 cA 13 P2D 0.398358 + 638 HX 4.211000 -0.108000 -1.500000 hE 13 P2D 0.063774 + 639 C3 3.735000 -1.551000 0.042000 cA 13 P2D -0.086991 + 640 HA 4.294000 -2.025000 0.843000 hE 13 P2D 0.101881 + 641 HB 3.541000 -2.288000 -0.727000 hE 13 P2D 0.101881 + 642 O31 2.564000 -1.059000 0.581000 Os 13 P2D -0.447681 + 643 P31 1.384000 -0.499000 -0.447000 P 13 P2D 1.356889 + 644 O32 0.189000 -0.694000 0.564000 Os 13 P2D -0.709135 + 645 O33 1.347000 -1.429000 -1.599000 O2 13 P2D -0.836054 + 646 O34 1.691000 0.940000 -0.673000 O2 13 P2D -0.836054 + 647 O21 5.893000 -1.077000 -0.777000 oS 13 P2D -0.578576 + 648 C21 6.744000 -0.585000 -1.648000 cC 13 P2D 0.803819 + 649 O22 6.543000 0.335000 -2.376000 oC 13 P2D -0.680444 + 650 C31 -1.226000 -0.392000 0.508000 Cp 13 P2D 0.427023 + 651 H1A -1.498000 -0.351000 1.554000 H1 13 P2D -0.060079 + 652 C32 -2.005000 -1.512000 -0.169000 Cg 13 P2D 0.163960 + 653 H2A -1.765000 -2.459000 0.305000 H1 13 P2D 0.019814 + 654 C33 -3.498000 -1.258000 0.057000 Cp 13 P2D 0.338739 + 655 H3A -3.638000 -1.264000 1.136000 H1 13 P2D -0.061969 + 656 C34 -3.938000 0.130000 -0.457000 Cg 13 P2D 0.233351 + 657 H4A -3.858000 0.122000 -1.542000 H1 13 P2D 0.003650 + 658 C35 -3.040000 1.285000 0.056000 Cp 13 P2D 0.232932 + 659 H5A -3.245000 1.451000 1.106000 H1 13 P2D 0.009091 + 660 C36 -1.543000 0.962000 -0.123000 Cg 13 P2D 0.482319 + 661 H6A -1.333000 0.926000 -1.186000 H1 13 P2D -0.063673 + 662 O35 -1.694000 -1.566000 -1.542000 Oh 13 P2D -0.789807 + 663 HO5A -0.767000 -1.753000 -1.644000 Ho 13 P2D 0.486650 + 664 O36 -4.250000 -2.259000 -0.527000 Os 13 P2D -0.594776 + 665 O37 -5.254000 0.384000 -0.075000 Oh 13 P2D -0.685544 + 666 O38 -3.344000 2.459000 -0.670000 Os 13 P2D -0.605970 + 667 O39 -0.748000 1.969000 0.472000 Oh 13 P2D -0.853614 + 668 HO9A 0.143000 1.843000 0.150000 Ho 13 P2D 0.508400 + 669 P36 -5.386000 -3.113000 0.451000 P 13 P2D 1.483521 + 670 O40 -5.948000 -4.129000 -0.503000 O2 13 P2D -0.972667 + 671 O41 -6.339000 -1.994000 0.850000 O2 13 P2D -0.972667 + 672 O42 -4.561000 -3.656000 1.592000 O2 13 P2D -0.972667 + 673 P38 -3.423000 3.983000 -0.082000 P 13 P2D 1.423657 + 674 O48 -4.253000 4.793000 -0.999000 O2 13 P2D -0.891826 + 675 O47 -3.684000 3.951000 1.377000 O2 13 P2D -0.891826 + 676 O46 -1.873000 4.458000 -0.324000 oH 13 P2D -0.771853 + 677 HOP8 -1.315000 3.766000 0.023000 hO 13 P2D 0.411346 + 678 HO7A -5.684000 -0.447000 0.198000 Ho 13 P2D 0.422648 + 679 C11 5.341000 -2.414000 -1.019000 cC 14 P2E 0.664437 + 680 O12 6.266000 -2.927000 -0.461000 oC 14 P2E -0.620198 + 681 O11 5.101000 -1.114000 -0.978000 oS 14 P2E -0.417732 + 682 C1 5.666000 -0.349000 0.070000 cA 14 P2E 0.098037 + 683 HR 6.675000 -0.057000 -0.193000 hE 14 P2E 0.024054 + 684 HS 5.690000 -0.938000 0.972000 hE 14 P2E 0.024054 + 685 C2 4.790000 0.864000 0.306000 cA 14 P2E 0.662336 + 686 HX 5.165000 1.382000 1.178000 hE 14 P2E -0.019546 + 687 C3 3.313000 0.523000 0.473000 cA 14 P2E 0.116542 + 688 HA 2.938000 0.124000 -0.458000 hE 14 P2E 0.048468 + 689 HB 2.749000 1.416000 0.707000 hE 14 P2E 0.048468 + 690 O31 3.224000 -0.405000 1.507000 Os 14 P2E -0.575343 + 691 P31 1.931000 -1.435000 1.521000 P 14 P2E 1.422469 + 692 O32 0.828000 -0.338000 1.183000 Os 14 P2E -0.679084 + 693 O33 1.847000 -1.996000 2.879000 O2 14 P2E -0.854854 + 694 O34 2.089000 -2.340000 0.341000 O2 14 P2E -0.854854 + 695 O21 4.852000 1.765000 -0.816000 oS 14 P2E -0.607431 + 696 C21 5.813000 2.643000 -0.928000 cC 14 P2E 0.773366 + 697 O22 6.763000 2.734000 -0.208000 oC 14 P2E -0.689523 + 698 C31 -0.601000 -0.392000 1.228000 Cp 14 P2E 0.292448 + 699 H1A -0.891000 -0.366000 2.273000 H1 14 P2E 0.026790 + 700 C32 -1.119000 -1.676000 0.596000 Cg 14 P2E 0.227578 + 701 H2A -0.832000 -2.522000 1.220000 H1 14 P2E 0.016314 + 702 C33 -2.641000 -1.643000 0.560000 Cg 14 P2E 0.212117 + 703 H3A -2.984000 -1.542000 1.588000 H1 14 P2E -0.042301 + 704 C34 -3.207000 -0.441000 -0.219000 Cp 14 P2E 0.316893 + 705 H4A -2.885000 -0.492000 -1.250000 H1 14 P2E 0.049186 + 706 C35 -2.696000 0.890000 0.399000 Cp 14 P2E -0.148942 + 707 H5A -3.134000 1.000000 1.388000 H1 14 P2E 0.139154 + 708 C36 -1.139000 0.866000 0.542000 Cg 14 P2E 0.161906 + 709 H6A -0.740000 0.877000 -0.473000 H1 14 P2E 0.080961 + 710 O35 -0.612000 -1.872000 -0.697000 Oh 14 P2E -0.679338 + 711 HO5A 0.324000 -2.035000 -0.601000 Ho 14 P2E 0.428284 + 712 O36 -3.117000 -2.893000 0.110000 Oh 14 P2E -0.730092 + 713 O37 -4.627000 -0.493000 -0.176000 Os 14 P2E -0.568427 + 714 O38 -3.064000 1.944000 -0.416000 Os 14 P2E -0.470721 + 715 O39 -0.662000 1.942000 1.276000 Oh 14 P2E -0.676949 + 716 HO9A -1.073000 2.781000 0.939000 Ho 14 P2E 0.460850 + 717 P38 -3.293000 3.568000 0.140000 P 14 P2E 1.439006 + 718 O46 -3.979000 4.224000 -1.020000 O2 14 P2E -0.945105 + 719 O47 -4.067000 3.437000 1.422000 O2 14 P2E -0.945105 + 720 O48 -1.851000 4.042000 0.348000 O2 14 P2E -0.945105 + 721 HO6A -3.369000 -2.812000 -0.815000 Ho 14 P2E 0.408317 + 722 P37 -5.558000 -1.521000 -1.017000 P 14 P2E 1.488794 + 723 O43 -5.934000 -2.605000 0.162000 oH 14 P2E -0.836642 + 724 O44 -6.826000 -0.898000 -1.427000 O2 14 P2E -0.907984 + 725 O45 -4.692000 -2.237000 -2.006000 O2 14 P2E -0.907984 + 726 HOP7 -5.106000 -2.970000 0.459000 hO 14 P2E 0.492432 + 727 C11 5.476000 3.584000 -1.093000 cC 15 P2F 0.803476 + 728 O12 6.500000 3.869000 -0.549000 oC 15 P2F -0.685548 + 729 O11 4.944000 2.384000 -1.094000 oS 15 P2F -0.495454 + 730 C1 5.537000 1.356000 -0.304000 cA 15 P2F 0.105867 + 731 HR 5.543000 1.664000 0.733000 hE 15 P2F 0.074920 + 732 HS 6.555000 1.196000 -0.629000 hE 15 P2F 0.074920 + 733 C2 4.705000 0.098000 -0.494000 cA 15 P2F 0.434756 + 734 HX 4.724000 -0.203000 -1.529000 hE 15 P2F 0.060104 + 735 C3 3.265000 0.222000 -0.012000 cA 15 P2F 0.223271 + 736 HA 2.781000 1.020000 -0.566000 hE 15 P2F -0.002397 + 737 HB 3.248000 0.468000 1.041000 hE 15 P2F -0.002397 + 738 O31 2.608000 -0.971000 -0.258000 Os 15 P2F -0.624798 + 739 P31 2.078000 -1.966000 0.963000 P 15 P2F 1.262631 + 740 O32 0.551000 -2.142000 0.567000 Os 15 P2F -0.420941 + 741 O33 2.228000 -1.208000 2.232000 O2 15 P2F -0.814482 + 742 O34 2.778000 -3.261000 0.793000 O2 15 P2F -0.814482 + 743 O21 5.328000 -0.908000 0.297000 oS 15 P2F -0.542863 + 744 C21 5.916000 -1.947000 -0.275000 cC 15 P2F 0.737851 + 745 O22 6.352000 -1.934000 -1.387000 oC 15 P2F -0.668610 + 746 C31 -0.398000 -1.149000 0.145000 Cp 15 P2F 0.180848 + 747 H1A -0.144000 -0.886000 -0.876000 H1 15 P2F 0.063871 + 748 C32 -0.375000 0.109000 1.016000 Cg 15 P2F 0.039690 + 749 H2A 0.580000 0.612000 0.890000 H1 15 P2F 0.070044 + 750 C33 -1.436000 1.111000 0.548000 Cg 15 P2F 0.552859 + 751 H3A -1.227000 1.292000 -0.515000 H1 15 P2F -0.159075 + 752 C34 -2.870000 0.543000 0.591000 Cp 15 P2F 0.494715 + 753 H4A -3.165000 0.374000 1.620000 H1 15 P2F -0.072591 + 754 C35 -2.882000 -0.782000 -0.196000 Cp 15 P2F 0.623087 + 755 H5A -2.758000 -0.517000 -1.241000 H1 15 P2F -0.052530 + 756 C36 -1.781000 -1.798000 0.151000 Cg 15 P2F -0.118028 + 757 H6A -1.966000 -2.173000 1.153000 H1 15 P2F 0.067150 + 758 O35 -0.531000 -0.226000 2.369000 Oh 15 P2F -0.758340 + 759 HO5A 0.296000 -0.616000 2.636000 Ho 15 P2F 0.467685 + 760 O36 -1.247000 2.281000 1.267000 Oh 15 P2F -0.756481 + 761 O37 -3.776000 1.377000 -0.058000 Os 15 P2F -0.765996 + 762 O38 -4.124000 -1.437000 -0.021000 Os 15 P2F -0.707043 + 763 O39 -1.791000 -2.864000 -0.763000 Oh 15 P2F -0.633978 + 764 HO9A -2.602000 -2.839000 -1.280000 Ho 15 P2F 0.405954 + 765 P38 -5.058000 -1.830000 -1.294000 P 15 P2F 1.583318 + 766 O46 -5.418000 -0.368000 -1.875000 oH 15 P2F -0.813162 + 767 O47 -4.190000 -2.534000 -2.290000 O2 15 P2F -0.933041 + 768 O48 -6.263000 -2.521000 -0.786000 O2 15 P2F -0.933041 + 769 HO6A -1.989000 2.900000 1.064000 Ho 15 P2F 0.392598 + 770 P37 -4.474000 2.819000 0.606000 P 15 P2F 1.345089 + 771 O43 -4.954000 2.434000 1.974000 O2 15 P2F -0.906632 + 772 O44 -3.285000 3.781000 0.618000 O2 15 P2F -0.906632 + 773 O45 -5.514000 3.134000 -0.427000 O2 15 P2F -0.906632 + 774 HOP8 -5.185000 0.351000 -1.280000 hO 15 P2F 0.430470 + 775 C11 -4.471000 2.516000 -1.291000 cC 16 P3- 0.848648 + 776 O12 -5.481000 2.789000 -1.911000 oC 16 P3- -0.870090 + 777 O11 -4.151000 1.262000 -1.117000 oS 16 P3- -0.423996 + 778 C1 -2.980000 0.879000 -0.333000 cA 16 P3- 0.027562 + 779 HR -2.098000 1.326000 -0.777000 hE 16 P3- 0.184001 + 780 HS -3.090000 1.240000 0.678000 hE 16 P3- 0.184001 + 781 C2 -2.864000 -0.634000 -0.288000 cA 16 P3- 0.554084 + 782 HX -2.114000 -0.858000 0.452000 hE 16 P3- -0.127683 + 783 C3 -2.442000 -1.308000 -1.587000 cA 16 P3- 0.309347 + 784 HA -3.294000 -1.354000 -2.265000 hE 16 P3- -0.071957 + 785 HB -1.682000 -0.706000 -2.063000 hE 16 P3- -0.071957 + 786 O31 -1.989000 -2.606000 -1.318000 Os 16 P3- -0.517728 + 787 P31 -0.599000 -3.226000 -1.975000 P 16 P3- 1.249891 + 788 O32 0.575000 -2.717000 -1.018000 Os 16 P3- -0.597556 + 789 O33 -0.725000 -4.701000 -1.810000 O2 16 P3- -0.883254 + 790 O34 -0.500000 -2.668000 -3.352000 O2 16 P3- -0.883254 + 791 O21 -4.149000 -1.194000 0.109000 oS 16 P3- -0.603138 + 792 C21 -4.419000 -1.712000 1.281000 cC 16 P3- 0.829113 + 793 O22 -5.549000 -2.113000 1.484000 oC 16 P3- -0.796197 + 794 C31 0.952000 -1.372000 -0.662000 Cp 16 P3- 0.502419 + 795 H1A 0.304000 -0.689000 -1.189000 H1 16 P3- 0.004943 + 796 C32 2.399000 -1.044000 -1.096000 Cg 16 P3- 0.104639 + 797 H2A 2.369000 -0.880000 -2.174000 H1 16 P3- 0.017978 + 798 C33 2.930000 0.309000 -0.432000 Cp 16 P3- 0.081622 + 799 H3A 3.355000 0.907000 -1.227000 H1 16 P3- 0.104535 + 800 C34 1.735000 1.184000 0.112000 Cp 16 P3- 0.507183 + 801 H4A 2.137000 2.019000 0.666000 H1 16 P3- -0.024494 + 802 C35 0.700000 0.415000 1.034000 Cp 16 P3- 0.081622 + 803 H5A -0.269000 0.709000 0.657000 H1 16 P3- 0.104535 + 804 C36 0.835000 -1.112000 0.847000 Cg 16 P3- 0.104639 + 805 H6A 1.753000 -1.441000 1.324000 H1 16 P3- 0.017978 + 806 O35 3.216000 -2.143000 -0.821000 Oh 16 P3- -0.629010 + 807 HO5A 4.130000 -1.834000 -0.714000 Ho 16 P3- 0.379556 + 808 O36 3.927000 0.070000 0.538000 Os 16 P3- -0.542655 + 809 O37 1.043000 1.623000 -1.046000 Os 16 P3- -0.732459 + 810 O38 0.757000 0.777000 2.402000 Os 16 P3- -0.542655 + 811 O39 -0.253000 -1.804000 1.404000 Oh 16 P3- -0.629010 + 812 HO9A -0.313000 -1.546000 2.340000 Ho 16 P3- 0.379556 + 813 P36 5.609000 -0.005000 0.241000 P 16 P3- 1.470033 + 814 O41 6.022000 1.282000 -0.437000 O2 16 P3- -1.003962 + 815 O40 6.210000 -0.207000 1.618000 O2 16 P3- -1.003962 + 816 O42 5.770000 -1.230000 -0.667000 O2 16 P3- -1.003962 + 817 P37 0.590000 3.163000 -1.616000 P 16 P3- 1.500871 + 818 O43 -0.925000 2.996000 -1.821000 O2 16 P3- -1.015975 + 819 O44 1.312000 3.346000 -2.934000 O2 16 P3- -1.015975 + 820 O45 0.951000 4.172000 -0.554000 O2 16 P3- -1.015975 + 821 P38 -0.508000 0.635000 3.509000 P 16 P3- 1.470033 + 822 O48 -0.506000 -0.844000 3.921000 O2 16 P3- -1.003962 + 823 O47 -1.794000 0.992000 2.772000 O2 16 P3- -1.003962 + 824 O46 -0.147000 1.566000 4.642000 O2 16 P3- -1.003962 + 825 C11 5.102000 -1.061000 -2.193000 cC 17 P3A 0.801165 + 826 O12 5.958000 -1.732000 -2.706000 oC 17 P3A -0.718168 + 827 O11 5.470000 -0.229000 -1.228000 oS 17 P3A -0.491335 + 828 C1 4.619000 0.781000 -0.667000 cA 17 P3A 0.118893 + 829 HR 3.887000 1.118000 -1.377000 hE 17 P3A 0.041293 + 830 HS 5.287000 1.602000 -0.451000 hE 17 P3A 0.041293 + 831 C2 3.934000 0.335000 0.606000 cA 17 P3A 0.371504 + 832 HX 3.390000 -0.584000 0.439000 hE 17 P3A 0.029215 + 833 C3 2.958000 1.407000 1.144000 cA 17 P3A 0.401128 + 834 HA 3.256000 1.639000 2.161000 hE 17 P3A -0.052018 + 835 HB 1.963000 0.997000 1.180000 hE 17 P3A -0.052018 + 836 O31 2.993000 2.620000 0.428000 Os 17 P3A -0.572817 + 837 P31 1.858000 3.076000 -0.666000 P 17 P3A 1.311019 + 838 O32 0.549000 2.519000 0.062000 Os 17 P3A -0.457964 + 839 O33 2.104000 2.331000 -1.938000 O2 17 P3A -0.860904 + 840 O34 1.895000 4.553000 -0.687000 O2 17 P3A -0.860904 + 841 O21 5.003000 0.113000 1.565000 oS 17 P3A -0.507770 + 842 C21 4.963000 -0.811000 2.503000 cC 17 P3A 0.797518 + 843 O22 5.899000 -0.900000 3.258000 oC 17 P3A -0.747431 + 844 C31 -0.567000 1.761000 -0.424000 Cp 17 P3A 0.105770 + 845 H1A -1.016000 2.316000 -1.239000 H1 17 P3A 0.049147 + 846 C32 -1.593000 1.698000 0.708000 Cg 17 P3A 0.202457 + 847 H2A -1.923000 2.708000 0.915000 H1 17 P3A 0.074035 + 848 C33 -2.807000 0.928000 0.175000 Cp 17 P3A 0.170040 + 849 H3A -3.033000 1.309000 -0.816000 H1 17 P3A 0.015285 + 850 C34 -2.480000 -0.560000 0.039000 Cp 17 P3A 0.420737 + 851 H4A -2.362000 -0.997000 1.019000 H1 17 P3A 0.049804 + 852 C35 -1.184000 -0.794000 -0.804000 Cp 17 P3A 0.045385 + 853 H5A -1.547000 -0.972000 -1.807000 H1 17 P3A 0.057600 + 854 C36 -0.162000 0.376000 -0.976000 Cg 17 P3A 0.344498 + 855 H6A 0.744000 0.081000 -0.485000 H1 17 P3A 0.028441 + 856 O35 -1.085000 1.199000 1.906000 Oh 17 P3A -0.771390 + 857 HO5A -0.572000 0.386000 1.790000 Ho 17 P3A 0.462782 + 858 O36 -3.927000 1.128000 1.010000 Os 17 P3A -0.635520 + 859 O37 -3.590000 -1.161000 -0.624000 Os 17 P3A -0.604581 + 860 O38 -0.553000 -1.992000 -0.414000 Os 17 P3A -0.583910 + 861 O39 0.087000 0.489000 -2.368000 Oh 17 P3A -0.698310 + 862 HO9A 0.829000 1.087000 -2.444000 Ho 17 P3A 0.403108 + 863 P36 -5.237000 1.971000 0.530000 P 17 P3A 1.499516 + 864 O41 -6.158000 2.067000 1.689000 O2 17 P3A -0.911779 + 865 O40 -5.845000 0.936000 -0.575000 oH 17 P3A -0.865419 + 866 O42 -4.832000 3.211000 -0.181000 O2 17 P3A -0.911779 + 867 P37 -4.078000 -2.729000 -0.530000 P 17 P3A 1.415340 + 868 O44 -5.208000 -2.856000 -1.482000 O2 17 P3A -0.902105 + 869 O43 -2.815000 -3.509000 -1.182000 oH 17 P3A -0.809989 + 870 O45 -4.240000 -3.125000 0.887000 O2 17 P3A -0.902105 + 871 P38 0.718000 -2.183000 0.693000 P 17 P3A 1.349219 + 872 O46 0.537000 -1.025000 1.666000 O2 17 P3A -0.900727 + 873 O47 0.524000 -3.558000 1.237000 O2 17 P3A -0.900727 + 874 O48 1.995000 -2.010000 -0.126000 O2 17 P3A -0.900727 + 875 HOP6 -5.371000 0.106000 -0.580000 hO 17 P3A 0.532241 + 876 HOP7 -1.989000 -3.288000 -0.746000 hO 17 P3A 0.481963 + 877 C11 -4.729000 -2.166000 2.095000 cC 18 P3B 0.740141 + 878 O12 -5.877000 -1.847000 2.254000 oC 18 P3B -0.686566 + 879 O11 -3.778000 -1.372000 1.694000 oS 18 P3B -0.423924 + 880 C1 -3.995000 -0.046000 1.238000 cA 18 P3B -0.102340 + 881 HR -3.017000 0.298000 0.946000 hE 18 P3B 0.116235 + 882 HS -4.396000 0.552000 2.048000 hE 18 P3B 0.116235 + 883 C2 -4.919000 0.042000 0.023000 cA 18 P3B 0.648671 + 884 HX -5.957000 -0.015000 0.316000 hE 18 P3B -0.089401 + 885 C3 -4.646000 -0.993000 -1.060000 cA 18 P3B 0.236192 + 886 HA -5.316000 -0.753000 -1.890000 hE 18 P3B -0.013784 + 887 HB -4.927000 -1.968000 -0.685000 hE 18 P3B -0.013784 + 888 O31 -3.348000 -0.978000 -1.504000 Os 18 P3B -0.627416 + 889 P31 -2.312000 -2.280000 -1.382000 P 18 P3B 1.432019 + 890 O32 -1.227000 -1.737000 -0.342000 Os 18 P3B -0.623426 + 891 O33 -1.752000 -2.473000 -2.732000 O2 18 P3B -0.870685 + 892 O34 -3.103000 -3.353000 -0.718000 O2 18 P3B -0.870685 + 893 O21 -4.682000 1.337000 -0.553000 oS 18 P3B -0.461876 + 894 C21 -5.268000 2.417000 -0.110000 cC 18 P3B 0.725654 + 895 O22 -6.179000 2.442000 0.671000 oC 18 P3B -0.706793 + 896 C31 -0.046000 -0.979000 -0.629000 Cp 18 P3B 0.395189 + 897 H1A -0.014000 -0.783000 -1.694000 H1 18 P3B -0.054198 + 898 C32 1.202000 -1.757000 -0.228000 Cg 18 P3B 0.380601 + 899 H2A 1.308000 -2.618000 -0.879000 H1 18 P3B -0.083666 + 900 C33 2.505000 -0.925000 -0.350000 Cp 18 P3B 0.244705 + 901 H3A 2.742000 -0.787000 -1.398000 H1 18 P3B -0.088358 + 902 C34 2.456000 0.488000 0.319000 Cp 18 P3B 0.811264 + 903 H4A 2.353000 0.372000 1.392000 H1 18 P3B -0.134835 + 904 C35 1.176000 1.187000 -0.234000 Cp 18 P3B 0.244705 + 905 H5A 1.265000 1.284000 -1.310000 H1 18 P3B -0.088358 + 906 C36 -0.067000 0.340000 0.127000 Cg 18 P3B 0.380601 + 907 H6A -0.035000 0.127000 1.190000 H1 18 P3B -0.083666 + 908 O35 1.065000 -2.194000 1.107000 Oh 18 P3B -0.688843 + 909 HO5A 1.964000 -2.342000 1.390000 Ho 18 P3B 0.361111 + 910 O36 3.484000 -1.723000 0.269000 Os 18 P3B -0.678754 + 911 O37 3.591000 1.206000 0.023000 Os 18 P3B -0.684473 + 912 O38 0.951000 2.494000 0.305000 Os 18 P3B -0.678754 + 913 O39 -1.266000 1.015000 -0.168000 Oh 18 P3B -0.688843 + 914 HO9A -1.156000 1.918000 0.103000 Ho 18 P3B 0.361111 + 915 P36 4.568000 -2.689000 -0.524000 P 18 P3B 1.524427 + 916 O41 4.915000 -3.775000 0.438000 O2 18 P3B -0.912833 + 917 O40 5.777000 -1.683000 -0.739000 oH 18 P3B -0.848533 + 918 O42 3.999000 -3.080000 -1.846000 O2 18 P3B -0.912833 + 919 P37 4.892000 1.478000 1.140000 P 18 P3B 1.417488 + 920 O43 5.355000 2.869000 0.811000 O2 18 P3B -0.958319 + 921 O44 4.276000 1.279000 2.500000 O2 18 P3B -0.958319 + 922 O45 5.918000 0.412000 0.758000 O2 18 P3B -0.958319 + 923 P38 0.596000 3.846000 -0.510000 P 18 P3B 1.524427 + 924 O48 0.748000 5.004000 0.386000 O2 18 P3B -0.912833 + 925 O47 1.084000 3.803000 -1.905000 O2 18 P3B -0.912833 + 926 O46 -1.077000 3.684000 -0.687000 oH 18 P3B -0.848533 + 927 HOP6 5.769000 -0.874000 -0.139000 hO 18 P3B 0.503004 + 928 HOP8 -1.201000 3.178000 -1.480000 hO 18 P3B 0.503004 + 929 C11 3.806000 0.579000 2.423000 cC 19 P3C 0.750013 + 930 O12 4.865000 1.133000 2.541000 oC 19 P3C -0.699285 + 931 O11 3.715000 -0.693000 2.046000 oS 19 P3C -0.408160 + 932 C1 4.864000 -1.334000 1.550000 cA 19 P3C 0.182237 + 933 HR 4.647000 -2.392000 1.606000 hE 19 P3C 0.003555 + 934 HS 5.716000 -1.107000 2.176000 hE 19 P3C 0.003555 + 935 C2 5.164000 -0.906000 0.116000 cA 19 P3C 0.456599 + 936 HX 4.917000 0.130000 -0.001000 hE 19 P3C -0.008599 + 937 C3 4.421000 -1.692000 -0.963000 cA 19 P3C 0.529687 + 938 HA 4.608000 -2.756000 -0.812000 hE 19 P3C -0.099629 + 939 HB 4.847000 -1.423000 -1.926000 hE 19 P3C -0.099629 + 940 O31 3.068000 -1.470000 -0.901000 Os 19 P3C -0.644510 + 941 P31 2.449000 -0.122000 -1.688000 P 19 P3C 1.188346 + 942 O32 1.333000 0.294000 -0.643000 Os 19 P3C -0.457628 + 943 O33 1.930000 -0.542000 -3.000000 O2 19 P3C -0.813804 + 944 O34 3.579000 0.855000 -1.625000 O2 19 P3C -0.813804 + 945 O21 6.584000 -1.120000 -0.076000 oS 19 P3C -0.578888 + 946 C21 7.361000 -0.252000 -0.690000 cC 19 P3C 0.845302 + 947 O22 8.513000 -0.532000 -0.881000 oC 19 P3C -0.758144 + 948 C31 -0.100000 0.225000 -0.738000 Cp 19 P3C 0.499018 + 949 H1A -0.362000 0.060000 -1.775000 H1 19 P3C 0.024830 + 950 C32 -0.686000 1.552000 -0.236000 Cg 19 P3C -0.364111 + 951 H2A -0.326000 2.349000 -0.877000 H1 19 P3C 0.245938 + 952 C33 -2.240000 1.593000 -0.297000 Cp 19 P3C 0.116129 + 953 H3A -2.542000 1.594000 -1.342000 H1 19 P3C 0.083418 + 954 C34 -2.850000 0.345000 0.400000 Cp 19 P3C 0.231003 + 955 H4A -2.678000 0.495000 1.456000 H1 19 P3C 0.080692 + 956 C35 -2.165000 -0.984000 0.029000 Cp 19 P3C 0.308350 + 957 H5A -2.451000 -1.302000 -0.963000 H1 19 P3C 0.036618 + 958 C36 -0.634000 -0.912000 0.117000 Cg 19 P3C 0.064791 + 959 H6A -0.365000 -0.709000 1.147000 H1 19 P3C 0.088283 + 960 O35 -0.242000 1.768000 1.075000 Oh 19 P3C -0.649213 + 961 HO5A -0.463000 2.714000 1.252000 Ho 19 P3C 0.479191 + 962 O36 -2.739000 2.713000 0.361000 Os 19 P3C -0.472867 + 963 O37 -4.264000 0.262000 0.293000 Os 19 P3C -0.584399 + 964 O38 -2.567000 -1.971000 0.981000 Os 19 P3C -0.635867 + 965 O39 -0.069000 -2.133000 -0.288000 Oh 19 P3C -0.655613 + 966 HO9A -0.398000 -2.814000 0.288000 Ho 19 P3C 0.406359 + 967 P36 -2.168000 4.327000 0.248000 P 19 P3C 1.349623 + 968 O40 -1.870000 4.555000 -1.211000 O2 19 P3C -0.952129 + 969 O41 -0.915000 4.289000 1.133000 O2 19 P3C -0.952129 + 970 O42 -3.290000 5.137000 0.838000 O2 19 P3C -0.952129 + 971 P37 -5.271000 -0.259000 -0.859000 P 19 P3C 1.466113 + 972 O43 -5.437000 -1.855000 -0.473000 oH 19 P3C -0.818001 + 973 O44 -6.602000 0.335000 -0.597000 O2 19 P3C -0.898042 + 974 O45 -4.633000 -0.211000 -2.196000 O2 19 P3C -0.898042 + 975 P38 -2.971000 -3.517000 0.750000 P 19 P3C 1.407053 + 976 O48 -3.335000 -3.752000 -0.672000 O2 19 P3C -0.855123 + 977 O47 -1.972000 -4.390000 1.415000 O2 19 P3C -0.855123 + 978 O46 -4.334000 -3.551000 1.643000 oH 19 P3C -0.816657 + 979 HOP8 -4.924000 -2.907000 1.249000 hO 19 P3C 0.455938 + 980 HOP7 -4.750000 -2.382000 -0.883000 hO 19 P3C 0.438884 + 981 C11 -5.181000 -0.532000 -1.983000 cC 20 P3D 0.769265 + 982 O12 -6.324000 -0.168000 -1.870000 oC 20 P3D -0.743527 + 983 O11 -4.127000 0.227000 -1.862000 oS 20 P3D -0.510169 + 984 C1 -4.078000 1.387000 -1.039000 cA 20 P3D 0.412648 + 985 HR -5.012000 1.929000 -1.132000 hE 20 P3D 0.027454 + 986 HS -3.235000 1.964000 -1.398000 hE 20 P3D 0.027454 + 987 C2 -3.805000 1.040000 0.415000 cA 20 P3D 0.092321 + 988 HX -3.833000 1.967000 0.975000 hE 20 P3D 0.072319 + 989 C3 -2.480000 0.336000 0.674000 cA 20 P3D 0.421812 + 990 HA -2.340000 0.237000 1.744000 hE 20 P3D -0.029611 + 991 HB -1.701000 0.964000 0.263000 hE 20 P3D -0.029611 + 992 O31 -2.502000 -0.917000 0.063000 Os 20 P3D -0.571401 + 993 P31 -1.947000 -2.299000 0.749000 P 20 P3D 1.173370 + 994 O32 -0.410000 -2.329000 0.335000 Os 20 P3D -0.584188 + 995 O33 -2.659000 -3.394000 0.034000 O2 20 P3D -0.822053 + 996 O34 -2.169000 -2.157000 2.219000 O2 20 P3D -0.822053 + 997 O21 -4.878000 0.183000 0.877000 oS 20 P3D -0.452957 + 998 C21 -5.191000 0.103000 2.150000 cC 20 P3D 0.681262 + 999 O22 -5.297000 1.041000 2.889000 oC 20 P3D -0.688487 + 1000 C31 0.575000 -1.269000 0.430000 Cp 20 P3D 0.780281 + 1001 H1A 0.359000 -0.683000 1.316000 H1 20 P3D -0.038472 + 1002 C32 0.568000 -0.370000 -0.823000 Cg 20 P3D -0.037982 + 1003 H2A -0.443000 -0.136000 -1.109000 H1 20 P3D 0.067712 + 1004 C33 1.283000 0.935000 -0.457000 Cp 20 P3D 0.062292 + 1005 H3A 0.724000 1.410000 0.334000 H1 20 P3D 0.030088 + 1006 C34 2.712000 0.694000 0.147000 Cp 20 P3D 0.616096 + 1007 H4A 3.446000 1.158000 -0.498000 H1 20 P3D -0.050647 + 1008 C35 3.123000 -0.829000 0.287000 Cp 20 P3D 0.038472 + 1009 H5A 3.746000 -0.853000 1.166000 H1 20 P3D 0.047767 + 1010 C36 1.969000 -1.875000 0.533000 Cg 20 P3D -0.073736 + 1011 H6A 2.037000 -2.587000 -0.277000 H1 20 P3D 0.081016 + 1012 O35 1.116000 -1.017000 -1.948000 Oh 20 P3D -0.632741 + 1013 HO5A 2.062000 -1.121000 -1.852000 Ho 20 P3D 0.354556 + 1014 O36 1.294000 1.797000 -1.593000 Os 20 P3D -0.595066 + 1015 O37 2.767000 1.244000 1.441000 Os 20 P3D -0.643952 + 1016 O38 3.863000 -1.258000 -0.841000 Os 20 P3D -0.515003 + 1017 O39 2.168000 -2.571000 1.739000 Oh 20 P3D -0.778903 + 1018 HO9A 3.037000 -2.960000 1.576000 Ho 20 P3D 0.539426 + 1019 P36 0.207000 2.979000 -1.856000 P 20 P3D 1.711461 + 1020 O41 0.273000 3.366000 -3.296000 O2 20 P3D -0.975696 + 1021 O40 0.772000 4.191000 -0.986000 oH 20 P3D -1.105242 + 1022 O42 -1.139000 2.552000 -1.330000 O2 20 P3D -0.975696 + 1023 P37 3.064000 2.877000 1.824000 P 20 P3D 1.451998 + 1024 O43 4.317000 3.277000 1.088000 O2 20 P3D -0.983005 + 1025 O44 1.810000 3.615000 1.341000 O2 20 P3D -0.983005 + 1026 O45 3.180000 2.852000 3.330000 O2 20 P3D -0.983005 + 1027 P38 4.938000 -2.614000 -0.754000 P 20 P3D 1.443615 + 1028 O48 4.563000 -3.293000 0.562000 O2 20 P3D -0.989068 + 1029 O47 4.594000 -3.417000 -1.987000 O2 20 P3D -0.989068 + 1030 O46 6.337000 -2.042000 -0.761000 O2 20 P3D -0.989068 + 1031 HOP6 1.210000 3.931000 -0.126000 hO 20 P3D 0.690725 + 1032 C11 5.149000 1.033000 -1.321000 cC 21 P3E 0.975211 + 1033 O12 5.706000 0.499000 -2.245000 oC 21 P3E -0.830543 + 1034 O11 4.661000 0.394000 -0.283000 oS 21 P3E -0.600967 + 1035 C1 4.141000 -0.937000 -0.389000 cA 21 P3E 0.372631 + 1036 HR 4.790000 -1.544000 -1.005000 hE 21 P3E -0.016286 + 1037 HS 3.153000 -0.878000 -0.815000 hE 21 P3E -0.016286 + 1038 C2 4.086000 -1.490000 1.024000 cA 21 P3E 0.376163 + 1039 HX 5.089000 -1.595000 1.424000 hE 21 P3E -0.100576 + 1040 C3 3.371000 -2.836000 1.089000 cA 21 P3E 0.593593 + 1041 HA 3.441000 -3.191000 2.121000 hE 21 P3E -0.133323 + 1042 HB 3.909000 -3.536000 0.457000 hE 21 P3E -0.133323 + 1043 O31 2.048000 -2.732000 0.734000 Os 21 P3E -0.493378 + 1044 P31 1.448000 -3.559000 -0.581000 P 21 P3E 1.009715 + 1045 O32 0.288000 -2.579000 -1.038000 Os 21 P3E -0.307001 + 1046 O33 0.949000 -4.864000 -0.075000 O2 21 P3E -0.828506 + 1047 O34 2.549000 -3.595000 -1.586000 O2 21 P3E -0.828506 + 1048 O21 3.365000 -0.586000 1.844000 oS 21 P3E -0.449360 + 1049 C21 3.951000 0.312000 2.600000 cC 21 P3E 0.761896 + 1050 O22 5.114000 0.278000 2.920000 oC 21 P3E -0.738031 + 1051 C31 -0.430000 -1.518000 -0.371000 Cp 21 P3E 0.119645 + 1052 H1A -0.090000 -1.487000 0.656000 H1 21 P3E -0.007525 + 1053 C32 -1.938000 -1.792000 -0.390000 Cg 21 P3E 0.334913 + 1054 H2A -2.119000 -2.699000 0.176000 H1 21 P3E -0.049617 + 1055 C33 -2.752000 -0.662000 0.309000 Cp 21 P3E 0.055646 + 1056 H3A -2.509000 -0.696000 1.373000 H1 21 P3E 0.044697 + 1057 C34 -2.355000 0.762000 -0.203000 Cp 21 P3E 0.372717 + 1058 H4A -2.720000 0.879000 -1.213000 H1 21 P3E 0.025010 + 1059 C35 -0.808000 0.953000 -0.194000 Cp 21 P3E 0.055646 + 1060 H5A -0.454000 0.898000 0.832000 H1 21 P3E 0.044697 + 1061 C36 -0.121000 -0.168000 -1.013000 Cg 21 P3E 0.334913 + 1062 H6A -0.537000 -0.161000 -2.019000 H1 21 P3E -0.049617 + 1063 O35 -2.380000 -1.979000 -1.705000 Oh 21 P3E -0.729289 + 1064 HO5A -3.263000 -2.369000 -1.589000 Ho 21 P3E 0.454905 + 1065 O36 -4.122000 -0.865000 0.133000 Os 21 P3E -0.505635 + 1066 O37 -2.955000 1.745000 0.626000 Os 21 P3E -0.544411 + 1067 O38 -0.480000 2.217000 -0.720000 Os 21 P3E -0.505635 + 1068 O39 1.265000 0.004000 -1.074000 Oh 21 P3E -0.729289 + 1069 HO9A 1.479000 0.919000 -1.308000 Ho 21 P3E 0.454905 + 1070 P36 -4.984000 -2.315000 0.418000 P 21 P3E 1.382442 + 1071 O40 -4.421000 -2.892000 1.697000 O2 21 P3E -0.962831 + 1072 O41 -4.671000 -3.148000 -0.828000 O2 21 P3E -0.962831 + 1073 O42 -6.428000 -1.882000 0.493000 O2 21 P3E -0.962831 + 1074 P37 -3.700000 3.127000 0.183000 P 21 P3E 1.400149 + 1075 O43 -4.699000 3.478000 1.230000 O2 21 P3E -0.917806 + 1076 O44 -2.521000 4.233000 0.360000 oH 21 P3E -0.832135 + 1077 O45 -4.117000 3.074000 -1.244000 O2 21 P3E -0.917806 + 1078 P38 0.901000 3.152000 -0.459000 P 21 P3E 1.382442 + 1079 O48 1.908000 2.614000 -1.491000 O2 21 P3E -0.962831 + 1080 O47 0.419000 4.543000 -0.766000 O2 21 P3E -0.962831 + 1081 O46 1.363000 2.915000 0.961000 O2 21 P3E -0.962831 + 1082 HOP7 -1.719000 4.029000 -0.122000 hO 21 P3E 0.489900 + 1083 C11 -6.175000 -2.047000 -0.922000 cC 22 P3F 0.761453 + 1084 O12 -6.318000 -2.539000 0.159000 oC 22 P3F -0.758622 + 1085 O11 -5.147000 -1.422000 -1.394000 oS 22 P3F -0.512957 + 1086 C1 -3.943000 -1.113000 -0.613000 cA 22 P3F 0.741364 + 1087 HR -3.101000 -1.468000 -1.196000 hE 22 P3F -0.098432 + 1088 HS -3.990000 -1.641000 0.323000 hE 22 P3F -0.098432 + 1089 C2 -3.890000 0.385000 -0.400000 cA 22 P3F -0.017963 + 1090 HX -4.326000 0.902000 -1.244000 hE 22 P3F 0.129801 + 1091 C3 -2.467000 0.844000 -0.157000 cA 22 P3F 0.318055 + 1092 HA -1.892000 0.612000 -1.043000 hE 22 P3F -0.047308 + 1093 HB -2.055000 0.294000 0.677000 hE 22 P3F -0.047308 + 1094 O31 -2.473000 2.219000 0.081000 Os 22 P3F -0.473516 + 1095 P31 -1.164000 2.888000 0.891000 P 22 P3F 1.223592 + 1096 O32 0.020000 2.057000 0.239000 Os 22 P3F -0.516269 + 1097 O33 -1.391000 2.620000 2.335000 O2 22 P3F -0.872399 + 1098 O34 -1.139000 4.298000 0.428000 O2 22 P3F -0.872399 + 1099 O21 -4.687000 0.684000 0.768000 oS 22 P3F -0.517063 + 1100 C21 -5.165000 1.904000 0.958000 cC 22 P3F 0.989113 + 1101 O22 -5.860000 2.477000 0.167000 oC 22 P3F -0.880558 + 1102 C31 1.020000 1.061000 0.619000 Cp 22 P3F 0.477343 + 1103 H1A 1.267000 1.212000 1.659000 H1 22 P3F 0.058586 + 1104 C32 2.268000 1.315000 -0.236000 Cg 22 P3F -0.212133 + 1105 H2A 2.617000 2.320000 -0.021000 H1 22 P3F 0.136582 + 1106 C33 3.419000 0.330000 0.140000 Cp 22 P3F 0.258965 + 1107 H3A 3.895000 0.733000 1.026000 H1 22 P3F 0.053669 + 1108 C34 2.905000 -1.088000 0.575000 Cp 22 P3F 0.386837 + 1109 H4A 3.603000 -1.831000 0.214000 H1 22 P3F -0.001185 + 1110 C35 1.483000 -1.448000 0.081000 Cp 22 P3F 0.095205 + 1111 H5A 1.186000 -2.346000 0.597000 H1 22 P3F 0.160031 + 1112 C36 0.449000 -0.361000 0.455000 Cg 22 P3F -0.144660 + 1113 H6A -0.241000 -0.320000 -0.361000 H1 22 P3F 0.194357 + 1114 O35 1.952000 1.256000 -1.603000 Oh 22 P3F -0.685707 + 1115 HO5A 2.772000 1.527000 -2.056000 Ho 22 P3F 0.511733 + 1116 O36 4.367000 0.230000 -0.887000 Os 22 P3F -0.567725 + 1117 O37 2.864000 -1.077000 1.985000 Os 22 P3F -0.639266 + 1118 O38 1.491000 -1.761000 -1.322000 Os 22 P3F -0.534945 + 1119 O39 -0.284000 -0.737000 1.592000 Oh 22 P3F -0.545512 + 1120 HO9A 0.260000 -1.286000 2.186000 Ho 22 P3F 0.316832 + 1121 P36 5.327000 1.506000 -1.480000 P 22 P3F 1.435932 + 1122 O40 5.755000 2.323000 -0.281000 O2 22 P3F -0.979099 + 1123 O41 4.366000 2.244000 -2.414000 O2 22 P3F -0.979099 + 1124 O42 6.454000 0.803000 -2.203000 O2 22 P3F -0.979099 + 1125 P37 2.680000 -2.412000 3.037000 P 22 P3F 1.404854 + 1126 O43 1.162000 -2.533000 3.159000 O2 22 P3F -0.963067 + 1127 O44 3.339000 -3.589000 2.356000 O2 22 P3F -0.963067 + 1128 O45 3.355000 -1.955000 4.311000 O2 22 P3F -0.963067 + 1129 P38 0.289000 -1.981000 -2.376000 P 22 P3F 1.402270 + 1130 O48 0.704000 -2.984000 -3.396000 O2 22 P3F -0.919056 + 1131 O47 -1.038000 -2.199000 -1.715000 O2 22 P3F -0.919056 + 1132 O46 0.267000 -0.508000 -3.065000 oH 22 P3F -0.809924 + 1133 HOP8 0.873000 0.082000 -2.601000 hO 22 P3F 0.462320 + 1134 C11 -5.853000 -2.625000 0.444000 cC 23 P3H 0.676726 + 1135 O12 -6.563000 -3.074000 -0.405000 oC 23 P3H -0.624236 + 1136 O11 -5.444000 -1.370000 0.470000 oS 23 P3H -0.324227 + 1137 C1 -5.445000 -0.625000 -0.736000 cA 23 P3H -0.044023 + 1138 HR -6.461000 -0.405000 -1.036000 hE 23 P3H 0.081964 + 1139 HS -4.956000 -1.205000 -1.503000 hE 23 P3H 0.081964 + 1140 C2 -4.660000 0.650000 -0.502000 cA 23 P3H 0.483670 + 1141 HX -4.573000 1.166000 -1.447000 hE 23 P3H 0.055517 + 1142 C3 -3.280000 0.426000 0.113000 cA 23 P3H 0.371052 + 1143 HA -3.380000 0.037000 1.113000 hE 23 P3H -0.036072 + 1144 HB -2.745000 1.365000 0.157000 hE 23 P3H -0.036072 + 1145 O31 -2.588000 -0.471000 -0.707000 Os 23 P3H -0.634710 + 1146 P31 -2.102000 -1.963000 -0.201000 P 23 P3H 1.251672 + 1147 O32 -0.553000 -1.883000 -0.655000 Os 23 P3H -0.668951 + 1148 O33 -2.775000 -2.952000 -1.066000 O2 23 P3H -0.793455 + 1149 O34 -2.225000 -2.028000 1.270000 O2 23 P3H -0.793455 + 1150 O21 -5.348000 1.515000 0.423000 oS 23 P3H -0.622089 + 1151 C21 -6.294000 2.321000 0.019000 cC 23 P3H 0.789722 + 1152 O22 -6.772000 2.347000 -1.076000 oC 23 P3H -0.666879 + 1153 C31 0.481000 -1.171000 0.037000 Cp 23 P3H 0.644270 + 1154 H1A 0.372000 -1.350000 1.099000 H1 23 P3H -0.054116 + 1155 C32 0.404000 0.317000 -0.252000 Cg 23 P3H -0.000039 + 1156 H2A -0.466000 0.732000 0.228000 H1 23 P3H 0.134604 + 1157 C33 1.622000 1.049000 0.314000 Cp 23 P3H 0.052518 + 1158 H3A 1.557000 0.975000 1.395000 H1 23 P3H 0.105519 + 1159 C34 2.984000 0.444000 -0.113000 Cp 23 P3H 0.108043 + 1160 H4A 3.133000 0.609000 -1.171000 H1 23 P3H 0.142369 + 1161 C35 3.021000 -1.086000 0.174000 Cp 23 P3H 0.052518 + 1162 H5A 3.021000 -1.249000 1.245000 H1 23 P3H 0.105519 + 1163 C36 1.791000 -1.765000 -0.452000 Cg 23 P3H -0.000039 + 1164 H6A 1.852000 -1.634000 -1.527000 H1 23 P3H 0.134604 + 1165 O35 0.307000 0.530000 -1.650000 Oh 23 P3H -0.655388 + 1166 HO5A -0.453000 0.036000 -1.934000 Ho 23 P3H 0.410661 + 1167 O36 1.591000 2.412000 -0.048000 Os 23 P3H -0.503702 + 1168 O37 4.002000 1.059000 0.617000 Os 23 P3H -0.478410 + 1169 O38 4.181000 -1.641000 -0.386000 Os 23 P3H -0.503702 + 1170 O39 1.769000 -3.149000 -0.156000 Oh 23 P3H -0.655388 + 1171 HO9A 0.919000 -3.462000 -0.441000 Ho 23 P3H 0.410661 + 1172 P36 0.339000 3.453000 0.164000 P 23 P3H 1.420457 + 1173 O41 -0.328000 3.340000 -1.325000 oH 23 P3H -0.778505 + 1174 O40 0.893000 4.810000 0.316000 O2 23 P3H -0.870394 + 1175 O42 -0.606000 2.893000 1.163000 O2 23 P3H -0.870394 + 1176 P37 4.962000 2.287000 0.064000 P 23 P3H 1.391719 + 1177 O43 4.870000 2.321000 -1.413000 O2 23 P3H -0.868077 + 1178 O44 4.192000 3.576000 0.688000 oH 23 P3H -0.778177 + 1179 O45 6.253000 2.209000 0.774000 O2 23 P3H -0.868077 + 1180 P38 5.019000 -2.923000 0.223000 P 23 P3H 1.420457 + 1181 O48 6.415000 -2.844000 -0.241000 O2 23 P3H -0.870394 + 1182 O47 4.660000 -3.090000 1.650000 O2 23 P3H -0.870394 + 1183 O46 4.332000 -4.142000 -0.631000 oH 23 P3H -0.778505 + 1184 H19 3.300000 3.636000 0.360000 hO 23 P3H 0.468351 + 1185 H20 -0.117000 2.484000 -1.688000 hO 23 P3H 0.426657 + 1186 H21 3.395000 -4.097000 -0.471000 hO 23 P3H 0.426657 + 1187 C11 4.694000 -2.109000 0.405000 cC 24 PC1 0.742885 + 1188 O12 4.576000 -2.896000 -0.477000 oC 24 PC1 -0.577293 + 1189 O11 3.902000 -1.060000 0.566000 oS 24 PC1 -0.412240 + 1190 C1 2.854000 -0.874000 -0.377000 cA 24 PC1 -0.025704 + 1191 HR 2.157000 -1.696000 -0.314000 hE 24 PC1 0.087518 + 1192 HS 3.266000 -0.836000 -1.375000 hE 24 PC1 0.087518 + 1193 C2 2.165000 0.425000 -0.026000 cA 24 PC1 0.391894 + 1194 HX 1.911000 0.438000 1.021000 hE 24 PC1 -0.011668 + 1195 C3 0.919000 0.667000 -0.863000 cA 24 PC1 -0.003240 + 1196 HA 1.151000 0.638000 -1.919000 hE 24 PC1 0.068359 + 1197 HB 0.516000 1.643000 -0.623000 hE 24 PC1 0.068359 + 1198 O31 -0.006000 -0.339000 -0.535000 oT 24 PC1 -0.423344 + 1199 P31 -1.430000 -0.403000 -1.277000 pA 24 PC1 1.180843 + 1200 O32 -2.179000 0.795000 -0.393000 oT 24 PC1 -0.458127 + 1201 C31 -3.461000 1.133000 -0.789000 cA 24 PC1 0.223407 + 1202 H1A -3.812000 0.531000 -1.617000 hE 24 PC1 0.026299 + 1203 H1B -3.484000 2.165000 -1.127000 hE 24 PC1 0.026299 + 1204 C32 -4.434000 1.074000 0.381000 cA 24 PC1 0.048208 + 1205 H2A -5.396000 1.448000 0.054000 hX 24 PC1 0.048094 + 1206 H2B -4.072000 1.701000 1.183000 hX 24 PC1 0.048094 + 1207 N31 -4.708000 -0.280000 1.003000 nA 24 PC1 0.103551 + 1208 C33 -5.092000 -1.293000 -0.033000 cA 24 PC1 -0.242785 + 1209 H3A -5.889000 -0.883000 -0.638000 hX 24 PC1 0.134244 + 1210 H3B -5.442000 -2.177000 0.479000 hX 24 PC1 0.134244 + 1211 H3C -4.222000 -1.537000 -0.624000 hX 24 PC1 0.134244 + 1212 C34 -3.521000 -0.799000 1.770000 cA 24 PC1 -0.242785 + 1213 H4A -3.178000 -0.023000 2.437000 hX 24 PC1 0.134244 + 1214 H4B -2.753000 -1.088000 1.075000 hX 24 PC1 0.134244 + 1215 H4C -3.853000 -1.656000 2.338000 hX 24 PC1 0.134244 + 1216 C35 -5.839000 -0.102000 1.952000 cA 24 PC1 -0.242785 + 1217 H5A -5.570000 0.638000 2.691000 hX 24 PC1 0.134244 + 1218 H5B -6.044000 -1.043000 2.440000 hX 24 PC1 0.134244 + 1219 H5C -6.716000 0.223000 1.411000 hX 24 PC1 0.134244 + 1220 O33 -2.106000 -1.653000 -0.869000 oP 24 PC1 -0.727134 + 1221 O34 -1.344000 0.050000 -2.666000 oP 24 PC1 -0.727134 + 1222 O21 3.096000 1.472000 -0.298000 oH 24 PC1 -0.640388 + 1223 H21 2.820000 2.255000 0.247000 hO 24 PC1 0.375103 + 1224 O11 3.902000 -1.060000 0.566000 oH 25 PC2 -0.666461 + 1225 C1 2.854000 -0.874000 -0.377000 cA 25 PC2 0.220846 + 1226 HR 2.157000 -1.696000 -0.314000 hE 25 PC2 0.023639 + 1227 HS 3.266000 -0.836000 -1.375000 hE 25 PC2 0.023639 + 1228 C2 2.165000 0.425000 -0.026000 cA 25 PC2 0.237620 + 1229 HX 1.911000 0.438000 1.021000 hE 25 PC2 0.029872 + 1230 C3 0.919000 0.667000 -0.863000 cA 25 PC2 0.058278 + 1231 HA 1.151000 0.638000 -1.919000 hE 25 PC2 0.052578 + 1232 HB 0.516000 1.643000 -0.623000 hE 25 PC2 0.052578 + 1233 O31 -0.006000 -0.339000 -0.535000 oT 25 PC2 -0.448369 + 1234 P31 -1.430000 -0.403000 -1.277000 pA 25 PC2 1.223464 + 1235 O32 -2.179000 0.795000 -0.393000 oT 25 PC2 -0.454370 + 1236 C31 -3.461000 1.133000 -0.789000 cA 25 PC2 0.214589 + 1237 H1A -3.812000 0.531000 -1.617000 hE 25 PC2 0.030325 + 1238 H1B -3.484000 2.165000 -1.127000 hE 25 PC2 0.030325 + 1239 C32 -4.434000 1.074000 0.381000 cA 25 PC2 0.006032 + 1240 H2A -5.396000 1.448000 0.054000 hX 25 PC2 0.058877 + 1241 H2B -4.072000 1.701000 1.183000 hX 25 PC2 0.058877 + 1242 N31 -4.708000 -0.280000 1.003000 nA 25 PC2 0.110582 + 1243 C33 -5.092000 -1.293000 -0.033000 cA 25 PC2 -0.233051 + 1244 H3A -5.889000 -0.883000 -0.638000 hX 25 PC2 0.132489 + 1245 H3B -5.442000 -2.177000 0.479000 hX 25 PC2 0.132489 + 1246 H3C -4.222000 -1.537000 -0.624000 hX 25 PC2 0.132489 + 1247 C34 -3.521000 -0.799000 1.770000 cA 25 PC2 -0.233051 + 1248 H4A -3.178000 -0.023000 2.437000 hX 25 PC2 0.132489 + 1249 H4B -2.753000 -1.088000 1.075000 hX 25 PC2 0.132489 + 1250 H4C -3.853000 -1.656000 2.338000 hX 25 PC2 0.132489 + 1251 C35 -5.839000 -0.102000 1.952000 cA 25 PC2 -0.233051 + 1252 H5A -5.570000 0.638000 2.691000 hX 25 PC2 0.132489 + 1253 H5B -6.044000 -1.043000 2.440000 hX 25 PC2 0.132489 + 1254 H5C -6.716000 0.223000 1.411000 hX 25 PC2 0.132489 + 1255 O33 -2.106000 -1.653000 -0.869000 oP 25 PC2 -0.745809 + 1256 O34 -1.344000 0.050000 -2.666000 oP 25 PC2 -0.745809 + 1257 O21 3.096000 1.472000 -0.298000 oS 25 PC2 -0.421580 + 1258 C21 3.110000 2.550000 0.475000 cC 25 PC2 0.728661 + 1259 O22 2.361000 2.734000 1.379000 oC 25 PC2 -0.588475 + 1260 H11 4.514000 -1.584000 0.283000 hO 25 PC2 0.416841 + 1261 C11 2.180000 2.916000 -0.064000 cC 26 PE1 0.751933 + 1262 O12 1.200000 3.445000 0.353000 oC 26 PE1 -0.565338 + 1263 O11 2.300000 1.607000 -0.216000 oS 26 PE1 -0.408042 + 1264 C1 1.186000 0.802000 0.148000 cA 26 PE1 -0.046044 + 1265 HR 0.303000 1.147000 -0.363000 hE 26 PE1 0.090184 + 1266 HS 1.024000 0.875000 1.214000 hE 26 PE1 0.090184 + 1267 C2 1.505000 -0.620000 -0.256000 cA 26 PE1 0.392483 + 1268 HX 1.807000 -0.650000 -1.291000 hE 26 PE1 -0.006166 + 1269 C3 0.330000 -1.562000 -0.031000 cA 26 PE1 0.127927 + 1270 HA 0.041000 -1.559000 1.011000 hE 26 PE1 0.043081 + 1271 HB 0.613000 -2.568000 -0.305000 hE 26 PE1 0.043081 + 1272 O31 -0.732000 -1.142000 -0.860000 oT 26 PE1 -0.478925 + 1273 P31 -2.233000 -1.108000 -0.332000 pA 26 PE1 1.271834 + 1274 O32 -2.136000 0.266000 0.585000 oT 26 PE1 -0.308424 + 1275 C31 -3.161000 0.573000 1.479000 cA 26 PE1 -0.361683 + 1276 H1A -2.877000 1.497000 1.967000 hE 26 PE1 0.180576 + 1277 H1B -3.275000 -0.198000 2.230000 hE 26 PE1 0.180576 + 1278 C32 -4.503000 0.755000 0.756000 cA 26 PE1 0.525293 + 1279 H2A -4.980000 -0.196000 0.574000 hX 26 PE1 0.021198 + 1280 H2B -5.171000 1.387000 1.325000 hX 26 PE1 0.021198 + 1281 N31 -4.264000 1.356000 -0.588000 nA 26 PE1 -1.845296 + 1282 HN1A -3.660000 2.160000 -0.543000 hN 26 PE1 0.657678 + 1283 HN1B -3.765000 0.574000 -1.162000 hN 26 PE1 0.657678 + 1284 HN1C -5.121000 1.620000 -1.043000 hN 26 PE1 0.693831 + 1285 O33 -2.580000 -2.225000 0.542000 oP 26 PE1 -0.764298 + 1286 O34 -3.100000 -0.702000 -1.484000 oP 26 PE1 -0.713325 + 1287 O21 2.581000 -1.061000 0.567000 oH 26 PE1 -0.630395 + 1288 H21 3.263000 -1.510000 0.516000 hO 26 PE1 0.379204 + 1289 O11 2.300000 1.607000 -0.216000 oH 27 PE2 -0.582221 + 1290 C1 1.186000 0.802000 0.148000 cA 27 PE2 0.085410 + 1291 HR 0.303000 1.147000 -0.363000 hE 27 PE2 0.048721 + 1292 HS 1.024000 0.875000 1.214000 hE 27 PE2 0.048721 + 1293 C2 1.505000 -0.620000 -0.256000 cA 27 PE2 0.314298 + 1294 HX 1.807000 -0.650000 -1.291000 hE 27 PE2 0.031864 + 1295 C3 0.330000 -1.562000 -0.031000 cA 27 PE2 0.101769 + 1296 HA 0.041000 -1.559000 1.011000 hE 27 PE2 0.049575 + 1297 HB 0.613000 -2.568000 -0.305000 hE 27 PE2 0.049575 + 1298 O31 -0.732000 -1.142000 -0.860000 oT 27 PE2 -0.423364 + 1299 P31 -2.233000 -1.108000 -0.332000 pA 27 PE2 1.215718 + 1300 O32 -2.136000 0.266000 0.585000 oT 27 PE2 -0.403816 + 1301 C31 -3.161000 0.573000 1.479000 cA 27 PE2 -0.089673 + 1302 H1A -2.877000 1.497000 1.967000 hE 27 PE2 0.119067 + 1303 H1B -3.275000 -0.198000 2.230000 hE 27 PE2 0.119067 + 1304 C32 -4.503000 0.755000 0.756000 cA 27 PE2 0.508643 + 1305 H2A -4.980000 -0.196000 0.574000 hX 27 PE2 0.009554 + 1306 H2B -5.171000 1.387000 1.325000 hX 27 PE2 0.009554 + 1307 N31 -4.264000 1.356000 -0.588000 nA 27 PE2 -1.856454 + 1308 HN1A -3.660000 2.160000 -0.543000 hN 27 PE2 0.720944 + 1309 HN1B -3.765000 0.574000 -1.162000 hN 27 PE2 0.658581 + 1310 HN1C -5.121000 1.620000 -1.043000 hN 27 PE2 0.658581 + 1311 O33 -2.580000 -2.225000 0.542000 oP 27 PE2 -0.743439 + 1312 O34 -3.100000 -0.702000 -1.484000 oP 27 PE2 -0.744966 + 1313 O21 2.581000 -1.061000 0.567000 oS 27 PE2 -0.455646 + 1314 C21 3.515000 -1.854000 0.049000 cC 27 PE2 0.713495 + 1315 O22 3.494000 -2.265000 -1.064000 oC 27 PE2 -0.548176 + 1316 H11 2.064000 2.536000 0.040000 hO 27 PE2 0.384617 + 1317 C11 -4.477000 -1.887000 -0.726000 cC 28 PG1 0.731855 + 1318 O12 -4.371000 -2.868000 -0.063000 oC 28 PG1 -0.594566 + 1319 O11 -3.639000 -0.870000 -0.694000 oS 28 PG1 -0.442639 + 1320 C1 -2.503000 -0.948000 0.169000 cA 28 PG1 0.103072 + 1321 HR -1.828000 -1.711000 -0.185000 hE 28 PG1 0.045614 + 1322 HS -2.829000 -1.195000 1.169000 hE 28 PG1 0.045614 + 1323 C2 -1.828000 0.404000 0.138000 cA 28 PG1 0.468682 + 1324 HX -1.630000 0.695000 -0.880000 hE 28 PG1 -0.018057 + 1325 C3 -0.548000 0.426000 0.959000 cA 28 PG1 0.124410 + 1326 HA -0.757000 0.182000 1.993000 hE 28 PG1 0.023730 + 1327 HB -0.133000 1.429000 0.931000 hE 28 PG1 0.023730 + 1328 O31 0.324000 -0.498000 0.392000 oT 28 PG1 -0.527765 + 1329 P31 1.792000 -0.746000 1.103000 pA 28 PG1 1.262400 + 1330 O32 2.555000 0.619000 0.624000 oT 28 PG1 -0.538055 + 1331 C31 2.986000 0.770000 -0.700000 cA 28 PG1 0.229332 + 1332 H1A 2.623000 1.721000 -1.076000 hE 28 PG1 0.003632 + 1333 H1B 2.604000 -0.014000 -1.340000 hE 28 PG1 0.003632 + 1334 C32 4.505000 0.786000 -0.761000 cA 28 PG1 0.261872 + 1335 H2A 4.809724 1.042668 -1.774392 hE 28 PG1 -0.003942 + 1336 O35 4.944181 1.781217 0.136205 oH 28 PG1 -0.667400 + 1337 C33 5.133000 -0.547000 -0.395000 cA 28 PG1 0.235702 + 1338 H3A 4.915000 -0.793000 0.640000 hE 28 PG1 -0.006655 + 1339 H3B 4.728000 -1.331000 -1.018000 hE 28 PG1 -0.006655 + 1340 O33 2.390000 -1.892000 0.403000 oP 28 PG1 -0.773309 + 1341 O34 1.622000 -0.640000 2.557000 oP 28 PG1 -0.773309 + 1342 O21 -2.727000 1.350000 0.732000 oH 28 PG1 -0.729767 + 1343 O36 6.529000 -0.405000 -0.601000 oH 28 PG1 -0.657113 + 1344 H21 -3.311000 1.698000 0.008000 hO 28 PG1 0.386599 + 1345 HO5A 5.047780 2.625020 -0.377257 hO 28 PG1 0.399070 + 1346 HO6A 6.907000 0.005000 0.221000 hO 28 PG1 0.390285 + 1347 O11 -3.639000 -0.870000 -0.694000 oH 29 PG2 -0.649182 + 1348 C1 -2.503000 -0.948000 0.169000 cA 29 PG2 0.228363 + 1349 HR -1.828000 -1.711000 -0.185000 hE 29 PG2 -0.004863 + 1350 HS -2.829000 -1.195000 1.169000 hE 29 PG2 -0.004863 + 1351 C2 -1.828000 0.404000 0.138000 cA 29 PG2 0.335578 + 1352 HX -1.630000 0.695000 -0.880000 hE 29 PG2 -0.017970 + 1353 C3 -0.548000 0.426000 0.959000 cA 29 PG2 0.117451 + 1354 HA -0.757000 0.182000 1.993000 hE 29 PG2 0.022026 + 1355 HB -0.133000 1.429000 0.931000 hE 29 PG2 0.022026 + 1356 O31 0.324000 -0.498000 0.392000 oT 29 PG2 -0.453096 + 1357 P31 1.792000 -0.746000 1.103000 pA 29 PG2 1.193744 + 1358 O32 2.555000 0.619000 0.624000 oT 29 PG2 -0.470382 + 1359 C31 2.986000 0.770000 -0.700000 cA 29 PG2 0.131432 + 1360 H1A 2.623000 1.721000 -1.076000 hE 29 PG2 0.013723 + 1361 H1B 2.604000 -0.014000 -1.340000 hE 29 PG2 0.013723 + 1362 C32 4.505000 0.786000 -0.761000 cA 29 PG2 0.398788 + 1363 H2A 4.809728 1.035181 -1.776258 hE 29 PG2 -0.033747 + 1364 O35 4.943811 1.788025 0.128778 oH 29 PG2 -0.708795 + 1365 HO5A 4.363524 2.530332 0.015690 hO 29 PG2 0.388093 + 1366 C33 5.129000 -0.545000 -0.390000 cA 29 PG2 0.255616 + 1367 H3A 4.915000 -0.793000 0.640000 hE 29 PG2 -0.018391 + 1368 H3B 4.728000 -1.331000 -1.018000 hE 29 PG2 -0.018391 + 1369 O33 2.390000 -1.892000 0.403000 oP 29 PG2 -0.762310 + 1370 O34 1.622000 -0.640000 2.557000 oP 29 PG2 -0.762310 + 1371 O21 -2.727000 1.350000 0.732000 oS 29 PG2 -0.473672 + 1372 C21 -3.217000 2.354000 0.028000 cC 29 PG2 0.731275 + 1373 O22 -2.963000 2.587000 -1.110000 oC 29 PG2 -0.602365 + 1374 O36 6.529000 -0.405000 -0.601000 oH 29 PG2 -0.668922 + 1375 HO6A 6.945000 -1.239000 -0.440000 hO 29 PG2 0.397871 + 1376 H11 -4.061000 -1.768000 -0.674000 hO 29 PG2 0.399552 + 1377 C11 -1.933000 -2.573000 -0.273000 cC 30 PH3 0.690208 + 1378 O12 -2.855000 -3.256000 -0.611000 oC 30 PH3 -0.569694 + 1379 O11 -1.710000 -1.356000 -0.746000 oS 30 PH3 -0.379623 + 1380 C1 -2.605000 -0.800000 -1.684000 cA 30 PH3 0.084766 + 1381 HR -3.066000 -1.594000 -2.252000 hE 30 PH3 0.040791 + 1382 HS -2.004000 -0.190000 -2.345000 hE 30 PH3 0.040791 + 1383 C2 -3.687000 0.069000 -1.049000 cA 30 PH3 0.319370 + 1384 HX -4.431000 0.252000 -1.813000 hE 30 PH3 0.015062 + 1385 C3 -3.247000 1.428000 -0.539000 cA 30 PH3 -0.001115 + 1386 HA -4.115000 1.909000 -0.096000 hE 30 PH3 0.064022 + 1387 HB -2.942000 2.023000 -1.393000 hE 30 PH3 0.064022 + 1388 O31 -2.210000 1.338000 0.381000 Os 30 PH3 -0.380550 + 1389 P31 -1.547000 2.736000 0.938000 P 30 PH3 1.222635 + 1390 O32 -0.045000 2.195000 1.210000 Os 30 PH3 -0.548558 + 1391 O33 -2.136000 3.098000 2.231000 O2 30 PH3 -0.793977 + 1392 O34 -1.521000 3.676000 -0.211000 O2 30 PH3 -0.793977 + 1393 O21 -4.307000 -0.652000 0.013000 oS 30 PH3 -0.428221 + 1394 C21 -5.364000 -1.400000 -0.237000 cC 30 PH3 0.690969 + 1395 O22 -5.944000 -1.432000 -1.279000 oC 30 PH3 -0.585604 + 1396 C31 0.798000 1.602000 0.238000 Cp 30 PH3 0.142703 + 1397 H1A 0.200000 1.174000 -0.558000 H1 30 PH3 0.006997 + 1398 C32 1.530000 0.482000 0.980000 Cg 30 PH3 0.259134 + 1399 H2A 0.797000 -0.265000 1.266000 H1 30 PH3 0.092533 + 1400 C33 2.577000 -0.184000 0.097000 Cp 30 PH3 -0.008190 + 1401 H3A 2.058000 -0.632000 -0.746000 H1 30 PH3 0.062176 + 1402 C34 3.568000 0.851000 -0.421000 Cg 30 PH3 0.326410 + 1403 H4A 4.085000 1.277000 0.433000 H1 30 PH3 0.050007 + 1404 C35 2.838000 1.961000 -1.174000 Cg 30 PH3 -0.067294 + 1405 H5A 2.362000 1.512000 -2.045000 H1 30 PH3 0.090758 + 1406 C36 1.749000 2.649000 -0.360000 Cg 30 PH3 0.482531 + 1407 H6A 2.215000 3.189000 0.460000 H1 30 PH3 -0.024408 + 1408 O35 2.146000 1.004000 2.130000 Oh 30 PH3 -0.673618 + 1409 HO5A 1.492000 1.549000 2.553000 Ho 30 PH3 0.411208 + 1410 O36 3.236000 -1.182000 0.836000 Os 30 PH3 -0.495507 + 1411 O37 4.499000 0.329000 -1.332000 Oh 30 PH3 -0.671035 + 1412 HO7A 4.706000 -0.592000 -1.138000 Ho 30 PH3 0.398902 + 1413 O38 3.778000 2.915000 -1.603000 Oh 30 PH3 -0.623408 + 1414 HO8A 4.500000 2.414000 -1.961000 Ho 30 PH3 0.374560 + 1415 O39 1.079000 3.529000 -1.213000 Oh 30 PH3 -0.661605 + 1416 HO9A 0.208000 3.734000 -0.864000 Ho 30 PH3 0.374213 + 1417 P36 3.587000 -2.620000 0.153000 P 30 PH3 1.316443 + 1418 O40 4.195000 -3.355000 1.465000 oH 30 PH3 -0.714429 + 1419 O41 4.685000 -2.411000 -0.816000 O2 30 PH3 -0.796692 + 1420 O42 2.323000 -3.290000 -0.222000 O2 30 PH3 -0.796692 + 1421 HOP6 3.471000 -3.668000 1.990000 hO 30 PH3 0.392986 + 1422 C11 -5.375000 3.092000 0.369000 cC 31 PH4 0.740794 + 1423 O12 -6.026000 3.065000 1.365000 oC 31 PH4 -0.613261 + 1424 O11 -4.378000 2.277000 0.086000 oS 31 PH4 -0.410303 + 1425 C1 -4.096000 1.160000 0.928000 cA 31 PH4 0.078573 + 1426 HR -4.772000 1.167000 1.766000 hE 31 PH4 0.057937 + 1427 HS -3.078000 1.251000 1.271000 hE 31 PH4 0.057937 + 1428 C2 -4.233000 -0.122000 0.136000 cA 31 PH4 0.352796 + 1429 HX -4.032000 -0.927000 0.823000 hE 31 PH4 0.016055 + 1430 C3 -3.281000 -0.212000 -1.058000 cA 31 PH4 0.121774 + 1431 HA -3.411000 0.659000 -1.686000 hE 31 PH4 0.046393 + 1432 HB -3.524000 -1.094000 -1.637000 hE 31 PH4 0.046393 + 1433 O31 -1.950000 -0.248000 -0.648000 Os 31 PH4 -0.523817 + 1434 P31 -1.440000 -1.540000 0.237000 P 31 PH4 1.259912 + 1435 O32 0.094000 -1.596000 -0.238000 Os 31 PH4 -0.507452 + 1436 O33 -2.149000 -2.732000 -0.260000 O2 31 PH4 -0.779088 + 1437 O34 -1.500000 -1.140000 1.662000 O2 31 PH4 -0.779088 + 1438 O21 -5.573000 -0.193000 -0.347000 oS 31 PH4 -0.518921 + 1439 C21 -6.238000 -1.336000 -0.496000 cC 31 PH4 0.787641 + 1440 O22 -7.295000 -1.317000 -1.043000 oC 31 PH4 -0.634550 + 1441 C31 1.045000 -0.555000 -0.034000 Cp 31 PH4 0.160252 + 1442 H1A 0.532000 0.378000 0.168000 H1 31 PH4 0.050895 + 1443 C32 1.810000 -0.447000 -1.351000 Cg 31 PH4 0.186520 + 1444 H2A 1.123000 -0.095000 -2.117000 H1 31 PH4 0.044192 + 1445 C33 2.948000 0.547000 -1.212000 Cg 31 PH4 0.140594 + 1446 H3A 2.513000 1.508000 -0.934000 H1 31 PH4 0.045548 + 1447 C34 3.901000 0.136000 -0.101000 Cp 31 PH4 0.148925 + 1448 H4A 4.366000 -0.804000 -0.370000 H1 31 PH4 0.087260 + 1449 C35 3.194000 -0.024000 1.245000 Cg 31 PH4 0.140594 + 1450 H5A 2.875000 0.967000 1.565000 H1 31 PH4 0.045548 + 1451 C36 1.941000 -0.906000 1.161000 Cg 31 PH4 0.186520 + 1452 H6A 2.270000 -1.934000 1.035000 H1 31 PH4 0.044192 + 1453 O35 2.320000 -1.703000 -1.716000 Oh 31 PH4 -0.636438 + 1454 HO5A 1.616000 -2.328000 -1.588000 Ho 31 PH4 0.393486 + 1455 O36 3.613000 0.675000 -2.436000 Oh 31 PH4 -0.629193 + 1456 HO6A 4.508000 0.944000 -2.255000 Ho 31 PH4 0.397313 + 1457 O37 4.868000 1.163000 -0.008000 Os 31 PH4 -0.516953 + 1458 O38 4.051000 -0.576000 2.201000 Oh 31 PH4 -0.629193 + 1459 HO8A 4.964000 -0.459000 1.934000 Ho 31 PH4 0.397313 + 1460 O39 1.247000 -0.758000 2.367000 Oh 31 PH4 -0.636438 + 1461 HO9A 0.329000 -1.008000 2.260000 Ho 31 PH4 0.393486 + 1462 P37 6.466000 0.842000 0.060000 P 31 PH4 1.307595 + 1463 O44 6.645000 -0.311000 0.979000 O2 31 PH4 -0.804782 + 1464 O43 6.702000 0.259000 -1.457000 oH 31 PH4 -0.707422 + 1465 O45 7.166000 2.120000 0.192000 O2 31 PH4 -0.804782 + 1466 HOP7 6.721000 -0.688000 -1.412000 hO 31 PH4 0.395243 + 1467 C11 2.847000 1.974000 2.166000 cC 32 PH5 0.739172 + 1468 O12 3.867000 2.541000 1.914000 oC 32 PH5 -0.603343 + 1469 O11 2.629000 0.698000 1.897000 oS 32 PH5 -0.450916 + 1470 C1 3.638000 -0.052000 1.232000 cA 32 PH5 -0.039514 + 1471 HR 3.449000 -1.079000 1.497000 hE 32 PH5 0.100768 + 1472 HS 4.607000 0.265000 1.583000 hE 32 PH5 0.100768 + 1473 C2 3.554000 0.149000 -0.282000 cA 32 PH5 0.456628 + 1474 HX 3.196000 1.140000 -0.505000 hE 32 PH5 -0.011950 + 1475 C3 2.710000 -0.915000 -0.979000 cA 32 PH5 0.201677 + 1476 HA 3.268000 -1.843000 -0.968000 hE 32 PH5 -0.004986 + 1477 HB 2.530000 -0.627000 -2.008000 hE 32 PH5 -0.004986 + 1478 O31 1.497000 -1.048000 -0.304000 Os 32 PH5 -0.450141 + 1479 P31 1.026000 -2.557000 0.166000 P 32 PH5 1.277841 + 1480 O32 -0.126000 -2.123000 1.191000 Os 32 PH5 -0.515918 + 1481 O33 2.136000 -3.147000 0.934000 O2 32 PH5 -0.803135 + 1482 O34 0.455000 -3.254000 -1.011000 O2 32 PH5 -0.803135 + 1483 O21 4.903000 0.029000 -0.770000 oS 32 PH5 -0.501131 + 1484 C21 5.301000 0.734000 -1.810000 cC 32 PH5 0.724785 + 1485 O22 4.620000 1.479000 -2.437000 oC 32 PH5 -0.611955 + 1486 C31 -1.423000 -1.578000 1.019000 Cp 32 PH5 0.048553 + 1487 H1A -1.805000 -1.564000 2.035000 H1 32 PH5 0.029370 + 1488 C32 -2.380000 -2.444000 0.207000 Cg 32 PH5 0.423018 + 1489 H2A -2.358000 -3.454000 0.613000 H1 32 PH5 -0.082735 + 1490 C33 -3.802000 -1.890000 0.348000 Cg 32 PH5 0.248683 + 1491 H3A -4.140000 -2.004000 1.372000 H1 32 PH5 -0.017624 + 1492 C34 -3.885000 -0.403000 -0.017000 Cg 32 PH5 0.249272 + 1493 H4A -3.708000 -0.311000 -1.084000 H1 32 PH5 0.030988 + 1494 C35 -2.826000 0.429000 0.730000 Cp 32 PH5 0.117340 + 1495 H5A -3.055000 0.364000 1.790000 H1 32 PH5 0.009407 + 1496 C36 -1.413000 -0.126000 0.537000 Cg 32 PH5 0.388882 + 1497 H6A -1.135000 -0.059000 -0.507000 H1 32 PH5 -0.007594 + 1498 O35 -2.114000 -2.471000 -1.165000 Oh 32 PH5 -0.724386 + 1499 HO5 -1.221000 -2.807000 -1.304000 Ho 32 PH5 0.451278 + 1500 O36 -4.670000 -2.663000 -0.438000 Oh 32 PH5 -0.647134 + 1501 HO6 -4.247000 -2.745000 -1.285000 Ho 32 PH5 0.365505 + 1502 O37 -5.167000 0.045000 0.309000 Oh 32 PH5 -0.685668 + 1503 HO7 -5.283000 0.891000 -0.134000 Ho 32 PH5 0.404091 + 1504 O38 -2.892000 1.799000 0.437000 Os 32 PH5 -0.514092 + 1505 O39 -0.522000 0.618000 1.327000 Oh 32 PH5 -0.639190 + 1506 HO9 0.341000 0.245000 1.195000 Ho 32 PH5 0.368983 + 1507 P38 -3.311000 2.482000 -0.985000 P 32 PH5 1.301926 + 1508 O46 -2.940000 4.018000 -0.596000 oH 32 PH5 -0.699569 + 1509 O47 -4.783000 2.389000 -1.124000 O2 32 PH5 -0.806827 + 1510 O48 -2.392000 2.032000 -2.046000 O2 32 PH5 -0.806827 + 1511 HOP8 -2.010000 4.127000 -0.740000 hO 32 PH5 0.393820 + 1512 C11 4.983000 1.726000 -0.782000 cC 33 PI 0.727278 + 1513 O12 5.245000 2.097000 0.322000 oC 33 PI -0.594162 + 1514 O11 3.830000 1.212000 -1.150000 oS 33 PI -0.452915 + 1515 C1 2.791000 0.989000 -0.199000 cA 33 PI 0.144555 + 1516 HR 1.874000 1.113000 -0.751000 hE 33 PI 0.067492 + 1517 HS 2.859000 1.720000 0.593000 hE 33 PI 0.067492 + 1518 C2 2.827000 -0.422000 0.358000 cA 33 PI 0.330603 + 1519 HX 2.931000 -1.135000 -0.444000 hE 33 PI 0.080584 + 1520 C3 1.586000 -0.728000 1.200000 cA 33 PI 0.093607 + 1521 HA 1.642000 -0.144000 2.113000 hE 33 PI 0.059405 + 1522 HB 1.594000 -1.778000 1.456000 hE 33 PI 0.059405 + 1523 O31 0.398000 -0.375000 0.560000 Os 33 PI -0.562722 + 1524 P31 -0.167000 -1.259000 -0.740000 P 33 PI 1.322077 + 1525 O32 -1.694000 -1.434000 -0.209000 Os 33 PI -0.471537 + 1526 O33 0.516000 -2.554000 -0.698000 O2 33 PI -0.789774 + 1527 O34 -0.168000 -0.347000 -1.895000 O2 33 PI -0.789774 + 1528 O21 3.909000 -0.583000 1.286000 oS 33 PI -0.534275 + 1529 C21 5.093000 -1.018000 0.901000 cC 33 PI 0.739996 + 1530 O22 5.384000 -1.313000 -0.215000 oC 33 PI -0.585908 + 1531 C31 -2.727000 -0.558000 -0.567000 Cp 33 PI 0.118653 + 1532 H1A -2.776000 -0.467000 -1.645000 H1 33 PI 0.069393 + 1533 C32 -2.467000 0.835000 0.032000 Cg 33 PI 0.119693 + 1534 H2A -1.640000 1.279000 -0.505000 H1 33 PI 0.058812 + 1535 C33 -3.674000 1.759000 -0.067000 Cg 33 PI 0.124089 + 1536 H3A -3.863000 2.003000 -1.107000 H1 33 PI 0.057037 + 1537 C34 -4.918000 1.102000 0.510000 Cg 33 PI 0.236809 + 1538 H4A -4.758000 0.902000 1.566000 H1 33 PI 0.032417 + 1539 C35 -5.196000 -0.202000 -0.205000 Cg 33 PI 0.124089 + 1540 H5A -5.352000 0.004000 -1.263000 H1 33 PI 0.057037 + 1541 C36 -4.030000 -1.172000 -0.063000 Cg 33 PI 0.119693 + 1542 H6A -3.908000 -1.415000 0.989000 H1 33 PI 0.058812 + 1543 O35 -2.152000 0.755000 1.403000 Oh 33 PI -0.597289 + 1544 HO5A -1.313000 0.306000 1.469000 Ho 33 PI 0.397044 + 1545 O36 -3.434000 2.977000 0.596000 Oh 33 PI -0.589294 + 1546 HO6A -2.895000 2.770000 1.352000 Ho 33 PI 0.369765 + 1547 O37 -6.046000 1.933000 0.352000 Oh 33 PI -0.615463 + 1548 HO7A -5.801000 2.800000 0.652000 Ho 33 PI 0.367051 + 1549 O38 -6.339000 -0.836000 0.319000 Oh 33 PI -0.589294 + 1550 HO8A -7.044000 -0.201000 0.326000 Ho 33 PI 0.369765 + 1551 O39 -4.298000 -2.331000 -0.802000 Oh 33 PI -0.597289 + 1552 HO9A -5.120000 -2.685000 -0.490000 Ho 33 PI 0.397044 + 1553 C11 -5.629000 -1.687000 -0.882000 cC 34 PI3 0.788628 + 1554 O12 -6.384000 -0.794000 -1.121000 oC 34 PI3 -0.653419 + 1555 O11 -4.358000 -1.707000 -1.226000 oS 34 PI3 -0.507120 + 1556 C1 -3.731000 -0.505000 -1.666000 cA 34 PI3 0.303262 + 1557 HR -4.412000 0.051000 -2.294000 hE 34 PI3 -0.013102 + 1558 HS -2.879000 -0.833000 -2.244000 hE 34 PI3 -0.013102 + 1559 C2 -3.273000 0.329000 -0.481000 cA 34 PI3 0.451172 + 1560 HX -4.115000 0.598000 0.135000 hE 34 PI3 -0.039972 + 1561 C3 -2.539000 1.588000 -0.923000 cA 34 PI3 0.153433 + 1562 HA -3.149000 2.120000 -1.652000 hE 34 PI3 -0.009489 + 1563 HB -1.615000 1.308000 -1.412000 hE 34 PI3 -0.009489 + 1564 O31 -2.329000 2.387000 0.187000 Os 34 PI3 -0.470821 + 1565 P31 -0.890000 3.154000 0.442000 P 34 PI3 1.202277 + 1566 O32 0.068000 1.908000 0.795000 Os 34 PI3 -0.488480 + 1567 O33 -1.113000 3.992000 1.626000 O2 34 PI3 -0.797757 + 1568 O34 -0.446000 3.718000 -0.864000 O2 34 PI3 -0.797757 + 1569 O21 -2.358000 -0.438000 0.273000 oS 34 PI3 -0.535157 + 1570 C21 -2.705000 -1.007000 1.415000 cC 34 PI3 0.826003 + 1571 O22 -3.837000 -1.063000 1.807000 oC 34 PI3 -0.689138 + 1572 C31 1.081000 1.395000 -0.061000 Cp 34 PI3 0.057965 + 1573 H1A 0.770000 1.541000 -1.089000 H1 34 PI3 0.007584 + 1574 C32 1.271000 -0.106000 0.177000 Cg 34 PI3 0.613553 + 1575 H2A 0.337000 -0.601000 -0.084000 H1 34 PI3 -0.058273 + 1576 C33 2.341000 -0.641000 -0.793000 Cp 34 PI3 0.094053 + 1577 H3A 2.028000 -0.334000 -1.796000 H1 34 PI3 -0.057651 + 1578 C34 3.701000 0.048000 -0.561000 Cg 34 PI3 0.608170 + 1579 H4A 4.034000 -0.143000 0.452000 H1 34 PI3 -0.087007 + 1580 C35 3.502000 1.548000 -0.746000 Cg 34 PI3 0.024872 + 1581 H5A 3.220000 1.720000 -1.787000 H1 34 PI3 0.027045 + 1582 C36 2.399000 2.129000 0.145000 Cg 34 PI3 0.296535 + 1583 H6A 2.691000 2.017000 1.181000 H1 34 PI3 0.007850 + 1584 O35 1.582000 -0.330000 1.515000 Oh 34 PI3 -0.777609 + 1585 HO5A 2.087000 -1.174000 1.592000 Ho 34 PI3 0.453342 + 1586 O36 2.358000 -2.026000 -0.820000 Os 34 PI3 -0.649399 + 1587 O37 4.648000 -0.409000 -1.476000 Oh 34 PI3 -0.818982 + 1588 HO7A 4.811000 -1.331000 -1.198000 Ho 34 PI3 0.476680 + 1589 O38 4.696000 2.260000 -0.486000 Oh 34 PI3 -0.633804 + 1590 HO8A 4.427000 3.168000 -0.431000 Ho 34 PI3 0.344516 + 1591 O39 2.316000 3.518000 -0.131000 Oh 34 PI3 -0.616275 + 1592 HO9A 1.464000 3.724000 -0.518000 Ho 34 PI3 0.357303 + 1593 P36 3.300000 -3.058000 0.167000 P 34 PI3 1.403647 + 1594 O40 4.719000 -2.798000 -0.305000 O2 34 PI3 -0.924696 + 1595 O41 2.744000 -4.408000 -0.145000 O2 34 PI3 -0.924696 + 1596 O42 3.020000 -2.528000 1.566000 O2 34 PI3 -0.924696 + 1597 C11 6.425000 -0.722000 -1.497000 cC 35 PI4 0.664646 + 1598 O12 6.425000 0.177000 -2.285000 oC 35 PI4 -0.604926 + 1599 O11 5.412000 -1.107000 -0.770000 oS 35 PI4 -0.305928 + 1600 C1 4.149000 -0.415000 -0.788000 cA 35 PI4 -0.007522 + 1601 HR 3.397000 -1.181000 -0.822000 hE 35 PI4 0.071082 + 1602 HS 4.101000 0.214000 -1.659000 hE 35 PI4 0.071082 + 1603 C2 3.993000 0.311000 0.537000 cA 35 PI4 0.324833 + 1604 HX 4.166000 -0.435000 1.294000 hE 35 PI4 -0.017911 + 1605 C3 2.600000 0.921000 0.784000 cA 35 PI4 0.492384 + 1606 HA 2.498000 1.871000 0.276000 hE 35 PI4 -0.079552 + 1607 HB 2.496000 1.103000 1.847000 hE 35 PI4 -0.079552 + 1608 O31 1.596000 0.093000 0.313000 Os 35 PI4 -0.538559 + 1609 P31 1.182000 -1.254000 1.202000 P 35 PI4 1.234809 + 1610 O32 -0.168000 -1.634000 0.465000 Os 35 PI4 -0.690227 + 1611 O33 0.954000 -0.792000 2.594000 O2 35 PI4 -0.783731 + 1612 O34 2.212000 -2.276000 0.921000 O2 35 PI4 -0.783731 + 1613 O21 5.043000 1.263000 0.803000 oS 35 PI4 -0.485882 + 1614 C21 5.340000 2.398000 0.190000 cC 35 PI4 0.732377 + 1615 O22 6.179000 3.100000 0.668000 oC 35 PI4 -0.642085 + 1616 C31 -1.297000 -0.818000 0.140000 Cp 35 PI4 0.656500 + 1617 H1A -1.014000 -0.194000 -0.699000 H1 35 PI4 -0.130546 + 1618 C32 -1.748000 0.081000 1.310000 Cg 35 PI4 0.207562 + 1619 H2A -0.987000 0.838000 1.475000 H1 35 PI4 -0.016595 + 1620 C33 -3.046000 0.793000 0.930000 Cg 35 PI4 0.069711 + 1621 H3A -2.855000 1.393000 0.045000 H1 35 PI4 0.047277 + 1622 C34 -4.169000 -0.186000 0.573000 Cp 35 PI4 0.545602 + 1623 H4A -4.296000 -0.874000 1.409000 H1 35 PI4 -0.097692 + 1624 C35 -3.700000 -0.996000 -0.632000 Cg 35 PI4 0.069711 + 1625 H5A -3.496000 -0.306000 -1.446000 H1 35 PI4 0.047277 + 1626 C36 -2.415000 -1.756000 -0.299000 Cg 35 PI4 0.207562 + 1627 H6A -2.613000 -2.444000 0.519000 H1 35 PI4 -0.016595 + 1628 O35 -1.905000 -0.669000 2.484000 Oh 35 PI4 -0.675433 + 1629 HO5A -1.022000 -0.846000 2.800000 Ho 35 PI4 0.391355 + 1630 O36 -3.456000 1.642000 1.971000 Oh 35 PI4 -0.640642 + 1631 HO6A -4.367000 1.823000 1.754000 Ho 35 PI4 0.363272 + 1632 O37 -5.369000 0.497000 0.435000 Os 35 PI4 -0.638409 + 1633 O38 -4.632000 -1.949000 -1.060000 Oh 35 PI4 -0.640642 + 1634 HO8A -5.377000 -1.399000 -1.411000 Ho 35 PI4 0.363272 + 1635 O39 -2.021000 -2.489000 -1.439000 Oh 35 PI4 -0.675433 + 1636 HO9A -2.847000 -2.727000 -1.847000 Ho 35 PI4 0.391355 + 1637 P37 -5.978000 1.145000 -1.053000 P 35 PI4 1.340114 + 1638 O43 -6.307000 -0.120000 -1.842000 O2 35 PI4 -0.913397 + 1639 O44 -7.163000 1.925000 -0.579000 O2 35 PI4 -0.913397 + 1640 O45 -4.815000 1.923000 -1.605000 O2 35 PI4 -0.913397 + 1641 C11 6.509000 -0.596000 -0.970000 cC 36 PI5 0.738302 + 1642 O12 6.288000 -0.029000 -1.997000 oC 36 PI5 -0.625973 + 1643 O11 5.612000 -0.986000 -0.100000 oS 36 PI5 -0.462084 + 1644 C1 4.217000 -0.666000 -0.267000 cA 36 PI5 0.041566 + 1645 HR 3.669000 -1.498000 0.137000 hE 36 PI5 0.112718 + 1646 HS 3.993000 -0.565000 -1.317000 hE 36 PI5 0.112718 + 1647 C2 3.861000 0.600000 0.487000 cA 36 PI5 0.268795 + 1648 HX 4.395000 0.635000 1.423000 hE 36 PI5 0.096255 + 1649 C3 2.361000 0.711000 0.749000 cA 36 PI5 0.013120 + 1650 HA 1.808000 0.590000 -0.173000 hE 36 PI5 0.041551 + 1651 HB 2.150000 1.702000 1.134000 hE 36 PI5 0.041551 + 1652 O31 2.018000 -0.240000 1.702000 Os 36 PI5 -0.488279 + 1653 P31 0.903000 -1.396000 1.344000 P 36 PI5 1.211937 + 1654 O32 -0.317000 -0.414000 1.009000 Os 36 PI5 -0.334696 + 1655 O33 0.704000 -2.200000 2.557000 O2 36 PI5 -0.795106 + 1656 O34 1.360000 -2.030000 0.069000 O2 36 PI5 -0.795106 + 1657 O21 4.219000 1.754000 -0.299000 oS 36 PI5 -0.371925 + 1658 C21 5.365000 2.368000 -0.144000 cC 36 PI5 0.672891 + 1659 O22 6.205000 2.082000 0.657000 oC 36 PI5 -0.615767 + 1660 C31 -1.684000 -0.769000 0.795000 Cp 36 PI5 -0.019611 + 1661 H1A -2.116000 -1.015000 1.760000 H1 36 PI5 0.064350 + 1662 C32 -1.796000 -1.997000 -0.115000 Cg 36 PI5 0.033839 + 1663 H2A -1.397000 -2.858000 0.417000 H1 36 PI5 0.072443 + 1664 C33 -3.244000 -2.320000 -0.493000 Cg 36 PI5 0.293411 + 1665 H3A -3.783000 -2.633000 0.395000 H1 36 PI5 0.021129 + 1666 C34 -3.982000 -1.121000 -1.086000 Cg 36 PI5 0.222519 + 1667 H4A -3.534000 -0.888000 -2.053000 H1 36 PI5 0.011679 + 1668 C35 -3.844000 0.101000 -0.183000 Cp 36 PI5 0.211615 + 1669 H5A -4.389000 -0.123000 0.731000 H1 36 PI5 0.051569 + 1670 C36 -2.415000 0.452000 0.233000 Cg 36 PI5 0.161858 + 1671 H6A -1.868000 0.788000 -0.646000 H1 36 PI5 0.027569 + 1672 O35 -1.073000 -1.838000 -1.315000 Oh 36 PI5 -0.622167 + 1673 HO5A -0.144000 -1.885000 -1.082000 Ho 36 PI5 0.404602 + 1674 O36 -3.232000 -3.438000 -1.359000 Oh 36 PI5 -0.687349 + 1675 HO6A -2.566000 -3.239000 -2.005000 Ho 36 PI5 0.378128 + 1676 O37 -5.333000 -1.441000 -1.254000 Oh 36 PI5 -0.719850 + 1677 HO7A -5.874000 -0.704000 -0.916000 Ho 36 PI5 0.432585 + 1678 O38 -4.399000 1.198000 -0.825000 Os 36 PI5 -0.568921 + 1679 O39 -2.448000 1.467000 1.194000 Oh 36 PI5 -0.575713 + 1680 HO9A -3.345000 1.838000 1.227000 Ho 36 PI5 0.357656 + 1681 P38 -5.772000 1.876000 -0.060000 P 36 PI5 1.350295 + 1682 O46 -5.199000 2.249000 1.295000 O2 36 PI5 -0.921368 + 1683 O47 -6.714000 0.682000 -0.036000 O2 36 PI5 -0.921368 + 1684 O48 -6.165000 3.009000 -0.954000 O2 36 PI5 -0.921368 + 1685 C1 -3.699000 -1.908000 -0.665000 cA 37 SIT -0.027519 + 1686 H11 -3.267000 -2.345000 0.233000 hA 37 SIT 0.019889 + 1687 H12 -3.104000 -2.267000 -1.498000 hA 37 SIT 0.019889 + 1688 C2 -5.128000 -2.435000 -0.815000 cA 37 SIT -0.221198 + 1689 H21 -5.570000 -2.118000 -1.755000 hA 37 SIT 0.058211 + 1690 H22 -5.120000 -3.521000 -0.826000 hA 37 SIT 0.058211 + 1691 C3 -6.011000 -1.951000 0.320000 cA 37 SIT 0.455968 + 1692 H31 -5.627000 -2.350000 1.260000 hE 37 SIT -0.011214 + 1693 C4 -5.990000 -0.425000 0.387000 cA 37 SIT -0.381111 + 1694 H41 -6.488000 -0.054000 -0.505000 hA 37 SIT 0.122013 + 1695 H42 -6.576000 -0.081000 1.236000 hA 37 SIT 0.122013 + 1696 C5 -4.581000 0.125000 0.487000 cB 37 SIT -0.028865 + 1697 C6 -4.264000 0.977000 1.448000 cB 37 SIT -0.277617 + 1698 H61 -5.020000 1.255000 2.166000 hB 37 SIT 0.142082 + 1699 C7 -2.915000 1.609000 1.637000 cA 37 SIT -0.008947 + 1700 H71 -3.044000 2.665000 1.863000 hA 37 SIT 0.035065 + 1701 H72 -2.440000 1.175000 2.518000 hA 37 SIT 0.035065 + 1702 C8 -2.004000 1.440000 0.419000 cA 37 SIT 0.002113 + 1703 H81 -2.338000 2.141000 -0.341000 hA 37 SIT 0.047395 + 1704 C9 -2.123000 -0.003000 -0.123000 cA 37 SIT -0.231270 + 1705 H91 -1.899000 -0.651000 0.724000 hA 37 SIT 0.058964 + 1706 C10 -3.578000 -0.363000 -0.564000 cA 37 SIT 0.313563 + 1707 C11 -1.061000 -0.305000 -1.202000 cA 37 SIT -0.004110 + 1708 H111 -1.083000 -1.362000 -1.448000 hA 37 SIT 0.019859 + 1709 H112 -1.299000 0.220000 -2.119000 hA 37 SIT 0.019859 + 1710 C12 0.372000 0.060000 -0.783000 cA 37 SIT -0.143221 + 1711 H121 0.675000 -0.580000 0.043000 hA 37 SIT 0.014064 + 1712 H122 1.040000 -0.161000 -1.610000 hA 37 SIT 0.014064 + 1713 C13 0.481000 1.528000 -0.349000 cA 37 SIT 0.394580 + 1714 C14 -0.548000 1.735000 0.787000 cA 37 SIT 0.020129 + 1715 H141 -0.286000 1.000000 1.550000 hA 37 SIT 0.017858 + 1716 C15 -0.197000 3.107000 1.372000 cA 37 SIT -0.141875 + 1717 H151 -0.683000 3.903000 0.814000 hA 37 SIT 0.033550 + 1718 H152 -0.516000 3.209000 2.403000 hA 37 SIT 0.033550 + 1719 C16 1.343000 3.187000 1.227000 cA 37 SIT -0.088269 + 1720 H161 1.633000 4.101000 0.723000 hA 37 SIT 0.030413 + 1721 H162 1.837000 3.203000 2.193000 hA 37 SIT 0.030413 + 1722 C17 1.777000 1.928000 0.424000 cA 37 SIT -0.014518 + 1723 H171 1.907000 1.141000 1.162000 hA 37 SIT -0.026417 + 1724 C18 0.208000 2.454000 -1.552000 cA 37 SIT -0.468679 + 1725 H181 0.411000 3.493000 -1.326000 hA 37 SIT 0.100703 + 1726 H182 -0.820000 2.397000 -1.885000 hA 37 SIT 0.100703 + 1727 H183 0.828000 2.177000 -2.397000 hA 37 SIT 0.100703 + 1728 C19 -3.938000 0.273000 -1.927000 cA 37 SIT -0.277370 + 1729 H191 -3.375000 -0.177000 -2.736000 hA 37 SIT 0.066868 + 1730 H192 -3.741000 1.339000 -1.929000 hA 37 SIT 0.066868 + 1731 H193 -4.987000 0.143000 -2.161000 hA 37 SIT 0.066868 + 1732 C20 3.178000 2.089000 -0.226000 cD 37 SIT 0.193853 + 1733 H201 3.800000 2.441000 0.597000 hL 37 SIT -0.030488 + 1734 C21 3.316000 3.144000 -1.336000 cD 37 SIT -0.363341 + 1735 H211 4.367000 3.371000 -1.486000 hL 37 SIT 0.082407 + 1736 H212 2.819000 4.076000 -1.097000 hL 37 SIT 0.082407 + 1737 H213 2.927000 2.794000 -2.284000 hL 37 SIT 0.082407 + 1738 C22 3.825000 0.776000 -0.713000 cD 37 SIT -0.040551 + 1739 H221 3.232000 0.344000 -1.515000 hL 37 SIT 0.006984 + 1740 H222 4.776000 1.041000 -1.165000 hL 37 SIT 0.006984 + 1741 C23 4.068000 -0.291000 0.363000 cD 37 SIT -0.031666 + 1742 H231 4.512000 0.176000 1.241000 hL 37 SIT 0.008997 + 1743 H232 3.116000 -0.691000 0.686000 hL 37 SIT 0.008997 + 1744 C24 4.980000 -1.442000 -0.116000 cD 37 SIT -0.066704 + 1745 C25 4.724000 -2.774000 0.636000 cD 37 SIT 0.386372 + 1746 H251 5.525000 -3.454000 0.359000 hL 37 SIT -0.071024 + 1747 C26 3.419000 -3.443000 0.188000 cD 37 SIT -0.333335 + 1748 H261 2.546000 -2.849000 0.442000 hL 37 SIT 0.068204 + 1749 H262 3.300000 -4.411000 0.667000 hL 37 SIT 0.068204 + 1750 H263 3.408000 -3.604000 -0.886000 hL 37 SIT 0.068204 + 1751 C27 4.756000 -2.644000 2.165000 cD 37 SIT -0.333335 + 1752 H271 4.682000 -3.624000 2.626000 hL 37 SIT 0.068204 + 1753 H272 3.928000 -2.048000 2.535000 hL 37 SIT 0.068204 + 1754 H273 5.675000 -2.188000 2.519000 hL 37 SIT 0.068204 + 1755 O1 -7.309000 -2.441000 0.092000 oH 37 SIT -0.641285 + 1756 HO1 -7.873000 -2.195000 0.813000 hO 37 SIT 0.358123 + 1757 C28 6.463000 -1.019000 -0.071000 cD 37 SIT 0.113910 + 1758 H241 4.737000 -1.631000 -1.162000 hL 37 SIT 0.001714 + 1759 C29 7.401000 -1.870000 -0.929000 cD 37 SIT -0.134162 + 1760 H281 6.813000 -1.020000 0.958000 hL 37 SIT -0.023882 + 1761 H282 6.547000 0.011000 -0.406000 hL 37 SIT -0.023882 + 1762 H291 8.416000 -1.485000 -0.883000 hL 37 SIT 0.027328 + 1763 H292 7.093000 -1.860000 -1.971000 hL 37 SIT 0.027328 + 1764 H293 7.434000 -2.905000 -0.606000 hL 37 SIT 0.027328 + 1765 C1 -5.062000 -1.516000 -0.149000 cA 38 STI -0.053919 + 1766 H11 -5.073000 -1.582000 0.937000 hA 38 STI 0.023792 + 1767 H12 -4.642000 -2.447000 -0.511000 hA 38 STI 0.023792 + 1768 C2 -6.509000 -1.401000 -0.637000 cA 38 STI -0.189319 + 1769 H21 -6.563000 -1.438000 -1.721000 hA 38 STI 0.054674 + 1770 H22 -7.079000 -2.252000 -0.269000 hA 38 STI 0.054674 + 1771 C3 -7.148000 -0.102000 -0.165000 cA 38 STI 0.435218 + 1772 H31 -7.183000 -0.100000 0.925000 hE 38 STI -0.007207 + 1773 C4 -6.320000 1.088000 -0.627000 cA 38 STI -0.393318 + 1774 H41 -6.405000 1.143000 -1.710000 hA 38 STI 0.131931 + 1775 H42 -6.752000 2.001000 -0.236000 hA 38 STI 0.131931 + 1776 C5 -4.867000 0.974000 -0.205000 cB 38 STI -0.033001 + 1777 C6 -4.283000 1.971000 0.436000 cB 38 STI -0.260722 + 1778 H61 -4.864000 2.850000 0.669000 hB 38 STI 0.138059 + 1779 C7 -2.849000 1.995000 0.883000 cA 38 STI -0.019544 + 1780 H71 -2.419000 2.967000 0.655000 hA 38 STI 0.035502 + 1781 H72 -2.814000 1.904000 1.969000 hA 38 STI 0.035502 + 1782 C8 -2.009000 0.889000 0.241000 cA 38 STI -0.007755 + 1783 H81 -1.797000 1.185000 -0.782000 hA 38 STI 0.045297 + 1784 C9 -2.806000 -0.437000 0.242000 cA 38 STI -0.157432 + 1785 H91 -3.093000 -0.601000 1.281000 hA 38 STI 0.045422 + 1786 C10 -4.144000 -0.332000 -0.558000 cA 38 STI 0.276869 + 1787 C11 -1.939000 -1.648000 -0.171000 cA 38 STI -0.013502 + 1788 H111 -2.494000 -2.565000 0.000000 hA 38 STI 0.023520 + 1789 H112 -1.735000 -1.617000 -1.234000 hA 38 STI 0.023520 + 1790 C12 -0.603000 -1.745000 0.584000 cA 38 STI -0.164660 + 1791 H121 -0.798000 -1.953000 1.634000 hA 38 STI 0.020578 + 1792 H122 -0.041000 -2.593000 0.199000 hA 38 STI 0.020578 + 1793 C13 0.200000 -0.444000 0.472000 cA 38 STI 0.351756 + 1794 C14 -0.695000 0.693000 1.001000 cA 38 STI 0.025882 + 1795 H141 -0.983000 0.388000 2.008000 hA 38 STI 0.017882 + 1796 C15 0.265000 1.878000 1.172000 cA 38 STI -0.121866 + 1797 H151 0.371000 2.431000 0.243000 hA 38 STI 0.031227 + 1798 H152 -0.085000 2.585000 1.916000 hA 38 STI 0.031227 + 1799 C16 1.609000 1.217000 1.580000 cA 38 STI -0.116205 + 1800 H161 2.412000 1.545000 0.932000 hA 38 STI 0.037456 + 1801 H162 1.898000 1.492000 2.589000 hA 38 STI 0.037456 + 1802 C17 1.386000 -0.315000 1.466000 cA 38 STI 0.001954 + 1803 H171 0.971000 -0.640000 2.417000 hA 38 STI -0.024089 + 1804 C18 0.644000 -0.221000 -0.987000 cA 38 STI -0.443420 + 1805 H181 1.200000 -1.080000 -1.349000 hA 38 STI 0.097714 + 1806 H182 1.278000 0.651000 -1.094000 hA 38 STI 0.097714 + 1807 H183 -0.196000 -0.087000 -1.655000 hA 38 STI 0.097714 + 1808 C19 -3.905000 -0.369000 -2.085000 cA 38 STI -0.283666 + 1809 H191 -4.809000 -0.144000 -2.635000 hA 38 STI 0.070252 + 1810 H192 -3.570000 -1.347000 -2.412000 hA 38 STI 0.070252 + 1811 H193 -3.161000 0.361000 -2.385000 hA 38 STI 0.070252 + 1812 C20 2.658000 -1.191000 1.271000 cD 38 STI 0.244565 + 1813 H201 2.317000 -2.147000 0.874000 hL 38 STI -0.026337 + 1814 C21 3.292000 -1.491000 2.636000 cD 38 STI -0.322941 + 1815 H211 2.576000 -1.990000 3.281000 hL 38 STI 0.074986 + 1816 H212 3.611000 -0.583000 3.140000 hL 38 STI 0.074986 + 1817 H213 4.156000 -2.140000 2.537000 hL 38 STI 0.074986 + 1818 C22 3.662000 -0.652000 0.268000 cB 38 STI -0.221338 + 1819 C23 4.911000 -0.286000 0.498000 cB 38 STI -0.210439 + 1820 C24 5.865000 0.253000 -0.545000 cD 38 STI -0.064315 + 1821 C25 7.115000 -0.657000 -0.673000 cD 38 STI 0.404864 + 1822 H251 7.599000 -0.679000 0.304000 hL 38 STI -0.068508 + 1823 C26 8.138000 -0.129000 -1.686000 cD 38 STI -0.342950 + 1824 H261 7.703000 -0.061000 -2.680000 hL 38 STI 0.070651 + 1825 H262 8.988000 -0.801000 -1.747000 hL 38 STI 0.070651 + 1826 H263 8.519000 0.850000 -1.420000 hL 38 STI 0.070651 + 1827 C27 6.736000 -2.096000 -1.043000 cD 38 STI -0.342950 + 1828 H271 6.077000 -2.545000 -0.310000 hL 38 STI 0.070651 + 1829 H272 7.624000 -2.717000 -1.119000 hL 38 STI 0.070651 + 1830 H273 6.230000 -2.127000 -2.005000 hL 38 STI 0.070651 + 1831 O1 -8.445000 0.068000 -0.681000 oH 38 STI -0.646680 + 1832 HO1 -9.000000 -0.637000 -0.378000 hO 38 STI 0.364742 + 1833 C28 6.220000 1.728000 -0.205000 cD 38 STI 0.169925 + 1834 H241 5.349000 0.240000 -1.504000 hL 38 STI 0.020617 + 1835 C29 6.177000 2.679000 -1.404000 cD 38 STI -0.166325 + 1836 H281 7.198000 1.775000 0.267000 hL 38 STI -0.033390 + 1837 H282 5.517000 2.098000 0.535000 hL 38 STI -0.033390 + 1838 H291 5.180000 2.707000 -1.834000 hL 38 STI 0.033985 + 1839 H292 6.864000 2.383000 -2.189000 hL 38 STI 0.033985 + 1840 H293 6.434000 3.690000 -1.104000 hL 38 STI 0.033985 + 1841 H221 3.307000 -0.582000 -0.746000 hB 38 STI 0.125257 + 1842 H231 5.315000 -0.338000 1.498000 hB 38 STI 0.128803 +@BOND + 1 1 2 2 + 2 1 3 1 + 3 3 4 1 + 4 4 5 1 + 5 4 6 1 + 6 4 7 1 + 7 7 8 1 + 8 7 9 1 + 9 7 17 1 + 10 9 10 1 + 11 9 11 1 + 12 9 12 1 + 13 12 13 1 + 14 13 14 1 + 15 13 15 1 + 16 13 16 1 + 17 14 20 1 + 18 17 18 1 + 19 18 19 2 + 20 20 21 1 + 21 20 22 1 + 22 20 30 1 + 23 22 23 1 + 24 22 24 1 + 25 22 32 1 + 26 24 25 1 + 27 24 26 1 + 28 24 34 1 + 29 26 27 1 + 30 26 28 1 + 31 26 35 1 + 32 28 29 1 + 33 28 30 1 + 34 28 36 1 + 35 30 31 1 + 36 30 38 1 + 37 32 33 1 + 38 34 40 1 + 39 35 44 1 + 40 36 37 1 + 41 38 39 1 + 42 40 41 1 + 43 40 42 1 + 44 40 43 1 + 45 44 45 1 + 46 44 46 1 + 47 44 47 1 + 48 48 49 2 + 49 48 50 1 + 50 50 51 1 + 51 51 52 1 + 52 51 53 1 + 53 51 54 1 + 54 54 55 1 + 55 54 56 1 + 56 54 64 1 + 57 56 57 1 + 58 56 58 1 + 59 56 59 1 + 60 59 60 1 + 61 60 61 1 + 62 60 62 1 + 63 60 63 1 + 64 61 67 1 + 65 64 65 1 + 66 65 66 2 + 67 67 68 1 + 68 67 69 1 + 69 67 77 1 + 70 69 70 1 + 71 69 71 1 + 72 69 79 1 + 73 71 72 1 + 74 71 73 1 + 75 71 81 1 + 76 73 74 1 + 77 73 75 1 + 78 73 82 1 + 79 75 76 1 + 80 75 77 1 + 81 75 84 1 + 82 77 78 1 + 83 77 85 1 + 84 79 80 1 + 85 81 87 1 + 86 82 83 1 + 87 84 91 1 + 88 85 86 1 + 89 87 88 1 + 90 87 89 1 + 91 87 90 1 + 92 91 92 1 + 93 91 93 1 + 94 91 94 1 + 95 95 96 2 + 96 95 97 1 + 97 97 98 1 + 98 98 99 1 + 99 98 100 1 + 100 98 101 1 + 101 101 102 1 + 102 101 103 1 + 103 101 111 1 + 104 103 104 1 + 105 103 105 1 + 106 103 106 1 + 107 106 107 1 + 108 107 108 1 + 109 107 109 1 + 110 107 110 1 + 111 108 114 1 + 112 111 112 1 + 113 112 113 2 + 114 114 115 1 + 115 114 116 1 + 116 114 124 1 + 117 116 117 1 + 118 116 118 1 + 119 116 126 1 + 120 118 119 1 + 121 118 120 1 + 122 118 128 1 + 123 120 121 1 + 124 120 122 1 + 125 120 130 1 + 126 122 123 1 + 127 122 124 1 + 128 122 131 1 + 129 124 125 1 + 130 124 132 1 + 131 126 127 1 + 132 128 129 1 + 133 130 134 1 + 134 131 138 1 + 135 132 133 1 + 136 134 135 1 + 137 134 136 1 + 138 134 137 1 + 139 138 139 1 + 140 138 140 1 + 141 138 141 1 + 142 142 143 1 + 143 142 144 1 + 144 142 145 1 + 145 142 163 1 + 146 145 146 1 + 147 145 147 1 + 148 145 148 1 + 149 148 149 1 + 150 148 150 1 + 151 148 212 1 + 152 150 151 1 + 153 150 152 1 + 154 150 153 1 + 155 153 154 2 + 156 153 163 1 + 157 154 155 1 + 158 154 156 1 + 159 156 157 1 + 160 156 158 1 + 161 156 159 1 + 162 159 160 1 + 163 159 161 1 + 164 159 171 1 + 165 161 162 1 + 166 161 163 1 + 167 161 164 1 + 168 163 185 1 + 169 164 165 1 + 170 164 166 1 + 171 164 167 1 + 172 167 168 1 + 173 167 169 1 + 174 167 170 1 + 175 170 171 1 + 176 170 179 1 + 177 170 181 1 + 178 171 172 1 + 179 171 173 1 + 180 173 174 1 + 181 173 175 1 + 182 173 176 1 + 183 176 177 1 + 184 176 178 1 + 185 176 179 1 + 186 179 180 1 + 187 179 189 1 + 188 181 182 1 + 189 181 183 1 + 190 181 184 1 + 191 185 186 1 + 192 185 187 1 + 193 185 188 1 + 194 189 190 1 + 195 189 191 1 + 196 189 195 1 + 197 191 192 1 + 198 191 193 1 + 199 191 194 1 + 200 195 196 1 + 201 195 197 1 + 202 195 198 1 + 203 198 199 1 + 204 198 200 1 + 205 198 201 1 + 206 201 202 1 + 207 201 214 1 + 208 201 215 1 + 209 202 203 1 + 210 202 204 1 + 211 202 208 1 + 212 204 205 1 + 213 204 206 1 + 214 204 207 1 + 215 208 209 1 + 216 208 210 1 + 217 208 211 1 + 218 212 213 1 + 219 214 216 1 + 220 214 217 1 + 221 214 218 1 + 222 219 220 2 + 223 219 221 1 + 224 221 222 1 + 225 222 223 1 + 226 222 224 1 + 227 222 225 1 + 228 225 226 1 + 229 225 227 1 + 230 225 242 1 + 231 227 228 1 + 232 227 229 1 + 233 227 230 1 + 234 230 231 1 + 235 231 232 1 + 236 231 240 1 + 237 231 241 1 + 238 232 233 1 + 239 233 234 1 + 240 233 235 1 + 241 233 236 1 + 242 236 237 1 + 243 236 238 1 + 244 236 239 1 + 245 238 266 1 + 246 239 257 1 + 247 239 264 1 + 248 239 265 1 + 249 242 243 1 + 250 243 244 2 + 251 245 246 2 + 252 246 248 1 + 253 247 249 1 + 254 248 249 1 + 255 249 251 1 + 256 249 254 1 + 257 250 253 1 + 258 252 253 1 + 259 252 256 1 + 260 253 257 1 + 261 253 260 1 + 262 254 255 1 + 263 254 256 1 + 264 254 258 1 + 265 256 259 1 + 266 256 262 1 + 267 258 261 1 + 268 261 263 2 + 269 267 268 1 + 270 267 269 1 + 271 267 270 1 + 272 267 285 1 + 273 270 271 1 + 274 270 272 1 + 275 270 273 1 + 276 273 274 1 + 277 273 275 1 + 278 273 330 1 + 279 275 276 1 + 280 275 277 1 + 281 275 278 1 + 282 278 279 2 + 283 278 285 1 + 284 279 280 1 + 285 279 281 1 + 286 281 282 2 + 287 281 332 1 + 288 282 283 1 + 289 282 293 1 + 290 283 284 1 + 291 283 285 1 + 292 283 286 1 + 293 285 307 1 + 294 286 287 1 + 295 286 288 1 + 296 286 289 1 + 297 289 290 1 + 298 289 291 1 + 299 289 292 1 + 300 292 293 1 + 301 292 301 1 + 302 292 303 1 + 303 293 294 1 + 304 293 295 1 + 305 295 296 1 + 306 295 297 1 + 307 295 298 1 + 308 298 299 1 + 309 298 300 1 + 310 298 301 1 + 311 301 302 1 + 312 301 311 1 + 313 303 304 1 + 314 303 305 1 + 315 303 306 1 + 316 307 308 1 + 317 307 309 1 + 318 307 310 1 + 319 311 312 1 + 320 311 313 1 + 321 311 317 1 + 322 313 314 1 + 323 313 315 1 + 324 313 316 1 + 325 317 318 2 + 326 317 333 1 + 327 318 319 1 + 328 318 334 1 + 329 319 320 1 + 330 319 335 1 + 331 319 336 1 + 332 320 321 1 + 333 320 322 1 + 334 320 326 1 + 335 322 323 1 + 336 322 324 1 + 337 322 325 1 + 338 326 327 1 + 339 326 328 1 + 340 326 329 1 + 341 330 331 1 + 342 335 337 1 + 343 335 338 1 + 344 335 339 1 + 345 340 341 2 + 346 340 342 1 + 347 342 343 1 + 348 343 344 1 + 349 343 345 1 + 350 343 346 1 + 351 346 347 1 + 352 346 348 1 + 353 346 356 1 + 354 348 349 1 + 355 348 350 1 + 356 348 351 1 + 357 351 352 1 + 358 352 353 1 + 359 352 354 1 + 360 352 355 1 + 361 353 359 1 + 362 356 357 1 + 363 357 358 2 + 364 359 360 1 + 365 359 361 1 + 366 359 369 1 + 367 361 362 1 + 368 361 363 1 + 369 361 371 1 + 370 363 364 1 + 371 363 365 1 + 372 363 373 1 + 373 365 366 1 + 374 365 367 1 + 375 365 374 1 + 376 367 368 1 + 377 367 369 1 + 378 367 375 1 + 379 369 370 1 + 380 369 377 1 + 381 371 372 1 + 382 373 379 1 + 383 374 383 1 + 384 375 376 1 + 385 377 378 1 + 386 379 380 1 + 387 379 381 1 + 388 379 382 1 + 389 380 387 1 + 390 383 384 1 + 391 383 385 1 + 392 383 386 1 + 393 384 388 1 + 394 389 390 2 + 395 389 391 1 + 396 391 392 1 + 397 392 393 1 + 398 392 394 1 + 399 392 395 1 + 400 395 396 1 + 401 395 397 1 + 402 395 405 1 + 403 397 398 1 + 404 397 399 1 + 405 397 400 1 + 406 400 401 1 + 407 401 402 1 + 408 401 403 1 + 409 401 404 1 + 410 402 408 1 + 411 405 406 1 + 412 406 407 2 + 413 408 409 1 + 414 408 410 1 + 415 408 418 1 + 416 410 411 1 + 417 410 412 1 + 418 410 420 1 + 419 412 413 1 + 420 412 414 1 + 421 412 422 1 + 422 414 415 1 + 423 414 416 1 + 424 414 423 1 + 425 416 417 1 + 426 416 418 1 + 427 416 425 1 + 428 418 419 1 + 429 418 426 1 + 430 420 421 1 + 431 422 428 1 + 432 423 424 1 + 433 425 432 1 + 434 426 427 1 + 435 428 429 1 + 436 428 430 1 + 437 428 431 1 + 438 429 437 1 + 439 432 433 1 + 440 432 434 1 + 441 432 435 1 + 442 433 436 1 + 443 438 439 2 + 444 438 440 1 + 445 440 441 1 + 446 441 442 1 + 447 441 443 1 + 448 441 444 1 + 449 444 445 1 + 450 444 446 1 + 451 444 454 1 + 452 446 447 1 + 453 446 448 1 + 454 446 449 1 + 455 449 450 1 + 456 450 451 1 + 457 450 452 1 + 458 450 453 1 + 459 451 457 1 + 460 454 455 1 + 461 455 456 2 + 462 457 458 1 + 463 457 459 1 + 464 457 467 1 + 465 459 460 1 + 466 459 461 1 + 467 459 469 1 + 468 461 462 1 + 469 461 463 1 + 470 461 471 1 + 471 463 464 1 + 472 463 465 1 + 473 463 473 1 + 474 465 466 1 + 475 465 467 1 + 476 465 474 1 + 477 467 468 1 + 478 467 475 1 + 479 469 470 1 + 480 471 472 1 + 481 473 477 1 + 482 474 481 1 + 483 475 476 1 + 484 477 478 1 + 485 477 479 1 + 486 477 480 1 + 487 479 485 1 + 488 481 482 1 + 489 481 483 1 + 490 481 484 1 + 491 484 486 1 + 492 487 488 2 + 493 487 489 1 + 494 489 490 1 + 495 490 491 1 + 496 490 492 1 + 497 490 493 1 + 498 493 494 1 + 499 493 495 1 + 500 493 503 1 + 501 495 496 1 + 502 495 497 1 + 503 495 498 1 + 504 498 499 1 + 505 499 500 1 + 506 499 501 1 + 507 499 502 1 + 508 500 506 1 + 509 503 504 1 + 510 504 505 2 + 511 506 507 1 + 512 506 508 1 + 513 506 516 1 + 514 508 509 1 + 515 508 510 1 + 516 508 518 1 + 517 510 511 1 + 518 510 512 1 + 519 510 520 1 + 520 512 513 1 + 521 512 514 1 + 522 512 521 1 + 523 514 515 1 + 524 514 516 1 + 525 514 522 1 + 526 516 517 1 + 527 516 524 1 + 528 518 519 1 + 529 520 526 1 + 530 521 530 1 + 531 522 523 1 + 532 524 525 1 + 533 526 527 1 + 534 526 528 1 + 535 526 529 1 + 536 528 534 1 + 537 530 531 1 + 538 530 532 1 + 539 530 533 1 + 540 535 536 2 + 541 535 537 1 + 542 537 538 1 + 543 538 539 1 + 544 538 540 1 + 545 538 541 1 + 546 541 542 1 + 547 541 543 1 + 548 541 551 1 + 549 543 544 1 + 550 543 545 1 + 551 543 546 1 + 552 546 547 1 + 553 547 548 1 + 554 547 549 1 + 555 547 550 1 + 556 548 554 1 + 557 551 552 1 + 558 552 553 2 + 559 554 555 1 + 560 554 556 1 + 561 554 564 1 + 562 556 557 1 + 563 556 558 1 + 564 556 566 1 + 565 558 559 1 + 566 558 560 1 + 567 558 568 1 + 568 560 561 1 + 569 560 562 1 + 570 560 569 1 + 571 562 563 1 + 572 562 564 1 + 573 562 570 1 + 574 564 565 1 + 575 564 572 1 + 576 566 567 1 + 577 568 574 1 + 578 569 578 1 + 579 570 571 1 + 580 572 573 1 + 581 574 575 1 + 582 574 576 1 + 583 574 577 1 + 584 578 579 1 + 585 578 580 1 + 586 578 581 1 + 587 581 582 1 + 588 583 584 2 + 589 583 585 1 + 590 585 586 1 + 591 586 587 1 + 592 586 588 1 + 593 586 589 1 + 594 589 590 1 + 595 589 591 1 + 596 589 599 1 + 597 591 592 1 + 598 591 593 1 + 599 591 594 1 + 600 594 595 1 + 601 595 596 1 + 602 595 597 1 + 603 595 598 1 + 604 596 602 1 + 605 599 600 1 + 606 600 601 2 + 607 602 603 1 + 608 602 604 1 + 609 602 612 1 + 610 604 605 1 + 611 604 606 1 + 612 604 614 1 + 613 606 607 1 + 614 606 608 1 + 615 606 616 1 + 616 608 609 1 + 617 608 610 1 + 618 608 617 1 + 619 610 611 1 + 620 610 612 1 + 621 610 618 1 + 622 612 613 1 + 623 612 619 1 + 624 614 615 1 + 625 616 621 1 + 626 617 629 1 + 627 618 625 1 + 628 619 620 1 + 629 621 622 1 + 630 621 623 1 + 631 621 624 1 + 632 622 630 1 + 633 625 626 1 + 634 625 627 1 + 635 625 628 1 + 636 631 632 2 + 637 631 633 1 + 638 633 634 1 + 639 634 635 1 + 640 634 636 1 + 641 634 637 1 + 642 637 638 1 + 643 637 639 1 + 644 637 647 1 + 645 639 640 1 + 646 639 641 1 + 647 639 642 1 + 648 642 643 1 + 649 643 644 1 + 650 643 645 1 + 651 643 646 1 + 652 644 650 1 + 653 647 648 1 + 654 648 649 2 + 655 650 651 1 + 656 650 652 1 + 657 650 660 1 + 658 652 653 1 + 659 652 654 1 + 660 652 662 1 + 661 654 655 1 + 662 654 656 1 + 663 654 664 1 + 664 656 657 1 + 665 656 658 1 + 666 656 665 1 + 667 658 659 1 + 668 658 660 1 + 669 658 666 1 + 670 660 661 1 + 671 660 667 1 + 672 662 663 1 + 673 664 669 1 + 674 665 678 1 + 675 666 673 1 + 676 667 668 1 + 677 669 670 1 + 678 669 671 1 + 679 669 672 1 + 680 673 674 1 + 681 673 675 1 + 682 673 676 1 + 683 676 677 1 + 684 679 680 2 + 685 679 681 1 + 686 681 682 1 + 687 682 683 1 + 688 682 684 1 + 689 682 685 1 + 690 685 686 1 + 691 685 687 1 + 692 685 695 1 + 693 687 688 1 + 694 687 689 1 + 695 687 690 1 + 696 690 691 1 + 697 691 692 1 + 698 691 693 1 + 699 691 694 1 + 700 692 698 1 + 701 695 696 1 + 702 696 697 2 + 703 698 699 1 + 704 698 700 1 + 705 698 708 1 + 706 700 701 1 + 707 700 702 1 + 708 700 710 1 + 709 702 703 1 + 710 702 704 1 + 711 702 712 1 + 712 704 705 1 + 713 704 706 1 + 714 704 713 1 + 715 706 707 1 + 716 706 708 1 + 717 706 714 1 + 718 708 709 1 + 719 708 715 1 + 720 710 711 1 + 721 712 721 1 + 722 713 722 1 + 723 714 717 1 + 724 715 716 1 + 725 717 718 1 + 726 717 719 1 + 727 717 720 1 + 728 722 723 1 + 729 722 724 1 + 730 722 725 1 + 731 723 726 1 + 732 727 728 2 + 733 727 729 1 + 734 729 730 1 + 735 730 731 1 + 736 730 732 1 + 737 730 733 1 + 738 733 734 1 + 739 733 735 1 + 740 733 743 1 + 741 735 736 1 + 742 735 737 1 + 743 735 738 1 + 744 738 739 1 + 745 739 740 1 + 746 739 741 1 + 747 739 742 1 + 748 740 746 1 + 749 743 744 1 + 750 744 745 2 + 751 746 747 1 + 752 746 748 1 + 753 746 756 1 + 754 748 749 1 + 755 748 750 1 + 756 748 758 1 + 757 750 751 1 + 758 750 752 1 + 759 750 760 1 + 760 752 753 1 + 761 752 754 1 + 762 752 761 1 + 763 754 755 1 + 764 754 756 1 + 765 754 762 1 + 766 756 757 1 + 767 756 763 1 + 768 758 759 1 + 769 760 769 1 + 770 761 770 1 + 771 762 765 1 + 772 763 764 1 + 773 765 766 1 + 774 765 767 1 + 775 765 768 1 + 776 766 774 1 + 777 770 771 1 + 778 770 772 1 + 779 770 773 1 + 780 775 776 2 + 781 775 777 1 + 782 777 778 1 + 783 778 779 1 + 784 778 780 1 + 785 778 781 1 + 786 781 782 1 + 787 781 783 1 + 788 781 791 1 + 789 783 784 1 + 790 783 785 1 + 791 783 786 1 + 792 786 787 1 + 793 787 788 1 + 794 787 789 1 + 795 787 790 1 + 796 788 794 1 + 797 791 792 1 + 798 792 793 2 + 799 794 795 1 + 800 794 796 1 + 801 794 804 1 + 802 796 797 1 + 803 796 798 1 + 804 796 806 1 + 805 798 799 1 + 806 798 800 1 + 807 798 808 1 + 808 800 801 1 + 809 800 802 1 + 810 800 809 1 + 811 802 803 1 + 812 802 804 1 + 813 802 810 1 + 814 804 805 1 + 815 804 811 1 + 816 806 807 1 + 817 808 813 1 + 818 809 817 1 + 819 810 821 1 + 820 811 812 1 + 821 813 814 1 + 822 813 815 1 + 823 813 816 1 + 824 817 818 1 + 825 817 819 1 + 826 817 820 1 + 827 821 822 1 + 828 821 823 1 + 829 821 824 1 + 830 825 826 1 + 831 825 827 1 + 832 827 828 1 + 833 828 829 1 + 834 828 830 1 + 835 828 831 1 + 836 831 832 1 + 837 831 833 1 + 838 831 841 1 + 839 833 834 1 + 840 833 835 1 + 841 833 836 1 + 842 836 837 1 + 843 837 838 1 + 844 837 839 1 + 845 837 840 1 + 846 838 844 1 + 847 841 842 1 + 848 842 843 1 + 849 844 845 1 + 850 844 846 1 + 851 844 854 1 + 852 846 847 1 + 853 846 848 1 + 854 846 856 1 + 855 848 849 1 + 856 848 850 1 + 857 848 858 1 + 858 850 851 1 + 859 850 852 1 + 860 850 859 1 + 861 852 853 1 + 862 852 854 1 + 863 852 860 1 + 864 854 855 1 + 865 854 861 1 + 866 856 857 1 + 867 858 863 1 + 868 859 867 1 + 869 860 871 1 + 870 861 862 1 + 871 863 864 1 + 872 863 865 1 + 873 863 866 1 + 874 865 875 1 + 875 867 868 1 + 876 867 869 1 + 877 867 870 1 + 878 869 876 1 + 879 871 872 1 + 880 871 873 1 + 881 871 874 1 + 882 877 878 2 + 883 877 879 1 + 884 879 880 1 + 885 880 881 1 + 886 880 882 1 + 887 880 883 1 + 888 883 884 1 + 889 883 885 1 + 890 883 893 1 + 891 885 886 1 + 892 885 887 1 + 893 885 888 1 + 894 888 889 1 + 895 889 890 1 + 896 889 891 1 + 897 889 892 1 + 898 890 896 1 + 899 893 894 1 + 900 894 895 2 + 901 896 897 1 + 902 896 898 1 + 903 896 906 1 + 904 898 899 1 + 905 898 900 1 + 906 898 908 1 + 907 900 901 1 + 908 900 902 1 + 909 900 910 1 + 910 902 903 1 + 911 902 904 1 + 912 902 911 1 + 913 904 905 1 + 914 904 906 1 + 915 904 912 1 + 916 906 907 1 + 917 906 913 1 + 918 908 909 1 + 919 910 915 1 + 920 911 919 1 + 921 912 923 1 + 922 913 914 1 + 923 915 916 1 + 924 915 917 1 + 925 915 918 1 + 926 917 927 1 + 927 919 920 1 + 928 919 921 1 + 929 919 922 1 + 930 923 924 1 + 931 923 925 1 + 932 923 926 1 + 933 926 928 1 + 934 929 930 2 + 935 929 931 1 + 936 931 932 1 + 937 932 933 1 + 938 932 934 1 + 939 932 935 1 + 940 935 936 1 + 941 935 937 1 + 942 935 945 1 + 943 937 938 1 + 944 937 939 1 + 945 937 940 1 + 946 940 941 1 + 947 941 942 1 + 948 941 943 1 + 949 941 944 1 + 950 942 948 1 + 951 945 946 1 + 952 946 947 2 + 953 948 949 1 + 954 948 950 1 + 955 948 958 1 + 956 950 951 1 + 957 950 952 1 + 958 950 960 1 + 959 952 953 1 + 960 952 954 1 + 961 952 962 1 + 962 954 955 1 + 963 954 956 1 + 964 954 963 1 + 965 956 957 1 + 966 956 958 1 + 967 956 964 1 + 968 958 959 1 + 969 958 965 1 + 970 960 961 1 + 971 962 967 1 + 972 963 971 1 + 973 964 975 1 + 974 965 966 1 + 975 967 968 1 + 976 967 969 1 + 977 967 970 1 + 978 971 972 1 + 979 971 973 1 + 980 971 974 1 + 981 972 980 1 + 982 975 976 1 + 983 975 977 1 + 984 975 978 1 + 985 978 979 1 + 986 981 982 2 + 987 981 983 1 + 988 983 984 1 + 989 984 985 1 + 990 984 986 1 + 991 984 987 1 + 992 987 988 1 + 993 987 989 1 + 994 987 997 1 + 995 989 990 1 + 996 989 991 1 + 997 989 992 1 + 998 992 993 1 + 999 993 994 1 + 1000 993 995 1 + 1001 993 996 1 + 1002 994 1000 1 + 1003 997 998 1 + 1004 998 999 2 + 1005 1000 1001 1 + 1006 1000 1002 1 + 1007 1000 1010 1 + 1008 1002 1003 1 + 1009 1002 1004 1 + 1010 1002 1012 1 + 1011 1004 1005 1 + 1012 1004 1006 1 + 1013 1004 1014 1 + 1014 1006 1007 1 + 1015 1006 1008 1 + 1016 1006 1015 1 + 1017 1008 1009 1 + 1018 1008 1010 1 + 1019 1008 1016 1 + 1020 1010 1011 1 + 1021 1010 1017 1 + 1022 1012 1013 1 + 1023 1014 1019 1 + 1024 1015 1023 1 + 1025 1016 1027 1 + 1026 1017 1018 1 + 1027 1019 1020 1 + 1028 1019 1021 1 + 1029 1019 1022 1 + 1030 1021 1031 1 + 1031 1023 1024 1 + 1032 1023 1025 1 + 1033 1023 1026 1 + 1034 1027 1028 1 + 1035 1027 1029 1 + 1036 1027 1030 1 + 1037 1032 1033 2 + 1038 1032 1034 1 + 1039 1034 1035 1 + 1040 1035 1036 1 + 1041 1035 1037 1 + 1042 1035 1038 1 + 1043 1038 1039 1 + 1044 1038 1040 1 + 1045 1038 1048 1 + 1046 1040 1041 1 + 1047 1040 1042 1 + 1048 1040 1043 1 + 1049 1043 1044 1 + 1050 1044 1045 1 + 1051 1044 1046 1 + 1052 1044 1047 1 + 1053 1045 1051 1 + 1054 1048 1049 1 + 1055 1049 1050 2 + 1056 1051 1052 1 + 1057 1051 1053 1 + 1058 1051 1061 1 + 1059 1053 1054 1 + 1060 1053 1055 1 + 1061 1053 1063 1 + 1062 1055 1056 1 + 1063 1055 1057 1 + 1064 1055 1065 1 + 1065 1057 1058 1 + 1066 1057 1059 1 + 1067 1057 1066 1 + 1068 1059 1060 1 + 1069 1059 1061 1 + 1070 1059 1067 1 + 1071 1061 1062 1 + 1072 1061 1068 1 + 1073 1063 1064 1 + 1074 1065 1070 1 + 1075 1066 1074 1 + 1076 1067 1078 1 + 1077 1068 1069 1 + 1078 1070 1071 1 + 1079 1070 1072 1 + 1080 1070 1073 1 + 1081 1074 1075 1 + 1082 1074 1076 1 + 1083 1074 1077 1 + 1084 1076 1082 1 + 1085 1078 1079 1 + 1086 1078 1080 1 + 1087 1078 1081 1 + 1088 1083 1084 2 + 1089 1083 1085 1 + 1090 1085 1086 1 + 1091 1086 1087 1 + 1092 1086 1088 1 + 1093 1086 1089 1 + 1094 1089 1090 1 + 1095 1089 1091 1 + 1096 1089 1099 1 + 1097 1091 1092 1 + 1098 1091 1093 1 + 1099 1091 1094 1 + 1100 1094 1095 1 + 1101 1095 1096 1 + 1102 1095 1097 1 + 1103 1095 1098 1 + 1104 1096 1102 1 + 1105 1099 1100 1 + 1106 1100 1101 2 + 1107 1102 1103 1 + 1108 1102 1104 1 + 1109 1102 1112 1 + 1110 1104 1105 1 + 1111 1104 1106 1 + 1112 1104 1114 1 + 1113 1106 1107 1 + 1114 1106 1108 1 + 1115 1106 1116 1 + 1116 1108 1109 1 + 1117 1108 1110 1 + 1118 1108 1117 1 + 1119 1110 1111 1 + 1120 1110 1112 1 + 1121 1110 1118 1 + 1122 1112 1113 1 + 1123 1112 1119 1 + 1124 1114 1115 1 + 1125 1116 1121 1 + 1126 1117 1125 1 + 1127 1118 1129 1 + 1128 1119 1120 1 + 1129 1121 1122 1 + 1130 1121 1123 1 + 1131 1121 1124 1 + 1132 1125 1126 1 + 1133 1125 1127 1 + 1134 1125 1128 1 + 1135 1129 1130 1 + 1136 1129 1131 1 + 1137 1129 1132 1 + 1138 1132 1133 1 + 1139 1134 1135 2 + 1140 1134 1136 1 + 1141 1136 1137 1 + 1142 1137 1138 1 + 1143 1137 1139 1 + 1144 1137 1140 1 + 1145 1140 1141 1 + 1146 1140 1142 1 + 1147 1140 1150 1 + 1148 1142 1143 1 + 1149 1142 1144 1 + 1150 1142 1145 1 + 1151 1145 1146 1 + 1152 1146 1147 1 + 1153 1146 1148 1 + 1154 1146 1149 1 + 1155 1147 1153 1 + 1156 1150 1151 1 + 1157 1151 1152 2 + 1158 1153 1154 1 + 1159 1153 1155 1 + 1160 1153 1163 1 + 1161 1155 1156 1 + 1162 1155 1157 1 + 1163 1155 1165 1 + 1164 1157 1158 1 + 1165 1157 1159 1 + 1166 1157 1167 1 + 1167 1159 1160 1 + 1168 1159 1161 1 + 1169 1159 1168 1 + 1170 1161 1162 1 + 1171 1161 1163 1 + 1172 1161 1169 1 + 1173 1163 1164 1 + 1174 1163 1170 1 + 1175 1165 1166 1 + 1176 1167 1172 1 + 1177 1168 1176 1 + 1178 1169 1180 1 + 1179 1170 1171 1 + 1180 1172 1173 1 + 1181 1172 1174 1 + 1182 1172 1175 1 + 1183 1173 1185 1 + 1184 1176 1177 1 + 1185 1176 1178 1 + 1186 1176 1179 1 + 1187 1178 1184 1 + 1188 1180 1181 1 + 1189 1180 1182 1 + 1190 1180 1183 1 + 1191 1183 1186 1 + 1192 1187 1188 2 + 1193 1187 1189 1 + 1194 1189 1190 1 + 1195 1190 1191 1 + 1196 1190 1192 1 + 1197 1190 1193 1 + 1198 1193 1194 1 + 1199 1193 1195 1 + 1200 1193 1222 1 + 1201 1195 1196 1 + 1202 1195 1197 1 + 1203 1195 1198 1 + 1204 1198 1199 1 + 1205 1199 1200 1 + 1206 1199 1220 1 + 1207 1199 1221 1 + 1208 1200 1201 1 + 1209 1201 1202 1 + 1210 1201 1203 1 + 1211 1201 1204 1 + 1212 1204 1205 1 + 1213 1204 1206 1 + 1214 1204 1207 1 + 1215 1207 1208 1 + 1216 1207 1212 1 + 1217 1207 1216 1 + 1218 1208 1209 1 + 1219 1208 1210 1 + 1220 1208 1211 1 + 1221 1212 1213 1 + 1222 1212 1214 1 + 1223 1212 1215 1 + 1224 1216 1217 1 + 1225 1216 1218 1 + 1226 1216 1219 1 + 1227 1222 1223 1 + 1228 1224 1225 1 + 1229 1224 1260 1 + 1230 1225 1226 1 + 1231 1225 1227 1 + 1232 1225 1228 1 + 1233 1228 1229 1 + 1234 1228 1230 1 + 1235 1228 1257 1 + 1236 1230 1231 1 + 1237 1230 1232 1 + 1238 1230 1233 1 + 1239 1233 1234 1 + 1240 1234 1235 1 + 1241 1234 1255 1 + 1242 1234 1256 1 + 1243 1235 1236 1 + 1244 1236 1237 1 + 1245 1236 1238 1 + 1246 1236 1239 1 + 1247 1239 1240 1 + 1248 1239 1241 1 + 1249 1239 1242 1 + 1250 1242 1243 1 + 1251 1242 1247 1 + 1252 1242 1251 1 + 1253 1243 1244 1 + 1254 1243 1245 1 + 1255 1243 1246 1 + 1256 1247 1248 1 + 1257 1247 1249 1 + 1258 1247 1250 1 + 1259 1251 1252 1 + 1260 1251 1253 1 + 1261 1251 1254 1 + 1262 1257 1258 1 + 1263 1258 1259 2 + 1264 1261 1262 2 + 1265 1261 1263 1 + 1266 1263 1264 1 + 1267 1264 1265 1 + 1268 1264 1266 1 + 1269 1264 1267 1 + 1270 1267 1268 1 + 1271 1267 1269 1 + 1272 1267 1287 1 + 1273 1269 1270 1 + 1274 1269 1271 1 + 1275 1269 1272 1 + 1276 1272 1273 1 + 1277 1273 1274 1 + 1278 1273 1285 1 + 1279 1273 1286 1 + 1280 1274 1275 1 + 1281 1275 1276 1 + 1282 1275 1277 1 + 1283 1275 1278 1 + 1284 1278 1279 1 + 1285 1278 1280 1 + 1286 1278 1281 1 + 1287 1281 1282 1 + 1288 1281 1283 1 + 1289 1281 1284 1 + 1290 1287 1288 1 + 1291 1289 1290 1 + 1292 1289 1316 1 + 1293 1290 1291 1 + 1294 1290 1292 1 + 1295 1290 1293 1 + 1296 1293 1294 1 + 1297 1293 1295 1 + 1298 1293 1313 1 + 1299 1295 1296 1 + 1300 1295 1297 1 + 1301 1295 1298 1 + 1302 1298 1299 1 + 1303 1299 1300 1 + 1304 1299 1311 1 + 1305 1299 1312 1 + 1306 1300 1301 1 + 1307 1301 1302 1 + 1308 1301 1303 1 + 1309 1301 1304 1 + 1310 1304 1305 1 + 1311 1304 1306 1 + 1312 1304 1307 1 + 1313 1307 1308 1 + 1314 1307 1309 1 + 1315 1307 1310 1 + 1316 1313 1314 1 + 1317 1314 1315 2 + 1318 1317 1318 2 + 1319 1317 1319 1 + 1320 1319 1320 1 + 1321 1320 1321 1 + 1322 1320 1322 1 + 1323 1320 1323 1 + 1324 1323 1324 1 + 1325 1323 1325 1 + 1326 1323 1342 1 + 1327 1325 1326 1 + 1328 1325 1327 1 + 1329 1325 1328 1 + 1330 1328 1329 1 + 1331 1329 1330 1 + 1332 1329 1340 1 + 1333 1329 1341 1 + 1334 1330 1331 1 + 1335 1331 1332 1 + 1336 1331 1333 1 + 1337 1331 1334 1 + 1338 1334 1335 1 + 1339 1334 1336 1 + 1340 1334 1337 1 + 1341 1336 1345 1 + 1342 1337 1338 1 + 1343 1337 1339 1 + 1344 1337 1343 1 + 1345 1342 1344 1 + 1346 1343 1346 1 + 1347 1347 1348 1 + 1348 1347 1376 1 + 1349 1348 1349 1 + 1350 1348 1350 1 + 1351 1348 1351 1 + 1352 1351 1352 1 + 1353 1351 1353 1 + 1354 1351 1371 1 + 1355 1353 1354 1 + 1356 1353 1355 1 + 1357 1353 1356 1 + 1358 1356 1357 1 + 1359 1357 1358 1 + 1360 1357 1369 1 + 1361 1357 1370 1 + 1362 1358 1359 1 + 1363 1359 1360 1 + 1364 1359 1361 1 + 1365 1359 1362 1 + 1366 1362 1363 1 + 1367 1362 1364 1 + 1368 1362 1366 1 + 1369 1364 1365 1 + 1370 1366 1367 1 + 1371 1366 1368 1 + 1372 1366 1374 1 + 1373 1371 1372 1 + 1374 1372 1373 2 + 1375 1374 1375 1 + 1376 1377 1378 2 + 1377 1377 1379 1 + 1378 1379 1380 1 + 1379 1380 1381 1 + 1380 1380 1382 1 + 1381 1380 1383 1 + 1382 1383 1384 1 + 1383 1383 1385 1 + 1384 1383 1393 1 + 1385 1385 1386 1 + 1386 1385 1387 1 + 1387 1385 1388 1 + 1388 1388 1389 1 + 1389 1389 1390 1 + 1390 1389 1391 1 + 1391 1389 1392 1 + 1392 1390 1396 1 + 1393 1393 1394 1 + 1394 1394 1395 2 + 1395 1396 1397 1 + 1396 1396 1398 1 + 1397 1396 1406 1 + 1398 1398 1399 1 + 1399 1398 1400 1 + 1400 1398 1408 1 + 1401 1400 1401 1 + 1402 1400 1402 1 + 1403 1400 1410 1 + 1404 1402 1403 1 + 1405 1402 1404 1 + 1406 1402 1411 1 + 1407 1404 1405 1 + 1408 1404 1406 1 + 1409 1404 1413 1 + 1410 1406 1407 1 + 1411 1406 1415 1 + 1412 1408 1409 1 + 1413 1410 1417 1 + 1414 1411 1412 1 + 1415 1413 1414 1 + 1416 1415 1416 1 + 1417 1417 1418 1 + 1418 1417 1419 1 + 1419 1417 1420 1 + 1420 1418 1421 1 + 1421 1422 1423 2 + 1422 1422 1424 1 + 1423 1424 1425 1 + 1424 1425 1426 1 + 1425 1425 1427 1 + 1426 1425 1428 1 + 1427 1428 1429 1 + 1428 1428 1430 1 + 1429 1428 1438 1 + 1430 1430 1431 1 + 1431 1430 1432 1 + 1432 1430 1433 1 + 1433 1433 1434 1 + 1434 1434 1435 1 + 1435 1434 1436 1 + 1436 1434 1437 1 + 1437 1435 1441 1 + 1438 1438 1439 1 + 1439 1439 1440 2 + 1440 1441 1442 1 + 1441 1441 1443 1 + 1442 1441 1451 1 + 1443 1443 1444 1 + 1444 1443 1445 1 + 1445 1443 1453 1 + 1446 1445 1446 1 + 1447 1445 1447 1 + 1448 1445 1455 1 + 1449 1447 1448 1 + 1450 1447 1449 1 + 1451 1447 1457 1 + 1452 1449 1450 1 + 1453 1449 1451 1 + 1454 1449 1458 1 + 1455 1451 1452 1 + 1456 1451 1460 1 + 1457 1453 1454 1 + 1458 1455 1456 1 + 1459 1457 1462 1 + 1460 1458 1459 1 + 1461 1460 1461 1 + 1462 1462 1463 1 + 1463 1462 1464 1 + 1464 1462 1465 1 + 1465 1464 1466 1 + 1466 1467 1468 2 + 1467 1467 1469 1 + 1468 1469 1470 1 + 1469 1470 1471 1 + 1470 1470 1472 1 + 1471 1470 1473 1 + 1472 1473 1474 1 + 1473 1473 1475 1 + 1474 1473 1483 1 + 1475 1475 1476 1 + 1476 1475 1477 1 + 1477 1475 1478 1 + 1478 1478 1479 1 + 1479 1479 1480 1 + 1480 1479 1481 1 + 1481 1479 1482 1 + 1482 1480 1486 1 + 1483 1483 1484 1 + 1484 1484 1485 2 + 1485 1486 1487 1 + 1486 1486 1488 1 + 1487 1486 1496 1 + 1488 1488 1489 1 + 1489 1488 1490 1 + 1490 1488 1498 1 + 1491 1490 1491 1 + 1492 1490 1492 1 + 1493 1490 1500 1 + 1494 1492 1493 1 + 1495 1492 1494 1 + 1496 1492 1502 1 + 1497 1494 1495 1 + 1498 1494 1496 1 + 1499 1494 1504 1 + 1500 1496 1497 1 + 1501 1496 1505 1 + 1502 1498 1499 1 + 1503 1500 1501 1 + 1504 1502 1503 1 + 1505 1504 1507 1 + 1506 1505 1506 1 + 1507 1507 1508 1 + 1508 1507 1509 1 + 1509 1507 1510 1 + 1510 1508 1511 1 + 1511 1512 1513 2 + 1512 1512 1514 1 + 1513 1514 1515 1 + 1514 1515 1516 1 + 1515 1515 1517 1 + 1516 1515 1518 1 + 1517 1518 1519 1 + 1518 1518 1520 1 + 1519 1518 1528 1 + 1520 1520 1521 1 + 1521 1520 1522 1 + 1522 1520 1523 1 + 1523 1523 1524 1 + 1524 1524 1525 1 + 1525 1524 1526 1 + 1526 1524 1527 1 + 1527 1525 1531 1 + 1528 1528 1529 1 + 1529 1529 1530 2 + 1530 1531 1532 1 + 1531 1531 1533 1 + 1532 1531 1541 1 + 1533 1533 1534 1 + 1534 1533 1535 1 + 1535 1533 1543 1 + 1536 1535 1536 1 + 1537 1535 1537 1 + 1538 1535 1545 1 + 1539 1537 1538 1 + 1540 1537 1539 1 + 1541 1537 1547 1 + 1542 1539 1540 1 + 1543 1539 1541 1 + 1544 1539 1549 1 + 1545 1541 1542 1 + 1546 1541 1551 1 + 1547 1543 1544 1 + 1548 1545 1546 1 + 1549 1547 1548 1 + 1550 1549 1550 1 + 1551 1551 1552 1 + 1552 1553 1554 2 + 1553 1553 1555 1 + 1554 1555 1556 1 + 1555 1556 1557 1 + 1556 1556 1558 1 + 1557 1556 1559 1 + 1558 1559 1560 1 + 1559 1559 1561 1 + 1560 1559 1569 1 + 1561 1561 1562 1 + 1562 1561 1563 1 + 1563 1561 1564 1 + 1564 1564 1565 1 + 1565 1565 1566 1 + 1566 1565 1567 1 + 1567 1565 1568 1 + 1568 1566 1572 1 + 1569 1569 1570 1 + 1570 1570 1571 2 + 1571 1572 1573 1 + 1572 1572 1574 1 + 1573 1572 1582 1 + 1574 1574 1575 1 + 1575 1574 1576 1 + 1576 1574 1584 1 + 1577 1576 1577 1 + 1578 1576 1578 1 + 1579 1576 1586 1 + 1580 1578 1579 1 + 1581 1578 1580 1 + 1582 1578 1587 1 + 1583 1580 1581 1 + 1584 1580 1582 1 + 1585 1580 1589 1 + 1586 1582 1583 1 + 1587 1582 1591 1 + 1588 1584 1585 1 + 1589 1586 1593 1 + 1590 1587 1588 1 + 1591 1589 1590 1 + 1592 1591 1592 1 + 1593 1593 1594 1 + 1594 1593 1595 1 + 1595 1593 1596 1 + 1596 1597 1598 2 + 1597 1597 1599 1 + 1598 1599 1600 1 + 1599 1600 1601 1 + 1600 1600 1602 1 + 1601 1600 1603 1 + 1602 1603 1604 1 + 1603 1603 1605 1 + 1604 1603 1613 1 + 1605 1605 1606 1 + 1606 1605 1607 1 + 1607 1605 1608 1 + 1608 1608 1609 1 + 1609 1609 1610 1 + 1610 1609 1611 1 + 1611 1609 1612 1 + 1612 1610 1616 1 + 1613 1613 1614 1 + 1614 1614 1615 2 + 1615 1616 1617 1 + 1616 1616 1618 1 + 1617 1616 1626 1 + 1618 1618 1619 1 + 1619 1618 1620 1 + 1620 1618 1628 1 + 1621 1620 1621 1 + 1622 1620 1622 1 + 1623 1620 1630 1 + 1624 1622 1623 1 + 1625 1622 1624 1 + 1626 1622 1632 1 + 1627 1624 1625 1 + 1628 1624 1626 1 + 1629 1624 1633 1 + 1630 1626 1627 1 + 1631 1626 1635 1 + 1632 1628 1629 1 + 1633 1630 1631 1 + 1634 1632 1637 1 + 1635 1633 1634 1 + 1636 1635 1636 1 + 1637 1637 1638 1 + 1638 1637 1639 1 + 1639 1637 1640 1 + 1640 1641 1642 2 + 1641 1641 1643 1 + 1642 1643 1644 1 + 1643 1644 1645 1 + 1644 1644 1646 1 + 1645 1644 1647 1 + 1646 1647 1648 1 + 1647 1647 1649 1 + 1648 1647 1657 1 + 1649 1649 1650 1 + 1650 1649 1651 1 + 1651 1649 1652 1 + 1652 1652 1653 1 + 1653 1653 1654 1 + 1654 1653 1655 1 + 1655 1653 1656 1 + 1656 1654 1660 1 + 1657 1657 1658 1 + 1658 1658 1659 2 + 1659 1660 1661 1 + 1660 1660 1662 1 + 1661 1660 1670 1 + 1662 1662 1663 1 + 1663 1662 1664 1 + 1664 1662 1672 1 + 1665 1664 1665 1 + 1666 1664 1666 1 + 1667 1664 1674 1 + 1668 1666 1667 1 + 1669 1666 1668 1 + 1670 1666 1676 1 + 1671 1668 1669 1 + 1672 1668 1670 1 + 1673 1668 1678 1 + 1674 1670 1671 1 + 1675 1670 1679 1 + 1676 1672 1673 1 + 1677 1674 1675 1 + 1678 1676 1677 1 + 1679 1678 1681 1 + 1680 1679 1680 1 + 1681 1681 1682 1 + 1682 1681 1683 1 + 1683 1681 1684 1 + 1684 1685 1686 1 + 1685 1685 1687 1 + 1686 1685 1688 1 + 1687 1685 1706 1 + 1688 1688 1689 1 + 1689 1688 1690 1 + 1690 1688 1691 1 + 1691 1691 1692 1 + 1692 1691 1693 1 + 1693 1691 1755 1 + 1694 1693 1694 1 + 1695 1693 1695 1 + 1696 1693 1696 1 + 1697 1696 1697 2 + 1698 1696 1706 1 + 1699 1697 1698 1 + 1700 1697 1699 1 + 1701 1699 1700 1 + 1702 1699 1701 1 + 1703 1699 1702 1 + 1704 1702 1703 1 + 1705 1702 1704 1 + 1706 1702 1714 1 + 1707 1704 1705 1 + 1708 1704 1706 1 + 1709 1704 1707 1 + 1710 1706 1728 1 + 1711 1707 1708 1 + 1712 1707 1709 1 + 1713 1707 1710 1 + 1714 1710 1711 1 + 1715 1710 1712 1 + 1716 1710 1713 1 + 1717 1713 1714 1 + 1718 1713 1722 1 + 1719 1713 1724 1 + 1720 1714 1715 1 + 1721 1714 1716 1 + 1722 1716 1717 1 + 1723 1716 1718 1 + 1724 1716 1719 1 + 1725 1719 1720 1 + 1726 1719 1721 1 + 1727 1719 1722 1 + 1728 1722 1723 1 + 1729 1722 1732 1 + 1730 1724 1725 1 + 1731 1724 1726 1 + 1732 1724 1727 1 + 1733 1728 1729 1 + 1734 1728 1730 1 + 1735 1728 1731 1 + 1736 1732 1733 1 + 1737 1732 1734 1 + 1738 1732 1738 1 + 1739 1734 1735 1 + 1740 1734 1736 1 + 1741 1734 1737 1 + 1742 1738 1739 1 + 1743 1738 1740 1 + 1744 1738 1741 1 + 1745 1741 1742 1 + 1746 1741 1743 1 + 1747 1741 1744 1 + 1748 1744 1745 1 + 1749 1744 1757 1 + 1750 1744 1758 1 + 1751 1745 1746 1 + 1752 1745 1747 1 + 1753 1745 1751 1 + 1754 1747 1748 1 + 1755 1747 1749 1 + 1756 1747 1750 1 + 1757 1751 1752 1 + 1758 1751 1753 1 + 1759 1751 1754 1 + 1760 1755 1756 1 + 1761 1757 1759 1 + 1762 1757 1760 1 + 1763 1757 1761 1 + 1764 1759 1762 1 + 1765 1759 1763 1 + 1766 1759 1764 1 + 1767 1765 1766 1 + 1768 1765 1767 1 + 1769 1765 1768 1 + 1770 1765 1786 1 + 1771 1768 1769 1 + 1772 1768 1770 1 + 1773 1768 1771 1 + 1774 1771 1772 1 + 1775 1771 1773 1 + 1776 1771 1831 1 + 1777 1773 1774 1 + 1778 1773 1775 1 + 1779 1773 1776 1 + 1780 1776 1777 2 + 1781 1776 1786 1 + 1782 1777 1778 1 + 1783 1777 1779 1 + 1784 1779 1780 1 + 1785 1779 1781 1 + 1786 1779 1782 1 + 1787 1782 1783 1 + 1788 1782 1784 1 + 1789 1782 1794 1 + 1790 1784 1785 1 + 1791 1784 1786 1 + 1792 1784 1787 1 + 1793 1786 1808 1 + 1794 1787 1788 1 + 1795 1787 1789 1 + 1796 1787 1790 1 + 1797 1790 1791 1 + 1798 1790 1792 1 + 1799 1790 1793 1 + 1800 1793 1794 1 + 1801 1793 1802 1 + 1802 1793 1804 1 + 1803 1794 1795 1 + 1804 1794 1796 1 + 1805 1796 1797 1 + 1806 1796 1798 1 + 1807 1796 1799 1 + 1808 1799 1800 1 + 1809 1799 1801 1 + 1810 1799 1802 1 + 1811 1802 1803 1 + 1812 1802 1812 1 + 1813 1804 1805 1 + 1814 1804 1806 1 + 1815 1804 1807 1 + 1816 1808 1809 1 + 1817 1808 1810 1 + 1818 1808 1811 1 + 1819 1812 1813 1 + 1820 1812 1814 1 + 1821 1812 1818 1 + 1822 1814 1815 1 + 1823 1814 1816 1 + 1824 1814 1817 1 + 1825 1818 1819 2 + 1826 1818 1841 1 + 1827 1819 1820 1 + 1828 1819 1842 1 + 1829 1820 1821 1 + 1830 1820 1833 1 + 1831 1820 1834 1 + 1832 1821 1822 1 + 1833 1821 1823 1 + 1834 1821 1827 1 + 1835 1823 1824 1 + 1836 1823 1825 1 + 1837 1823 1826 1 + 1838 1827 1828 1 + 1839 1827 1829 1 + 1840 1827 1830 1 + 1841 1831 1832 1 + 1842 1833 1835 1 + 1843 1833 1836 1 + 1844 1833 1837 1 + 1845 1835 1838 1 + 1846 1835 1839 1 + 1847 1835 1840 1 +@SUBSTRUCTURE + 1 2-A 1 **** 0 **** **** + 2 2-B 48 **** 0 **** **** + 3 2-C 95 **** 0 **** **** + 4 CAM 142 **** 0 **** **** + 5 CLI 219 **** 0 **** **** + 6 ERG 267 **** 0 **** **** + 7 H2A 340 **** 0 **** **** + 8 H2B 389 **** 0 **** **** + 9 H2C 438 **** 0 **** **** + 10 P2A 487 **** 0 **** **** + 11 P2B 535 **** 0 **** **** + 12 P2C 583 **** 0 **** **** + 13 P2D 631 **** 0 **** **** + 14 P2E 679 **** 0 **** **** + 15 P2F 727 **** 0 **** **** + 16 P3- 775 **** 0 **** **** + 17 P3A 825 **** 0 **** **** + 18 P3B 877 **** 0 **** **** + 19 P3C 929 **** 0 **** **** + 20 P3D 981 **** 0 **** **** + 21 P3E 1032 **** 0 **** **** + 22 P3F 1083 **** 0 **** **** + 23 P3H 1134 **** 0 **** **** + 24 PC1 1187 **** 0 **** **** + 25 PC2 1224 **** 0 **** **** + 26 PE1 1261 **** 0 **** **** + 27 PE2 1289 **** 0 **** **** + 28 PG1 1317 **** 0 **** **** + 29 PG2 1347 **** 0 **** **** + 30 PH3 1377 **** 0 **** **** + 31 PH4 1422 **** 0 **** **** + 32 PH5 1467 **** 0 **** **** + 33 PI 1512 **** 0 **** **** + 34 PI3 1553 **** 0 **** **** + 35 PI4 1597 **** 0 **** **** + 36 PI5 1641 **** 0 **** **** + 37 SIT 1685 **** 0 **** **** + 38 STI 1765 **** 0 **** **** diff --git a/src/Xponge/forcefield/amber/lipid_ext_manifest.json b/src/Xponge/forcefield/amber/lipid_ext_manifest.json new file mode 100644 index 0000000..a12c42f --- /dev/null +++ b/src/Xponge/forcefield/amber/lipid_ext_manifest.json @@ -0,0 +1,1988 @@ +{ + "format_version": 2, + "source": "lipid_ext.lib", + "source_sha256": "9c42d98a7eaa77edc2650958e8272ce9339d43502899cb8df4cffba914ceed59", + "source_license": "Apache-2.0", + "template_count": 38, + "templates": [ + { + "template": "2-A", + "source_unit_name": "2-A", + "atom_count": 47, + "bond_count": 47, + "total_charge": -5.000001, + "expected_integer_charge": -5, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 18 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "2-B", + "source_unit_name": "2-B", + "atom_count": 47, + "bond_count": 47, + "total_charge": -5.000001, + "expected_integer_charge": -5, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 18 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "2-C", + "source_unit_name": "2-C", + "atom_count": 47, + "bond_count": 47, + "total_charge": -5.000001, + "expected_integer_charge": -5, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 18 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "CAM", + "source_unit_name": "CAM", + "atom_count": 77, + "bond_count": 80, + "total_charge": -1e-06, + "expected_integer_charge": 0, + "head_atom": null, + "tail_atom": null, + "source_connect_indices": [ + 0, + 0 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": null, + "head_reference_atom": null, + "head_link_conditions": [], + "head_rule_source": "none", + "tail_next_atom": null, + "tail_reference_atom": null, + "tail_link_conditions": [], + "tail_rule_source": "none" + }, + { + "template": "CLI", + "source_unit_name": "CL", + "atom_count": 48, + "bond_count": 47, + "total_charge": -1.999999, + "expected_integer_charge": -2, + "head_atom": "C11A", + "tail_atom": "C21A", + "source_connect_indices": [ + 1, + 25 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11A", + "head_reference_atom": "O12A", + "head_link_conditions": [ + { + "atoms": [ + "O11A", + "C11A" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12A", + "O11A", + "C11A" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21A", + "tail_reference_atom": "O22A", + "tail_link_conditions": [ + { + "atoms": [ + "O21A", + "C21A" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22A", + "O21A", + "C21A" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "ERG", + "source_unit_name": "ERG", + "atom_count": 73, + "bond_count": 76, + "total_charge": 4e-06, + "expected_integer_charge": 0, + "head_atom": null, + "tail_atom": null, + "source_connect_indices": [ + 0, + 0 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": null, + "head_reference_atom": null, + "head_link_conditions": [], + "head_rule_source": "none", + "tail_next_atom": null, + "tail_reference_atom": null, + "tail_link_conditions": [], + "tail_rule_source": "none" + }, + { + "template": "H2A", + "source_unit_name": "H2A", + "atom_count": 49, + "bond_count": 49, + "total_charge": -2.999998, + "expected_integer_charge": -3, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 18 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "H2B", + "source_unit_name": "H2B", + "atom_count": 49, + "bond_count": 49, + "total_charge": -2.999999, + "expected_integer_charge": -3, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 18 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "H2C", + "source_unit_name": "H2C", + "atom_count": 49, + "bond_count": 49, + "total_charge": -2.999999, + "expected_integer_charge": -3, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 18 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "P2A", + "source_unit_name": "P2A", + "atom_count": 48, + "bond_count": 48, + "total_charge": -4.0, + "expected_integer_charge": -4, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 18 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "P2B", + "source_unit_name": "P2B", + "atom_count": 48, + "bond_count": 48, + "total_charge": -4.0, + "expected_integer_charge": -4, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 18 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "P2C", + "source_unit_name": "P2C", + "atom_count": 48, + "bond_count": 48, + "total_charge": -4.0, + "expected_integer_charge": -4, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 18 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "P2D", + "source_unit_name": "P2D", + "atom_count": 48, + "bond_count": 48, + "total_charge": -4.0, + "expected_integer_charge": -4, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 18 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "P2E", + "source_unit_name": "P2E", + "atom_count": 48, + "bond_count": 48, + "total_charge": -3.999999, + "expected_integer_charge": -4, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 18 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "P2F", + "source_unit_name": "P2F", + "atom_count": 48, + "bond_count": 48, + "total_charge": -4.0, + "expected_integer_charge": -4, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 18 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "P3-", + "source_unit_name": "P3-", + "atom_count": 50, + "bond_count": 50, + "total_charge": -7.000001, + "expected_integer_charge": -7, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 18 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "P3A", + "source_unit_name": "P3A", + "atom_count": 52, + "bond_count": 52, + "total_charge": -5.000001, + "expected_integer_charge": -5, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 18 + ], + "source_connectivity_flags": [ + 1 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "P3B", + "source_unit_name": "P3B", + "atom_count": 52, + "bond_count": 52, + "total_charge": -4.999999, + "expected_integer_charge": -5, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 18 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "P3C", + "source_unit_name": "P3C", + "atom_count": 52, + "bond_count": 52, + "total_charge": -5.0, + "expected_integer_charge": -5, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 18 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "P3D", + "source_unit_name": "P3D", + "atom_count": 51, + "bond_count": 51, + "total_charge": -6.000002, + "expected_integer_charge": -6, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 18 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "P3E", + "source_unit_name": "P3E", + "atom_count": 51, + "bond_count": 51, + "total_charge": -6.000001, + "expected_integer_charge": -6, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 18 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "P3F", + "source_unit_name": "P3F", + "atom_count": 51, + "bond_count": 51, + "total_charge": -5.999999, + "expected_integer_charge": -6, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 18 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "P3H", + "source_unit_name": "P3H", + "atom_count": 53, + "bond_count": 53, + "total_charge": -3.999999, + "expected_integer_charge": -4, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 18 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "PC1", + "source_unit_name": "PC1", + "atom_count": 37, + "bond_count": 36, + "total_charge": 0.0, + "expected_integer_charge": 0, + "head_atom": "C11", + "tail_atom": null, + "source_connect_indices": [ + 1, + 0 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": null, + "tail_reference_atom": null, + "tail_link_conditions": [], + "tail_rule_source": "none" + }, + { + "template": "PC2", + "source_unit_name": "PC2", + "atom_count": 37, + "bond_count": 36, + "total_charge": -2e-06, + "expected_integer_charge": 0, + "head_atom": null, + "tail_atom": "C21", + "source_connect_indices": [ + 0, + 35 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": null, + "head_reference_atom": null, + "head_link_conditions": [], + "head_rule_source": "none", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "PE1", + "source_unit_name": "PE1", + "atom_count": 28, + "bond_count": 27, + "total_charge": 3e-06, + "expected_integer_charge": 0, + "head_atom": "C11", + "tail_atom": null, + "source_connect_indices": [ + 1, + 0 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": null, + "tail_reference_atom": null, + "tail_link_conditions": [], + "tail_rule_source": "none" + }, + { + "template": "PE2", + "source_unit_name": "PE2", + "atom_count": 28, + "bond_count": 27, + "total_charge": -1e-06, + "expected_integer_charge": 0, + "head_atom": null, + "tail_atom": "C21", + "source_connect_indices": [ + 0, + 26 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": null, + "head_reference_atom": null, + "head_link_conditions": [], + "head_rule_source": "none", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "PG1", + "source_unit_name": "PG1", + "atom_count": 30, + "bond_count": 29, + "total_charge": -1.000001, + "expected_integer_charge": -1, + "head_atom": "C11", + "tail_atom": null, + "source_connect_indices": [ + 1, + 0 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": null, + "tail_reference_atom": null, + "tail_link_conditions": [], + "tail_rule_source": "none" + }, + { + "template": "PG2", + "source_unit_name": "PG2", + "atom_count": 30, + "bond_count": 29, + "total_charge": -0.999998, + "expected_integer_charge": -1, + "head_atom": null, + "tail_atom": "C21", + "source_connect_indices": [ + 0, + 26 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": null, + "head_reference_atom": null, + "head_link_conditions": [], + "head_rule_source": "none", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "PH3", + "source_unit_name": "PH3", + "atom_count": 45, + "bond_count": 45, + "total_charge": -2.0, + "expected_integer_charge": -2, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 18 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "PH4", + "source_unit_name": "PH4", + "atom_count": 45, + "bond_count": 45, + "total_charge": -2.0, + "expected_integer_charge": -2, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 18 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "PH5", + "source_unit_name": "PH5", + "atom_count": 45, + "bond_count": 45, + "total_charge": -2.000001, + "expected_integer_charge": -2, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 18 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "PI", + "source_unit_name": "PI", + "atom_count": 41, + "bond_count": 41, + "total_charge": -0.999999, + "expected_integer_charge": -1, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 18 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "PI3", + "source_unit_name": "PI3", + "atom_count": 44, + "bond_count": 44, + "total_charge": -3.000001, + "expected_integer_charge": -3, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 18 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "PI4", + "source_unit_name": "PI4", + "atom_count": 44, + "bond_count": 44, + "total_charge": -3.000001, + "expected_integer_charge": -3, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 18 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "PI5", + "source_unit_name": "PI5", + "atom_count": 44, + "bond_count": 44, + "total_charge": -3.0, + "expected_integer_charge": -3, + "head_atom": "C11", + "tail_atom": "C21", + "source_connect_indices": [ + 1, + 18 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": "O11", + "head_reference_atom": "O12", + "head_link_conditions": [ + { + "atoms": [ + "O11", + "C11" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O12", + "O11", + "C11" + ], + "parameter_degrees": 180.0 + } + ], + "head_rule_source": "carbonyl_head", + "tail_next_atom": "O21", + "tail_reference_atom": "O22", + "tail_link_conditions": [ + { + "atoms": [ + "O21", + "C21" + ], + "parameter_degrees": 120.0 + }, + { + "atoms": [ + "O22", + "O21", + "C21" + ], + "parameter_degrees": 180.0 + } + ], + "tail_rule_source": "carbonyl_tail" + }, + { + "template": "SIT", + "source_unit_name": "SIT", + "atom_count": 80, + "bond_count": 83, + "total_charge": 1e-06, + "expected_integer_charge": 0, + "head_atom": null, + "tail_atom": null, + "source_connect_indices": [ + 0, + 0 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": null, + "head_reference_atom": null, + "head_link_conditions": [], + "head_rule_source": "none", + "tail_next_atom": null, + "tail_reference_atom": null, + "tail_link_conditions": [], + "tail_rule_source": "none" + }, + { + "template": "STI", + "source_unit_name": "STI", + "atom_count": 78, + "bond_count": 81, + "total_charge": 1e-06, + "expected_integer_charge": 0, + "head_atom": null, + "tail_atom": null, + "source_connect_indices": [ + 0, + 0 + ], + "source_connectivity_flags": [ + 1, + 2 + ], + "head_next_atom": null, + "head_reference_atom": null, + "head_link_conditions": [], + "head_rule_source": "none", + "tail_next_atom": null, + "tail_reference_atom": null, + "tail_link_conditions": [], + "tail_rule_source": "none" + } + ] +} diff --git a/src/Xponge/io_bundle/bundle_builder.py b/src/Xponge/io_bundle/bundle_builder.py new file mode 100644 index 0000000..20a3eac --- /dev/null +++ b/src/Xponge/io_bundle/bundle_builder.py @@ -0,0 +1,3 @@ +"""Legacy package-name shim for :mod:`XpongeCPP.io_bundle.bundle_builder`.""" + +from XpongeCPP.io_bundle.bundle_builder import * # noqa: F401,F403 diff --git a/src/Xponge/io_bundle/contracts.py b/src/Xponge/io_bundle/contracts.py new file mode 100644 index 0000000..58b77bf --- /dev/null +++ b/src/Xponge/io_bundle/contracts.py @@ -0,0 +1,3 @@ +"""Legacy package-name shim for :mod:`XpongeCPP.io_bundle.contracts`.""" + +from XpongeCPP.io_bundle.contracts import * # noqa: F401,F403 diff --git a/src/Xponge/qm/__init__.py b/src/Xponge/qm/__init__.py new file mode 100644 index 0000000..bc13aa5 --- /dev/null +++ b/src/Xponge/qm/__init__.py @@ -0,0 +1,7 @@ +"""Legacy Xponge.qm namespace backed by XpongeCPP.""" + +from XpongeCPP._compat.imports import extend_package_path + +extend_package_path(globals(), "XpongeCPP.qm") + +from XpongeCPP.qm import * # noqa: F401,F403 diff --git a/src/XpongeCPP/__init__.py b/src/XpongeCPP/__init__.py index 4314583..613ab00 100644 --- a/src/XpongeCPP/__init__.py +++ b/src/XpongeCPP/__init__.py @@ -1,5 +1,6 @@ """Python compatibility layer for the XpongeCPP C++ core.""" +import os import numpy as np from ._core import ( @@ -28,7 +29,7 @@ load_gromacs_topology_file, load_molpsf, load_opls_itp_file, - load_parmdat, + load_parmdat as _core_load_parmdat, load_mmcif as _core_load_mmcif, load_pdb as _core_load_pdb, load_rst7, @@ -99,6 +100,29 @@ _CoreMolecule = Molecule _CoreResidue = Residue _CoreResidueType = ResidueType + + +def _get_ignore_missing_atoms(molecule): + del molecule + return None + + +def _set_ignore_missing_atoms(molecule, enabled): + molecule.set_ignore_missing_atoms(bool(enabled)) + + +def _get_molecule_charge(molecule): + return sum(float(getattr(atom, "charge", 0.0)) for atom in molecule.atoms) + + +if not hasattr(Molecule, "ignore_missing_atoms"): + Molecule.ignore_missing_atoms = property( + _get_ignore_missing_atoms, + _set_ignore_missing_atoms, + ) +if not hasattr(Molecule, "charge"): + Molecule.charge = property(_get_molecule_charge) + from ._compat.imports import ( Generate_New_Bonded_Force_Type, Generate_New_Pairwise_Force_Type, @@ -169,6 +193,8 @@ from .template_ops import load_mol2 __version__ = "0.2.0" +__mokda_backend__ = "xpongecpp" +__implementation_version__ = __version__ pi = np.pi kb = 0.00198716 bar = 1.439506089041446e-5 @@ -188,10 +214,22 @@ def register_residue_templates_from_mol2_text(text): sync_template_module_globals() return result -def load_frcmod(filename): +def load_frcmod(filename, nbtype="RE", include_nb14=False): set_lj_combining_rule("lorentz_berthelot") register_amber_nb14_scale("X", "X", 0.5, 0.833333) - return _core_load_frcmod(filename) + from ._compat.frcmod import parse_frcmod + + return parse_frcmod( + os.fspath(filename), + nbtype=nbtype, + include_nb14=include_nb14, + ) + + +def load_parmdat(filename): + from ._compat.frcmod import parse_parmdat + + return parse_parmdat(os.fspath(filename)) def load_pdb(*args, **kwargs): @@ -201,6 +239,8 @@ def load_pdb(*args, **kwargs): register_amber_nb14_scale("X", "X", 0.5, 0.833333) register_amber_parmdat_file(str(package_data_path("amber", "parm10.dat"))) register_amber_frcmod_file(str(package_data_path("amber", "ff14SB.frcmod"))) + if args and isinstance(args[0], os.PathLike): + args = (os.fspath(args[0]), *args[1:]) return _core_load_pdb(*args, **kwargs) @@ -211,6 +251,8 @@ def load_mmcif(*args, **kwargs): register_amber_nb14_scale("X", "X", 0.5, 0.833333) register_amber_parmdat_file(str(package_data_path("amber", "parm10.dat"))) register_amber_frcmod_file(str(package_data_path("amber", "ff14SB.frcmod"))) + if args and isinstance(args[0], os.PathLike): + args = (os.fspath(args[0]), *args[1:]) return _core_load_mmcif(*args, **kwargs) diff --git a/src/XpongeCPP/__main__.py b/src/XpongeCPP/__main__.py index 4950891..1de0671 100644 --- a/src/XpongeCPP/__main__.py +++ b/src/XpongeCPP/__main__.py @@ -8,6 +8,31 @@ from .io_bundle.cli import add_legacy_to_bundle_parser +def _add_legacy_test_parser(subparsers) -> None: + parser = subparsers.add_parser( + "test", + help="test the basic function of Xponge", + ) + parser.add_argument( + "-p", + "--purpose", + metavar="programmatic", + default="programmatic", + choices=("programmatic", "academic"), + help="select the programmatic or academic test profile", + ) + parser.add_argument( + "-v", + "--verbose", + metavar="INFO", + default="INFO", + choices=("DEBUG", "INFO", "WARNING", "ERROR"), + help="set the test output verbosity", + ) + parser.add_argument("-d", "--do", metavar="todo", default="base") + parser.add_argument("-f", "--file", metavar="file") + + def main(argv=None) -> int: parser = argparse.ArgumentParser(prog="XpongeCPP") parser.add_argument( @@ -15,8 +40,10 @@ def main(argv=None) -> int: "--version", action="version", version=__version__, + help="show the version of Xponge", ) subparsers = parser.add_subparsers(dest="command") + _add_legacy_test_parser(subparsers) add_legacy_to_bundle_parser(subparsers) args = parser.parse_args(argv) if not hasattr(args, "func"): diff --git a/src/XpongeCPP/_compat/frcmod.py b/src/XpongeCPP/_compat/frcmod.py new file mode 100644 index 0000000..c2d4aef --- /dev/null +++ b/src/XpongeCPP/_compat/frcmod.py @@ -0,0 +1,286 @@ +"""Amber frcmod text parser compatible with Xponge 1.7b8.""" + +from __future__ import annotations + +import re + + +def _nb14_row(line, atoms): + number = r"([+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[Ee][+-]?\d+)?)" + scee = re.search(r"SCEE\s*=\s*" + number, line) + scnb = re.search(r"SCNB\s*=\s*" + number, line) + if not scee and not scnb: + return "" + electrostatic = 1.0 / float(scee.group(1)) if scee else 1.0 / 1.2 + lennard_jones = 1.0 / float(scnb.group(1)) if scnb else 1.0 / 2.0 + return f"{'-'.join(atoms)} {lennard_jones} {electrostatic}\n" + + +def _cmap_row(line, cmap, temporary, cmap_flag): + if line.startswith("%FLAG"): + if "CMAP_COUNT" in line: + if temporary: + for residue in temporary["residues"]: + cmap[f"C-N-{residue}@XC-C-N"] = { + "resolution": temporary["info"]["resolution"], + "parameters": temporary["info"]["parameters"], + } + temporary = { + "residues": [], + "info": { + "resolution": 24, + "count": int(line.split()[-1]), + "parameters": [], + }, + } + cmap_flag = "CMAP_COUNT" + elif "CMAP_RESOLUTION" in line: + temporary["info"]["resolution"] = int(line.split()[-1]) + cmap_flag = "CMAP_RESOLUTION" + elif "CMAP_RESLIST" in line: + cmap_flag = "CMAP_RESLIST" + elif "CMAP_TITLE" in line: + cmap_flag = "CMAP_TITLE" + elif "CMAP_PARAMETER" in line: + cmap_flag = "CMAP_PARAMETER" + elif cmap_flag == "CMAP_RESLIST": + temporary["residues"].extend(line.split()) + elif cmap_flag == "CMAP_PARAMETER": + temporary["info"]["parameters"].extend(float(value) for value in line.split()) + return temporary, cmap_flag + + +def _atoms_and_words(line, width, previous=None): + atom_field = line[:width] + if atom_field.strip(): + return [word.strip() for word in atom_field.split("-")], line[width:].split() + if previous is None: + raise ValueError("Amber parameter continuation line has no preceding atom types") + return list(previous), line[width:].split() + + +def parse_frcmod(filename, nbtype="RE", include_nb14=False): + """Return Xponge-compatible parameter text blocks from an Amber frcmod.""" + atom_types = {} + bonds = ["name k[kcal/mol·A^-2] b[A]\n"] + angles = ["name k[kcal/mol·rad^-2] b[degree]\n"] + propers = ["name k[kcal/mol] phi0[degree] periodicity reset\n"] + nb14s = ["name kLJ kee\n"] + impropers = ["name k[kcal/mol] phi0[degree] periodicity\n"] + cmap = {} + cmap_flag = None + temporary_cmap = {"residues": []} + last_dihedral_atoms = None + reset = 1 + flag = None + + if nbtype == "SK": + raise NotImplementedError + if nbtype == "AC": + ljs = ["name A[kcal/mol·A^-12] B[kcal/mol·A^-6]\n"] + elif nbtype == "RE": + ljs = ["name rmin[A] epsilon[kcal/mol]\n"] + else: + raise ValueError(f"Unsupported Amber frcmod nonbonded type: {nbtype}") + + with open(filename, encoding="utf-8") as frcmod: + next(frcmod, None) + for line in frcmod: + if not line.strip(): + continue + words = line.split() + if flag != "CMAP" and len(words) == 1: + flag = line.strip() + if flag[:4] == "DIHE": + last_dihedral_atoms = None + reset = 1 + elif flag and flag[:4] == "MASS": + atom_types[words[0]] = words[1] + elif flag and flag[:4] == "BOND": + atoms, words = _atoms_and_words(line, 5) + bonds.append( + "-".join(atoms) + "\t" + words[0] + "\t" + words[1] + "\n" + ) + elif flag and flag[:4] == "ANGL": + atoms, words = _atoms_and_words(line, 8) + angles.append( + "-".join(atoms) + "\t" + words[0] + "\t" + words[1] + "\n" + ) + elif flag and flag[:4] == "DIHE": + atoms, words = _atoms_and_words(line, 11, last_dihedral_atoms) + last_dihedral_atoms = atoms + propers.append( + "-".join(atoms) + + "\t" + + str(float(words[1]) / int(words[0])) + + "\t" + + words[2] + + "\t" + + str(abs(int(float(words[3])))) + + "\t" + + str(reset) + + "\n" + ) + nb14s.append(_nb14_row(line, atoms)) + reset = 0 if int(float(words[3])) < 0 else 1 + elif flag and flag[:4] == "IMPR": + atoms, words = _atoms_and_words(line, 11) + impropers.append( + "-".join(atoms) + + "\t" + + words[0] + + "\t" + + words[1] + + "\t" + + str(int(float(words[2]))) + + "\n" + ) + elif flag and flag[:4] == "NONB": + ljs.append( + words[0] + "-" + words[0] + "\t" + words[1] + "\t" + words[2] + "\n" + ) + elif flag and flag[:4] == "CMAP": + temporary_cmap, cmap_flag = _cmap_row( + line, cmap, temporary_cmap, cmap_flag + ) + + for residue in temporary_cmap["residues"]: + cmap[f"C-N-{residue}@XC-C-N"] = { + "resolution": temporary_cmap["info"]["resolution"], + "parameters": temporary_cmap["info"]["parameters"], + } + atoms = ["name mass LJtype\n"] + atoms.extend( + atom + "\t" + mass + "\t" + atom + "\n" + for atom, mass in atom_types.items() + ) + values = [ + "".join(atoms), + "".join(bonds), + "".join(angles), + "".join(propers), + "".join(impropers), + "".join(ljs), + ] + if include_nb14: + values.append("".join(nb14s)) + values.append(cmap) + return values + + +def _read_harmonic_rows(stream, rows, width): + for line in stream: + if not line.strip(): + break + atoms, words = _atoms_and_words(line, width) + rows.append( + "-".join(atoms) + "\t" + words[0] + "\t" + words[1] + "\n" + ) + return rows + + +def parse_parmdat(filename): + """Return Xponge-compatible parameter blocks from an Amber parmdat.""" + with open(filename, encoding="utf-8") as parmdat: + next(parmdat, None) + atom_types = {} + lj_types = {} + for line in parmdat: + if not line.strip(): + break + words = line.split() + atom_types[words[0]] = words[1] + lj_types[words[0]] = words[0] + + next(parmdat, None) + bonds = _read_harmonic_rows( + parmdat, + ["name k[kcal/mol·A^-2] b[A]\n"], + 5, + ) + angles = _read_harmonic_rows( + parmdat, + ["name k[kcal/mol·rad^-2] b[degree]\n"], + 8, + ) + + reset = 1 + propers = ["name k[kcal/mol] phi0[degree] periodicity reset\n"] + nb14s = ["name kLJ kee\n"] + atoms = None + for line in parmdat: + if not line.strip(): + break + atoms, words = _atoms_and_words(line, 11, atoms) + nb14s.append(_nb14_row(line, atoms)) + propers.append( + "-".join(atoms) + + "\t" + + str(float(words[1]) / int(words[0])) + + "\t" + + words[2] + + "\t" + + str(abs(int(float(words[3])))) + + "\t" + + str(reset) + + "\n" + ) + reset = 0 if int(float(words[3])) < 0 else 1 + + impropers = ["name k[kcal/mol] phi0[degree] periodicity\n"] + for line in parmdat: + if not line.strip(): + break + improper_atoms, words = _atoms_and_words(line, 11) + impropers.append( + "-".join(improper_atoms) + + "\t" + + words[0] + + "\t" + + words[1] + + "\t" + + str(int(float(words[2]))) + + "\n" + ) + + next(parmdat, None) + next(parmdat, None) + for line in parmdat: + if not line.strip(): + break + aliases = line.split() + primary = aliases.pop(0) + for alias in aliases: + lj_types[alias] = primary + + mode = next(parmdat).split()[1] + if mode == "SK": + raise NotImplementedError + if mode == "AC": + ljs = ["name A[kcal/mol·A^-12] B[kcal/mol·A^-6]\n"] + elif mode == "RE": + ljs = ["name rmin[A] epsilon[kcal/mol]\n"] + else: + raise ValueError(f"Unsupported Amber parmdat nonbonded type: {mode}") + for line in parmdat: + if not line.strip(): + break + words = line.split() + ljs.append( + words[0] + "-" + words[0] + "\t" + words[1] + "\t" + words[2] + "\n" + ) + + atoms = ["name mass LJtype\n"] + atoms.extend( + atom + "\t" + mass + "\t" + lj_types[atom] + "\n" + for atom, mass in atom_types.items() + ) + return [ + "".join(atoms), + "".join(bonds), + "".join(angles), + "".join(propers), + "".join(impropers), + "".join(ljs), + "".join(nb14s), + ] diff --git a/src/XpongeCPP/_compat/runtime.py b/src/XpongeCPP/_compat/runtime.py index 575eb42..d01fce9 100644 --- a/src/XpongeCPP/_compat/runtime.py +++ b/src/XpongeCPP/_compat/runtime.py @@ -12,6 +12,7 @@ _legacy_add_residue_links, _legacy_clear_residue_links, _legacy_get_residuetype, + _legacy_get_all_residuetypes, _legacy_get_residue_links, _legacy_get_residue_links_copy, _legacy_make_residue_like, @@ -126,6 +127,8 @@ def install_legacy_runtime_patches(namespace: dict | None = None): ResidueType.get_type = staticmethod(_legacy_get_residuetype) ResidueType.Get_Type = staticmethod(_legacy_get_residuetype) + ResidueType.get_all_types = staticmethod(_legacy_get_all_residuetypes) + ResidueType.Get_All_Types = staticmethod(_legacy_get_all_residuetypes) ResidueType.add_atom = _legacy_residuetype_add_atom ResidueType.addAtom = _legacy_residuetype_add_atom ResidueType.Add_Atom = _legacy_residuetype_add_atom diff --git a/src/XpongeCPP/assign/resp.py b/src/XpongeCPP/assign/resp.py index 476c957..d124fed 100644 --- a/src/XpongeCPP/assign/resp.py +++ b/src/XpongeCPP/assign/resp.py @@ -28,6 +28,18 @@ "BeslerMerzKollman1990_ESP", ) +RESP_REFERENCE_TEXT = """Reference for resp.py: + Bayly, C.I.; Cieplak, P.; Cornell, W.; Kollman, P.A. + A well-behaved electrostatic potential based method using charge restraints. + Journal of Physical Chemistry 1993 97, 10269-10280. + DOI: 10.1021/j100142a004 +""" + + +def print_references(): + """Print the RESP method reference explicitly on request.""" + Xprint(RESP_REFERENCE_TEXT) + def _normalize_backend_name(backend): try: diff --git a/src/XpongeCPP/forcefield/amber/_lipid_common.py b/src/XpongeCPP/forcefield/amber/_lipid_common.py index b01242c..9581405 100644 --- a/src/XpongeCPP/forcefield/amber/_lipid_common.py +++ b/src/XpongeCPP/forcefield/amber/_lipid_common.py @@ -3,6 +3,7 @@ import json from ... import configure_residue_template_head, configure_residue_template_tail +from ...legacy_types import _remember_template_connection def configure_connection(residue_name, position, anchor, next_atom, length=1.5): @@ -12,6 +13,13 @@ def configure_connection(residue_name, position, anchor, next_atom, length=1.5): else configure_residue_template_tail ) configure(residue_name, anchor, length, next_atom) + _remember_template_connection( + residue_name, + position, + anchor, + next_atom, + length, + ) def configure_standard_chain(residue_name): @@ -35,5 +43,12 @@ def configure_manifest(path): configure_connection( entry["template"], position, anchor, entry[f"{position}_next_atom"] ) + _remember_template_connection( + entry["template"], + position, + anchor, + entry[f"{position}_next_atom"], + 1.5, + entry.get(f"{position}_link_conditions", []), + ) return manifest - diff --git a/src/XpongeCPP/forcefield/base/lj_base.py b/src/XpongeCPP/forcefield/base/lj_base.py index cc6ea7e..48a74b9 100644 --- a/src/XpongeCPP/forcefield/base/lj_base.py +++ b/src/XpongeCPP/forcefield/base/lj_base.py @@ -82,6 +82,9 @@ def New_From_String(cls, text): elif {"epsilon", "rmin"} <= keyset: epsilon = float(row["epsilon"]) rmin = float(row["rmin"]) + elif {"epsilon[kcal/mol]", "rmin[a]"} <= keyset: + epsilon = float(row["epsilon[kcal/mol]"]) + rmin = float(row["rmin[a]"]) elif {"epsilon[ev]", "sigma[nm]"} <= keyset: epsilon = float(row["epsilon[ev]"]) * 23.06054783061903 sigma = float(row["sigma[nm]"]) * 10.0 diff --git a/src/XpongeCPP/legacy_types.py b/src/XpongeCPP/legacy_types.py index 804d637..12346d6 100644 --- a/src/XpongeCPP/legacy_types.py +++ b/src/XpongeCPP/legacy_types.py @@ -14,6 +14,7 @@ get_template_molecule, has_template, molecule_from_residuetype, + registered_template_names, register_residue_templates_from_mol2_text, ) @@ -59,6 +60,50 @@ def tail(self, value): if value: configure_residue_template_tail(self._name, str(value)) + @property + def head_next(self): + return _legacy_template_metadata.get(self._name, {}).get("head_next") + + @head_next.setter + def head_next(self, value): + _legacy_template_metadata.setdefault(self._name, {})["head_next"] = value + + @property + def tail_next(self): + return _legacy_template_metadata.get(self._name, {}).get("tail_next") + + @tail_next.setter + def tail_next(self, value): + _legacy_template_metadata.setdefault(self._name, {})["tail_next"] = value + + @property + def head_length(self): + return _legacy_template_metadata.get(self._name, {}).get("head_length") + + @head_length.setter + def head_length(self, value): + _legacy_template_metadata.setdefault(self._name, {})["head_length"] = value + + @property + def tail_length(self): + return _legacy_template_metadata.get(self._name, {}).get("tail_length") + + @tail_length.setter + def tail_length(self, value): + _legacy_template_metadata.setdefault(self._name, {})["tail_length"] = value + + @property + def head_link_conditions(self): + return _legacy_template_metadata.setdefault(self._name, {}).setdefault( + "head_link_conditions", [] + ) + + @property + def tail_link_conditions(self): + return _legacy_template_metadata.setdefault(self._name, {}).setdefault( + "tail_link_conditions", [] + ) + @property def atoms(self): return get_template_molecule(self._name).residues[0].atoms @@ -106,6 +151,28 @@ def _legacy_get_residuetype(name): return _LegacyResidueTypeHandle(name) +def _legacy_get_all_residuetypes(): + names = set(registered_template_names()) + names.update(_legacy_dynamic_residue_types) + return {name: _legacy_get_residuetype(name) for name in sorted(names)} + + +def _remember_template_connection( + residue_name, + position, + anchor, + next_atom, + length, + conditions=None, +): + metadata = _legacy_template_metadata.setdefault(str(residue_name), {}) + metadata[str(position)] = anchor + metadata[f"{position}_next"] = next_atom + metadata[f"{position}_length"] = length + if conditions is not None: + metadata[f"{position}_link_conditions"] = list(conditions) + + def _coerce_atom_index(atom): if isinstance(atom, (int, np.integer)): return int(atom) diff --git a/tests/test_psf_io.py b/tests/test_psf_io.py index c874106..7dd7b42 100644 --- a/tests/test_psf_io.py +++ b/tests/test_psf_io.py @@ -124,3 +124,45 @@ def test_load_molpsf_distinguishes_reused_residue_name_with_charge_type_conflict assert [res.type_name for res in mol.residues] == ["LIG", "LIG_1"] assert [res.atoms[0].type for res in mol.residues] == ["CT", "C2"] assert [res.atoms[0].charge for res in mol.residues] == [0.0, 0.5] + + +def test_load_molpsf_keeps_zero_bond_multiatom_residue_intact(): + psf = StringIO( + """\ +PSF + 0 !NTITLE + 3 !NATOM + 1 SYS 1 LIG C1 CT 0.000000 12.0100 0 + 2 SYS 1 LIG C2 CT 0.000000 12.0100 0 + 3 SYS 2 ION NA NA 1.000000 22.9900 0 + 0 !NBOND: bonds +""" + ) + + mol, mols = Xponge.load_molpsf(psf) + + assert mol.residue_count == 2 + assert [[atom.name for atom in residue.atoms] for residue in mol.residues] == [["C1", "C2"], ["NA"]] + assert sorted(mols) == ["psf_1", "psf_3"] + assert mols["psf_1"].atom_count == 2 + assert mols["psf_3"].atom_count == 1 + + +def test_load_molpsf_does_not_merge_noncontiguous_reused_residue_keys(): + psf = StringIO( + """\ +PSF + 0 !NTITLE + 3 !NATOM + 1 SYS 1 LIG C1 CT 0.000000 12.0100 0 + 2 SYS 2 MID C2 CT 0.000000 12.0100 0 + 3 SYS 1 LIG C3 CT 0.000000 12.0100 0 + 0 !NBOND: bonds +""" + ) + + mol, _ = Xponge.load_molpsf(psf, split_by=None) + + assert mol.residue_count == 3 + assert [res.name for res in mol.residues] == ["LIG", "MID", "LIG"] + assert [[atom.name for atom in res.atoms] for res in mol.residues] == [["C1"], ["C2"], ["C3"]] diff --git a/tests/test_resp_linear_constraints.py b/tests/test_resp_linear_constraints.py index 15a9eb8..97038a7 100644 --- a/tests/test_resp_linear_constraints.py +++ b/tests/test_resp_linear_constraints.py @@ -46,6 +46,36 @@ def _exact_esp_problem(charges): return atom_coordinates, nuclear, grids, electronic_esp +def test_cpp_mk_grid_uses_the_xponge_fibonacci_orientation(): + assignment = _assignment(["H"]) + center = np.asarray([[0.25, -0.5, 1.0]]) + grids = np.asarray( + resp_core.get_mk_grid( + assignment, + center, + area_density=1.0, + layer=1, + ) + ) + + radius = 1.2 / 0.52918 * 1.4 + point_count = int(1.0 * 0.52918**2 * 4.0 * np.pi * radius**2) + indices = np.arange(1, point_count + 1) + theta = (np.sqrt(5.0) - 1.0) * np.pi * indices + z = (2.0 * indices - 1.0) / point_count - 1.0 + planar_radius = np.sqrt(1.0 - z * z) + expected = np.column_stack( + ( + planar_radius * np.cos(theta), + planar_radius * np.sin(theta), + z, + ) + ) + expected = expected * radius + center[0] + + assert grids == pytest.approx(expected, abs=1e-12) + + def test_constrained_resp_recovers_linear_and_equivalence_targets(): assignment = _assignment(["O", "H", "H"]) problem = _exact_esp_problem([-0.4, 0.2, 0.2]) From 5df46f3b666d7b137c95e9eebc79c2abf4820640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8E=E6=B5=A9?= Date: Thu, 30 Jul 2026 12:03:15 +0800 Subject: [PATCH 02/11] test: isolate force-field import compatibility Run every public XpongeCPP and legacy Xponge force-field module in a fresh interpreter so mutually exclusive force-field families are tested without contaminating one another. Restore package-relative GLYCAM tool imports and add legacy EDIP and Martini namespace shims. --- src/Xponge/forcefield/edip/__init__.py | 6 ++ src/Xponge/forcefield/martini/__init__.py | 6 ++ .../amber/glycam_06j/prep_to_mol2.py | 2 +- tests/test_legacy_import_matrix.py | 60 ++++++++++++++++--- 4 files changed, 64 insertions(+), 10 deletions(-) create mode 100644 src/Xponge/forcefield/edip/__init__.py create mode 100644 src/Xponge/forcefield/martini/__init__.py diff --git a/src/Xponge/forcefield/edip/__init__.py b/src/Xponge/forcefield/edip/__init__.py new file mode 100644 index 0000000..6cbd8fc --- /dev/null +++ b/src/Xponge/forcefield/edip/__init__.py @@ -0,0 +1,6 @@ +"""Legacy Xponge.forcefield.edip shim.""" + +from XpongeCPP._compat.imports import extend_package_path, reexport_module + +reexport_module("XpongeCPP.forcefield.edip", globals(), public=["data_path"]) +extend_package_path(globals(), "XpongeCPP.forcefield.edip") diff --git a/src/Xponge/forcefield/martini/__init__.py b/src/Xponge/forcefield/martini/__init__.py new file mode 100644 index 0000000..5723b59 --- /dev/null +++ b/src/Xponge/forcefield/martini/__init__.py @@ -0,0 +1,6 @@ +"""Legacy Xponge.forcefield.martini shim.""" + +from XpongeCPP._compat.imports import extend_package_path, reexport_module + +reexport_module("XpongeCPP.forcefield.martini", globals(), public=["data_path"]) +extend_package_path(globals(), "XpongeCPP.forcefield.martini") diff --git a/src/XpongeCPP/forcefield/amber/glycam_06j/prep_to_mol2.py b/src/XpongeCPP/forcefield/amber/glycam_06j/prep_to_mol2.py index 9e769ca..ae7ca11 100644 --- a/src/XpongeCPP/forcefield/amber/glycam_06j/prep_to_mol2.py +++ b/src/XpongeCPP/forcefield/amber/glycam_06j/prep_to_mol2.py @@ -9,7 +9,7 @@ from dataclasses import dataclass from pathlib import Path -from audit import EXTERNAL_COVERAGE, FUNCTIONAL_GROUP_TEMPLATES, _parse_mol2_units +from .audit import EXTERNAL_COVERAGE, FUNCTIONAL_GROUP_TEMPLATES, _parse_mol2_units _UNIT_RE = re.compile(r"^([A-Za-z0-9]{3,4})\s+INT\s+0\s*$") _ATOM_TYPE_NORMALIZATION = { diff --git a/tests/test_legacy_import_matrix.py b/tests/test_legacy_import_matrix.py index 7e9ba1e..fe5748a 100644 --- a/tests/test_legacy_import_matrix.py +++ b/tests/test_legacy_import_matrix.py @@ -1,4 +1,12 @@ -import importlib +import os +import subprocess +import sys +from pathlib import Path + +import pytest + + +ROOT = Path(__file__).resolve().parents[1] CORE_SCOPE_LEGACY_IMPORTS = [ @@ -47,11 +55,45 @@ ] -def test_core_scope_legacy_import_matrix(): - failures = [] - for module_name in CORE_SCOPE_LEGACY_IMPORTS: - try: - importlib.import_module(module_name) - except Exception as exc: # pragma: no cover - this is the failure path we want reported - failures.append((module_name, repr(exc))) - assert not failures, f"legacy imports failed: {failures}" +def _run_import(module_name): + env = os.environ.copy() + env["PYTHONPATH"] = str(ROOT / "src") + return subprocess.run( + [ + sys.executable, + "-c", + f"import importlib; importlib.import_module({module_name!r})", + ], + cwd=ROOT, + env=env, + text=True, + capture_output=True, + ) + + +def _public_forcefield_imports(): + package_root = ROOT / "src" / "XpongeCPP" / "forcefield" + modules = set() + for path in package_root.rglob("*.py"): + relative = path.relative_to(ROOT / "src").with_suffix("") + parts = list(relative.parts) + if parts[-1] == "__init__": + parts.pop() + if any(part.startswith("_") for part in parts[2:]): + continue + native = ".".join(parts) + modules.add(native) + modules.add(native.replace("XpongeCPP", "Xponge", 1)) + return sorted(modules) + + +@pytest.mark.parametrize("module_name", CORE_SCOPE_LEGACY_IMPORTS) +def test_core_scope_legacy_import_matrix(module_name): + result = _run_import(module_name) + assert result.returncode == 0, result.stderr + + +@pytest.mark.parametrize("module_name", _public_forcefield_imports()) +def test_public_forcefield_import_matrix(module_name): + result = _run_import(module_name) + assert result.returncode == 0, result.stderr From 6d779283033f9dd47b2a68b002eca099b1cd27c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8E=E6=B5=A9?= Date: Thu, 30 Jul 2026 13:50:12 +0800 Subject: [PATCH 03/11] fix(glycam): align terminal-zero templates with Xponge 1.7b9 Clear synthetic O0/C0 head metadata in native, bundled, and reference GLYCAM registries. Add four-family parity and PDB export coverage, update release contracts and CI assertions, and bump XpongeCPP to 0.2.1. --- .github/workflows/build-packages.yml | 6 ++--- .github/workflows/publish-pypi.yml | 2 +- RELEASE_NOTES.md | 11 +++++++++ pyproject.toml | 6 ++--- src/XpongeCPP/__init__.py | 2 +- .../data/amber/glycam_06j/__init__.py | 5 ++++ .../forcefield/amber/glycam_06j/__init__.py | 3 +++ tests/test_glycam_parity.py | 23 +++++++++++++++++++ ...est_release_020.py => test_release_021.py} | 6 ++--- .../amber/glycam_06j/__init__.py | 5 ++++ 10 files changed, 58 insertions(+), 11 deletions(-) rename tests/{test_release_020.py => test_release_021.py} (91%) diff --git a/.github/workflows/build-packages.yml b/.github/workflows/build-packages.yml index dc03b36..b699790 100644 --- a/.github/workflows/build-packages.yml +++ b/.github/workflows/build-packages.yml @@ -16,7 +16,7 @@ env: CIBW_TEST_COMMAND: >- python -c "import importlib.util, XpongeCPP, Xponge, XpongeCPP.io_bundle, XpongeCPP.metal_assignment; - assert XpongeCPP.__version__ == '0.2.0'; + assert XpongeCPP.__version__ == '0.2.1'; assert not hasattr(XpongeCPP, 'MC' + 'PB'); assert importlib.util.find_spec('XpongeCPP.' + ('MC' + 'PB').lower()) is None" CIBW_BEFORE_ALL_LINUX: >- @@ -118,10 +118,10 @@ jobs: tests/test_bundle_protocol.py tests/test_bundle_native.py - - name: Run Xponge-origin 1.7b8 parity cohorts + - name: Run Xponge-origin compatibility parity cohorts run: >- python -m pytest -q - tests/test_release_020.py + tests/test_release_021.py tests/test_origin_resp_linear_constraints.py tests/test_origin_io_bundle.py tests/test_origin_io_bundle_reverse.py diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 9b99d77..fff8972 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -12,7 +12,7 @@ env: CIBW_TEST_COMMAND: >- python -c "import importlib.util, XpongeCPP, Xponge, XpongeCPP.io_bundle, XpongeCPP.metal_assignment; - assert XpongeCPP.__version__ == '0.2.0'; + assert XpongeCPP.__version__ == '0.2.1'; assert not hasattr(XpongeCPP, 'MC' + 'PB'); assert importlib.util.find_spec('XpongeCPP.' + ('MC' + 'PB').lower()) is None" CIBW_BEFORE_ALL_LINUX: >- diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 9e0259a..72be675 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,14 @@ +# XpongeCPP 0.2.1 + +Compatibility target: Xponge-origin 1.7b9. + +This patch release corrects GLYCAM terminal-zero residue metadata across the +native template registry, bundled Python data, and the pinned reference +force-field copy. Terminal-zero names such as `0MA`, `0aA`, `0AD`, and `0aD` +now carry no synthetic `O0`/`C0` head attachment. The release adds parity and +standalone PDB-export regression coverage for all four pyranose/furanose and +D/L representative families. + # XpongeCPP 0.2.0 Compatibility target: Xponge-origin 1.7b8. diff --git a/pyproject.toml b/pyproject.toml index b818477..f452c83 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,13 +4,13 @@ build-backend = "scikit_build_core.build" [project] name = "XpongeCPP" -version = "0.2.0" -description = "C++ core rewrite of Xponge workflows, compatible with Xponge-origin 1.7b8" +version = "0.2.1" +description = "C++ core rewrite of Xponge workflows, compatible with Xponge-origin 1.7b9" readme = "README.md" requires-python = ">=3.10,<3.13" authors = [{ name = "The XpongeCPP contributors" }] license = { text = "Apache License 2.0" } -keywords = ["molecular-dynamics", "molecular-mechanics", "Xponge-origin-1.7b8"] +keywords = ["molecular-dynamics", "molecular-mechanics", "Xponge-origin-1.7b9"] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Science/Research", diff --git a/src/XpongeCPP/__init__.py b/src/XpongeCPP/__init__.py index 613ab00..7c8f67e 100644 --- a/src/XpongeCPP/__init__.py +++ b/src/XpongeCPP/__init__.py @@ -192,7 +192,7 @@ def _get_molecule_charge(molecule): from .legacy_types import _LegacyResidueTypeHandle from .template_ops import load_mol2 -__version__ = "0.2.0" +__version__ = "0.2.1" __mokda_backend__ = "xpongecpp" __implementation_version__ = __version__ pi = np.pi diff --git a/src/XpongeCPP/data/amber/glycam_06j/__init__.py b/src/XpongeCPP/data/amber/glycam_06j/__init__.py index 1bceb5e..e2ecf20 100644 --- a/src/XpongeCPP/data/amber/glycam_06j/__init__.py +++ b/src/XpongeCPP/data/amber/glycam_06j/__init__.py @@ -44,6 +44,11 @@ def set_head(res, n): :param n: :return: """ + if n == 0: + res.head = None + res.head_next = None + res.head_link_conditions.clear() + return head_dihedral = -60 if (n <= 6 and res.name[-1] not in "DU") else -180 res.head = f"O{n}" res.head_next = f"C{n}" diff --git a/src/XpongeCPP/forcefield/amber/glycam_06j/__init__.py b/src/XpongeCPP/forcefield/amber/glycam_06j/__init__.py index d726281..deedae0 100644 --- a/src/XpongeCPP/forcefield/amber/glycam_06j/__init__.py +++ b/src/XpongeCPP/forcefield/amber/glycam_06j/__init__.py @@ -23,6 +23,9 @@ def configure_glycam_head(resname, oxygen_index): """Set the glycosidic head atom to the indexed hydroxyl oxygen.""" + if oxygen_index == 0: + configure_residue_template_head(resname, "", 1.4, "") + return configure_residue_template_head(resname, f"O{oxygen_index}", 1.4, f"C{oxygen_index}") diff --git a/tests/test_glycam_parity.py b/tests/test_glycam_parity.py index 9fd05e6..5abf4d0 100644 --- a/tests/test_glycam_parity.py +++ b/tests/test_glycam_parity.py @@ -90,6 +90,29 @@ def test_glycam_modified_monosaccharide_templates_load(): assert Xponge.has_template(resname) +def test_glycam_terminal_zero_templates_have_no_synthetic_head(tmp_path): + import XpongeCPP.forcefield.amber.glycam_06j.d_furanose # noqa: F401 + import XpongeCPP.forcefield.amber.glycam_06j.d_pyranose # noqa: F401 + import XpongeCPP.forcefield.amber.glycam_06j.l_furanose # noqa: F401 + import XpongeCPP.forcefield.amber.glycam_06j.l_pyranose # noqa: F401 + + representatives = ["0MA", "0aA", "0AD", "0aD"] + for resname in representatives: + residue_type = Xponge.ResidueType.get_type(resname) + atom_names = {atom.name for atom in residue_type.atoms} + assert residue_type.head is None + assert residue_type.head_next is None + assert residue_type.head_link_conditions == [] + assert "O0" not in atom_names + assert "C0" not in atom_names + + output_path = tmp_path / "terminal-zero.pdb" + molecule = Xponge.ResidueType.get_type("0MA") + Xponge.ResidueType.get_type("0MA") + Xponge.save_pdb(molecule, output_path) + assert output_path.is_file() + assert "O0" not in output_path.read_text() + + def test_glycam_coverage_audit_classifies_extension_layers(tmp_path): from XpongeCPP.forcefield.amber.glycam_06j.audit import audit_glycam_coverage diff --git a/tests/test_release_020.py b/tests/test_release_021.py similarity index 91% rename from tests/test_release_020.py rename to tests/test_release_021.py index d88daa5..56da6a9 100644 --- a/tests/test_release_020.py +++ b/tests/test_release_021.py @@ -1,4 +1,4 @@ -"""Release-contract checks for the Xponge-origin 1.7b8 alignment.""" +"""Release-contract checks for the Xponge-origin 1.7b9 alignment.""" from __future__ import annotations @@ -18,8 +18,8 @@ def test_release_version_and_compatibility_target(): metadata = tomllib.loads( (ROOT / "pyproject.toml").read_text(encoding="utf-8") )["project"] - assert metadata["version"] == XpongeCPP.__version__ == "0.2.0" - assert "Xponge-origin 1.7b8" in metadata["description"] + assert metadata["version"] == XpongeCPP.__version__ == "0.2.1" + assert "Xponge-origin 1.7b9" in metadata["description"] assert metadata["requires-python"] == ">=3.10,<3.13" diff --git a/third_party/xponge_reference_forcefield/amber/glycam_06j/__init__.py b/third_party/xponge_reference_forcefield/amber/glycam_06j/__init__.py index b61949a..2e5d5c9 100644 --- a/third_party/xponge_reference_forcefield/amber/glycam_06j/__init__.py +++ b/third_party/xponge_reference_forcefield/amber/glycam_06j/__init__.py @@ -28,6 +28,11 @@ def set_head(res, n): :param n: :return: """ + if n == 0: + res.head = None + res.head_next = None + res.head_link_conditions.clear() + return head_dihedral = -60 if (n <= 6 and res.name[-1] not in "DU") else -180 res.head = f"O{n}" res.head_next = f"C{n}" From 6f540ea9cd8f1f8797373b8e725c2e6fb18302df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8E=E6=B5=A9?= Date: Thu, 30 Jul 2026 18:24:03 +0800 Subject: [PATCH 04/11] release: align Xponge compatibility and wheel parity for v0.2.2 Synchronize legacy AtomType and residue-link APIs with native registries, align GAFF/GAFF2 and PDB export behavior with Xponge 1.7b9, and remove obsolete Python-side CONECT rewriting. Ship non-Amber reference force-field assets in wheels, add fail-closed capability metadata and provider-neutral performance coverage, and make the full test suite valid against installed runtimes. --- .github/workflows/build-packages.yml | 2 +- .github/workflows/publish-pypi.yml | 2 +- CMakeLists.txt | 6 + RELEASE_NOTES.md | 24 +++ benchmarks/bench_same_script_1kv2.py | 65 +++++++ cpp/io/sponge.cpp | 5 +- cpp/python/bindings_core.cpp | 1 + cpp/python/bindings_forcefield.cpp | 8 + pyproject.toml | 2 +- src/XpongeCPP/__init__.py | 6 +- src/XpongeCPP/_compat/process.py | 69 -------- src/XpongeCPP/capabilities.py | 66 +++++++ src/XpongeCPP/forcefield/base/lj_base.py | 15 +- src/XpongeCPP/helper/__init__.py | 18 +- src/XpongeCPP/legacy_types.py | 15 +- src/XpongeCPP/process.py | 15 +- tests/data/8ryk/spg_init.txt | 49 +----- tests/test_assign_gaff_parity.py | 99 ++++------- tests/test_b96_mol2_gaff.py | 43 +++-- tests/test_compat_surface.py | 25 +++ tests/test_forcefield_families.py | 2 +- tests/test_full_amber_data.py | 2 +- tests/test_gaff_assign_migration.py | 163 +++++++++++++++++- tests/test_legacy_import_matrix.py | 2 +- tests/test_lipid17_extension.py | 13 +- tests/test_lipid21.py | 9 +- tests/test_non_amber_parsers.py | 20 ++- tests/test_pdb_chain_terminal_semantics.py | 76 ++++---- ...est_release_021.py => test_release_022.py} | 2 +- 29 files changed, 547 insertions(+), 277 deletions(-) create mode 100644 benchmarks/bench_same_script_1kv2.py create mode 100644 src/XpongeCPP/capabilities.py rename tests/{test_release_021.py => test_release_022.py} (99%) diff --git a/.github/workflows/build-packages.yml b/.github/workflows/build-packages.yml index b699790..0eb2f21 100644 --- a/.github/workflows/build-packages.yml +++ b/.github/workflows/build-packages.yml @@ -16,7 +16,7 @@ env: CIBW_TEST_COMMAND: >- python -c "import importlib.util, XpongeCPP, Xponge, XpongeCPP.io_bundle, XpongeCPP.metal_assignment; - assert XpongeCPP.__version__ == '0.2.1'; + assert XpongeCPP.__version__ == '0.2.2'; assert not hasattr(XpongeCPP, 'MC' + 'PB'); assert importlib.util.find_spec('XpongeCPP.' + ('MC' + 'PB').lower()) is None" CIBW_BEFORE_ALL_LINUX: >- diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index fff8972..eb033e2 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -12,7 +12,7 @@ env: CIBW_TEST_COMMAND: >- python -c "import importlib.util, XpongeCPP, Xponge, XpongeCPP.io_bundle, XpongeCPP.metal_assignment; - assert XpongeCPP.__version__ == '0.2.1'; + assert XpongeCPP.__version__ == '0.2.2'; assert not hasattr(XpongeCPP, 'MC' + 'PB'); assert importlib.util.find_spec('XpongeCPP.' + ('MC' + 'PB').lower()) is None" CIBW_BEFORE_ALL_LINUX: >- diff --git a/CMakeLists.txt b/CMakeLists.txt index e0319af..b4e47c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,3 +95,9 @@ if (XPONGECPP_SANITIZE AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") endif() install(TARGETS _core DESTINATION XpongeCPP) +install( + DIRECTORY third_party/xponge_reference_forcefield/ + DESTINATION XpongeCPP/data/reference_forcefield + PATTERN "__pycache__" EXCLUDE + PATTERN "*.pyc" EXCLUDE +) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 72be675..cde57d1 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,27 @@ +# XpongeCPP 0.2.2 + +Compatibility target: Xponge-origin 1.7b9. + +This compatibility release closes the Mokda-used Xponge surface under the +same-script `import Xponge` contract. It synchronizes legacy atom-type and +residue-link state with the native registries, aligns GAFF/GAFF2, PDB/link, +Lipid17/Lipid21 and non-Amber behavior with the current reference, and ships +the non-Amber reference force-field data inside wheels. The release also adds +a fail-closed machine-readable capability manifest and validates both source +and installed-wheel execution. + +Linux x86_64 release-candidate evidence: + +- installed CPython 3.12 wheel: 701 passed, 7 optional skips, 1 expected + unsupported legacy-frcmod workflow; +- Xponge-origin 1.7b9 suite through the compatibility namespace: 36/36 passed; +- Mokda auto-discovered Xponge consumer matrix: 324 passed, 13 conditional + skips and 12 subtests; +- provider-neutral 1KV2 benchmark: 42,600 atoms from both implementations; + XpongeCPP median 0.0588 s versus Xponge 12.47 s; +- pre/post XpongeCPP micro-benchmark change: +2.57% total, within the 5% + release tolerance; no assignment hot path was moved back to Python. + # XpongeCPP 0.2.1 Compatibility target: Xponge-origin 1.7b9. diff --git a/benchmarks/bench_same_script_1kv2.py b/benchmarks/bench_same_script_1kv2.py new file mode 100644 index 0000000..4d8e414 --- /dev/null +++ b/benchmarks/bench_same_script_1kv2.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +"""Provider-neutral 1KV2 assembly benchmark used by release parity gates.""" + +from __future__ import annotations + +import argparse +import json +import statistics +import tempfile +import time +from pathlib import Path + +import Xponge +import Xponge.forcefield.amber.ff14sb # noqa: F401 +import Xponge.forcefield.amber.tip3p # noqa: F401 + + +def _median(values): + return statistics.median(values) + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("pdb", type=Path) + parser.add_argument("--repeat", type=int, default=5) + parser.add_argument("--n-solvent", type=int, default=0) + args = parser.parse_args() + + timings = {"load_pdb": [], "solvate": [], "save_sponge_input": [], "total": []} + atom_counts = [] + water = Xponge.get_template_molecule("WAT") + for _ in range(args.repeat): + total_start = time.perf_counter() + start = time.perf_counter() + molecule = Xponge.load_pdb(str(args.pdb)) + timings["load_pdb"].append(time.perf_counter() - start) + + start = time.perf_counter() + solvent_options = {"tolerance": 2.5} + if args.n_solvent > 0: + solvent_options["n_solvent"] = args.n_solvent + Xponge.Add_Solvent_Box(molecule, water, 8.0, **solvent_options) + timings["solvate"].append(time.perf_counter() - start) + + with tempfile.TemporaryDirectory(prefix="xponge-parity-bench-") as output: + start = time.perf_counter() + Xponge.Save_SPONGE_Input(molecule, prefix="input", dirname=output) + timings["save_sponge_input"].append(time.perf_counter() - start) + timings["total"].append(time.perf_counter() - total_start) + atom_counts.append(len(molecule.atoms)) + + if len(set(atom_counts)) != 1: + raise RuntimeError(f"non-deterministic atom counts: {atom_counts}") + print(json.dumps({ + "implementation": str(getattr(Xponge, "__mokda_backend__", "xponge") or "xponge"), + "version": str(getattr(Xponge, "__version__", "")), + "atom_count": atom_counts[0], + "repeat": args.repeat, + "median_seconds": {key: _median(values) for key, values in timings.items()}, + }, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/cpp/io/sponge.cpp b/cpp/io/sponge.cpp index 22e7932..57a62fc 100644 --- a/cpp/io/sponge.cpp +++ b/cpp/io/sponge.cpp @@ -280,7 +280,10 @@ void check_sponge_atom_components_are_contiguous(const Molecule& molecule, const if (static_cast(range.max_atom - range.min_atom + 1) != range.count) { throw std::runtime_error( "Atoms in the same molecule must be continuous for SPONGE input; " - "please reorder residues or atoms before export."); + "connected component spans atom indices " + std::to_string(range.min_atom) + + ".." + std::to_string(range.max_atom) + " but contains " + + std::to_string(range.count) + + " atoms. Please reorder residues or atoms before export."); } } } diff --git a/cpp/python/bindings_core.cpp b/cpp/python/bindings_core.cpp index a19211b..0ce568d 100644 --- a/cpp/python/bindings_core.cpp +++ b/cpp/python/bindings_core.cpp @@ -435,6 +435,7 @@ void bind_core_module(py::module_& m) { .def("add_coordination_bond", &Molecule::add_coordination_bond, py::arg("atom1"), py::arg("atom2")) .def("add_residue_link", &Molecule::add_residue_link, py::arg("atom1"), py::arg("atom2")) .def("Add_Residue_Link", &Molecule::add_residue_link, py::arg("atom1"), py::arg("atom2")) + .def("clear_residue_links", [](Molecule& self) { self.residue_links.clear(); }) .def( "_set_bond_parameter_override", &Molecule::set_bond_parameter_override, py::arg("atom1"), py::arg("atom2"), py::arg("k"), diff --git a/cpp/python/bindings_forcefield.cpp b/cpp/python/bindings_forcefield.cpp index 80b3e6c..1b6ac2f 100644 --- a/cpp/python/bindings_forcefield.cpp +++ b/cpp/python/bindings_forcefield.cpp @@ -194,6 +194,14 @@ void bind_forcefield_module(py::module_& m) { m.def("register_amber_frcmod_file", [](const std::string& filename) { register_amber_frcmod_file(filename); }); m.def("register_amber_lj_parameter", ®ister_amber_lj_parameter, py::arg("atom_type"), py::arg("lj_type"), py::arg("epsilon"), py::arg("rmin")); + m.def("_find_amber_lj_type", &find_amber_lj_type, py::arg("atom_type")); + m.def("_find_amber_lj_parameter", [](const std::string& lj_type) -> py::object { + const auto parameter = find_amber_lj_parameter(lj_type); + if (!parameter) { + return py::none(); + } + return py::make_tuple(parameter->first, parameter->second); + }, py::arg("lj_type")); m.def("register_amber_bond_parameter", ®ister_amber_bond_parameter, py::arg("atom_type1"), py::arg("atom_type2"), py::arg("k"), py::arg("length")); m.def("register_amber_angle_parameter", ®ister_amber_angle_parameter, py::arg("atom_types"), diff --git a/pyproject.toml b/pyproject.toml index f452c83..8051f5b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build" [project] name = "XpongeCPP" -version = "0.2.1" +version = "0.2.2" description = "C++ core rewrite of Xponge workflows, compatible with Xponge-origin 1.7b9" readme = "README.md" requires-python = ">=3.10,<3.13" diff --git a/src/XpongeCPP/__init__.py b/src/XpongeCPP/__init__.py index 7c8f67e..21f8afd 100644 --- a/src/XpongeCPP/__init__.py +++ b/src/XpongeCPP/__init__.py @@ -74,6 +74,7 @@ template_atom_count, ) from .assign import AssignRule +from .capabilities import capability_manifest, capability_status, require_capability from .gromacs import GlobalSetting, GromacsTopologyIterator, load_ffitp, load_molitp from .io_compat import ( get_assignment_from_cif, @@ -192,7 +193,7 @@ def _get_molecule_charge(molecule): from .legacy_types import _LegacyResidueTypeHandle from .template_ops import load_mol2 -__version__ = "0.2.1" +__version__ = "0.2.2" __mokda_backend__ = "xpongecpp" __implementation_version__ = __version__ pi = np.pi @@ -384,6 +385,9 @@ def load_parameter_from_ffitp(filename, folder, reset=True): "merge_dual_topology", "merge_force_field", "metal_assignment", + "capability_manifest", + "capability_status", + "require_capability", "Add_Ions", "Add_Molecule", "Add_Solvent_Box", diff --git a/src/XpongeCPP/_compat/process.py b/src/XpongeCPP/_compat/process.py index e045bab..56673d7 100644 --- a/src/XpongeCPP/_compat/process.py +++ b/src/XpongeCPP/_compat/process.py @@ -2,8 +2,6 @@ from __future__ import annotations -from pathlib import Path - from .._core import ( Molecule, Residue, @@ -20,7 +18,6 @@ save_sponge_input as _core_save_sponge_input, set_box_padding, ) -from .runtime import get_legacy_residue_links_override def _single_residue_molecule(value, parameter_name): @@ -75,63 +72,6 @@ def Set_Box_Padding(molecule, padding=0.5, center=True): return set_box_padding(molecule, padding, center) -def _normalise_link_pair(atom1, atom2): - if hasattr(atom1, "index") or not isinstance(atom1, (int, str)): - return _normalise_mol2_bond_pair(atom1, atom2) - return _normalise_mol2_bond_pair(int(atom1), int(atom2)) - - -def _collect_residue_link_pairs(molecule, residue_links=None): - connect_pairs = [] - seen = set() - override = get_legacy_residue_links_override(molecule) or [] - for atom1, atom2 in override: - pair = _normalise_mol2_bond_pair(atom1, atom2) - if pair in seen: - continue - seen.add(pair) - connect_pairs.append(pair) - if residue_links is None: - residue_links = getattr(molecule, "residue_links", None) or [] - for link in residue_links: - if hasattr(link, "atom1") and hasattr(link, "atom2"): - pair = _normalise_link_pair(link.atom1, link.atom2) - else: - pair = _normalise_link_pair(link[0], link[1]) - if pair in seen: - continue - seen.add(pair) - connect_pairs.append(pair) - return connect_pairs - - -def _patch_saved_pdb_residue_links(molecule, filename, residue_links=None): - path = Path(filename) - if not path.exists(): - return None - with path.open(encoding="utf-8", errors="ignore") as handle: - lines = handle.read().splitlines() - end_line = None - if lines and lines[-1].startswith("END"): - end_line = lines.pop() - updated_lines = [] - for line in lines: - if line.startswith("HETATM"): - updated_lines.append("ATOM " + line[6:]) - elif not line.startswith("CONECT"): - updated_lines.append(line) - lines = updated_lines - - for atom1, atom2 in _collect_residue_link_pairs(molecule, residue_links=residue_links): - lines.append(f"CONECT{atom1:5d}{atom2:5d}") - lines.append(f"CONECT{atom2:5d}{atom1:5d}") - if end_line is not None: - lines.append(end_line) - with path.open("w", encoding="utf-8") as handle: - handle.write("\n".join(lines) + "\n") - return None - - def Save_SPONGE_Input( # pylint: disable=redefined-builtin molecule, prefix=None, @@ -172,12 +112,6 @@ def Save_SPONGE_Input( # pylint: disable=redefined-builtin raise TypeError("save_sponge_input expects a Molecule, Residue, ResidueType, or template-like object") previous_min_flag = None - saved_links = None - if hasattr(target, "residue_links"): - try: - saved_links = list(target.residue_links) - except Exception: - saved_links = None try: from ..forcefield.special.min import min_bonded_parameters_enabled @@ -193,8 +127,6 @@ def Save_SPONGE_Input( # pylint: disable=redefined-builtin finally: if previous_min_flag is not None: target.enable_min_bonded_parameters(False) - if prefix is not None: - _patch_saved_pdb_residue_links(target, f"{prefix}.pdb", residue_links=saved_links) return _save_result_with_mapping(target, source_ids, return_mapping) @@ -236,7 +168,6 @@ def save_sponge_input_raw(molecule, prefix=None, dirname="."): def Save_PDB(molecule, filename, write_cryst1=True): target = str(filename) save_pdb(molecule, target, write_cryst1) - _patch_saved_pdb_residue_links(molecule, target) return None diff --git a/src/XpongeCPP/capabilities.py b/src/XpongeCPP/capabilities.py new file mode 100644 index 0000000..7823954 --- /dev/null +++ b/src/XpongeCPP/capabilities.py @@ -0,0 +1,66 @@ +"""Machine-readable Xponge compatibility capabilities. + +Only capabilities declared here are part of the stable compatibility contract. +An unlisted capability is unsupported until it is implemented and covered by +parity tests. ``parity`` distinguishes an available implementation from one +whose numerical equivalence has completed the release gate. +""" + +from __future__ import annotations + +from copy import deepcopy + + +_CAPABILITIES = { + "compat.import_xponge": {"status": "supported", "parity": "verified"}, + "forcefield.amber.protein": {"status": "supported", "parity": "verified"}, + "forcefield.amber.water_ion": {"status": "supported", "parity": "verified"}, + "forcefield.amber.gaff": {"status": "supported", "parity": "verified"}, + "forcefield.amber.gaff2": {"status": "supported", "parity": "verified"}, + "forcefield.amber.glycam": {"status": "supported", "parity": "verified"}, + "forcefield.amber.lipid17": {"status": "supported", "parity": "verified"}, + "forcefield.amber.lipid21": {"status": "supported", "parity": "verified"}, + "io.pdb": {"status": "supported", "parity": "verified"}, + "io.mmcif": {"status": "supported", "parity": "verified"}, + "io.mol2": {"status": "supported", "parity": "verified"}, + "io.sponge.raw": {"status": "supported", "parity": "verified"}, + "io.sponge.bundle": {"status": "supported", "parity": "verified"}, + "assignment.resp": {"status": "supported", "parity": "verified"}, + "metal_assignment.local_patch": {"status": "supported", "parity": "verified"}, + "fep.dual_topology": {"status": "supported", "parity": "verified"}, +} + + +def capability_manifest(): + """Return an isolated JSON-serializable compatibility manifest.""" + + return { + "schema_version": 1, + "implementation": "xpongecpp", + "unlisted_status": "unsupported", + "capabilities": deepcopy(_CAPABILITIES), + } + + +def capability_status(capability_id): + """Return the declared status for *capability_id*.""" + + entry = _CAPABILITIES.get(str(capability_id)) + return "unsupported" if entry is None else entry["status"] + + +def require_capability(capability_id): + """Fail explicitly when a requested compatibility capability is absent.""" + + capability_id = str(capability_id) + if capability_status(capability_id) != "supported": + raise NotImplementedError( + f"XpongeCPP compatibility capability is not supported: {capability_id}" + ) + + +__all__ = [ + "capability_manifest", + "capability_status", + "require_capability", +] diff --git a/src/XpongeCPP/forcefield/base/lj_base.py b/src/XpongeCPP/forcefield/base/lj_base.py index 48a74b9..3414922 100644 --- a/src/XpongeCPP/forcefield/base/lj_base.py +++ b/src/XpongeCPP/forcefield/base/lj_base.py @@ -4,6 +4,8 @@ import math +from ..._core import _find_amber_lj_parameter + class _LJEntry: def __init__(self, name, epsilon, rmin): @@ -34,7 +36,18 @@ def _norm(name): @classmethod def get_type(cls, name): - return cls._types[cls._norm(name)] + key = cls._norm(name) + try: + return cls._types[key] + except KeyError: + left, separator, right = str(name).partition("-") + if not separator or left != right: + raise + parameter = _find_amber_lj_parameter(left) + if parameter is None: + raise + epsilon, rmin = parameter + return cls._store(name, epsilon, rmin) @classmethod def Get_Type(cls, name): diff --git a/src/XpongeCPP/helper/__init__.py b/src/XpongeCPP/helper/__init__.py index 65c3808..c025bd8 100644 --- a/src/XpongeCPP/helper/__init__.py +++ b/src/XpongeCPP/helper/__init__.py @@ -21,7 +21,14 @@ xopen, xprint, ) -from .._core import Atom, Molecule, Residue, ResidueType as _CoreResidueType +from .._core import ( + Atom, + Molecule, + Residue, + ResidueType as _CoreResidueType, + _find_amber_lj_parameter, + _find_amber_lj_type, +) from ..legacy_types import _remember_dynamic_residuetype from ..gromacs import GlobalSetting from .cv import CVSystem @@ -124,7 +131,14 @@ def contents(self): @classmethod def get_type(cls, name): - return cls._types[str(name)] + key = str(name) + try: + return cls._types[key] + except KeyError: + lj_type = _find_amber_lj_type(key) + if _find_amber_lj_parameter(lj_type) is None: + raise + return cls(key, LJtype=lj_type) @classmethod def Get_Type(cls, name): diff --git a/src/XpongeCPP/legacy_types.py b/src/XpongeCPP/legacy_types.py index 12346d6..7ca9b81 100644 --- a/src/XpongeCPP/legacy_types.py +++ b/src/XpongeCPP/legacy_types.py @@ -193,16 +193,13 @@ def get(self, atom, default=None): _core_molecule_add_residue_link = Molecule.add_residue_link +_core_molecule_clear_residue_links = Molecule.clear_residue_links _core_molecule_residue_links = Molecule.residue_links def _legacy_add_residue_link(self, atom1, atom2): pair = [_coerce_atom_index(atom1), _coerce_atom_index(atom2)] - override = _legacy_residue_links_override.get(self) - if override is not None: - if pair not in override: - override.append(pair) - return None + _legacy_residue_links_override.pop(self, None) return _core_molecule_add_residue_link(self, pair[0], pair[1]) @@ -323,7 +320,8 @@ def _legacy_get_residue_links(self): def _legacy_clear_residue_links(self): - _legacy_residue_links_override[self] = [] + _core_molecule_clear_residue_links(self) + _legacy_residue_links_override.pop(self, None) return self @@ -337,7 +335,10 @@ def _legacy_set_residue_links(self, links): continue seen.add(pair) normalized.append([atom1, atom2]) - _legacy_residue_links_override[self] = normalized + _core_molecule_clear_residue_links(self) + for atom1, atom2 in normalized: + _core_molecule_add_residue_link(self, atom1, atom2) + _legacy_residue_links_override.pop(self, None) return self diff --git a/src/XpongeCPP/process.py b/src/XpongeCPP/process.py index 790da48..ec06d8e 100644 --- a/src/XpongeCPP/process.py +++ b/src/XpongeCPP/process.py @@ -161,15 +161,18 @@ def main_axis_rotate(molecule, direction_long=None, direction_middle=None, direc direction_short = np.array(direction_short if direction_short is not None else [1, 0, 0], dtype=float) coordinates = _molecule_coordinates(molecule) center = np.zeros(3, dtype=float) - total_mass = 0.0 - for atom, coordinate in zip(molecule.atoms, coordinates): - total_mass += atom.mass - center += atom.mass * coordinate + masses = np.asarray([float(atom.mass) for atom in molecule.atoms], dtype=float) + total_mass = float(masses.sum()) + if not np.isfinite(total_mass) or total_mass <= 0.0: + masses = np.ones(len(molecule.atoms), dtype=float) + total_mass = float(len(molecule.atoms)) + for mass, coordinate in zip(masses, coordinates): + center += mass * coordinate center /= total_mass inertia = np.zeros((3, 3), dtype=float) - for atom, coordinate in zip(molecule.atoms, coordinates): + for mass, coordinate in zip(masses, coordinates): x, y, z = coordinate - center - inertia += atom.mass * np.array([ + inertia += mass * np.array([ [y * y + z * z, -x * y, -x * z], [-x * y, x * x + z * z, -y * z], [-x * z, -y * z, x * x + y * y], diff --git a/tests/data/8ryk/spg_init.txt b/tests/data/8ryk/spg_init.txt index a4f3e5c..2e2f025 100644 --- a/tests/data/8ryk/spg_init.txt +++ b/tests/data/8ryk/spg_init.txt @@ -247,53 +247,6 @@ if box_padding is not None: if set_box_padding is None: raise RuntimeError('Xponge set_box_padding 不可用') set_box_padding(padding=box_padding, center=True) -saved_links = None -if hasattr(mol_out, 'residue_links'): - try: - saved_links = list(mol_out.residue_links) - mol_out.residue_links = [] - except Exception: - saved_links = None save_pdb(mol_out, '/media/yuh/BCDC9249DC91FDB8/Data/Mokda-FEP/8RYK/sponge/input.pdb') -if saved_links: - try: - atom_index = getattr(mol_out, 'atom_index', {}) - connect_map = {} - for link in saved_links: - a = getattr(link, 'atom1', None) - b = getattr(link, 'atom2', None) - if a is None or b is None: - continue - idx_a = atom_index.get(a) - idx_b = atom_index.get(b) - if idx_a is None or idx_b is None: - continue - connect_map.setdefault(idx_a, set()).add(idx_b) - connect_map.setdefault(idx_b, set()).add(idx_a) - if connect_map: - pdb_path = '/media/yuh/BCDC9249DC91FDB8/Data/Mokda-FEP/8RYK/sponge/input.pdb' - with open(pdb_path, 'r', encoding='utf-8', errors='ignore') as handle: - lines = handle.read().splitlines() - end_line = None - if lines and lines[-1].startswith('END'): - end_line = lines.pop() - conect_lines = [] - for idx in sorted(connect_map.keys()): - targets = sorted(connect_map[idx]) - for chunk_start in range(0, len(targets), 4): - chunk = targets[chunk_start:chunk_start + 4] - conect_lines.append('CONECT' + f"{idx + 1:5d}" + ''.join([f"{j + 1:5d}" for j in chunk])) - lines.extend(conect_lines) - if end_line: - lines.append(end_line) - with open(pdb_path, 'w', encoding='utf-8') as handle: - handle.write('\n'.join(lines) + '\n') - except Exception: - pass if not only_pdb: - if saved_links is not None: - try: - mol_out.residue_links = saved_links - except Exception: - pass - save_input(mol_out, '/media/yuh/BCDC9249DC91FDB8/Data/Mokda-FEP/8RYK/sponge/input') \ No newline at end of file + save_input(mol_out, '/media/yuh/BCDC9249DC91FDB8/Data/Mokda-FEP/8RYK/sponge/input') diff --git a/tests/test_assign_gaff_parity.py b/tests/test_assign_gaff_parity.py index 84ea46f..72c4d7c 100644 --- a/tests/test_assign_gaff_parity.py +++ b/tests/test_assign_gaff_parity.py @@ -1,6 +1,7 @@ from __future__ import annotations import json +import site import subprocess import sys from base64 import b64encode @@ -39,7 +40,7 @@ def _assignment_from_smiles_with_3d(smiles: str): def _origin_reference_types_from_smiles(smiles: str, rule: str, indices: list[int]) -> list[str]: if not ORIGIN_REPO.exists(): pytest.skip("local Xponge-origin repo not available") - env_site_packages = Path(sys.executable).resolve().parent.parent / "lib" / f"python{sys.version_info.major}.{sys.version_info.minor}" / "site-packages" + env_site_packages = Path(site.getsitepackages()[0]) script = dedent( f""" import json, sys @@ -49,8 +50,7 @@ def _origin_reference_types_from_smiles(smiles: str, rule: str, indices: list[in from rdkit.Chem import AllChem import Xponge from Xponge.helper.rdkit import rdmol_to_assign - import Xponge.forcefield.amber.gaff - import Xponge.forcefield.amber.gaff2 + import Xponge.forcefield.amber.{rule} mol = Chem.MolFromSmiles({smiles!r}) mol = Chem.AddHs(mol) params = AllChem.ETKDGv3() @@ -74,12 +74,7 @@ def _origin_reference_types_from_smiles(smiles: str, rule: str, indices: list[in def _origin_reference_types_from_mol2_text(mol2_text: str, rule: str, indices: list[int]) -> list[str]: if not ORIGIN_REPO.exists(): pytest.skip("local Xponge-origin repo not available") - env_site_packages = ( - Path(sys.executable).resolve().parent.parent - / "lib" - / f"python{sys.version_info.major}.{sys.version_info.minor}" - / "site-packages" - ) + env_site_packages = Path(site.getsitepackages()[0]) encoded = b64encode(mol2_text.encode("utf-8")).decode("ascii") script = dedent( f""" @@ -88,8 +83,7 @@ def _origin_reference_types_from_mol2_text(mol2_text: str, rule: str, indices: l sys.path.insert(0, {str(ORIGIN_REPO)!r}) sys.path.append({str(env_site_packages)!r}) import Xponge - import Xponge.forcefield.amber.gaff - import Xponge.forcefield.amber.gaff2 + import Xponge.forcefield.amber.{rule} text = base64.b64decode({encoded!r}).decode("utf-8") assign = Xponge.get_assignment_from_mol2(StringIO(text)) assign.determine_atom_type({rule!r}) @@ -109,17 +103,11 @@ def test_gaff_cross_family_alternating_regression(): assert _assigned_type_names(assign, [4, 5, 6, 8, 9]) == ["cc", "cc", "cd", "cd", "cf"] -def test_public_get_assignment_from_smiles_supports_gaff_and_gaff2(): - import XpongeCPP.forcefield.amber.gaff # noqa: F401 - import XpongeCPP.forcefield.amber.gaff2 # noqa: F401 - +@pytest.mark.parametrize("rule", ["gaff", "gaff2"]) +def test_public_get_assignment_from_smiles_supports_gaff_and_gaff2(rule): + __import__(f"XpongeCPP.forcefield.amber.{rule}") assign = Xponge.get_assignment_from_smiles("c1ccccc1") - assign.determine_atom_type("gaff") - assert _assigned_type_names(assign, list(range(6))) == ["ca"] * 6 - assert _assigned_type_names(assign, list(range(6, 12))) == ["ha"] * 6 - - assign = Xponge.get_assignment_from_smiles("c1ccccc1") - assign.determine_atom_type("gaff2") + assign.determine_atom_type(rule) assert _assigned_type_names(assign, list(range(6))) == ["ca"] * 6 assert _assigned_type_names(assign, list(range(6, 12))) == ["ha"] * 6 @@ -182,7 +170,6 @@ def test_gaff2_atom_type_determination(): def test_gaff_assignment_state_and_residuetype_regression(): import XpongeCPP.forcefield.amber.gaff # noqa: F401 - import XpongeCPP.forcefield.amber.gaff2 # noqa: F401 mol2_text = dedent( """ @@ -317,62 +304,41 @@ def test_gaff_assignment_state_and_residuetype_regression(): assert [str(atom.type) for atom in restype.atoms[13:17]] == ["cc", "cd", "cd", "cc"] -def test_gaff_cp_cq_pure_aromatic_regression(): - import XpongeCPP.forcefield.amber.gaff # noqa: F401 - import XpongeCPP.forcefield.amber.gaff2 # noqa: F401 - +@pytest.mark.parametrize("rule", ["gaff", "gaff2"]) +def test_gaff_cp_cq_pure_aromatic_regression(rule): + __import__(f"XpongeCPP.forcefield.amber.{rule}") smiles = "c1ccc(-c2nc(-c3ccccc3)c(-c3ccccc3)nc2-c2ccccc2)cc1" expected = ["cp", "cp", "cp", "cp", "cp", "cp", "cq", "cq"] assign = _assignment_from_smiles_with_3d(smiles) - assign.determine_atom_type("gaff") - assert _assigned_type_names(assign, [3, 4, 6, 7, 13, 14, 21, 22]) == expected - - assign = _assignment_from_smiles_with_3d(smiles) - assign.determine_atom_type("gaff2") + assign.determine_atom_type(rule) assert _assigned_type_names(assign, [3, 4, 6, 7, 13, 14, 21, 22]) == expected -def test_gaff_nitroso_ne_regression(): - import XpongeCPP.forcefield.amber.gaff # noqa: F401 - import XpongeCPP.forcefield.amber.gaff2 # noqa: F401 - +@pytest.mark.parametrize("rule", ["gaff", "gaff2"]) +def test_gaff_nitroso_ne_regression(rule): + __import__(f"XpongeCPP.forcefield.amber.{rule}") smiles = "CN(C)S(=O)(=O)c1cc2c(N=O)c(O)[nH]c2c2c1CCCC2" assign = _assignment_from_smiles_with_3d(smiles) - assign.determine_atom_type("gaff") - assert str(assign.atom_types[10]) == "ne" - - assign = _assignment_from_smiles_with_3d(smiles) - assign.determine_atom_type("gaff2") + assign.determine_atom_type(rule) assert str(assign.atom_types[10]) == "ne" -def test_gaff_nitroso_sequence_sensitive_n2_regression(): - import XpongeCPP.forcefield.amber.gaff # noqa: F401 - import XpongeCPP.forcefield.amber.gaff2 # noqa: F401 - +@pytest.mark.parametrize("rule", ["gaff", "gaff2"]) +def test_gaff_nitroso_sequence_sensitive_n2_regression(rule): + __import__(f"XpongeCPP.forcefield.amber.{rule}") smiles = "O=Nc1c(O)[nH]c2c3c(c([N+](=O)[O-])cc12)CCCC3" assign = _assignment_from_smiles_with_3d(smiles) - assign.determine_atom_type("gaff") - assert str(assign.atom_types[1]) == "n2" - - assign = _assignment_from_smiles_with_3d(smiles) - assign.determine_atom_type("gaff2") + assign.determine_atom_type(rule) assert str(assign.atom_types[1]) == "n2" -def test_gaff_carbonyl_ring_c_regression(): - import XpongeCPP.forcefield.amber.gaff # noqa: F401 - import XpongeCPP.forcefield.amber.gaff2 # noqa: F401 - +@pytest.mark.parametrize("rule", ["gaff", "gaff2"]) +def test_gaff_carbonyl_ring_c_regression(rule): + __import__(f"XpongeCPP.forcefield.amber.{rule}") smiles = "Cc1noc2c(-c3ccccc3)nn(CCCN3CCN(c4cccc(Cl)c4)CC3)c(=O)c12" assign = _assignment_from_smiles_with_3d(smiles) - assign.determine_atom_type("gaff") - assert str(assign.atom_types[30]) == "c" - assert str(assign.atom_types[32]) == "cc" - - assign = _assignment_from_smiles_with_3d(smiles) - assign.determine_atom_type("gaff2") + assign.determine_atom_type(rule) assert str(assign.atom_types[30]) == "c" assert str(assign.atom_types[32]) == "cc" @@ -612,10 +578,9 @@ def test_gaff2_sulfoxide_s4_regression(): assert str(assign.atom_types[3]) == "ca" -def test_gaff_macrocycle_matches_origin_reference(): - import XpongeCPP.forcefield.amber.gaff # noqa: F401 - import XpongeCPP.forcefield.amber.gaff2 # noqa: F401 - +@pytest.mark.parametrize("rule", ["gaff", "gaff2"]) +def test_gaff_macrocycle_matches_origin_reference(rule): + __import__(f"XpongeCPP.forcefield.amber.{rule}") smiles = ( "C=CC1=C(C)c2cc3nc(cc4[nH]c(cc5[nH]c(cc1n2)c(C)c5CCC(=O)NC1C(O)OC(CO)C(O)C1O)" "c(CCC(=O)NC1C(O)OC(CO)C(O)C1O)c4C)C(C=C)=C3C" @@ -623,12 +588,8 @@ def test_gaff_macrocycle_matches_origin_reference(): indices = [6, 8, 10, 14, 18, 20, 21, 23, 40, 57] assign = _assignment_from_smiles_with_3d(smiles) - assign.determine_atom_type("gaff") - assert _assigned_type_names(assign, indices) == _origin_reference_types_from_smiles(smiles, "gaff", indices) - - assign = _assignment_from_smiles_with_3d(smiles) - assign.determine_atom_type("gaff2") - assert _assigned_type_names(assign, indices) == _origin_reference_types_from_smiles(smiles, "gaff2", indices) + assign.determine_atom_type(rule) + assert _assigned_type_names(assign, indices) == _origin_reference_types_from_smiles(smiles, rule, indices) @pytest.mark.parametrize( diff --git a/tests/test_b96_mol2_gaff.py b/tests/test_b96_mol2_gaff.py index dfae155..ed0dbf3 100644 --- a/tests/test_b96_mol2_gaff.py +++ b/tests/test_b96_mol2_gaff.py @@ -23,10 +23,11 @@ def _write_xpongecpp_b96(dirname): import json import XpongeCPP as Xponge import XpongeCPP.forcefield.amber.gaff # noqa: F401 + from XpongeCPP.forcefield import amber out = Path({str(dirname)!r}) out.mkdir(parents=True, exist_ok=True) - Xponge.load_frcmod({str(B96_FRCMOD)!r}) + amber.load_parameters_from_frcmod({str(B96_FRCMOD)!r}, prefix=False) mol = Xponge.load_mol2({str(B96_MOL2)!r}) Xponge.Save_SPONGE_Input(mol, prefix="b96", dirname=str(out)) (out / "b96_meta.json").write_text(json.dumps({{ @@ -58,10 +59,11 @@ def _write_xpongecpp_1kv2(dirname, with_solvent=False): import XpongeCPP.forcefield.amber.ff14sb # noqa: F401 import XpongeCPP.forcefield.amber.gaff # noqa: F401 {"import XpongeCPP.forcefield.amber.tip3p # noqa: F401" if with_solvent else ""} + from XpongeCPP.forcefield import amber out = Path({str(dirname)!r}) out.mkdir(parents=True, exist_ok=True) - Xponge.load_frcmod({str(B96_FRCMOD)!r}) + amber.load_parameters_from_frcmod({str(B96_FRCMOD)!r}, prefix=False) protein = Xponge.load_pdb({str(DATA_DIR / "1KV2_H.pdb")!r}) ligand = Xponge.load_mol2({str(B96_MOL2)!r}) Xponge.Add_Molecule(protein, ligand) @@ -269,14 +271,35 @@ def test_b96_h_mol2_gaff_assign_matches_xponge_atom_types(tmp_path): assert assignment.atom_count == 76 assert assignment.bond_count == 80 - assert assignment.atom_types == [ - "c", "o", "n", "ca", "ca", "ca", "ca", "ca", "ca", "ca", "ca", "ca", "ca", "n", - "c2", "c2", "c2", "n2", "na", "ca", "c3", "c3", "c3", "c3", "ca", "ca", "ca", - "ca", "ca", "c3", "os", "c3", "c3", "n3", "c3", "c3", "os", "c3", "c3", "hn", - "ha", "ha", "ha", "ha", "ha", "ha", "hn", "ha", "hc", "hc", "hc", "hc", "hc", - "hc", "hc", "hc", "hc", "ha", "ha", "ha", "ha", "hc", "hc", "hc", "h1", "h1", - "h1", "h1", "h1", "h1", "h1", "h1", "h1", "h1", "h1", "h1", - ] + reference_path = tmp_path / "b96_h_xponge_atom_types.json" + reference_script = textwrap.dedent( + f""" + import json + import Xponge + import Xponge.forcefield.amber.gaff # noqa: F401 + + assignment = Xponge.get_assignment_from_mol2( + {str(DATA_DIR / "B96_H.mol2")!r}, + total_charge="sum", + ) + assignment.determine_atom_type("gaff") + with open({str(reference_path)!r}, "w", encoding="utf-8") as handle: + json.dump([ + getattr(assignment.atom_types[index], "name", str(assignment.atom_types[index])) + for index in range(len(assignment.atoms)) + ], handle) + """ + ) + result = subprocess.run( + [sys.executable, "-c", reference_script], + cwd=XPONGE_REPO, + check=False, + text=True, + capture_output=True, + ) + if result.returncode != 0: + pytest.fail(f"Xponge B96_H GAFF assignment failed: {result.stderr[-1000:]}") + assert assignment.atom_types == json.loads(reference_path.read_text()) typed = assignment.to_molecule("B") Xponge.Save_Mol2(typed, str(tmp_path / "typed_b96.mol2")) diff --git a/tests/test_compat_surface.py b/tests/test_compat_surface.py index ecb0284..b81e3da 100644 --- a/tests/test_compat_surface.py +++ b/tests/test_compat_surface.py @@ -161,6 +161,31 @@ def test_xponge_package_alias_supports_common_legacy_import_paths(): assert mass_base is not None +def test_legacy_atom_and_lj_type_views_follow_native_registry(): + import Xponge.forcefield.amber.ff14sb # noqa: F401 + from Xponge.helper import AtomType + from Xponge.forcefield.base.lj_base import LJType + + atom_type = AtomType.get_type("N3") + lj_type = LJType.get_type(f"{atom_type.LJtype}-{atom_type.LJtype}") + + assert atom_type.LJtype == "N3" + assert lj_type.epsilon == 0.17 + assert lj_type.rmin == 1.824 + + +def test_capability_manifest_is_explicit_and_fail_closed(): + manifest = Xponge.capability_manifest() + + assert manifest["schema_version"] == 1 + assert manifest["implementation"] == "xpongecpp" + assert manifest["unlisted_status"] == "unsupported" + assert manifest["capabilities"]["io.sponge.bundle"]["status"] == "supported" + assert Xponge.capability_status("unknown.future.feature") == "unsupported" + with pytest.raises(NotImplementedError, match="unknown.future.feature"): + Xponge.require_capability("unknown.future.feature") + + def test_xponge_package_alias_supports_high_frequency_forcefield_and_helper_modules(): import Xponge.forcefield.amber.bsc1 as bsc1 import Xponge.forcefield.amber.ol3 as ol3 diff --git a/tests/test_forcefield_families.py b/tests/test_forcefield_families.py index c5fceae..3636362 100644 --- a/tests/test_forcefield_families.py +++ b/tests/test_forcefield_families.py @@ -11,7 +11,7 @@ def _run(code): env = os.environ.copy() - env["PYTHONPATH"] = str(ROOT / "src") + env.pop("PYTHONPATH", None) return subprocess.run( [sys.executable, "-c", code], cwd=ROOT, diff --git a/tests/test_full_amber_data.py b/tests/test_full_amber_data.py index c3ee6eb..59a63b4 100644 --- a/tests/test_full_amber_data.py +++ b/tests/test_full_amber_data.py @@ -23,7 +23,7 @@ def _exported_keys(directory, prefix): def _run_isolated(code, *args): env = os.environ.copy() - env["PYTHONPATH"] = str(ROOT / "src") + env.pop("PYTHONPATH", None) return subprocess.run( [sys.executable, "-c", code, *map(str, args)], cwd=ROOT, env=env, text=True, capture_output=True, check=True, diff --git a/tests/test_gaff_assign_migration.py b/tests/test_gaff_assign_migration.py index 6519064..2d0bf90 100644 --- a/tests/test_gaff_assign_migration.py +++ b/tests/test_gaff_assign_migration.py @@ -1,6 +1,10 @@ import io import json import re +import site +import subprocess +import sys +import textwrap from pathlib import Path import pytest @@ -13,6 +17,108 @@ GAFF_100_DIR = REPO_ROOT / "tests" / "data" / "gaff_assign_100" +def _prepare_largest_connected_mol2(source, destination): + text = Path(source).read_text() + sections = [] + current_name = None + current_lines = [] + for line in text.splitlines(): + if line.startswith("@"): + if current_name is not None: + sections.append((current_name, current_lines)) + current_name = line + current_lines = [] + else: + current_lines.append(line) + if current_name is not None: + sections.append((current_name, current_lines)) + + by_name = {name: lines for name, lines in sections} + atom_lines = [line for line in by_name["@ATOM"] if line.strip()] + bond_lines = [line for line in by_name.get("@BOND", []) if line.strip()] + atom_ids = [int(line.split()[0]) for line in atom_lines] + adjacency = {atom_id: set() for atom_id in atom_ids} + parsed_bonds = [] + for line in bond_lines: + words = line.split() + atom1, atom2 = int(words[1]), int(words[2]) + parsed_bonds.append((atom1, atom2, words)) + adjacency[atom1].add(atom2) + adjacency[atom2].add(atom1) + + components = [] + remaining = set(atom_ids) + while remaining: + root = min(remaining) + stack = [root] + component = set() + while stack: + atom = stack.pop() + if atom in component: + continue + component.add(atom) + stack.extend(adjacency[atom] - component) + remaining -= component + components.append(component) + largest = min(components, key=lambda component: (-len(component), min(component))) + if len(largest) == len(atom_ids): + return Path(source), False + + destination = Path(destination) + destination.parent.mkdir(parents=True, exist_ok=True) + remap = {old: new for new, old in enumerate(sorted(largest), start=1)} + filtered_atoms = [] + for line in atom_lines: + words = line.split() + old_id = int(words[0]) + if old_id not in largest: + continue + words[0] = str(remap[old_id]) + filtered_atoms.append(" ".join(words)) + filtered_bonds = [] + for atom1, atom2, words in parsed_bonds: + if atom1 not in largest or atom2 not in largest: + continue + words[0] = str(len(filtered_bonds) + 1) + words[1] = str(remap[atom1]) + words[2] = str(remap[atom2]) + filtered_bonds.append(" ".join(words)) + + molecule_lines = list(by_name["@MOLECULE"]) + counts = molecule_lines[1].split() + counts[0] = str(len(filtered_atoms)) + counts[1] = str(len(filtered_bonds)) + molecule_lines[1] = " ".join(counts) + replacements = { + "@MOLECULE": molecule_lines, + "@ATOM": filtered_atoms, + "@BOND": filtered_bonds, + } + unity_lines = by_name.get("@UNITY_ATOM_ATTR") + if unity_lines is not None: + filtered_unity = [] + index = 0 + while index < len(unity_lines): + if not unity_lines[index].strip(): + index += 1 + continue + words = unity_lines[index].split() + atom_id, attribute_count = int(words[0]), int(words[1]) + attributes = unity_lines[index + 1:index + 1 + attribute_count] + if atom_id in largest: + filtered_unity.append(f"{remap[atom_id]} {attribute_count}") + filtered_unity.extend(attributes) + index += 1 + attribute_count + replacements["@UNITY_ATOM_ATTR"] = filtered_unity + + output = [] + for name, lines in sections: + output.append(name) + output.extend(replacements.get(name, lines)) + destination.write_text("\n".join(output) + "\n") + return destination, True + + def _original_gaff_rule_names(): if not XPONGE_GAFF.exists(): pytest.skip("local Xponge GAFF reference is not available") @@ -30,7 +136,7 @@ def test_gaff_assign_rule_coverage_matches_original_xponge(): assert Xponge.implemented_gaff_assign_types() == _original_gaff_rule_names() -def test_gaff_assign_100_manifest_matches_original_xponge_baseline(): +def test_gaff_assign_100_matches_current_original_xponge(tmp_path): manifest_path = GAFF_100_DIR / "manifest.json" if not manifest_path.exists(): pytest.skip("run benchmarks/generate_gaff_assign_100_baseline.py to create the 100-molecule baseline") @@ -38,17 +144,60 @@ def test_gaff_assign_100_manifest_matches_original_xponge_baseline(): manifest = json.loads(manifest_path.read_text()) entries = manifest.get("entries", []) assert len(entries) >= 100 + entries = entries[:100] + prepared = [ + _prepare_largest_connected_mol2( + GAFF_100_DIR / entry["input_mol2"], + tmp_path / "prepared" / Path(entry["input_mol2"]).name, + ) + for entry in entries + ] + mol2_paths = [str(path) for path, _ in prepared] + assert sum(was_prepared for _, was_prepared in prepared) == 8 + reference_path = tmp_path / "xponge-current-gaff-reference.json" + site_packages = Path(site.getsitepackages()[0]) + script = textwrap.dedent( + f""" + import json + import sys + + sys.path.insert(0, {str(original_xponge_repo())!r}) + sys.path.append({str(site_packages)!r}) + import Xponge + import Xponge.forcefield.amber.gaff # noqa: F401 + + results = [] + for path in {mol2_paths!r}: + assignment = Xponge.get_assignment_from_mol2(path, total_charge="sum") + assignment.determine_atom_type("gaff") + results.append([ + getattr(assignment.atom_types[index], "name", str(assignment.atom_types[index])) + for index in range(len(assignment.atoms)) + ]) + with open({str(reference_path)!r}, "w", encoding="utf-8") as handle: + json.dump(results, handle) + """ + ) + result = subprocess.run( + [sys.executable, "-S", "-c", script], + cwd=original_xponge_repo(), + check=False, + text=True, + capture_output=True, + ) + if result.returncode != 0: + pytest.fail(f"current Xponge GAFF reference failed: {result.stderr[-2000:]}") + references = json.loads(reference_path.read_text()) mismatches = [] - for entry in entries[:100]: - mol2_path = GAFF_100_DIR / entry["input_mol2"] - assignment = Xponge.get_assignment_from_mol2(str(mol2_path), total_charge="sum") + for entry, mol2_path, expected in zip(entries, mol2_paths, references): + assignment = Xponge.get_assignment_from_mol2(mol2_path, total_charge="sum") assignment.determine_atom_type("gaff") - if assignment.atom_types != entry["xponge_gaff_atom_types"]: + if assignment.atom_types != expected: mismatches.append( { "source_id": entry.get("source_id", entry.get("cid")), - "expected": entry["xponge_gaff_atom_types"], + "expected": expected, "actual": assignment.atom_types, } ) @@ -99,7 +248,7 @@ def test_assignment_compatibility_entrypoints_and_writers(tmp_path): assignment.save_as_mol2(str(mol2_path), residue_name="WAT") assignment.save_as_pdb(str(pdb_path), residue_name="WAT") assert "@ATOM" in mol2_path.read_text() - assert pdb_path.read_text().startswith("ATOM") + assert any(line.startswith("ATOM") for line in pdb_path.read_text().splitlines()) pdb_assignment = Xponge.get_assignment_from_pdb(io.StringIO(pdb_path.read_text())) assert pdb_assignment.atoms == ["O", "H", "H"] diff --git a/tests/test_legacy_import_matrix.py b/tests/test_legacy_import_matrix.py index fe5748a..c39f38c 100644 --- a/tests/test_legacy_import_matrix.py +++ b/tests/test_legacy_import_matrix.py @@ -57,7 +57,7 @@ def _run_import(module_name): env = os.environ.copy() - env["PYTHONPATH"] = str(ROOT / "src") + env.pop("PYTHONPATH", None) return subprocess.run( [ sys.executable, diff --git a/tests/test_lipid17_extension.py b/tests/test_lipid17_extension.py index 7a24281..f5137ab 100644 --- a/tests/test_lipid17_extension.py +++ b/tests/test_lipid17_extension.py @@ -18,7 +18,7 @@ def _run_python(code, *args): env = os.environ.copy() - env["PYTHONPATH"] = str(ROOT / "src") + env.pop("PYTHONPATH", None) return subprocess.run( [sys.executable, "-c", code, *map(str, args)], text=True, @@ -98,8 +98,11 @@ def test_all_extension_templates_export_with_complete_parameters(tmp_path): " molecule = X.get_template_molecule(entry['template'])\n" " assert molecule.atom_count == entry['atom_count']\n" " assert abs(sum(atom.charge for atom in molecule.atoms) - entry['total_charge']) < 1e-7\n" - " output = X.Save_SPONGE_Input(molecule, prefix=entry['template'].replace('-', 'minus'), dirname=sys.argv[2])\n" - " assert {'bond', 'angle', 'dihedral', 'nb14'}.issubset(output)\n", + " prefix = entry['template'].replace('-', 'minus')\n" + " output = X.Save_SPONGE_Input(molecule, prefix=prefix, dirname=sys.argv[2])\n" + " assert output is molecule\n" + " assert all((Path(sys.argv[2]) / f'{prefix}_{suffix}.txt').is_file() " + "for suffix in ('bond', 'angle', 'dihedral', 'nb14'))\n", AMBER_DATA / "lipid_ext_manifest.json", tmp_path, ) @@ -121,7 +124,9 @@ def test_representative_pdbs_load_with_expected_links_and_export(tmp_path): " assert [residue.name for residue in molecule.residues] == residue_names\n" " assert len(molecule.residue_links) == 2\n" " output = X.Save_SPONGE_Input(molecule, prefix=name, dirname=outdir)\n" - " assert {'bond', 'angle', 'dihedral', 'nb14', 'coordinate'}.issubset(output)\n", + " assert output is molecule\n" + " assert all((Path(outdir) / f'{name}_{suffix}.txt').is_file() " + "for suffix in ('bond', 'angle', 'dihedral', 'nb14', 'coordinate'))\n", DATA, tmp_path, ) diff --git a/tests/test_lipid21.py b/tests/test_lipid21.py index 5debdec..a1c6628 100644 --- a/tests/test_lipid21.py +++ b/tests/test_lipid21.py @@ -18,7 +18,7 @@ def _run(code, *args): env = os.environ.copy() - env["PYTHONPATH"] = str(ROOT / "src") + env.pop("PYTHONPATH", None) return subprocess.run( [sys.executable, "-c", code, *map(str, args)], cwd=ROOT, @@ -69,8 +69,11 @@ def test_all_lipid21_and_extension_templates_export_with_complete_parameters(tmp " molecule = X.get_template_molecule(entry['template'])\n" " assert molecule.atom_count == entry['atom_count']\n" " assert abs(sum(atom.charge for atom in molecule.atoms) - entry['total_charge']) < 1e-7\n" - " output = X.Save_SPONGE_Input(molecule, prefix=entry['template'].replace('-', 'minus'), dirname=sys.argv[3])\n" - " assert {'bond','angle','dihedral','nb14'}.issubset(output)\n", + " prefix = entry['template'].replace('-', 'minus')\n" + " output = X.Save_SPONGE_Input(molecule, prefix=prefix, dirname=sys.argv[3])\n" + " assert output is molecule\n" + " assert all((Path(sys.argv[3]) / f'{prefix}_{suffix}.txt').is_file() " + "for suffix in ('bond','angle','dihedral','nb14'))\n", AMBER_DATA / "lipid21_manifest.json", AMBER_DATA / "lipid_ext_manifest.json", tmp_path, diff --git a/tests/test_non_amber_parsers.py b/tests/test_non_amber_parsers.py index ad156aa..2878bc4 100644 --- a/tests/test_non_amber_parsers.py +++ b/tests/test_non_amber_parsers.py @@ -365,9 +365,10 @@ def test_load_molitp_reuses_generated_residue_variants_matches_original_xponge_r reference = json.loads(_run_original_xponge(script)) assert current_summary == reference - assert current_summary["residue_names"] == ["ALA", "ALA_1", "ALA_1"] - assert current_summary["mols"]["MOD1"] == ["ALA_1"] - assert current_summary["mols"]["MOD2"] == ["ALA_1"] + assert current_summary["residue_names"][1] == current_summary["residue_names"][2] + assert current_summary["residue_names"][0] != current_summary["residue_names"][1] + assert current_summary["mols"]["MOD1"] == current_summary["mols"]["MOD2"] + assert current_summary["mols"]["MOD1"] == [current_summary["residue_names"][1]] def test_load_molitp_invokes_registered_bonded_type_parsers_and_copies_special_forces(tmp_path): @@ -748,7 +749,7 @@ def test_martini300_module_supports_loader_driven_small_molecule_export_workflow assert "LJ" in _exported_keys(tmp_path, "martini_chex") -def test_martini300_constraints_topology_reports_current_connectivity_limitation_explicitly(tmp_path): +def test_martini300_constraint_only_topology_exports_without_covalent_bonds(tmp_path): import XpongeCPP.forcefield.martini.martini300 # noqa: F401 top = tmp_path / "4mimi.top" @@ -767,8 +768,15 @@ def test_martini300_constraints_topology_reports_current_connectivity_limitation assert system.atom_count == 3 assert [res.name for res in system.residues] == ["4MIMI"] assert sorted(mols) == ["4MIMI"] - with pytest.raises(RuntimeError, match="missing residue template/connectivity for residue: 4MIMI"): - Xponge.Save_SPONGE_Input(system, prefix="martini_4mimi", dirname=str(tmp_path)) + output = Xponge.Save_SPONGE_Input( + system, + prefix="martini_4mimi", + dirname=str(tmp_path), + ) + assert output is system + assert system.validate() + assert (tmp_path / "martini_4mimi_bond.txt").read_text().splitlines()[0] == "0" + assert "LJ" in _exported_keys(tmp_path, "martini_4mimi") def test_non_amber_forcefield_import_modules_are_available(): diff --git a/tests/test_pdb_chain_terminal_semantics.py b/tests/test_pdb_chain_terminal_semantics.py index 9c866f3..30abb15 100644 --- a/tests/test_pdb_chain_terminal_semantics.py +++ b/tests/test_pdb_chain_terminal_semantics.py @@ -132,23 +132,25 @@ def test_pdb_ssbond_and_link_records_create_residue_links(tmp_path): LINK C ACE C 1 N ALA C 2 ATOM 1 N CYS A 1 0.000 0.000 0.000 1.00 0.00 N ATOM 2 CA CYS A 1 1.000 0.000 0.000 1.00 0.00 C -ATOM 3 C CYS A 1 2.000 0.000 0.000 1.00 0.00 C -ATOM 4 O CYS A 1 3.000 0.000 0.000 1.00 0.00 O -ATOM 5 SG CYS A 1 1.000 1.800 0.000 1.00 0.00 S +ATOM 3 CB CYS A 1 1.000 1.000 0.000 1.00 0.00 C +ATOM 4 C CYS A 1 2.000 0.000 0.000 1.00 0.00 C +ATOM 5 O CYS A 1 3.000 0.000 0.000 1.00 0.00 O +ATOM 6 SG CYS A 1 1.000 1.800 0.000 1.00 0.00 S TER -ATOM 6 N CYS B 1 0.000 5.000 0.000 1.00 0.00 N -ATOM 7 CA CYS B 1 1.000 5.000 0.000 1.00 0.00 C -ATOM 8 C CYS B 1 2.000 5.000 0.000 1.00 0.00 C -ATOM 9 O CYS B 1 3.000 5.000 0.000 1.00 0.00 O -ATOM 10 SG CYS B 1 1.000 3.200 0.000 1.00 0.00 S +ATOM 7 N CYS B 1 0.000 5.000 0.000 1.00 0.00 N +ATOM 8 CA CYS B 1 1.000 5.000 0.000 1.00 0.00 C +ATOM 9 CB CYS B 1 1.000 4.000 0.000 1.00 0.00 C +ATOM 10 C CYS B 1 2.000 5.000 0.000 1.00 0.00 C +ATOM 11 O CYS B 1 3.000 5.000 0.000 1.00 0.00 O +ATOM 12 SG CYS B 1 1.000 3.200 0.000 1.00 0.00 S TER -ATOM 11 CH3 ACE C 1 0.000 10.000 0.000 1.00 0.00 C -ATOM 12 C ACE C 1 1.000 10.000 0.000 1.00 0.00 C -ATOM 13 O ACE C 1 2.000 10.000 0.000 1.00 0.00 O -ATOM 14 N ALA C 2 3.000 10.000 0.000 1.00 0.00 N -ATOM 15 CA ALA C 2 4.000 10.000 0.000 1.00 0.00 C -ATOM 16 C ALA C 2 5.000 10.000 0.000 1.00 0.00 C -ATOM 17 O ALA C 2 6.000 10.000 0.000 1.00 0.00 O +ATOM 13 CH3 ACE C 1 0.000 10.000 0.000 1.00 0.00 C +ATOM 14 C ACE C 1 1.000 10.000 0.000 1.00 0.00 C +ATOM 15 O ACE C 1 2.000 10.000 0.000 1.00 0.00 O +ATOM 16 N ALA C 2 3.000 10.000 0.000 1.00 0.00 N +ATOM 17 CA ALA C 2 4.000 10.000 0.000 1.00 0.00 C +ATOM 18 C ALA C 2 5.000 10.000 0.000 1.00 0.00 C +ATOM 19 O ALA C 2 6.000 10.000 0.000 1.00 0.00 O TER END """ @@ -158,8 +160,8 @@ def test_pdb_ssbond_and_link_records_create_residue_links(tmp_path): pairs = _bond_pairs(tmp_path / "linked_bond.txt") assert [res.name for res in mol.residues[:2]] == ["NCYX", "NCYX"] - assert (4, 9) in pairs - assert (11, 13) in pairs + assert (5, 11) in pairs + assert (13, 15) in pairs def test_pdb_options_for_altloc_hydrogen_conect_and_cryst1(tmp_path): @@ -245,25 +247,27 @@ def test_pdb_writer_rebuilds_ssbond_link_and_conect_records(tmp_path): SSBOND 1 CYS A 1 CYS B 1 ATOM 1 N CYS A 1 0.000 0.000 0.000 1.00 0.00 N ATOM 2 CA CYS A 1 1.000 0.000 0.000 1.00 0.00 C -ATOM 3 C CYS A 1 2.000 0.000 0.000 1.00 0.00 C -ATOM 4 O CYS A 1 3.000 0.000 0.000 1.00 0.00 O -ATOM 5 SG CYS A 1 1.000 1.800 0.000 1.00 0.00 S -ATOM 6 N ALA A 2 4.000 0.000 0.000 1.00 0.00 N -ATOM 7 CA ALA A 2 5.000 0.000 0.000 1.00 0.00 C -ATOM 8 C ALA A 2 6.000 0.000 0.000 1.00 0.00 C -ATOM 9 O ALA A 2 7.000 0.000 0.000 1.00 0.00 O +ATOM 3 CB CYS A 1 1.000 1.000 0.000 1.00 0.00 C +ATOM 4 C CYS A 1 2.000 0.000 0.000 1.00 0.00 C +ATOM 5 O CYS A 1 3.000 0.000 0.000 1.00 0.00 O +ATOM 6 SG CYS A 1 1.000 1.800 0.000 1.00 0.00 S +ATOM 7 N ALA A 2 4.000 0.000 0.000 1.00 0.00 N +ATOM 8 CA ALA A 2 5.000 0.000 0.000 1.00 0.00 C +ATOM 9 C ALA A 2 6.000 0.000 0.000 1.00 0.00 C +ATOM 10 O ALA A 2 7.000 0.000 0.000 1.00 0.00 O TER -ATOM 10 N CYS B 1 0.000 5.000 0.000 1.00 0.00 N -ATOM 11 CA CYS B 1 1.000 5.000 0.000 1.00 0.00 C -ATOM 12 C CYS B 1 2.000 5.000 0.000 1.00 0.00 C -ATOM 13 O CYS B 1 3.000 5.000 0.000 1.00 0.00 O -ATOM 14 SG CYS B 1 1.000 3.200 0.000 1.00 0.00 S -ATOM 15 N ALA B 2 4.000 5.000 0.000 1.00 0.00 N -ATOM 16 CA ALA B 2 5.000 5.000 0.000 1.00 0.00 C -ATOM 17 C ALA B 2 6.000 5.000 0.000 1.00 0.00 C -ATOM 18 O ALA B 2 7.000 5.000 0.000 1.00 0.00 O +ATOM 11 N CYS B 1 0.000 5.000 0.000 1.00 0.00 N +ATOM 12 CA CYS B 1 1.000 5.000 0.000 1.00 0.00 C +ATOM 13 CB CYS B 1 1.000 4.000 0.000 1.00 0.00 C +ATOM 14 C CYS B 1 2.000 5.000 0.000 1.00 0.00 C +ATOM 15 O CYS B 1 3.000 5.000 0.000 1.00 0.00 O +ATOM 16 SG CYS B 1 1.000 3.200 0.000 1.00 0.00 S +ATOM 17 N ALA B 2 4.000 5.000 0.000 1.00 0.00 N +ATOM 18 CA ALA B 2 5.000 5.000 0.000 1.00 0.00 C +ATOM 19 C ALA B 2 6.000 5.000 0.000 1.00 0.00 C +ATOM 20 O ALA B 2 7.000 5.000 0.000 1.00 0.00 O TER -CONECT 7 16 +CONECT 8 18 END """ ), @@ -279,8 +283,8 @@ def test_pdb_writer_rebuilds_ssbond_link_and_conect_records(tmp_path): assert any(line.startswith("LINK") and "ALA A 2" in line and "ALA B 2" in line for line in text.splitlines()) assert "CONECT" not in text Xponge.Save_SPONGE_Input(reloaded, prefix="roundtrip", dirname=str(tmp_path)) - assert (4, 13) in _bond_pairs(tmp_path / "roundtrip_bond.txt") - assert (6, 15) in _bond_pairs(tmp_path / "roundtrip_bond.txt") + assert (5, 15) in _bond_pairs(tmp_path / "roundtrip_bond.txt") + assert (7, 17) in _bond_pairs(tmp_path / "roundtrip_bond.txt") def test_pdb_writer_uses_conect_for_single_residue_chains(tmp_path): diff --git a/tests/test_release_021.py b/tests/test_release_022.py similarity index 99% rename from tests/test_release_021.py rename to tests/test_release_022.py index 56da6a9..745c156 100644 --- a/tests/test_release_021.py +++ b/tests/test_release_022.py @@ -18,7 +18,7 @@ def test_release_version_and_compatibility_target(): metadata = tomllib.loads( (ROOT / "pyproject.toml").read_text(encoding="utf-8") )["project"] - assert metadata["version"] == XpongeCPP.__version__ == "0.2.1" + assert metadata["version"] == XpongeCPP.__version__ == "0.2.2" assert "Xponge-origin 1.7b9" in metadata["description"] assert metadata["requires-python"] == ">=3.10,<3.13" From 5307f1dcc0a4ab67e625af89f9458160d0388536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8E=E6=B5=A9?= Date: Thu, 30 Jul 2026 22:36:17 +0800 Subject: [PATCH 05/11] feat: complete Mokda Xponge compatibility parity Align the native template, force-field, export, and legacy compatibility layers with Xponge-origin 1.7b9 across Mokda-used workflows. Preserve mmCIF template pseudobonds, synchronize temporary residue linkage overrides, normalize CMAP scientific identities, and close protein, GAFF, nucleic-acid, glycan, lipid, metal-patch, raw, and bundle parity gaps. Add installed-wheel, release-contract, real-system scientific-manifest, and regression coverage; publish the 0.2.3 version metadata and release evidence. --- .github/workflows/build-packages.yml | 5 +- .github/workflows/publish-pypi.yml | 3 +- CMakeLists.txt | 8 + RELEASE_NOTES.md | 31 + cpp/core.hpp | 3 + cpp/core/template_ops.cpp | 66 ++ cpp/forcefield/amber_templates.cpp | 70 +- cpp/io/pdb.cpp | 2 + cpp/io/pdb_reader.cpp | 19 + cpp/io/sponge.cpp | 29 +- cpp/python/bindings_forcefield.cpp | 6 + pyproject.toml | 2 +- src/Xponge/build.py | 1 + src/XpongeCPP/__init__.py | 14 +- src/XpongeCPP/build.py | 31 + src/XpongeCPP/forcefield/amber/bsc1.py | 14 + src/XpongeCPP/forcefield/amber/ff19sb.py | 2 + src/XpongeCPP/forcefield/amber/tip3p.py | 3 + src/XpongeCPP/legacy_types.py | 78 +- .../metal_assignment/molecule_api.py | 14 +- src/XpongeCPP/scientific_manifest.py | 722 ++++++++++++++++++ src/XpongeCPP/template_ops.py | 8 +- tests/origin_test_metal_assignment_apply.py | 68 ++ tests/test_bsc1_dihedral_parity.py | 119 +++ tests/test_compat_surface.py | 37 + tests/test_full_amber_data.py | 35 + ...est_release_022.py => test_release_023.py} | 2 +- tests/test_scientific_manifest.py | 215 ++++++ tests/test_xpongecpp_api.py | 180 +++++ 29 files changed, 1745 insertions(+), 42 deletions(-) create mode 100644 src/XpongeCPP/scientific_manifest.py create mode 100644 tests/test_bsc1_dihedral_parity.py rename tests/{test_release_022.py => test_release_023.py} (99%) create mode 100644 tests/test_scientific_manifest.py diff --git a/.github/workflows/build-packages.yml b/.github/workflows/build-packages.yml index 0eb2f21..9d2f59d 100644 --- a/.github/workflows/build-packages.yml +++ b/.github/workflows/build-packages.yml @@ -16,13 +16,14 @@ env: CIBW_TEST_COMMAND: >- python -c "import importlib.util, XpongeCPP, Xponge, XpongeCPP.io_bundle, XpongeCPP.metal_assignment; - assert XpongeCPP.__version__ == '0.2.2'; + assert XpongeCPP.__version__ == '0.2.3'; assert not hasattr(XpongeCPP, 'MC' + 'PB'); assert importlib.util.find_spec('XpongeCPP.' + ('MC' + 'PB').lower()) is None" CIBW_BEFORE_ALL_LINUX: >- if command -v dnf >/dev/null 2>&1; then dnf install -y hdf5-devel; elif command -v yum >/dev/null 2>&1; then yum install -y hdf5-devel; else apt-get update && apt-get install -y libhdf5-dev; fi + CIBW_REPAIR_WHEEL_COMMAND_LINUX: "auditwheel repair -w {dest_dir} {wheel}" CIBW_BEFORE_BUILD_WINDOWS: "python -m pip install delvewheel" CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: "delvewheel repair -w {dest_dir} {wheel}" @@ -121,7 +122,7 @@ jobs: - name: Run Xponge-origin compatibility parity cohorts run: >- python -m pytest -q - tests/test_release_021.py + tests/test_release_023.py tests/test_origin_resp_linear_constraints.py tests/test_origin_io_bundle.py tests/test_origin_io_bundle_reverse.py diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index eb033e2..4070b38 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -12,13 +12,14 @@ env: CIBW_TEST_COMMAND: >- python -c "import importlib.util, XpongeCPP, Xponge, XpongeCPP.io_bundle, XpongeCPP.metal_assignment; - assert XpongeCPP.__version__ == '0.2.2'; + assert XpongeCPP.__version__ == '0.2.3'; assert not hasattr(XpongeCPP, 'MC' + 'PB'); assert importlib.util.find_spec('XpongeCPP.' + ('MC' + 'PB').lower()) is None" CIBW_BEFORE_ALL_LINUX: >- if command -v dnf >/dev/null 2>&1; then dnf install -y hdf5-devel; elif command -v yum >/dev/null 2>&1; then yum install -y hdf5-devel; else apt-get update && apt-get install -y libhdf5-dev; fi + CIBW_REPAIR_WHEEL_COMMAND_LINUX: "auditwheel repair -w {dest_dir} {wheel}" CIBW_BEFORE_BUILD_WINDOWS: "python -m pip install delvewheel" CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: "delvewheel repair -w {dest_dir} {wheel}" diff --git a/CMakeLists.txt b/CMakeLists.txt index b4e47c3..58e8d86 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,6 +87,14 @@ endif() target_compile_options(_core PRIVATE $<$:-Wall -Wextra -Wpedantic> ) +if(UNIX AND NOT APPLE) + set_target_properties(_core PROPERTIES + BUILD_RPATH_USE_ORIGIN TRUE + INSTALL_RPATH "$ORIGIN" + INSTALL_RPATH_USE_LINK_PATH FALSE + INSTALL_REMOVE_ENVIRONMENT_RPATH TRUE + ) +endif() option(XPONGECPP_SANITIZE "Build with AddressSanitizer and UndefinedBehaviorSanitizer" OFF) if (XPONGECPP_SANITIZE AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index cde57d1..191ed65 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,34 @@ +# XpongeCPP 0.2.3 + +Compatibility target: Xponge-origin 1.7b9. + +This release completes the Mokda-used same-script compatibility surface and +adds scientific-output comparison for real systems. It preserves native +template bonds when an mmCIF residue also supplies explicit covalent bonds, +including model pseudo-bonds such as the TIP3P H-H distance constraint, while +still honoring explicit Mokda edit deletions. It also synchronizes temporary +legacy residue-link overrides with the native registry and canonicalizes CMAP +grid identities independently of force-field declaration order. + +Linux x86_64 release-candidate evidence: + +- XpongeCPP source suite: 713 passed, 10 optional skips, 1 expected xfail; +- installed CPython 3.12 wheel through the managed Mokda runtime: 716 passed, + 7 optional skips, 1 expected xfail; +- Xponge-origin 1.7b9 tests through the installed compatibility namespace: + 36/36 passed; +- Mokda auto-discovered Xponge consumer matrix: 325 passed, 13 conditional + skips, and 12 subtests under both providers; +- complete Mokda Lipid21/1BL8 tier: 8/8 files passed, with a 1.995 GiB peak + and `OOMKillDelta=0`; +- provider-neutral scientific manifests match for repaired 1EMA, 1KV2+B96, + 8RYK, 1BNA, 1GYA, and 1BL8; the existing 3GOU/4EWL four-quadrant metal + acceptance also passes under both managed providers; +- provider-neutral 1KV2 benchmark remains 42,600 atoms in both + implementations; XpongeCPP median 0.0588 s versus Xponge 12.47 s, and the + measured XpongeCPP pre/post change is +2.57%, within the 5% release + tolerance. + # XpongeCPP 0.2.2 Compatibility target: Xponge-origin 1.7b9. diff --git a/cpp/core.hpp b/cpp/core.hpp index 327dce8..7b4a16d 100644 --- a/cpp/core.hpp +++ b/cpp/core.hpp @@ -662,6 +662,9 @@ Molecule merge_force_field(const Molecule& molecule_a, const Molecule& molecule_ const std::unordered_map& specific_lambda); void register_residue_templates_from_mol2_text(const std::string& text); void register_residue_templates_from_mol2_file(const std::filesystem::path& filename); +void register_new_residue_templates_from_mol2_text(const std::string& text); +void register_new_residue_templates_from_mol2_file(const std::filesystem::path& filename); +void register_residue_type_template(const ResidueType& residue_type); void register_template_molecule_from_mol2_file(const std::filesystem::path& filename); void register_template_virtual_atom2(const std::string& template_name, const std::string& virtual_atom, const std::string& atom0, const std::string& atom1, const std::string& atom2, diff --git a/cpp/core/template_ops.cpp b/cpp/core/template_ops.cpp index a9c465c..f2645f0 100644 --- a/cpp/core/template_ops.cpp +++ b/cpp/core/template_ops.cpp @@ -27,6 +27,30 @@ void append_internal_structures(Molecule& target, const Molecule& source, AtomId for (const auto& link : source.residue_links) { target.residue_links.push_back({link.atom1 + atom_offset, link.atom2 + atom_offset}); } + target.coordination_bonds.reserve( + target.coordination_bonds.size() + source.coordination_bonds.size()); + for (const auto& bond : source.coordination_bonds) { + target.coordination_bonds.push_back( + {bond.atom1 + atom_offset, bond.atom2 + atom_offset}); + } + target.bond_parameter_overrides.reserve( + target.bond_parameter_overrides.size() + source.bond_parameter_overrides.size()); + for (const auto& term : source.bond_parameter_overrides) { + target.bond_parameter_overrides.push_back( + {term.atom1 + atom_offset, term.atom2 + atom_offset, + term.k, term.length, term.source}); + } + target.angle_parameter_overrides.reserve( + target.angle_parameter_overrides.size() + source.angle_parameter_overrides.size()); + for (const auto& term : source.angle_parameter_overrides) { + target.angle_parameter_overrides.push_back( + {term.atom1 + atom_offset, term.atom2 + atom_offset, + term.atom3 + atom_offset, term.k, term.theta, term.source}); + } + target.lj_parameter_overrides.insert( + target.lj_parameter_overrides.end(), + source.lj_parameter_overrides.begin(), + source.lj_parameter_overrides.end()); target.virtual_atoms.reserve(target.virtual_atoms.size() + source.virtual_atoms.size()); for (const auto& vatom : source.virtual_atoms) { target.virtual_atoms.push_back({vatom.virtual_atom + atom_offset, vatom.atom0 + atom_offset, @@ -103,6 +127,41 @@ void remap_internal_structures(const Molecule& source, Molecule& target, const s } target.residue_links.push_back({atom1, atom2}); } + target.coordination_bonds.reserve( + target.coordination_bonds.size() + source.coordination_bonds.size()); + for (const auto& bond : source.coordination_bonds) { + const AtomId atom1 = remap_atom_id(old_to_new_atom, bond.atom1); + const AtomId atom2 = remap_atom_id(old_to_new_atom, bond.atom2); + if (atom1 == invalid_atom_id || atom2 == invalid_atom_id) { + continue; + } + target.coordination_bonds.push_back({atom1, atom2}); + } + target.bond_parameter_overrides.reserve( + target.bond_parameter_overrides.size() + source.bond_parameter_overrides.size()); + for (const auto& term : source.bond_parameter_overrides) { + const AtomId atom1 = remap_atom_id(old_to_new_atom, term.atom1); + const AtomId atom2 = remap_atom_id(old_to_new_atom, term.atom2); + if (atom1 == invalid_atom_id || atom2 == invalid_atom_id) { + continue; + } + target.bond_parameter_overrides.push_back( + {atom1, atom2, term.k, term.length, term.source}); + } + target.angle_parameter_overrides.reserve( + target.angle_parameter_overrides.size() + source.angle_parameter_overrides.size()); + for (const auto& term : source.angle_parameter_overrides) { + const AtomId atom1 = remap_atom_id(old_to_new_atom, term.atom1); + const AtomId atom2 = remap_atom_id(old_to_new_atom, term.atom2); + const AtomId atom3 = remap_atom_id(old_to_new_atom, term.atom3); + if (atom1 == invalid_atom_id || atom2 == invalid_atom_id || + atom3 == invalid_atom_id) { + continue; + } + target.angle_parameter_overrides.push_back( + {atom1, atom2, atom3, term.k, term.theta, term.source}); + } + target.lj_parameter_overrides = source.lj_parameter_overrides; target.virtual_atoms.reserve(target.virtual_atoms.size() + source.virtual_atoms.size()); for (const auto& vatom : source.virtual_atoms) { const AtomId virtual_atom = remap_atom_id(old_to_new_atom, vatom.virtual_atom); @@ -205,12 +264,16 @@ void Molecule::replace_residues(const std::unordered_map& r Molecule rebuilt(name); rebuilt.box_length = box_length; + rebuilt.box_origin = box_origin; rebuilt.box_angle = box_angle; rebuilt.has_box = has_box; + rebuilt.has_box_origin = has_box_origin; rebuilt.has_gb_parameters = has_gb_parameters; rebuilt.write_min_bonded_parameters = write_min_bonded_parameters; rebuilt.write_subsys_division = write_subsys_division; rebuilt.write_lj_soft_core = write_lj_soft_core; + rebuilt.ignore_missing_atoms = ignore_missing_atoms; + rebuilt.listed_force_definitions = listed_force_definitions; rebuilt.sw_parameters = sw_parameters; rebuilt.edip_parameters = edip_parameters; @@ -298,12 +361,15 @@ void Molecule::reorder_atoms_by_template(const Molecule& template_molecule) { Molecule rebuilt(name); rebuilt.box_length = box_length; + rebuilt.box_origin = box_origin; rebuilt.box_angle = box_angle; rebuilt.has_box = has_box; + rebuilt.has_box_origin = has_box_origin; rebuilt.has_gb_parameters = has_gb_parameters; rebuilt.write_min_bonded_parameters = write_min_bonded_parameters; rebuilt.write_subsys_division = write_subsys_division; rebuilt.write_lj_soft_core = write_lj_soft_core; + rebuilt.ignore_missing_atoms = ignore_missing_atoms; std::vector old_to_new_atom(atoms.size(), invalid_atom_id); rebuilt.residues.reserve(residues.size()); diff --git a/cpp/forcefield/amber_templates.cpp b/cpp/forcefield/amber_templates.cpp index d0d6a97..5bed749 100644 --- a/cpp/forcefield/amber_templates.cpp +++ b/cpp/forcefield/amber_templates.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include namespace xpongecpp { @@ -25,14 +26,22 @@ ResidueType residue_type_from_molecule_residue(const Molecule& molecule, const R return residue_type; } -void register_residue_templates_from_mol2_text(const std::string& text) { +namespace { + +void register_residue_templates_from_mol2_text_impl(const std::string& text, bool preserve_existing) { const auto molecule = load_mol2_text(text); std::vector> residue_types; residue_types.reserve(molecule.residues.size()); std::unordered_map residue_to_type; + std::unordered_set newly_registered_names; for (ResidueId residue_id = 0; residue_id < molecule.residues.size(); ++residue_id) { const auto& residue = molecule.residues[residue_id]; + if ((preserve_existing && has_template(residue.name)) || + newly_registered_names.find(residue.name) != newly_registered_names.end()) { + continue; + } + newly_registered_names.insert(residue.name); residue_to_type[residue_id] = residue_types.size(); residue_types.emplace_back(residue_id, ResidueType(residue.name)); auto& residue_type = residue_types.back().second; @@ -45,13 +54,36 @@ void register_residue_templates_from_mol2_text(const std::string& text) { for (const auto& bond : molecule.explicit_bonds) { const auto res1 = molecule.atoms[bond.atom1].residue; const auto res2 = molecule.atoms[bond.atom2].residue; - if (res1 != res2) { + if (res1 == res2) { + const auto type_it = residue_to_type.find(res1); + if (type_it == residue_to_type.end()) { + continue; + } + auto& residue_type = residue_types[type_it->second].second; + try { + residue_type.add_connectivity(molecule.atoms[bond.atom1].name, molecule.atoms[bond.atom2].name); + } catch (const std::exception&) { + } continue; } - auto& residue_type = residue_types[residue_to_type.at(res1)].second; - try { - residue_type.add_connectivity(molecule.atoms[bond.atom1].name, molecule.atoms[bond.atom2].name); - } catch (const std::exception&) { + if (res1 + 1 == res2) { + const auto left_it = residue_to_type.find(res1); + if (left_it != residue_to_type.end()) { + residue_types[left_it->second].second.set_tail(molecule.atoms[bond.atom1].name); + } + const auto right_it = residue_to_type.find(res2); + if (right_it != residue_to_type.end()) { + residue_types[right_it->second].second.set_head(molecule.atoms[bond.atom2].name); + } + } else if (res2 + 1 == res1) { + const auto left_it = residue_to_type.find(res2); + if (left_it != residue_to_type.end()) { + residue_types[left_it->second].second.set_tail(molecule.atoms[bond.atom2].name); + } + const auto right_it = residue_to_type.find(res1); + if (right_it != residue_to_type.end()) { + residue_types[right_it->second].second.set_head(molecule.atoms[bond.atom1].name); + } } } @@ -60,14 +92,36 @@ void register_residue_templates_from_mol2_text(const std::string& text) { } } -void register_residue_templates_from_mol2_file(const std::filesystem::path& filename) { +std::string read_mol2_template_file(const std::filesystem::path& filename) { std::ifstream input(filename); if (!input) { throw std::runtime_error("failed to open mol2 template file: " + filename.string()); } std::ostringstream buffer; buffer << input.rdbuf(); - register_residue_templates_from_mol2_text(buffer.str()); + return buffer.str(); +} + +} // namespace + +void register_residue_templates_from_mol2_text(const std::string& text) { + register_residue_templates_from_mol2_text_impl(text, false); +} + +void register_residue_templates_from_mol2_file(const std::filesystem::path& filename) { + register_residue_templates_from_mol2_text(read_mol2_template_file(filename)); +} + +void register_new_residue_templates_from_mol2_text(const std::string& text) { + register_residue_templates_from_mol2_text_impl(text, true); +} + +void register_new_residue_templates_from_mol2_file(const std::filesystem::path& filename) { + register_new_residue_templates_from_mol2_text(read_mol2_template_file(filename)); +} + +void register_residue_type_template(const ResidueType& residue_type) { + put_template(residue_type); } void register_template_molecule_from_mol2_file(const std::filesystem::path& filename) { diff --git a/cpp/io/pdb.cpp b/cpp/io/pdb.cpp index 88a5770..5e406cb 100644 --- a/cpp/io/pdb.cpp +++ b/cpp/io/pdb.cpp @@ -248,6 +248,8 @@ void apply_template_atom_properties(Molecule& molecule, bool ignore_unknown_name const ResidueType* residue_type = nullptr; if (has_template(residue.name)) { residue_type = &get_residue_template(residue.name); + residue.name = residue_type->name(); + residue.type_name = residue.name; } for (std::uint32_t local = 0; local < residue.atom_count; ++local) { auto atom = molecule.atoms[residue.atom_begin + local]; diff --git a/cpp/io/pdb_reader.cpp b/cpp/io/pdb_reader.cpp index 16c9dc6..8e4e081 100644 --- a/cpp/io/pdb_reader.cpp +++ b/cpp/io/pdb_reader.cpp @@ -903,6 +903,25 @@ Molecule load_mmcif_text(const std::string& text, const MmcifLoadOptions& option if (it == chem_comp_bonds.end()) { continue; } + // A Chemcore/self-contained mmCIF describes ordinary covalent bonds in + // _chem_comp_bond, while a registered force-field template may also + // contain model-defining pseudo-bonds (TIP3P H1-H2 is the canonical + // example). Once a residue has any explicit bond, build_topology() + // deliberately treats that explicit graph as authoritative. Seed the + // graph with the complete registered template before overlaying the + // mmCIF bonds so an otherwise complete covalent loop does not silently + // discard those force-field terms. Later _mokda_edit_operation rows + // still run last and can remove an explicitly deleted bond. + if (has_template(residue.name)) { + const auto& residue_type = get_residue_template(residue.name); + for (const auto& bond : residue_type.bonds()) { + const AtomId atom1 = find_atom( + molecule, residue, residue_type.atoms()[bond.atom1].name); + const AtomId atom2 = find_atom( + molecule, residue, residue_type.atoms()[bond.atom2].name); + mmcif_add_connection(molecule, atom1, atom2); + } + } for (const auto& [atom1_name, atom2_name] : it->second) { const AtomId atom1 = find_atom(molecule, residue, atom1_name); const AtomId atom2 = find_atom(molecule, residue, atom2_name); diff --git a/cpp/io/sponge.cpp b/cpp/io/sponge.cpp index 57a62fc..1b7bd86 100644 --- a/cpp/io/sponge.cpp +++ b/cpp/io/sponge.cpp @@ -210,16 +210,37 @@ std::vector real_lj_types(const std::vector& lj_types, } bool reorder_residues_by_linked_components(Molecule& molecule) { - if (molecule.residues.size() < 2 || molecule.residue_links.empty()) { + if (molecule.residues.size() < 2 || + (molecule.residue_links.empty() && + molecule.coordination_bonds.empty() && + molecule.explicit_bonds.empty() && + molecule.bond_parameter_overrides.empty())) { return false; } IndexDisjointSet components(molecule.residues.size()); + const auto unite_link = [&](AtomId atom1, AtomId atom2) { + if (atom1 >= molecule.atoms.size() || atom2 >= molecule.atoms.size()) { + throw std::invalid_argument("component link atom index out of range"); + } + components.unite( + molecule.atoms[atom1].residue, + molecule.atoms[atom2].residue + ); + }; for (const auto& link : molecule.residue_links) { - if (link.atom1 >= molecule.atoms.size() || link.atom2 >= molecule.atoms.size()) { - throw std::invalid_argument("residue link atom index out of range"); + unite_link(link.atom1, link.atom2); + } + for (const auto& link : molecule.coordination_bonds) { + unite_link(link.atom1, link.atom2); + } + for (const auto& link : molecule.explicit_bonds) { + unite_link(link.atom1, link.atom2); + } + for (const auto& parameter_override : molecule.bond_parameter_overrides) { + if (parameter_override.k != 0.0) { + unite_link(parameter_override.atom1, parameter_override.atom2); } - components.unite(molecule.atoms[link.atom1].residue, molecule.atoms[link.atom2].residue); } std::unordered_map root_to_sort_key; diff --git a/cpp/python/bindings_forcefield.cpp b/cpp/python/bindings_forcefield.cpp index 1b6ac2f..b338778 100644 --- a/cpp/python/bindings_forcefield.cpp +++ b/cpp/python/bindings_forcefield.cpp @@ -225,6 +225,12 @@ void bind_forcefield_module(py::module_& m) { py::arg("text")); m.def("register_residue_templates_from_mol2_file", [](const std::string& filename) { register_residue_templates_from_mol2_file(filename); }); + m.def("register_new_residue_templates_from_mol2_text", + ®ister_new_residue_templates_from_mol2_text, py::arg("text")); + m.def("register_new_residue_templates_from_mol2_file", + [](const std::string& filename) { register_new_residue_templates_from_mol2_file(filename); }); + m.def("register_residue_type_template", ®ister_residue_type_template, + py::arg("residue_type")); m.def("register_template_molecule_from_mol2_file", [](const std::string& filename) { register_template_molecule_from_mol2_file(filename); }); m.def("register_template_virtual_atom2", ®ister_template_virtual_atom2, py::arg("template_name"), diff --git a/pyproject.toml b/pyproject.toml index 8051f5b..21e7dbb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build" [project] name = "XpongeCPP" -version = "0.2.2" +version = "0.2.3" description = "C++ core rewrite of Xponge workflows, compatible with Xponge-origin 1.7b9" readme = "README.md" requires-python = ">=3.10,<3.13" diff --git a/src/Xponge/build.py b/src/Xponge/build.py index 6d01a5d..9b8fbe4 100644 --- a/src/Xponge/build.py +++ b/src/Xponge/build.py @@ -1,3 +1,4 @@ """Legacy Xponge.build shim.""" from XpongeCPP.build import * # noqa: F401,F403 +from XpongeCPP.build import _pdb_guess_element diff --git a/src/XpongeCPP/__init__.py b/src/XpongeCPP/__init__.py index 21f8afd..34f60a6 100644 --- a/src/XpongeCPP/__init__.py +++ b/src/XpongeCPP/__init__.py @@ -193,7 +193,7 @@ def _get_molecule_charge(molecule): from .legacy_types import _LegacyResidueTypeHandle from .template_ops import load_mol2 -__version__ = "0.2.2" +__version__ = "0.2.3" __mokda_backend__ = "xpongecpp" __implementation_version__ = __version__ pi = np.pi @@ -234,24 +234,12 @@ def load_parmdat(filename): def load_pdb(*args, **kwargs): - from .forcefield import package_data_path - - set_lj_combining_rule("lorentz_berthelot") - register_amber_nb14_scale("X", "X", 0.5, 0.833333) - register_amber_parmdat_file(str(package_data_path("amber", "parm10.dat"))) - register_amber_frcmod_file(str(package_data_path("amber", "ff14SB.frcmod"))) if args and isinstance(args[0], os.PathLike): args = (os.fspath(args[0]), *args[1:]) return _core_load_pdb(*args, **kwargs) def load_mmcif(*args, **kwargs): - from .forcefield import package_data_path - - set_lj_combining_rule("lorentz_berthelot") - register_amber_nb14_scale("X", "X", 0.5, 0.833333) - register_amber_parmdat_file(str(package_data_path("amber", "parm10.dat"))) - register_amber_frcmod_file(str(package_data_path("amber", "ff14SB.frcmod"))) if args and isinstance(args[0], os.PathLike): args = (os.fspath(args[0]), *args[1:]) return _core_load_mmcif(*args, **kwargs) diff --git a/src/XpongeCPP/build.py b/src/XpongeCPP/build.py index c296b33..4fe5388 100644 --- a/src/XpongeCPP/build.py +++ b/src/XpongeCPP/build.py @@ -1,5 +1,6 @@ """Legacy-style build module shim for XpongeCPP.""" +from .helper.math import guess_element_from_mass from ._compat.workflows import build_bonded_force, get_mindsponge_system_energy from ._compat.imports import reexport_module @@ -22,6 +23,36 @@ ], ) + +def _pdb_guess_element(atom): + """Return the two-column PDB element token used by Xponge writers.""" + + element = getattr(atom, "element", None) + if not element: + element = getattr(getattr(atom, "type", None), "element", None) + if not element: + mass = getattr(atom, "mass", None) + if mass is not None and mass > 0: + try: + element = guess_element_from_mass(mass) + except Exception: + element = None + if not element: + name = getattr(atom, "name", "") + letters = "".join(character for character in name if character.isalpha()) + if letters: + element = ( + letters[0].upper() + letters[1].lower() + if len(letters) >= 2 and letters[1].islower() + else letters[0].upper() + ) + else: + element = "X" + if len(element) == 1: + return f"{element:>2}" + return f"{element[:2]:>2}" + + __all__ = [ "save_gro", "save_mol2", diff --git a/src/XpongeCPP/forcefield/amber/bsc1.py b/src/XpongeCPP/forcefield/amber/bsc1.py index 970519a..9136c83 100644 --- a/src/XpongeCPP/forcefield/amber/bsc1.py +++ b/src/XpongeCPP/forcefield/amber/bsc1.py @@ -1,8 +1,11 @@ """Register Amber bsc1 DNA templates and parameters.""" from ... import ( + configure_residue_template_head, + configure_residue_template_tail, register_amber_frcmod_file, register_amber_parmdat_file, + register_pdb_residue_name_mapping, register_residue_templates_from_mol2_file, ) from . import data_path @@ -11,3 +14,14 @@ register_amber_frcmod_file(str(data_path("parmbsc1.frcmod"))) register_residue_templates_from_mol2_file(str(data_path("RNA.mol2"))) register_residue_templates_from_mol2_file(str(data_path("bsc1.mol2"))) + +for base in "ATCG": + residue = f"D{base}" + residue5 = f"{residue}5" + residue3 = f"{residue}3" + configure_residue_template_tail(residue, "O3'", 1.5, "C3'") + configure_residue_template_tail(residue5, "O3'", 1.5, "C3'") + configure_residue_template_head(residue, "P", 1.5, "OP2") + configure_residue_template_head(residue3, "P", 1.5, "OP2") + register_pdb_residue_name_mapping("head", residue, residue5) + register_pdb_residue_name_mapping("tail", residue, residue3) diff --git a/src/XpongeCPP/forcefield/amber/ff19sb.py b/src/XpongeCPP/forcefield/amber/ff19sb.py index 681343e..8673c67 100644 --- a/src/XpongeCPP/forcefield/amber/ff19sb.py +++ b/src/XpongeCPP/forcefield/amber/ff19sb.py @@ -7,6 +7,7 @@ from ... import ( register_amber_frcmod_file, register_amber_parmdat_file, + register_his_mapping, register_residue_templates_from_mol2_file, ) from . import configure_proline_like_terminal_mapping, data_path @@ -15,4 +16,5 @@ register_amber_frcmod_file(str(data_path("ff19SB.frcmod"))) register_residue_templates_from_mol2_file(str(data_path("ff19SB.mol2"))) register_residue_templates_from_mol2_file(str(data_path("ff19SB_nhyp.mol2"))) +register_his_mapping("HIS", "HID", "HIE", "HIP") configure_proline_like_terminal_mapping("HYP", "CHYP", "NHYP") diff --git a/src/XpongeCPP/forcefield/amber/tip3p.py b/src/XpongeCPP/forcefield/amber/tip3p.py index 96fa019..2d163d5 100644 --- a/src/XpongeCPP/forcefield/amber/tip3p.py +++ b/src/XpongeCPP/forcefield/amber/tip3p.py @@ -3,6 +3,7 @@ from ... import ( AtomType, register_amber_frcmod_file, + register_pdb_residue_alias_mapping, register_residue_template_alias, register_residue_templates_from_mol2_file, register_tip3p, @@ -21,6 +22,8 @@ register_residue_templates_from_mol2_file(str(data_path("tip3p.mol2"))) register_residue_template_alias("H2O", "WAT") register_residue_template_alias("HOH", "WAT") +register_pdb_residue_alias_mapping("H2O", "WAT") +register_pdb_residue_alias_mapping("HOH", "WAT") register_amber_frcmod_file(str(data_path("ions1lm_126_tip3p.frcmod"))) register_amber_frcmod_file(str(data_path("ionsjc_tip3p.frcmod"))) register_amber_frcmod_file(str(data_path("ions234lm_126_tip3p.frcmod"))) diff --git a/src/XpongeCPP/legacy_types.py b/src/XpongeCPP/legacy_types.py index 7ca9b81..1a640b2 100644 --- a/src/XpongeCPP/legacy_types.py +++ b/src/XpongeCPP/legacy_types.py @@ -14,6 +14,7 @@ get_template_molecule, has_template, molecule_from_residuetype, + register_residue_type_template, registered_template_names, register_residue_templates_from_mol2_text, ) @@ -47,8 +48,10 @@ def head(self): @head.setter def head(self, value): _legacy_template_metadata.setdefault(self._name, {})["head"] = value - if value: - configure_residue_template_head(self._name, str(value)) + configure_residue_template_head( + self._name, + "" if value is None else str(value), + ) @property def tail(self): @@ -57,8 +60,10 @@ def tail(self): @tail.setter def tail(self, value): _legacy_template_metadata.setdefault(self._name, {})["tail"] = value - if value: - configure_residue_template_tail(self._name, str(value)) + configure_residue_template_tail( + self._name, + "" if value is None else str(value), + ) @property def head_next(self): @@ -106,8 +111,29 @@ def tail_link_conditions(self): @property def atoms(self): + dynamic = _legacy_dynamic_residue_types.get(self._name) + if dynamic is not None: + return dynamic.atoms return get_template_molecule(self._name).residues[0].atoms + def name2atom(self, name): + for atom in self.atoms: + if atom.name == name: + return atom + raise KeyError(name) + + def add_atom(self, name, atom_type, x, y, z, charge=0.0, mass=0.0): + residue_type = _materialize_dynamic_residuetype(self._name) + return residue_type.add_atom( + name, + atom_type, + x, + y, + z, + charge=charge, + mass=mass, + ) + def deepcopy(self, name): name = str(name) _register_template_variant(self._name, name) @@ -157,6 +183,41 @@ def _legacy_get_all_residuetypes(): return {name: _legacy_get_residuetype(name) for name in sorted(names)} +def _materialize_dynamic_residuetype(name): + """Return a writable copy of a registered residue template.""" + + name = str(name) + dynamic = _legacy_dynamic_residue_types.get(name) + if dynamic is not None: + return dynamic + if not has_template(name): + raise KeyError(f"ResidueType {name!r} is not registered") + + template = get_template_molecule(name) + if template.residue_count != 1: + raise TypeError( + "residue template compatibility expects one-residue templates" + ) + residue = template.residues[0] + dynamic = _remember_dynamic_residuetype(ResidueType(name)) + for atom in residue.atoms: + dynamic.add_atom( + atom.name, + atom.type, + atom.x, + atom.y, + atom.z, + charge=atom.charge, + mass=atom.mass, + ) + for atom1, atom2 in template.explicit_bonds: + dynamic.add_connectivity( + template.atoms[int(atom1)].name, + template.atoms[int(atom2)].name, + ) + return dynamic + + def _remember_template_connection( residue_name, position, @@ -293,9 +354,12 @@ def _ensure_dynamic_residuetype_template(name): residue_type = _legacy_dynamic_residue_types.get(str(name)) if residue_type is None: return False - register_residue_templates_from_mol2_text( - _dynamic_residuetype_mol2_text(residue_type) - ) + register_residue_type_template(residue_type) + metadata = _legacy_template_metadata.get(str(name), {}) + if metadata.get("head"): + configure_residue_template_head(str(name), str(metadata["head"])) + if metadata.get("tail"): + configure_residue_template_tail(str(name), str(metadata["tail"])) return True diff --git a/src/XpongeCPP/metal_assignment/molecule_api.py b/src/XpongeCPP/metal_assignment/molecule_api.py index a35da6c..347b2f9 100644 --- a/src/XpongeCPP/metal_assignment/molecule_api.py +++ b/src/XpongeCPP/metal_assignment/molecule_api.py @@ -174,7 +174,19 @@ def _normalized_patch_mapping( try: index = molecule_atom_index[value] except (KeyError, TypeError) as exc: - raise ValidationError("molecule_atom_not_found", external_id) from exc + native_index = getattr(value, "index", None) + if isinstance(native_index, bool): + raise ValidationError( + "molecule_atom_not_found", + external_id, + ) from exc + try: + index = int(native_index) + except (TypeError, ValueError): + raise ValidationError( + "molecule_atom_not_found", + external_id, + ) from exc if index < 0 or index >= len(molecule.atoms): raise ValidationError("invalid_molecule_atom_index", external_id) molecule_atom = molecule.atoms[index] diff --git a/src/XpongeCPP/scientific_manifest.py b/src/XpongeCPP/scientific_manifest.py new file mode 100644 index 0000000..0921e29 --- /dev/null +++ b/src/XpongeCPP/scientific_manifest.py @@ -0,0 +1,722 @@ +"""Provider-neutral scientific manifests for SPONGE input cases. + +The manifest intentionally describes parsed scientific state instead of file +bytes. It is therefore suitable for comparing SPONGE input generated by +Xponge-origin and XpongeCPP even when harmless formatting differs. +""" + +from __future__ import annotations + +import hashlib +import json +from pathlib import Path +from typing import Any + +import numpy as np + +from ._core import load_coordinate +from .io_bundle import convert_bundle_to_legacy, scan_legacy_case +from .io_bundle.contracts import contracts_by_legacy_key +from .io_bundle.legacy_case import LegacyCase +from .io_bundle.topology_parsers import parse_topology_file + + +SCHEMA = "xponge.scientific_manifest" +SCHEMA_VERSION = 1 + +_TEXT_KEYS = { + "atom_name_in_file": "/atoms/name", + "atom_type_name_in_file": "/atoms/type_name", + "resname_in_file": "/residues/name", +} + + +def _sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as handle: + for chunk in iter(lambda: handle.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def _json_array(value: np.ndarray) -> dict[str, Any]: + array = np.asarray(value) + return { + "dtype": str(array.dtype), + "shape": list(array.shape), + "values": array.tolist(), + } + + +def _read_counted_text(path: Path) -> list[str]: + lines = path.read_text(encoding="utf-8").splitlines() + if not lines: + return [] + try: + count = int(lines[0].strip()) + except ValueError: + return lines + values = lines[1:] + if len(values) != count: + raise ValueError( + f"{path} declares {count} text records but contains {len(values)}" + ) + return values + + +def _find_mdin(root: Path, mdin: str | Path | None) -> Path | None: + if mdin is not None: + path = Path(mdin) + if not path.is_absolute(): + path = root / path + if not path.is_file(): + raise FileNotFoundError(f"SPONGE mdin does not exist: {path}") + return path + candidates = sorted(root.glob("mdin*.toml")) + if not candidates: + return None + if len(candidates) != 1: + raise ValueError( + f"expected one mdin*.toml in {root}, found {len(candidates)}" + ) + return candidates[0] + + +def _materialize_raw_case( + root: Path, + *, + mdin: str | Path | None, + temporary_root: Path | None, +) -> tuple[Path, Path | None]: + topology_h5 = root / "topology.spgp.h5" + if not topology_h5.is_file(): + return root, _find_mdin(root, mdin) + if temporary_root is None: + raise ValueError( + "temporary_root is required when building a manifest from a bundle" + ) + prefix = str(root.name or "system") + converted = temporary_root / "legacy" + result = convert_bundle_to_legacy(root, converted, prefix=prefix) + return converted, Path(result.generated_mdin) + + +def _set_string_dataset( + datasets: dict[str, dict[str, Any]], + path: str, + values: list[str], +) -> None: + datasets[path] = { + "dtype": "string", + "shape": [len(values)], + "values": values, + } + + +def _canonicalize_interactions( + datasets: dict[str, dict[str, Any]], + atom_map: np.ndarray, + *, + stem: str, + atom_arity: int, + parameter_names: tuple[str, ...], + reversible: bool, +) -> None: + atom_path = f"/forcefield/{stem}/atoms" + if atom_path not in datasets: + return + parameter_paths = tuple( + f"/forcefield/{stem}/{name}" for name in parameter_names + ) + if any(path not in datasets for path in parameter_paths): + return + atoms = np.asarray(datasets[atom_path]["values"], dtype=np.int64) + if atoms.ndim != 2 or atoms.shape[1] != atom_arity: + return + mapped_atoms = atom_map[atoms] + parameters = [ + np.asarray(datasets[path]["values"]) for path in parameter_paths + ] + records: list[tuple[Any, ...]] = [] + for index, raw_atoms in enumerate(mapped_atoms): + atom_tuple = tuple(int(value) for value in raw_atoms) + if reversible: + atom_tuple = min(atom_tuple, atom_tuple[::-1]) + records.append(( + atom_tuple, + *(parameter[index].tolist() for parameter in parameters), + )) + order = sorted(range(len(records)), key=records.__getitem__) + datasets[atom_path] = _json_array(np.asarray( + [records[index][0] for index in order], + dtype=np.int32, + )) + for offset, path in enumerate(parameter_paths, start=1): + datasets[path] = _json_array(np.asarray([ + records[index][offset] for index in order + ])) + + +def _canonicalize_lj_types( + datasets: dict[str, dict[str, Any]], + atom_order: np.ndarray, +) -> None: + type_path = "/forcefield/lj/type" + count_path = "/forcefield/lj/atom_type_count" + atom_type_path = "/atoms/type_name" + pair_a_path = "/forcefield/lj/pair_A_12" + pair_b_path = "/forcefield/lj/pair_B_6" + required = ( + type_path, + count_path, + atom_type_path, + pair_a_path, + pair_b_path, + ) + if any(path not in datasets for path in required): + return + raw_ids = np.asarray(datasets[type_path]["values"], dtype=np.int64) + atom_type_names = list(datasets[atom_type_path]["values"]) + type_count = int(np.asarray( + datasets[count_path]["values"], + ).reshape(())) + if ( + raw_ids.shape != (len(atom_type_names),) + or type_count <= 0 + or np.any(raw_ids < 0) + or np.any(raw_ids >= type_count) + ): + return + labels: list[tuple[str, ...]] = [] + for type_id in range(type_count): + names = sorted({ + str(atom_type_names[index]) + for index in np.flatnonzero(raw_ids == type_id) + }) + labels.append(tuple(names) if names else (f"",)) + canonical_old_ids = sorted(range(type_count), key=labels.__getitem__) + old_to_new = np.empty(type_count, dtype=np.int32) + for new_id, old_id in enumerate(canonical_old_ids): + old_to_new[old_id] = new_id + datasets[type_path] = _json_array(old_to_new[raw_ids[atom_order]]) + + def reorder_pairs(path: str) -> None: + flat = np.asarray(datasets[path]["values"]) + if flat.size != type_count * (type_count + 1) // 2: + return + matrix = np.zeros((type_count, type_count), dtype=flat.dtype) + cursor = 0 + for first in range(type_count): + for second in range(first + 1): + matrix[first, second] = flat[cursor] + matrix[second, first] = flat[cursor] + cursor += 1 + reordered: list[Any] = [] + for first in canonical_old_ids: + first_new = int(old_to_new[first]) + for second_new in range(first_new + 1): + second = canonical_old_ids[second_new] + reordered.append(matrix[first, second]) + datasets[path] = _json_array(np.asarray(reordered, dtype=flat.dtype)) + + reorder_pairs(pair_a_path) + reorder_pairs(pair_b_path) + + +def _canonicalize_exclusions( + datasets: dict[str, dict[str, Any]], + atom_order: np.ndarray, + atom_map: np.ndarray, +) -> None: + offset_path = "/topology/exclusions/offset" + list_path = "/topology/exclusions/list" + if offset_path not in datasets or list_path not in datasets: + return + offsets = np.asarray(datasets[offset_path]["values"], dtype=np.int64) + excluded = np.asarray(datasets[list_path]["values"], dtype=np.int64) + if offsets.shape != (len(atom_order) + 1,): + return + pairs: set[tuple[int, int]] = set() + for old_atom in range(len(atom_order)): + mapped_atom = int(atom_map[old_atom]) + values = atom_map[excluded[offsets[old_atom]:offsets[old_atom + 1]]] + for mapped_excluded in values: + pair = tuple(sorted((mapped_atom, int(mapped_excluded)))) + if pair[0] != pair[1]: + pairs.add(pair) + del datasets[offset_path] + del datasets[list_path] + datasets["/topology/exclusions/pairs"] = _json_array( + np.asarray(sorted(pairs), dtype=np.int32).reshape(-1, 2) + ) + + +def _canonicalize_atom_order( + datasets: dict[str, dict[str, Any]], +) -> None: + """Make atom-indexed state independent of provider insertion order.""" + + name_path = "/atoms/name" + residue_path = "/atoms/residue_index" + if name_path not in datasets or residue_path not in datasets: + return + names = [str(value) for value in datasets[name_path]["values"]] + residue_indices = np.asarray( + datasets[residue_path]["values"], + dtype=np.int64, + ) + if residue_indices.shape != (len(names),): + return + keys = [ + (int(residue_indices[index]), names[index]) + for index in range(len(names)) + ] + if len(set(keys)) != len(keys): + return + atom_order = np.asarray(sorted( + range(len(keys)), + key=keys.__getitem__, + ), dtype=np.int64) + atom_map = np.empty(len(atom_order), dtype=np.int32) + atom_map[atom_order] = np.arange(len(atom_order), dtype=np.int32) + + atom_paths = ( + "/atoms/charge", + "/atoms/mass", + "/atoms/residue_index", + "/atoms/type_name", + "/restart/position", + ) + _canonicalize_lj_types(datasets, atom_order) + for path in atom_paths: + if path not in datasets: + continue + if path == "/atoms/type_name": + values = [str(value) for value in datasets[path]["values"]] + _set_string_dataset( + datasets, + path, + [values[index] for index in atom_order], + ) + continue + values = np.asarray(datasets[path]["values"]) + if values.shape and values.shape[0] == len(atom_order): + datasets[path] = _json_array(values[atom_order]) + _set_string_dataset( + datasets, + name_path, + [names[index] for index in atom_order], + ) + + _canonicalize_interactions( + datasets, + atom_map, + stem="bond", + atom_arity=2, + parameter_names=("k", "r0"), + reversible=True, + ) + _canonicalize_interactions( + datasets, + atom_map, + stem="angle", + atom_arity=3, + parameter_names=("k", "theta0"), + reversible=True, + ) + _canonicalize_interactions( + datasets, + atom_map, + stem="nb14", + atom_arity=2, + parameter_names=("params",), + reversible=True, + ) + dihedral_path = "/forcefield/dihedral/atoms" + if dihedral_path in datasets: + atoms = np.asarray( + datasets[dihedral_path]["values"], + dtype=np.int64, + ) + datasets[dihedral_path] = _json_array(atom_map[atoms]) + _canonicalize_exclusions(datasets, atom_order, atom_map) + + +def _canonicalize_dihedrals( + datasets: dict[str, dict[str, Any]], +) -> None: + atom_path = "/forcefield/dihedral/atoms" + if atom_path not in datasets: + return + parameter_paths = ( + "/forcefield/dihedral/periodicity", + "/forcefield/dihedral/k", + "/forcefield/dihedral/phi0", + ) + if any(path not in datasets for path in parameter_paths): + return + bond_atoms = np.asarray( + datasets.get("/forcefield/bond/atoms", {}).get("values", []), + dtype=np.int64, + ) + bond_pairs = { + frozenset((int(atom1), int(atom2))) for atom1, atom2 in bond_atoms + } + atoms = np.asarray(datasets[atom_path]["values"], dtype=np.int64) + parameters = [ + np.asarray(datasets[path]["values"]) for path in parameter_paths + ] + records = [] + kinds = [] + for index, raw_atoms in enumerate(atoms): + atom_tuple = tuple(int(value) for value in raw_atoms) + centers = [ + atom + for atom in atom_tuple + if all( + other == atom or frozenset((atom, other)) in bond_pairs + for other in atom_tuple + ) + ] + if len(centers) == 1: + center = centers[0] + outer = sorted(atom for atom in atom_tuple if atom != center) + canonical_atoms = (outer[0], outer[1], center, outer[2]) + kind = "improper" + else: + reverse = atom_tuple[::-1] + canonical_atoms = min(atom_tuple, reverse) + kind = "proper" + record = ( + canonical_atoms, + *(parameter[index].item() for parameter in parameters), + ) + records.append(record) + kinds.append(kind) + order = sorted(range(len(records)), key=records.__getitem__) + datasets[atom_path] = _json_array( + np.asarray([records[index][0] for index in order], dtype=np.int32) + ) + for offset, path in enumerate(parameter_paths, start=1): + datasets[path] = _json_array( + np.asarray([records[index][offset] for index in order]) + ) + ordered_kinds = [kinds[index] for index in order] + datasets["/forcefield/dihedral/kind"] = { + "dtype": "string", + "shape": [len(ordered_kinds)], + "values": ordered_kinds, + } + + +def _canonicalize_cmaps( + datasets: dict[str, dict[str, Any]], +) -> None: + """Canonicalize CMAP tables independently of provider type numbering. + + SPONGE CMAP type identifiers are local indices into the grids written in + ``system_cmap.txt``. Xponge-origin numbers them by first topology use, + whereas XpongeCPP retains force-field declaration order. Moreover, + multiple chemical CMAP types can share an identical numerical grid. The + numerical grid is the complete scientific identity available in SPONGE + input, so manifests deduplicate and sort by that identity before remapping + interaction records. + """ + + atom_path = "/forcefield/cmap/atoms" + type_path = "/forcefield/cmap/type" + resolution_path = "/forcefield/cmap/resolution" + grid_path = "/forcefield/cmap/grid_value" + required = (atom_path, type_path, resolution_path, grid_path) + if any(path not in datasets for path in required): + return + + atoms = np.asarray(datasets[atom_path]["values"], dtype=np.int64) + raw_types = np.asarray(datasets[type_path]["values"], dtype=np.int64) + resolutions = np.asarray( + datasets[resolution_path]["values"], dtype=np.int64 + ) + grid_values = np.asarray(datasets[grid_path]["values"]) + if ( + atoms.ndim != 2 + or atoms.shape[1] != 5 + or raw_types.shape != (atoms.shape[0],) + or np.any(resolutions <= 0) + or np.any(raw_types < 0) + or np.any(raw_types >= len(resolutions)) + ): + return + + signatures: list[tuple[int, tuple[Any, ...]]] = [] + cursor = 0 + for resolution in resolutions: + size = int(resolution) ** 2 + block = grid_values[cursor:cursor + size] + if block.size != size: + return + signatures.append(( + int(resolution), + tuple(block.tolist()), + )) + cursor += size + if cursor != grid_values.size: + return + + canonical_signatures = sorted(set(signatures)) + signature_to_type = { + signature: index + for index, signature in enumerate(canonical_signatures) + } + remapped_types = np.asarray( + [signature_to_type[signatures[int(type_id)]] for type_id in raw_types], + dtype=np.int32, + ) + records = [ + (tuple(int(value) for value in atoms[index]), int(remapped_types[index])) + for index in range(len(atoms)) + ] + order = sorted(range(len(records)), key=records.__getitem__) + datasets[atom_path] = _json_array(np.asarray( + [records[index][0] for index in order], dtype=np.int32 + )) + datasets[type_path] = _json_array(np.asarray( + [records[index][1] for index in order], dtype=np.int32 + )) + datasets[resolution_path] = _json_array(np.asarray( + [signature[0] for signature in canonical_signatures], dtype=np.int32 + )) + canonical_grid = [ + value + for _, values in canonical_signatures + for value in values + ] + datasets[grid_path] = _json_array(np.asarray( + canonical_grid, dtype=grid_values.dtype + )) + + +def _scan_case(root: Path, mdin_path: Path | None) -> LegacyCase: + if mdin_path is not None: + return scan_legacy_case(root, mdin_path) + coordinate_files = sorted(root.glob("*_coordinate.txt")) + if len(coordinate_files) != 1: + raise ValueError( + f"cannot infer SPONGE prefix in {root}: expected one " + f"*_coordinate.txt, found {len(coordinate_files)}" + ) + coordinate_suffix = "_coordinate.txt" + prefix = str(coordinate_files[0])[: -len(coordinate_suffix)] + return LegacyCase( + root=root, + mdin_path=root / "", + mdin_text="", + commands={"default_in_file_prefix": prefix}, + ) + + +def build_scientific_manifest( + case_root: str | Path, + *, + mdin: str | Path | None = None, + case_id: str | None = None, + metadata: dict[str, Any] | None = None, + temporary_root: str | Path | None = None, +) -> dict[str, Any]: + """Parse a raw or bundled SPONGE input case into a stable JSON manifest.""" + + root = Path(case_root).resolve() + temporary = ( + Path(temporary_root).resolve() if temporary_root is not None else None + ) + raw_root, mdin_path = _materialize_raw_case( + root, + mdin=mdin, + temporary_root=temporary, + ) + case = _scan_case(raw_root, mdin_path) + datasets: dict[str, dict[str, Any]] = {} + files: dict[str, dict[str, Any]] = {} + + coordinate_path = case.resolve_legacy_input_path("coordinate_in_file") + if coordinate_path is not None and coordinate_path.is_file(): + coordinate, box = load_coordinate(str(coordinate_path)) + datasets["/restart/position"] = _json_array( + np.asarray(coordinate, dtype=np.float64) + ) + datasets["/restart/box"] = _json_array( + np.asarray(box, dtype=np.float64) + ) + + input_keys = { + key for key in case.commands if key.endswith("_in_file") + } | { + key for key in contracts_by_legacy_key() if key.endswith("_in_file") + } | set(_TEXT_KEYS) + for key in sorted(input_keys): + if not key.endswith("_in_file"): + continue + path = case.resolve_legacy_input_path(key) + if path is None or not path.is_file(): + continue + files[key] = { + "name": path.name, + "sha256": _sha256(path), + "size": path.stat().st_size, + } + text_path = _TEXT_KEYS.get(key) + if text_path is not None: + values = _read_counted_text(path) + datasets[text_path] = { + "dtype": "string", + "shape": [len(values)], + "values": values, + } + continue + parsed = parse_topology_file(key, path) + if parsed is None: + continue + for dataset in parsed: + if dataset.path in datasets: + raise ValueError(f"duplicate scientific dataset: {dataset.path}") + datasets[dataset.path] = _json_array(dataset.data) + + _canonicalize_atom_order(datasets) + _canonicalize_dihedrals(datasets) + _canonicalize_cmaps(datasets) + return { + "schema": SCHEMA, + "schema_version": SCHEMA_VERSION, + "case_id": case_id or root.name, + "metadata": dict(metadata or {}), + "source": { + "root": str(root), + "mdin": mdin_path.name if mdin_path is not None else None, + "mdin_sha256": _sha256(mdin_path) if mdin_path is not None else None, + }, + "datasets": dict(sorted(datasets.items())), + "files": files, + } + + +def _tolerance(path: str) -> tuple[float, float]: + if path == "/restart/position" or path == "/restart/box": + return 0.0, 1.0e-6 + if path == "/atoms/charge": + return 0.0, 1.0e-8 + return 1.0e-10, 1.0e-12 + + +def compare_scientific_manifests( + reference: dict[str, Any], + candidate: dict[str, Any], +) -> dict[str, Any]: + """Return a compact, machine-readable semantic diff.""" + + issues: list[dict[str, Any]] = [] + if reference.get("schema") != SCHEMA or candidate.get("schema") != SCHEMA: + issues.append({"kind": "schema_mismatch"}) + if reference.get("schema_version") != candidate.get("schema_version"): + issues.append({ + "kind": "schema_version_mismatch", + "reference": reference.get("schema_version"), + "candidate": candidate.get("schema_version"), + }) + + reference_datasets = reference.get("datasets") or {} + candidate_datasets = candidate.get("datasets") or {} + reference_keys = set(reference_datasets) + candidate_keys = set(candidate_datasets) + if reference_keys != candidate_keys: + issues.append({ + "kind": "dataset_set_mismatch", + "missing": sorted(reference_keys - candidate_keys), + "unexpected": sorted(candidate_keys - reference_keys), + }) + + compared = 0 + for path in sorted(reference_keys & candidate_keys): + expected = reference_datasets[path] + actual = candidate_datasets[path] + if expected.get("shape") != actual.get("shape"): + issues.append({ + "kind": "shape_mismatch", + "path": path, + "reference": expected.get("shape"), + "candidate": actual.get("shape"), + }) + continue + expected_dtype = str(expected.get("dtype")) + actual_dtype = str(actual.get("dtype")) + if expected_dtype == "string" or actual_dtype == "string": + if expected_dtype != actual_dtype or expected.get("values") != actual.get("values"): + issues.append({"kind": "exact_value_mismatch", "path": path}) + compared += 1 + continue + expected_values = np.asarray(expected.get("values")) + actual_values = np.asarray(actual.get("values")) + if expected_values.dtype.kind in "iu" or actual_values.dtype.kind in "iu": + if not np.array_equal(expected_values, actual_values): + mismatch = np.flatnonzero( + expected_values.reshape(-1) != actual_values.reshape(-1) + ) + issues.append({ + "kind": "exact_value_mismatch", + "path": path, + "first_index": int(mismatch[0]) if mismatch.size else None, + "count": int(mismatch.size), + }) + else: + rtol, atol = _tolerance(path) + delta = np.abs( + expected_values.astype(np.float64) + - actual_values.astype(np.float64) + ) + allowed = atol + rtol * np.abs(expected_values.astype(np.float64)) + failed = delta > allowed + if np.any(failed): + flat_delta = delta.reshape(-1) + issues.append({ + "kind": "numeric_mismatch", + "path": path, + "rtol": rtol, + "atol": atol, + "count": int(np.count_nonzero(failed)), + "max_abs": float(np.max(flat_delta)), + "rms": float(np.sqrt(np.mean(flat_delta * flat_delta))), + "first_index": int(np.flatnonzero(failed.reshape(-1))[0]), + }) + compared += 1 + + return { + "schema": "xponge.scientific_manifest.diff", + "schema_version": 1, + "ok": not issues, + "reference_case_id": reference.get("case_id"), + "candidate_case_id": candidate.get("case_id"), + "compared_dataset_count": compared, + "issues": issues, + } + + +def write_scientific_manifest( + manifest: dict[str, Any], + path: str | Path, +) -> Path: + """Write a manifest or diff with deterministic JSON formatting.""" + + output = Path(path) + output.parent.mkdir(parents=True, exist_ok=True) + output.write_text( + json.dumps(manifest, ensure_ascii=False, indent=2, sort_keys=True) + "\n", + encoding="utf-8", + ) + return output + + +__all__ = [ + "SCHEMA", + "SCHEMA_VERSION", + "build_scientific_manifest", + "compare_scientific_manifests", + "write_scientific_manifest", +] diff --git a/src/XpongeCPP/template_ops.py b/src/XpongeCPP/template_ops.py index 41365f5..3fc2912 100644 --- a/src/XpongeCPP/template_ops.py +++ b/src/XpongeCPP/template_ops.py @@ -12,8 +12,8 @@ has_template, load_mol2 as _core_load_mol2, molecule_from_residuetype, - register_residue_templates_from_mol2_file, - register_residue_templates_from_mol2_text, + register_new_residue_templates_from_mol2_file, + register_new_residue_templates_from_mol2_text, reorder_atoms_by_template, replace_residues, ) @@ -24,7 +24,7 @@ def load_mol2(source, ignore_atom_type=False, as_template=False): del ignore_atom_type # First-wave compatibility: current core already tolerates raw MOL2 atom-type strings. if isinstance(source, (str, Path)): try: - register_residue_templates_from_mol2_file(str(source)) + register_new_residue_templates_from_mol2_file(str(source)) sync_template_module_globals() except ValueError as exc: if as_template or "duplicate atom name in ResidueType" not in str(exc): @@ -33,7 +33,7 @@ def load_mol2(source, ignore_atom_type=False, as_template=False): if hasattr(source, "read"): text = source.read() try: - register_residue_templates_from_mol2_text(text) + register_new_residue_templates_from_mol2_text(text) sync_template_module_globals() except ValueError as exc: if as_template or "duplicate atom name in ResidueType" not in str(exc): diff --git a/tests/origin_test_metal_assignment_apply.py b/tests/origin_test_metal_assignment_apply.py index 2fd3108..e507a7c 100644 --- a/tests/origin_test_metal_assignment_apply.py +++ b/tests/origin_test_metal_assignment_apply.py @@ -5,6 +5,7 @@ from uuid import uuid4 import XpongeCPP.forcefield.amber # noqa: F401 - register native force types +from XpongeCPP import register_residue_templates_from_mol2_text from XpongeCPP.forcefield.base.lj_base import LJType from XpongeCPP.helper import AtomType, Molecule, ResidueType from XpongeCPP.metal_assignment import ( @@ -79,6 +80,57 @@ def _ordinary_molecule(patch): class MetalAssignmentApplyTests(unittest.TestCase): + def test_prepare_residue_templates_materializes_registered_template(self): + patch = _local_patch() + residue_name = f"PATCH_STATIC_{uuid4().hex[:8]}" + register_residue_templates_from_mol2_text( + "\n".join(( + "@MOLECULE", + residue_name, + "2 1 1", + "SMALL", + "USER_CHARGES", + "@ATOM", + f"1 C1 0.0 0.0 0.0 C 1 {residue_name} -0.1", + f"2 C2 1.4 0.0 0.0 C 1 {residue_name} 0.1", + "@BOND", + "1 1 2 1", + "", + )) + ) + residue_type = ResidueType.get_type(residue_name) + metal_id = patch.target_metal_atom_ids[0] + metal_identity = next( + atom for atom in patch.atoms + if atom.external_id == metal_id + ) + + report = prepare_residue_templates(patch, [{ + "external_id": metal_id, + "residue_name": residue_name, + "atom_name": metal_identity.atom_name, + }]) + + self.assertEqual(report["prepared_atom_ids"], (metal_id,)) + self.assertEqual( + {atom.name for atom in ResidueType.get_type(residue_name).atoms}, + {"C1", "C2", metal_identity.atom_name}, + ) + prepared_metal = ResidueType.get_type(residue_name).name2atom( + metal_identity.atom_name + ) + self.assertEqual(prepared_metal.element, metal_identity.element) + self.assertAlmostEqual( + prepared_metal.mass, + patch.parameterization_result.metal_overlay.masses[metal_id], + ) + repeated = prepare_residue_templates(patch, [{ + "external_id": metal_id, + "residue_name": residue_name, + "atom_name": metal_identity.atom_name, + }]) + self.assertEqual(repeated["prepared_atom_ids"], ()) + def test_prepare_residue_templates_adds_only_missing_embedded_metal(self): patch = _local_patch() residue_name = f"PATCH_EMBEDDED_{uuid4().hex[:8]}" @@ -155,6 +207,22 @@ def test_hash_closed_patch_applies_to_an_existing_molecule(self): patch.parameterization_result.metal_overlay.masses[metal_id], ) + def test_apply_accepts_equivalent_native_atom_handles(self): + patch = _local_patch() + molecule, _mapping = _ordinary_molecule(patch) + mapping = { + identity.external_id: atom + for identity, atom in zip( + patch.atoms, + molecule.residues[0].atoms, + ) + } + + output = apply(molecule, patch, mapping) + + self.assertEqual(output.application_report["patch_hash"], patch.patch_hash) + self.assertTrue(output.application_report["topology_preserved"]) + def test_identity_mismatch_is_rejected_without_mutating_the_input(self): patch = _local_patch() molecule, mapping = _ordinary_molecule(patch) diff --git a/tests/test_bsc1_dihedral_parity.py b/tests/test_bsc1_dihedral_parity.py new file mode 100644 index 0000000..6d9a1fc --- /dev/null +++ b/tests/test_bsc1_dihedral_parity.py @@ -0,0 +1,119 @@ +from __future__ import annotations + +from io import StringIO + +import pytest + +import Xponge +import Xponge.forcefield.amber.bsc1 # noqa: F401 +import Xponge.forcefield.amber.tip3p # noqa: F401 + + +MOL2 = """@MOLECULE +BSC1_DIHEDRAL +4 3 1 0 0 +SMALL +USER_CHARGES + +@ATOM +1 A1 0.0 0.0 0.0 CM 1 TMP 0.0 +2 A2 1.0 0.0 0.0 N* 1 TMP 0.0 +3 A3 2.0 0.0 0.0 CT 1 TMP 0.0 +4 A4 3.0 0.0 0.0 OS 1 TMP 0.0 +@BOND +1 1 2 1 +2 2 3 1 +3 3 4 1 +@SUBSTRUCTURE +1 TMP 1 +""" + + +@pytest.mark.parametrize( + ("periodicity", "k", "phi0"), + ( + (1, 1.03, 184.8), + (2, 1.52, 7.8), + (3, 0.26, 209.6), + ), +) +def test_bsc1_cytosine_chi_uses_all_frcmod_terms( + tmp_path, + periodicity, + k, + phi0, +): + molecule = Xponge.load_mol2(StringIO(MOL2)) + Xponge.save_sponge_input(molecule, "system", tmp_path, format="raw") + rows = [ + line.split() + for line in (tmp_path / "system_dihedral.txt").read_text().splitlines()[1:] + ] + assert any( + int(row[4]) == periodicity + and float(row[5]) == pytest.approx(k) + and float(row[6]) == pytest.approx( + phi0 / 180.0 * Xponge.pi, + abs=1.0e-6, + ) + for row in rows + ) + + +def test_bsc1_completed_cytosine_keeps_chi_frcmod_terms(tmp_path): + control = Xponge.load_mol2(StringIO(MOL2)) + control_before = tmp_path / "control-before" + Xponge.save_sponge_input(control, "system", control_before, format="raw") + control_repeated = tmp_path / "control-repeated" + Xponge.save_sponge_input(control, "system", control_repeated, format="raw") + assert ( + control_before / "system_dihedral.txt" + ).read_text() == ( + control_repeated / "system_dihedral.txt" + ).read_text() + pdb = tmp_path / "cytosine.pdb" + pdb.write_text( + "\n".join( + ( + "ATOM 1 O4' DC A 1 0.000 0.000 0.000 1.00 0.00 O", + "ATOM 2 C1' DC A 1 1.000 0.000 0.000 1.00 0.00 C", + "ATOM 3 N1 DC A 1 2.000 0.000 0.000 1.00 0.00 N", + "ATOM 4 C6 DC A 1 3.000 0.000 0.000 1.00 0.00 C", + "TER", + "END", + ) + ) + + "\n", + encoding="utf-8", + ) + molecule = Xponge.load_pdb(pdb) + control_loaded = tmp_path / "control-loaded" + Xponge.save_sponge_input(control, "system", control_loaded, format="raw") + assert ( + control_before / "system_dihedral.txt" + ).read_text() == ( + control_loaded / "system_dihedral.txt" + ).read_text() + molecule.add_missing_atoms() + control_after = tmp_path / "control-after" + Xponge.save_sponge_input(control, "system", control_after, format="raw") + assert ( + control_before / "system_dihedral.txt" + ).read_text() == ( + control_after / "system_dihedral.txt" + ).read_text() + Xponge.save_sponge_input(molecule, "system", tmp_path, format="raw") + names = (tmp_path / "system_atom_name.txt").read_text().splitlines()[1:] + atom_ids = {name: names.index(name) for name in ("O4'", "C1'", "N1", "C6")} + expected_atoms = [ + atom_ids["O4'"], + atom_ids["C1'"], + atom_ids["N1"], + atom_ids["C6"], + ] + matching_rows = [ + line.split() + for line in (tmp_path / "system_dihedral.txt").read_text().splitlines()[1:] + if [int(value) for value in line.split()[:4]] == expected_atoms + ] + assert [int(row[4]) for row in matching_rows] == [1, 2, 3] diff --git a/tests/test_compat_surface.py b/tests/test_compat_surface.py index b81e3da..845b27a 100644 --- a/tests/test_compat_surface.py +++ b/tests/test_compat_surface.py @@ -1,3 +1,5 @@ +from types import SimpleNamespace + import XpongeCPP as Xponge import pytest @@ -134,6 +136,41 @@ def test_xpongecpp_common_legacy_import_paths_resolve(): assert mass_base is not None +def test_build_pdb_element_helper_preserves_two_letter_metals(): + import Xponge.build as build + + assert build._pdb_guess_element( + SimpleNamespace(name="ZN", element="Zn", mass=65.38) + ).strip() == "Zn" + assert build._pdb_guess_element( + SimpleNamespace(name="ZN", element="", mass=65.38) + ).strip() == "Zn" + + +def test_pdb_reader_applies_nucleic_terminals_and_template_alias_names(tmp_path): + import XpongeCPP.forcefield.amber.bsc1 # noqa: F401 + import XpongeCPP.forcefield.amber.tip3p # noqa: F401 + + text = "\n".join( + [ + "ATOM 1 O5' DC A 1 0.000 0.000 0.000 1.00 0.00 O", + "ATOM 2 O5' DG A 2 1.000 0.000 0.000 1.00 0.00 O", + "TER", + "HETATM 3 O HOH B 1 3.000 0.000 0.000 1.00 0.00 O", + "END", + ] + ) + path = tmp_path / "nucleic-and-water.pdb" + path.write_text(text + "\n", encoding="utf-8") + molecule = Xponge.load_pdb(path) + + assert [residue.name for residue in molecule.residues] == [ + "DC5", + "DG3", + "WAT", + ] + + def test_xponge_package_alias_supports_common_legacy_import_paths(): import Xponge.assign as assign import Xponge.build as build diff --git a/tests/test_full_amber_data.py b/tests/test_full_amber_data.py index 59a63b4..14be896 100644 --- a/tests/test_full_amber_data.py +++ b/tests/test_full_amber_data.py @@ -117,6 +117,41 @@ def test_ff19sb_import_registers_real_templates_and_cmap_parameters(tmp_path): assert (tmp_path / "ff19_cmap.txt").read_text().splitlines()[0] == "1 1" +def test_ff19sb_maps_generic_mmcif_histidine_to_a_registered_protonation_template(): + completed = _run_isolated( + "from io import StringIO\n" + "import XpongeCPP as X\n" + "import XpongeCPP.forcefield.amber.ff19sb\n" + "text = '''data_his\n" + "loop_\n" + "_atom_site.group_PDB\n" + "_atom_site.id\n" + "_atom_site.type_symbol\n" + "_atom_site.label_atom_id\n" + "_atom_site.label_comp_id\n" + "_atom_site.label_asym_id\n" + "_atom_site.label_seq_id\n" + "_atom_site.Cartn_x\n" + "_atom_site.Cartn_y\n" + "_atom_site.Cartn_z\n" + "_atom_site.auth_seq_id\n" + "_atom_site.auth_comp_id\n" + "_atom_site.auth_asym_id\n" + "_atom_site.auth_atom_id\n" + "_atom_site.pdbx_PDB_model_num\n" + "ATOM 1 N N HIS A 1 0.0 0.0 0.0 1 HIS A N 1\n" + "ATOM 2 C CA HIS A 1 1.4 0.0 0.0 1 HIS A CA 1\n" + "ATOM 3 C C HIS A 1 2.8 0.0 0.0 1 HIS A C 1\n" + "ATOM 4 O O HIS A 1 3.8 0.0 0.0 1 HIS A O 1\n" + "ATOM 5 H HE2 HIS A 1 1.0 1.0 0.0 1 HIS A HE2 1\n" + "#\n" + "'''\n" + "molecule = X.load_mmcif(StringIO(text), infer_terminals=False)\n" + "assert [residue.name for residue in molecule.residues] == ['HIE']\n" + ) + assert completed.returncode == 0 + + def test_gaff_and_gaff2_imports_register_packaged_parameters(tmp_path): mol2 = tmp_path / "eth.mol2" mol2.write_text("""@MOLECULE diff --git a/tests/test_release_022.py b/tests/test_release_023.py similarity index 99% rename from tests/test_release_022.py rename to tests/test_release_023.py index 745c156..953b4d4 100644 --- a/tests/test_release_022.py +++ b/tests/test_release_023.py @@ -18,7 +18,7 @@ def test_release_version_and_compatibility_target(): metadata = tomllib.loads( (ROOT / "pyproject.toml").read_text(encoding="utf-8") )["project"] - assert metadata["version"] == XpongeCPP.__version__ == "0.2.2" + assert metadata["version"] == XpongeCPP.__version__ == "0.2.3" assert "Xponge-origin 1.7b9" in metadata["description"] assert metadata["requires-python"] == ">=3.10,<3.13" diff --git a/tests/test_scientific_manifest.py b/tests/test_scientific_manifest.py new file mode 100644 index 0000000..a8cfe86 --- /dev/null +++ b/tests/test_scientific_manifest.py @@ -0,0 +1,215 @@ +from __future__ import annotations + +from copy import deepcopy +import subprocess +import sys + +from XpongeCPP.scientific_manifest import ( + _canonicalize_atom_order, + build_scientific_manifest, + compare_scientific_manifests, + write_scientific_manifest, +) + + +def _write_case(root): + subprocess.run( + [ + sys.executable, + "-c", + ( + "import XpongeCPP as Xponge;" + "import XpongeCPP.forcefield.amber.ff14sb;" + "molecule=Xponge.get_peptide_from_sequence('AA');" + "molecule.set_box_padding(4.0);" + "Xponge.save_sponge_input(" + f"molecule,'system',{str(root)!r},format='raw')" + ), + ], + check=True, + ) + + +def test_scientific_manifest_round_trip_and_numeric_diff(tmp_path): + case_root = tmp_path / "case" + _write_case(case_root) + manifest = build_scientific_manifest(case_root, case_id="ala2") + + assert manifest["schema"] == "xponge.scientific_manifest" + assert manifest["case_id"] == "ala2" + assert "/atoms/mass" in manifest["datasets"] + assert "/atoms/charge" in manifest["datasets"] + assert "/forcefield/bond/atoms" in manifest["datasets"] + assert "/restart/position" in manifest["datasets"] + assert compare_scientific_manifests(manifest, manifest)["ok"] + + changed = deepcopy(manifest) + changed["datasets"]["/atoms/charge"]["values"][0] += 1.0e-4 + diff = compare_scientific_manifests(manifest, changed) + assert not diff["ok"] + assert diff["issues"][0]["path"] == "/atoms/charge" + + output = write_scientific_manifest(manifest, tmp_path / "manifest.json") + assert output.is_file() + + +def test_scientific_manifest_canonicalizes_improper_permutations(): + from XpongeCPP.scientific_manifest import _canonicalize_dihedrals + + def manifest_atoms(values): + return { + "/forcefield/bond/atoms": { + "dtype": "int32", + "shape": [3, 2], + "values": [[2, 1], [2, 3], [2, 4]], + }, + "/forcefield/dihedral/atoms": { + "dtype": "int32", + "shape": [1, 4], + "values": [values], + }, + "/forcefield/dihedral/periodicity": { + "dtype": "int32", + "shape": [1], + "values": [2], + }, + "/forcefield/dihedral/k": { + "dtype": "float32", + "shape": [1], + "values": [10.5], + }, + "/forcefield/dihedral/phi0": { + "dtype": "float32", + "shape": [1], + "values": [3.141593], + }, + } + + first = manifest_atoms([1, 3, 2, 4]) + second = manifest_atoms([4, 2, 1, 3]) + _canonicalize_dihedrals(first) + _canonicalize_dihedrals(second) + assert first["/forcefield/dihedral/atoms"] == second["/forcefield/dihedral/atoms"] + assert first["/forcefield/dihedral/kind"]["values"] == ["improper"] + + +def test_scientific_manifest_canonicalizes_atom_insertion_order(): + first = { + "/atoms/name": { + "dtype": "string", + "shape": [3], + "values": ["C", "A", "B"], + }, + "/atoms/type_name": { + "dtype": "string", + "shape": [3], + "values": ["tc", "ta", "tb"], + }, + "/atoms/residue_index": { + "dtype": "int32", + "shape": [3], + "values": [0, 0, 0], + }, + "/atoms/charge": { + "dtype": "float32", + "shape": [3], + "values": [3.0, 1.0, 2.0], + }, + "/restart/position": { + "dtype": "float32", + "shape": [3, 3], + "values": [[3.0, 0.0, 0.0], [1.0, 0.0, 0.0], [2.0, 0.0, 0.0]], + }, + "/forcefield/bond/atoms": { + "dtype": "int32", + "shape": [2, 2], + "values": [[0, 2], [2, 1]], + }, + "/forcefield/bond/k": { + "dtype": "float32", + "shape": [2], + "values": [20.0, 10.0], + }, + "/forcefield/bond/r0": { + "dtype": "float32", + "shape": [2], + "values": [2.0, 1.0], + }, + "/topology/exclusions/offset": { + "dtype": "int32", + "shape": [4], + "values": [0, 0, 1, 1], + }, + "/topology/exclusions/list": { + "dtype": "int32", + "shape": [1], + "values": [2], + }, + } + second = deepcopy(first) + second["/atoms/name"]["values"] = ["A", "B", "C"] + second["/atoms/type_name"]["values"] = ["ta", "tb", "tc"] + second["/atoms/charge"]["values"] = [1.0, 2.0, 3.0] + second["/restart/position"]["values"] = [ + [1.0, 0.0, 0.0], + [2.0, 0.0, 0.0], + [3.0, 0.0, 0.0], + ] + second["/forcefield/bond/atoms"]["values"] = [[2, 1], [1, 0]] + second["/forcefield/bond/k"]["values"] = [20.0, 10.0] + second["/forcefield/bond/r0"]["values"] = [2.0, 1.0] + second["/topology/exclusions/list"]["values"] = [0] + + _canonicalize_atom_order(first) + _canonicalize_atom_order(second) + assert first == second + + +def test_scientific_manifest_canonicalizes_cmap_grid_order_and_duplicates(): + from XpongeCPP.scientific_manifest import _canonicalize_cmaps + + first = { + "/forcefield/cmap/atoms": { + "dtype": "int32", + "shape": [3, 5], + "values": [[5, 6, 7, 8, 9], [0, 1, 2, 3, 4], [10, 11, 12, 13, 14]], + }, + "/forcefield/cmap/type": { + "dtype": "int32", + "shape": [3], + "values": [0, 1, 2], + }, + "/forcefield/cmap/resolution": { + "dtype": "int32", + "shape": [3], + "values": [2, 2, 2], + }, + "/forcefield/cmap/grid_value": { + "dtype": "float32", + "shape": [12], + "values": [4.0, 3.0, 2.0, 1.0, 1.0, 2.0, 3.0, 4.0, 4.0, 3.0, 2.0, 1.0], + }, + } + second = { + "/forcefield/cmap/atoms": deepcopy(first["/forcefield/cmap/atoms"]), + "/forcefield/cmap/type": { + "dtype": "int32", + "shape": [3], + "values": [1, 0, 1], + }, + "/forcefield/cmap/resolution": { + "dtype": "int32", + "shape": [2], + "values": [2, 2], + }, + "/forcefield/cmap/grid_value": { + "dtype": "float32", + "shape": [8], + "values": [1.0, 2.0, 3.0, 4.0, 4.0, 3.0, 2.0, 1.0], + }, + } + + _canonicalize_cmaps(first) + _canonicalize_cmaps(second) + + assert first == second diff --git a/tests/test_xpongecpp_api.py b/tests/test_xpongecpp_api.py index 4d3df2c..fabd4b9 100644 --- a/tests/test_xpongecpp_api.py +++ b/tests/test_xpongecpp_api.py @@ -88,6 +88,40 @@ """ +MMCIF_WATER_CHEM_COMP_BOND_TEXT = """\ +data_water +loop_ +_atom_site.group_PDB +_atom_site.id +_atom_site.type_symbol +_atom_site.label_atom_id +_atom_site.auth_atom_id +_atom_site.label_comp_id +_atom_site.auth_comp_id +_atom_site.label_asym_id +_atom_site.auth_asym_id +_atom_site.label_seq_id +_atom_site.auth_seq_id +_atom_site.pdbx_PDB_ins_code +_atom_site.label_alt_id +_atom_site.Cartn_x +_atom_site.Cartn_y +_atom_site.Cartn_z +_atom_site.occupancy +_atom_site.B_iso_or_equiv +_atom_site.pdbx_PDB_model_num +HETATM 1 O O O WAT WAT A A 1 1 ? . 0.000 0.000 0.000 1.00 0.00 1 +HETATM 2 H H1 H1 WAT WAT A A 1 1 ? . 0.957 0.000 0.000 1.00 0.00 1 +HETATM 3 H H2 H2 WAT WAT A A 1 1 ? . -0.240 0.927 0.000 1.00 0.00 1 +loop_ +_chem_comp_bond.comp_id +_chem_comp_bond.atom_id_1 +_chem_comp_bond.atom_id_2 +WAT O H1 +WAT O H2 +""" + + MMCIF_AUTH_LABEL_COLLISION_TEXT = """\ data_auth_label_collision loop_ @@ -315,6 +349,17 @@ def test_load_mmcif_reads_internal_links_and_deduplicates_external_links(): assert mol.residue_links == [[0, 1]] +def test_load_mmcif_preserves_template_pseudobonds_with_chem_comp_bonds(): + import XpongeCPP.forcefield.amber.tip3p # noqa: F401 + + mol = Xponge.load_mmcif( + StringIO(MMCIF_WATER_CHEM_COMP_BOND_TEXT), + infer_terminals=False, + ) + + assert sorted(map(tuple, mol.explicit_bonds)) == [(0, 1), (0, 2), (1, 2)] + + def test_load_mmcif_prefers_auth_identity_for_struct_conn(): import XpongeCPP.forcefield.amber.ff14sb # noqa: F401 @@ -420,6 +465,112 @@ def test_load_mol2_as_template_registers_legacy_residuetype_lookup(): assert Xponge.has_template("WAT") +def test_load_mol2_as_template_preserves_existing_caps_and_infers_new_head_tail(): + Xponge.load_mol2( + StringIO( + """@MOLECULE +CAPS +2 0 2 +SMALL +USER_CHARGES +@ATOM +1 KEEP 0.0 0.0 0.0 c3 1 ZCA 0.0 +2 KEEP 4.0 0.0 0.0 c3 2 ZCB 0.0 +@BOND +""" + ), + as_template=True, + ) + Xponge.configure_residue_template_tail("ZCA", "KEEP") + Xponge.configure_residue_template_head("ZCB", "KEEP") + Xponge.load_mol2( + StringIO( + """@MOLECULE +CAPPED_NEW +5 4 3 +SMALL +USER_CHARGES +@ATOM +1 REPLACEMENT 0.0 0.0 0.0 c 1 ZCA 0.0 +2 HEAD 1.0 0.0 0.0 n 2 ZNEW 0.0 +3 TAIL 2.0 0.0 0.0 c 2 ZNEW 0.0 +4 H 1.0 1.0 0.0 hn 2 ZNEW 0.0 +5 REPLACEMENT 3.0 0.0 0.0 n 3 ZCB 0.0 +@BOND +1 1 2 1 +2 2 3 1 +3 2 4 1 +4 3 5 1 +""" + ), + as_template=True, + ) + + left = Xponge.ResidueType.get_type("ZCA") + center = Xponge.ResidueType.get_type("ZNEW") + right = Xponge.ResidueType.get_type("ZCB") + + assert [atom.name for atom in left.atoms] == ["KEEP"] + assert [atom.name for atom in right.atoms] == ["KEEP"] + assert {atom.name for atom in center.atoms} == {"HEAD", "TAIL", "H"} + molecule = ( + Xponge.get_template_molecule("ZCA") + + Xponge.get_template_molecule("ZNEW") + + Xponge.get_template_molecule("ZCB") + ) + assert len(molecule.residue_links) == 2 + assert { + tuple(sorted((molecule.atoms[int(atom1)].name, molecule.atoms[int(atom2)].name))) + for atom1, atom2 in molecule.residue_links + } == {("HEAD", "KEEP"), ("KEEP", "TAIL")} + + +def test_legacy_residuetype_none_temporarily_disables_native_auto_linking(): + Xponge.load_mol2( + StringIO( + """@MOLECULE +AUTO_LINK_TYPES +2 0 2 +SMALL +USER_CHARGES +@ATOM +1 TAIL 0.0 0.0 0.0 c3 1 ZTL 0.0 +2 HEAD 2.0 0.0 0.0 n 2 ZHD 0.0 +@BOND +""" + ), + as_template=True, + ) + left = Xponge.ResidueType.get_type("ZTL") + right = Xponge.ResidueType.get_type("ZHD") + left.tail = "TAIL" + right.head = "HEAD" + assert len( + ( + Xponge.get_template_molecule("ZTL") + + Xponge.get_template_molecule("ZHD") + ).residue_links + ) == 1 + + saved_tail = left.tail + saved_head = right.head + left.tail = None + right.head = None + assert not ( + Xponge.get_template_molecule("ZTL") + + Xponge.get_template_molecule("ZHD") + ).residue_links + + left.tail = saved_tail + right.head = saved_head + assert len( + ( + Xponge.get_template_molecule("ZTL") + + Xponge.get_template_molecule("ZHD") + ).residue_links + ) == 1 + + def test_template_atoms_follow_forcefield_mass_and_element_inference(): import importlib import XpongeCPP.forcefield.amber.ff14sb as ff14sb @@ -963,6 +1114,35 @@ def test_save_sponge_input_reorders_linked_residue_components_for_export(tmp_pat assert (tmp_path / "linked_resname.txt").read_text().splitlines() == ["3", "FAR", "WAT", "LIG"] +def test_save_sponge_input_reorders_coordination_components_for_export(tmp_path): + Xponge.register_tip3p() + mol = Xponge.load_mol2(StringIO(CUSTOM_MOL2_TEXT)) | Xponge.load_mol2(StringIO(MOL2_TEXT)) + + assert [res.name for res in mol.residues] == ["FAR", "LIG", "WAT"] + + mol.add_coordination_bond( + mol.residues[0].name2atom("O1").index, + mol.residues[2].name2atom("O").index, + ) + mol._set_bond_parameter_override( + mol.residues[0].name2atom("O1").index, + mol.residues[2].name2atom("O").index, + 100.0, + 2.0, + "test:coordination", + ) + Xponge.Save_SPONGE_Input(mol, prefix="coordinated", dirname=str(tmp_path)) + + assert [res.name for res in mol.residues] == ["FAR", "WAT", "LIG"] + assert mol.coordination_bonds == [(0, 3)] + assert (tmp_path / "coordinated_resname.txt").read_text().splitlines() == [ + "3", + "FAR", + "WAT", + "LIG", + ] + + def test_save_sponge_input_rejects_noncontiguous_atom_components(tmp_path): Xponge.register_tip3p() mol = Xponge.load_mol2(StringIO(SPLIT_COMPONENT_MOL2_TEXT)) From 1503b804e60932a5d8da08e484744ddc007c06ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8E=E6=B5=A9?= Date: Tue, 11 Aug 2026 21:16:12 +0800 Subject: [PATCH 06/11] fix: close Mokda compatibility gaps for XpongeCPP 0.2.4 Preserve residue-link bonds in MOL2 exports and keep mixed standard/GAFF systems intact during parameterization. Align the legacy MDAnalysis raw reader with Xponge format hints, add focused regressions, and advance package and CI release metadata to 0.2.4. --- .github/workflows/build-packages.yml | 2 +- .github/workflows/publish-pypi.yml | 2 +- RELEASE_NOTES.md | 32 +++++- cpp/io/mol2.cpp | 28 ++++- pyproject.toml | 2 +- src/XpongeCPP/__init__.py | 2 +- src/XpongeCPP/analysis/md_analysis.py | 6 + src/XpongeCPP/forcefield/amber/_parmchk2.py | 105 ------------------ src/XpongeCPP/forcefield/amber/gaff.py | 93 ---------------- src/XpongeCPP/forcefield/amber/gaff2.py | 5 - tests/test_8ryk_manual_frcmod.py | 2 + tests/test_compat_process.py | 25 +++++ tests/test_legacy_mdanalysis_raw.py | 15 +++ ...est_release_023.py => test_release_024.py} | 2 +- 14 files changed, 104 insertions(+), 217 deletions(-) create mode 100644 tests/test_legacy_mdanalysis_raw.py rename tests/{test_release_023.py => test_release_024.py} (99%) diff --git a/.github/workflows/build-packages.yml b/.github/workflows/build-packages.yml index 9d2f59d..10fcc47 100644 --- a/.github/workflows/build-packages.yml +++ b/.github/workflows/build-packages.yml @@ -16,7 +16,7 @@ env: CIBW_TEST_COMMAND: >- python -c "import importlib.util, XpongeCPP, Xponge, XpongeCPP.io_bundle, XpongeCPP.metal_assignment; - assert XpongeCPP.__version__ == '0.2.3'; + assert XpongeCPP.__version__ == '0.2.4'; assert not hasattr(XpongeCPP, 'MC' + 'PB'); assert importlib.util.find_spec('XpongeCPP.' + ('MC' + 'PB').lower()) is None" CIBW_BEFORE_ALL_LINUX: >- diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 4070b38..9b6e487 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -12,7 +12,7 @@ env: CIBW_TEST_COMMAND: >- python -c "import importlib.util, XpongeCPP, Xponge, XpongeCPP.io_bundle, XpongeCPP.metal_assignment; - assert XpongeCPP.__version__ == '0.2.3'; + assert XpongeCPP.__version__ == '0.2.4'; assert not hasattr(XpongeCPP, 'MC' + 'PB'); assert importlib.util.find_spec('XpongeCPP.' + ('MC' + 'PB').lower()) is None" CIBW_BEFORE_ALL_LINUX: >- diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 191ed65..dfbc525 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,19 @@ +# XpongeCPP 0.2.4 + +Compatibility target: Xponge-origin 1.7b9. + +This compatibility patch preserves residue-link bonds in public MOL2 exports, +keeps mixed standard/GAFF systems intact when invoking GAFF or GAFF2 +parameterization, and aligns the legacy MDAnalysis raw-SPONGE reader with the +format hints exposed by Xponge. These fixes cover the Mokda SPONGE-input path +without moving force-field assignment back to Python. + +Release validation includes the focused MOL2, mixed-force-field, and +MDAnalysis compatibility regressions. The complete repository suite passes +with 715 tests, 10 optional skips, and 1 expected xfail. A CPython 3.11 wheel +was built and imported from an isolated environment under the Mokda resource +gate with `OOMKillDelta=0`. + # XpongeCPP 0.2.3 Compatibility target: Xponge-origin 1.7b9. @@ -8,17 +24,23 @@ template bonds when an mmCIF residue also supplies explicit covalent bonds, including model pseudo-bonds such as the TIP3P H-H distance constraint, while still honoring explicit Mokda edit deletions. It also synchronizes temporary legacy residue-link overrides with the native registry and canonicalizes CMAP -grid identities independently of force-field declaration order. +grid identities independently of force-field declaration order. The legacy +MDAnalysis raw-SPONGE reader now advertises the `SPONGE_MASS` format and +recognizes `_mass.txt`, matching the Xponge interface used by Mokda H5MD +analysis. Linux x86_64 release-candidate evidence: -- XpongeCPP source suite: 713 passed, 10 optional skips, 1 expected xfail; -- installed CPython 3.12 wheel through the managed Mokda runtime: 716 passed, - 7 optional skips, 1 expected xfail; +- XpongeCPP repository suite under the coherent managed 0.2.3 runtime after the + raw-reader compatibility fix: 717 passed, 7 optional skips, 1 expected + xfail; - Xponge-origin 1.7b9 tests through the installed compatibility namespace: 36/36 passed; -- Mokda auto-discovered Xponge consumer matrix: 325 passed, 13 conditional +- Mokda auto-discovered Xponge consumer matrix: 327 passed, 14 conditional skips, and 12 subtests under both providers; +- real 3GOU/4EWL Life Modeling under the isolated XpongeCPP 0.2.3 managed + runtime completed in 205.656 s with a 1.09 GiB peak and + `OOMKillDelta=0`; - complete Mokda Lipid21/1BL8 tier: 8/8 files passed, with a 1.995 GiB peak and `OOMKillDelta=0`; - provider-neutral scientific manifests match for repaired 1EMA, 1KV2+B96, diff --git a/cpp/io/mol2.cpp b/cpp/io/mol2.cpp index 88db4f6..c36bb83 100644 --- a/cpp/io/mol2.cpp +++ b/cpp/io/mol2.cpp @@ -28,6 +28,25 @@ std::vector split_ws(const std::string& line) { return out; } +std::vector> mol2_bond_pairs(const Molecule& molecule) { + std::vector> bonds; + bonds.reserve(molecule.explicit_bonds.size() + molecule.residue_links.size()); + const auto append = [&bonds](const ResidueLink& bond) { + const auto atom1 = std::min(bond.atom1, bond.atom2); + const auto atom2 = std::max(bond.atom1, bond.atom2); + bonds.emplace_back(atom1, atom2); + }; + for (const auto& bond : molecule.explicit_bonds) { + append(bond); + } + for (const auto& link : molecule.residue_links) { + append(link); + } + std::sort(bonds.begin(), bonds.end()); + bonds.erase(std::unique(bonds.begin(), bonds.end()), bonds.end()); + return bonds; +} + } // namespace Molecule load_mol2_text(const std::string& text) { @@ -116,9 +135,10 @@ void save_mol2(const Molecule& molecule, const std::filesystem::path& filename) if (!out) { throw std::runtime_error("failed to open MOL2 output: " + filename.string()); } + const auto bond_pairs = mol2_bond_pairs(molecule); out << "@MOLECULE\n"; out << molecule.name << "\n"; - out << std::setw(6) << molecule.atoms.size() << std::setw(6) << molecule.explicit_bonds.size() + out << std::setw(6) << molecule.atoms.size() << std::setw(6) << bond_pairs.size() << std::setw(6) << molecule.residues.size() << " 0 1\n"; out << "SMALL\nUSER_CHARGES\n"; out << "@ATOM\n"; @@ -140,9 +160,9 @@ void save_mol2(const Molecule& molecule, const std::filesystem::path& filename) << "\n"; } out << "@BOND\n"; - for (std::size_t i = 0; i < molecule.explicit_bonds.size(); ++i) { - const auto& bond = molecule.explicit_bonds[i]; - out << std::setw(6) << i + 1 << std::setw(6) << bond.atom1 + 1 << std::setw(6) << bond.atom2 + 1 + for (std::size_t i = 0; i < bond_pairs.size(); ++i) { + const auto& bond = bond_pairs[i]; + out << std::setw(6) << i + 1 << std::setw(6) << bond.first + 1 << std::setw(6) << bond.second + 1 << " 1\n"; } out << "@SUBSTRUCTURE\n"; diff --git a/pyproject.toml b/pyproject.toml index 21e7dbb..8af927c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build" [project] name = "XpongeCPP" -version = "0.2.3" +version = "0.2.4" description = "C++ core rewrite of Xponge workflows, compatible with Xponge-origin 1.7b9" readme = "README.md" requires-python = ">=3.10,<3.13" diff --git a/src/XpongeCPP/__init__.py b/src/XpongeCPP/__init__.py index 34f60a6..7cdb7b8 100644 --- a/src/XpongeCPP/__init__.py +++ b/src/XpongeCPP/__init__.py @@ -193,7 +193,7 @@ def _get_molecule_charge(molecule): from .legacy_types import _LegacyResidueTypeHandle from .template_ops import load_mol2 -__version__ = "0.2.3" +__version__ = "0.2.4" __mokda_backend__ = "xpongecpp" __implementation_version__ = __version__ pi = np.pi diff --git a/src/XpongeCPP/analysis/md_analysis.py b/src/XpongeCPP/analysis/md_analysis.py index d406529..e496a2a 100644 --- a/src/XpongeCPP/analysis/md_analysis.py +++ b/src/XpongeCPP/analysis/md_analysis.py @@ -65,6 +65,12 @@ def close(self): class SpongeInputReader(TopologyReaderBase): + format = "SPONGE_MASS" + + @staticmethod + def _format_hint(thing): + return isinstance(thing, str) and thing.endswith("_mass.txt") + def parse(self, **kwargs): attrs = [topologyattrs.Segids(np.array(["SYSTEM"], dtype=object))] has_names = False diff --git a/src/XpongeCPP/forcefield/amber/_parmchk2.py b/src/XpongeCPP/forcefield/amber/_parmchk2.py index 0a76775..38a504b 100644 --- a/src/XpongeCPP/forcefield/amber/_parmchk2.py +++ b/src/XpongeCPP/forcefield/amber/_parmchk2.py @@ -52,112 +52,7 @@ def coerce_parmchk2_input(ifname): ) -def _is_gaff_like_type(atom_type): - return any(character.islower() for character in atom_type) - - -def filter_mixed_gaff_mol2(mol2_path): - sections = {} - current = None - with open(mol2_path, encoding="utf-8", errors="ignore") as handle: - for line in handle.read().splitlines(): - if line.startswith("@"): - current = line.strip() - sections[current] = [] - continue - if current is not None: - sections[current].append(line) - atom_records = [] - for line in sections.get("@ATOM", []): - if not line.strip(): - continue - fields = line.split() - if len(fields) < 6: - return mol2_path, None - atom_records.append(fields) - if not atom_records: - return mol2_path, None - keep_old_ids = { - int(fields[0]) - for fields in atom_records - if _is_gaff_like_type(fields[5]) - } - if not keep_old_ids or len(keep_old_ids) == len(atom_records): - return mol2_path, None - tempdir = TemporaryDirectory() - filtered_path = os.path.join(tempdir.name, "gaff_subset.mol2") - old_to_new = { - old_id: index + 1 for index, old_id in enumerate(sorted(keep_old_ids)) - } - sub_old_to_new = {} - filtered_atoms = [] - for fields in atom_records: - old_id = int(fields[0]) - if old_id not in old_to_new: - continue - fields = list(fields) - fields[0] = str(old_to_new[old_id]) - sub_id = int(fields[6]) if len(fields) > 6 else 1 - sub_old_to_new.setdefault(sub_id, len(sub_old_to_new) + 1) - if len(fields) > 6: - fields[6] = str(sub_old_to_new[sub_id]) - filtered_atoms.append(fields) - filtered_bonds = [] - for line in sections.get("@BOND", []): - fields = line.split() - if len(fields) < 4: - continue - old_a = int(fields[1]) - old_b = int(fields[2]) - if old_a not in old_to_new or old_b not in old_to_new: - continue - fields = list(fields) - fields[0] = str(len(filtered_bonds) + 1) - fields[1] = str(old_to_new[old_a]) - fields[2] = str(old_to_new[old_b]) - filtered_bonds.append(fields) - filtered_substructures = [] - substructure_lines = sections.get("@SUBSTRUCTURE", []) - for old_sub_id, new_sub_id in sorted( - sub_old_to_new.items(), key=lambda item: item[1] - ): - source = None - for line in substructure_lines: - fields = line.split() - if fields and int(fields[0]) == old_sub_id: - source = list(fields) - break - if source is None: - source = [ - str(old_sub_id), "MOL", "1", "TEMP", "0", - "****", "****", "0", "ROOT", - ] - source[0] = str(new_sub_id) - if len(source) > 2: - source[2] = "1" - filtered_substructures.append(source) - with open(filtered_path, "w", encoding="utf-8") as handle: - handle.write("@MOLECULE\n") - handle.write("GAFF_SUBSET\n") - handle.write( - f"{len(filtered_atoms):6d}{len(filtered_bonds):6d}" - f"{len(filtered_substructures):6d} 0 1\n" - ) - handle.write("SMALL\nUSER_CHARGES\n") - handle.write("@ATOM\n") - for fields in filtered_atoms: - handle.write(" ".join(fields) + "\n") - handle.write("@BOND\n") - for fields in filtered_bonds: - handle.write(" ".join(fields) + "\n") - handle.write("@SUBSTRUCTURE\n") - for fields in filtered_substructures: - handle.write(" ".join(fields) + "\n") - return filtered_path, tempdir - - __all__ = [ "coerce_parmchk2_input", - "filter_mixed_gaff_mol2", "import_xpongelib", ] diff --git a/src/XpongeCPP/forcefield/amber/gaff.py b/src/XpongeCPP/forcefield/amber/gaff.py index 1d6b021..3693b78 100644 --- a/src/XpongeCPP/forcefield/amber/gaff.py +++ b/src/XpongeCPP/forcefield/amber/gaff.py @@ -56,102 +56,11 @@ def _coerce_parmchk2_input(ifname): raise TypeError("parmchk2_gaff expects a mol2 path or a template-like molecule object") -def _is_gaff_like_type(atom_type): - return any(ch.islower() for ch in atom_type) - - -def _filter_mixed_gaff_mol2(mol2_path): - sections = {} - current = None - with open(mol2_path, encoding="utf-8", errors="ignore") as handle: - for line in handle.read().splitlines(): - if line.startswith("@"): - current = line.strip() - sections[current] = [] - continue - if current is not None: - sections[current].append(line) - atom_records = [] - for line in sections.get("@ATOM", []): - if not line.strip(): - continue - fields = line.split() - if len(fields) < 6: - return mol2_path, None - atom_records.append(fields) - if not atom_records: - return mol2_path, None - keep_old_ids = {int(fields[0]) for fields in atom_records if _is_gaff_like_type(fields[5])} - if not keep_old_ids or len(keep_old_ids) == len(atom_records): - return mol2_path, None - tempdir = TemporaryDirectory() - filtered_path = os.path.join(tempdir.name, "gaff_subset.mol2") - old_to_new = {old_id: index + 1 for index, old_id in enumerate(sorted(keep_old_ids))} - sub_old_to_new = {} - filtered_atoms = [] - for fields in atom_records: - old_id = int(fields[0]) - if old_id not in old_to_new: - continue - fields = list(fields) - fields[0] = str(old_to_new[old_id]) - sub_id = int(fields[6]) if len(fields) > 6 else 1 - sub_old_to_new.setdefault(sub_id, len(sub_old_to_new) + 1) - if len(fields) > 6: - fields[6] = str(sub_old_to_new[sub_id]) - filtered_atoms.append(fields) - filtered_bonds = [] - for line in sections.get("@BOND", []): - fields = line.split() - if len(fields) < 4: - continue - old_a = int(fields[1]) - old_b = int(fields[2]) - if old_a not in old_to_new or old_b not in old_to_new: - continue - fields = list(fields) - fields[0] = str(len(filtered_bonds) + 1) - fields[1] = str(old_to_new[old_a]) - fields[2] = str(old_to_new[old_b]) - filtered_bonds.append(fields) - filtered_substructures = [] - for old_sub_id, new_sub_id in sorted(sub_old_to_new.items(), key=lambda item: item[1]): - source = None - for line in sections.get("@SUBSTRUCTURE", []): - fields = line.split() - if fields and int(fields[0]) == old_sub_id: - source = list(fields) - break - if source is None: - source = [str(old_sub_id), "MOL", "1", "TEMP", "0", "****", "****", "0", "ROOT"] - source[0] = str(new_sub_id) - if len(source) > 2: - source[2] = "1" - filtered_substructures.append(source) - with open(filtered_path, "w", encoding="utf-8") as handle: - handle.write("@MOLECULE\n") - handle.write("GAFF_SUBSET\n") - handle.write(f"{len(filtered_atoms):6d}{len(filtered_bonds):6d}{len(filtered_substructures):6d} 0 1\n") - handle.write("SMALL\nUSER_CHARGES\n") - handle.write("@ATOM\n") - for fields in filtered_atoms: - handle.write(" ".join(fields) + "\n") - handle.write("@BOND\n") - for fields in filtered_bonds: - handle.write(" ".join(fields) + "\n") - handle.write("@SUBSTRUCTURE\n") - for fields in filtered_substructures: - handle.write(" ".join(fields) + "\n") - return filtered_path, tempdir - - def parmchk2_gaff(ifname, ofname, direct_load=True, keep=True): """Generate frcmod parameters with legacy Xponge-compatible semantics.""" xlib = _import_xpongelib() mol2_path, tempdir = _coerce_parmchk2_input(ifname) - filtered_tempdir = None try: - mol2_path, filtered_tempdir = _filter_mixed_gaff_mol2(mol2_path) datapath = os.path.dirname(xlib.__file__) xlib._parmchk2(mol2_path, "mol2", str(ofname), datapath, 0, 1, 1) if direct_load: @@ -159,7 +68,5 @@ def parmchk2_gaff(ifname, ofname, direct_load=True, keep=True): if not keep: os.remove(ofname) finally: - if filtered_tempdir is not None: - filtered_tempdir.cleanup() if tempdir is not None: tempdir.cleanup() diff --git a/src/XpongeCPP/forcefield/amber/gaff2.py b/src/XpongeCPP/forcefield/amber/gaff2.py index 0bbe6bf..d6034f5 100644 --- a/src/XpongeCPP/forcefield/amber/gaff2.py +++ b/src/XpongeCPP/forcefield/amber/gaff2.py @@ -11,7 +11,6 @@ from . import load_parameters_from_frcmod from ._parmchk2 import ( coerce_parmchk2_input, - filter_mixed_gaff_mol2, import_xpongelib, ) @@ -23,9 +22,7 @@ def parmchk2_gaff2(ifname, ofname, direct_load=True, keep=True): """Generate GAFF2 frcmod parameters with Xponge-compatible semantics.""" xlib = import_xpongelib() mol2_path, tempdir = coerce_parmchk2_input(ifname) - filtered_tempdir = None try: - mol2_path, filtered_tempdir = filter_mixed_gaff_mol2(mol2_path) datapath = os.path.dirname(xlib.__file__) xlib._parmchk2(mol2_path, "mol2", str(ofname), datapath, 0, 1, 2) if direct_load: @@ -33,7 +30,5 @@ def parmchk2_gaff2(ifname, ofname, direct_load=True, keep=True): if not keep: os.remove(ofname) finally: - if filtered_tempdir is not None: - filtered_tempdir.cleanup() if tempdir is not None: tempdir.cleanup() diff --git a/tests/test_8ryk_manual_frcmod.py b/tests/test_8ryk_manual_frcmod.py index 5908cf1..fd1cc18 100644 --- a/tests/test_8ryk_manual_frcmod.py +++ b/tests/test_8ryk_manual_frcmod.py @@ -126,3 +126,5 @@ def test_xpongecpp_matches_manual_8ryk_script_contract(tmp_path): frcmod_text = raw_frcmod.read_text() assert "MASS" in frcmod_text assert "NONBON" in frcmod_text + bond_section = frcmod_text.split("BOND", 1)[1].split("ANGLE", 1)[0] + assert bond_section.strip(), "mixed standard/GAFF boundary bonds must reach parmchk2" diff --git a/tests/test_compat_process.py b/tests/test_compat_process.py index 2a0f13b..ded8a00 100644 --- a/tests/test_compat_process.py +++ b/tests/test_compat_process.py @@ -205,6 +205,31 @@ def test_save_mol2_exports_core_residue_links_as_bonds(tmp_path): assert int(count_fields[1]) == len(bond_pairs) +def test_save_mol2_exports_links_from_public_legacy_molecule_factory(tmp_path): + import Xponge + import Xponge.forcefield.amber.ff19sb # noqa: F401 + + molecule = Xponge.Molecule("public_factory_links") + molecule.add_residue( + Xponge.Residue(Xponge.ResidueType.get_type("ALA"), directly_copy=True) + ) + molecule.add_residue( + Xponge.Residue(Xponge.ResidueType.get_type("GLY"), directly_copy=True) + ) + molecule.add_residue_link( + molecule.residues[0].name2atom("C"), + molecule.residues[1].name2atom("N"), + ) + + path = tmp_path / "public_factory_links.mol2" + Xponge.Save_Mol2(molecule, path) + + count_fields, bond_pairs = _mol2_bond_pairs(path) + assert count_fields is not None + assert (9, 11) in bond_pairs + assert int(count_fields[1]) == len(bond_pairs) + + def test_save_mol2_exports_legacy_override_residue_links_as_bonds(tmp_path): import Xponge diff --git a/tests/test_legacy_mdanalysis_raw.py b/tests/test_legacy_mdanalysis_raw.py new file mode 100644 index 0000000..2721b58 --- /dev/null +++ b/tests/test_legacy_mdanalysis_raw.py @@ -0,0 +1,15 @@ +from __future__ import annotations + +import pytest + + +pytest.importorskip("MDAnalysis") + + +def test_sponge_input_reader_exposes_origin_format_contract(): + from Xponge.analysis import md_analysis as xmda + + assert xmda.SpongeInputReader.format == "SPONGE_MASS" + assert xmda.SpongeInputReader._format_hint("system_mass.txt") is True + assert xmda.SpongeInputReader._format_hint("system_charge.txt") is False + diff --git a/tests/test_release_023.py b/tests/test_release_024.py similarity index 99% rename from tests/test_release_023.py rename to tests/test_release_024.py index 953b4d4..e36d0a5 100644 --- a/tests/test_release_023.py +++ b/tests/test_release_024.py @@ -18,7 +18,7 @@ def test_release_version_and_compatibility_target(): metadata = tomllib.loads( (ROOT / "pyproject.toml").read_text(encoding="utf-8") )["project"] - assert metadata["version"] == XpongeCPP.__version__ == "0.2.3" + assert metadata["version"] == XpongeCPP.__version__ == "0.2.4" assert "Xponge-origin 1.7b9" in metadata["description"] assert metadata["requires-python"] == ">=3.10,<3.13" From ea0d544644c1c60dfb67e73c0bff7240fdb7b9f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8E=E6=B5=A9?= Date: Wed, 12 Aug 2026 00:40:40 +0800 Subject: [PATCH 07/11] fix(export): preserve source mapping across residue reorder Expose the native SPONGE save permutation and apply it to compatibility-layer source atom IDs before raw serialization. This keeps Mokda atom-order mappings and trajectory CIF topology aligned when linked residue components are made contiguous, including repeated solvent residues.\n\nBump XpongeCPP to 0.2.5, update release automation and notes, and add focused mapping regression coverage. --- .github/workflows/build-packages.yml | 2 +- .github/workflows/publish-pypi.yml | 2 +- RELEASE_NOTES.md | 19 +++++++++++ cpp/core.hpp | 1 + cpp/io/sponge.cpp | 34 ++++++++++++++++--- cpp/python/bindings_core.cpp | 7 ++++ pyproject.toml | 2 +- src/XpongeCPP/__init__.py | 2 +- src/XpongeCPP/_compat/process.py | 10 ++++++ ...est_release_024.py => test_release_025.py} | 2 +- tests/test_xpongecpp_api.py | 28 +++++++++++++++ 11 files changed, 99 insertions(+), 10 deletions(-) rename tests/{test_release_024.py => test_release_025.py} (99%) diff --git a/.github/workflows/build-packages.yml b/.github/workflows/build-packages.yml index 10fcc47..4d7d934 100644 --- a/.github/workflows/build-packages.yml +++ b/.github/workflows/build-packages.yml @@ -16,7 +16,7 @@ env: CIBW_TEST_COMMAND: >- python -c "import importlib.util, XpongeCPP, Xponge, XpongeCPP.io_bundle, XpongeCPP.metal_assignment; - assert XpongeCPP.__version__ == '0.2.4'; + assert XpongeCPP.__version__ == '0.2.5'; assert not hasattr(XpongeCPP, 'MC' + 'PB'); assert importlib.util.find_spec('XpongeCPP.' + ('MC' + 'PB').lower()) is None" CIBW_BEFORE_ALL_LINUX: >- diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 9b6e487..2c7cbd1 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -12,7 +12,7 @@ env: CIBW_TEST_COMMAND: >- python -c "import importlib.util, XpongeCPP, Xponge, XpongeCPP.io_bundle, XpongeCPP.metal_assignment; - assert XpongeCPP.__version__ == '0.2.4'; + assert XpongeCPP.__version__ == '0.2.5'; assert not hasattr(XpongeCPP, 'MC' + 'PB'); assert importlib.util.find_spec('XpongeCPP.' + ('MC' + 'PB').lower()) is None" CIBW_BEFORE_ALL_LINUX: >- diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index dfbc525..c538879 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,22 @@ +# XpongeCPP 0.2.5 + +Compatibility target: Xponge-origin 1.7b9. + +This patch preserves source-atom identity when raw SPONGE export reorders +noncontiguous linked residue components. The native layer now reports the +saved atom permutation before serialization, and the compatibility layer +applies the same permutation to caller-provided source IDs. This keeps Mokda +trajectory topology and atom-order mappings aligned for proteins, ligands, +metals, solvent, and repeated solvent molecules without matching atoms by +name or coordinates. + +Focused release validation covers covalent and coordination-driven residue +reordering, source-ID mapping, and the complete native bundle regression +cohort. The complete repository suite passes with 716 tests, 10 optional +skips, and 1 expected xfail. The change adds one linear residue/atom +permutation pass before raw save and does not move force-field assignment back +to Python. + # XpongeCPP 0.2.4 Compatibility target: Xponge-origin 1.7b9. diff --git a/cpp/core.hpp b/cpp/core.hpp index 7b4a16d..9e8338d 100644 --- a/cpp/core.hpp +++ b/cpp/core.hpp @@ -579,6 +579,7 @@ void add_solvent_box(Molecule& molecule, const Molecule& solvent, const std::arr double tolerance, std::int64_t n_solvent, std::uint64_t seed = 0); void add_ions(Molecule& molecule, const std::unordered_map& counts, std::uint64_t seed = 0, const std::string& solvent_residue = "WAT"); +std::vector prepare_sponge_atom_order(Molecule& molecule); std::unordered_map save_sponge_input(Molecule& molecule, const std::string& prefix, const std::filesystem::path& dirname); diff --git a/cpp/io/sponge.cpp b/cpp/io/sponge.cpp index 1b7bd86..775cd20 100644 --- a/cpp/io/sponge.cpp +++ b/cpp/io/sponge.cpp @@ -209,13 +209,17 @@ std::vector real_lj_types(const std::vector& lj_types, return real; } -bool reorder_residues_by_linked_components(Molecule& molecule) { +std::vector reorder_residues_by_linked_components(Molecule& molecule) { + std::vector new_to_old_atom_order(molecule.atoms.size()); + for (AtomId atom_id = 0; atom_id < molecule.atoms.size(); ++atom_id) { + new_to_old_atom_order[atom_id] = atom_id; + } if (molecule.residues.size() < 2 || (molecule.residue_links.empty() && molecule.coordination_bonds.empty() && molecule.explicit_bonds.empty() && molecule.bond_parameter_overrides.empty())) { - return false; + return new_to_old_atom_order; } IndexDisjointSet components(molecule.residues.size()); @@ -263,11 +267,27 @@ bool reorder_residues_by_linked_components(Molecule& molecule) { previous_key = key; } if (already_contiguous) { - return false; + return new_to_old_atom_order; } + std::vector residue_order(molecule.residues.size()); + for (ResidueId residue_id = 0; residue_id < molecule.residues.size(); ++residue_id) { + residue_order[residue_id] = residue_id; + } + std::stable_sort(residue_order.begin(), residue_order.end(), + [&](ResidueId lhs, ResidueId rhs) { + return residue_sort_keys[lhs] < residue_sort_keys[rhs]; + }); + new_to_old_atom_order.clear(); + new_to_old_atom_order.reserve(molecule.atoms.size()); + for (const ResidueId old_residue_id : residue_order) { + const auto& residue = molecule.residues[old_residue_id]; + for (std::uint32_t local = 0; local < residue.atom_count; ++local) { + new_to_old_atom_order.push_back(residue.atom_begin + local); + } + } molecule.replace_residues({}, residue_sort_keys, true); - return true; + return new_to_old_atom_order; } void check_sponge_atom_components_are_contiguous(const Molecule& molecule, const Topology& topology) { @@ -311,10 +331,14 @@ void check_sponge_atom_components_are_contiguous(const Molecule& molecule, const } // namespace +std::vector prepare_sponge_atom_order(Molecule& molecule) { + return reorder_residues_by_linked_components(molecule); +} + std::unordered_map save_sponge_input(Molecule& input_molecule, const std::string& prefix, const std::filesystem::path& dirname) { - reorder_residues_by_linked_components(input_molecule); + prepare_sponge_atom_order(input_molecule); std::optional molecule_with_generated_cmaps; if (input_molecule.cmaps.empty() && has_amber_cmap_parameters()) { molecule_with_generated_cmaps = input_molecule; diff --git a/cpp/python/bindings_core.cpp b/cpp/python/bindings_core.cpp index 0ce568d..88451a4 100644 --- a/cpp/python/bindings_core.cpp +++ b/cpp/python/bindings_core.cpp @@ -29,6 +29,12 @@ void replace_residues_object(const std::shared_ptr& molecule, molecule->replace_residues(copied_replacements, residue_sort_keys, sort); } +std::vector prepare_sponge_atom_order_object( + const std::shared_ptr& molecule +) { + return prepare_sponge_atom_order(*molecule); +} + void reorder_atoms_by_template_object(const std::shared_ptr& molecule, const std::shared_ptr& template_molecule) { molecule->reorder_atoms_by_template(*template_molecule); @@ -567,6 +573,7 @@ void bind_core_module(py::module_& m) { m.def("add_molecule", &add_molecule_object, py::arg("molecule"), py::arg("other")); m.def("replace_residues", &replace_residues_object, py::arg("molecule"), py::arg("replacements"), py::arg("residue_sort_keys") = std::vector{}, py::arg("sort") = true); + m.def("prepare_sponge_atom_order", &prepare_sponge_atom_order_object, py::arg("molecule")); m.def("reorder_atoms_by_template", &reorder_atoms_by_template_object, py::arg("molecule"), py::arg("template_molecule")); m.def("set_box_padding", &set_box_padding_object, py::arg("molecule"), py::arg("padding") = 0.5, diff --git a/pyproject.toml b/pyproject.toml index 8af927c..b837ae7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build" [project] name = "XpongeCPP" -version = "0.2.4" +version = "0.2.5" description = "C++ core rewrite of Xponge workflows, compatible with Xponge-origin 1.7b9" readme = "README.md" requires-python = ">=3.10,<3.13" diff --git a/src/XpongeCPP/__init__.py b/src/XpongeCPP/__init__.py index 7cdb7b8..f2dd773 100644 --- a/src/XpongeCPP/__init__.py +++ b/src/XpongeCPP/__init__.py @@ -193,7 +193,7 @@ def _get_molecule_charge(molecule): from .legacy_types import _LegacyResidueTypeHandle from .template_ops import load_mol2 -__version__ = "0.2.4" +__version__ = "0.2.5" __mokda_backend__ = "xpongecpp" __implementation_version__ = __version__ pi = np.pi diff --git a/src/XpongeCPP/_compat/process.py b/src/XpongeCPP/_compat/process.py index 56673d7..22b9eb0 100644 --- a/src/XpongeCPP/_compat/process.py +++ b/src/XpongeCPP/_compat/process.py @@ -111,6 +111,10 @@ def Save_SPONGE_Input( # pylint: disable=redefined-builtin else: raise TypeError("save_sponge_input expects a Molecule, Residue, ResidueType, or template-like object") + new_to_old_atom_order = _prepare_sponge_atom_order(target) + if source_ids is not None: + source_ids = tuple(source_ids[old_index] for old_index in new_to_old_atom_order) + previous_min_flag = None try: from ..forcefield.special.min import min_bonded_parameters_enabled @@ -130,6 +134,12 @@ def Save_SPONGE_Input( # pylint: disable=redefined-builtin return _save_result_with_mapping(target, source_ids, return_mapping) +def _prepare_sponge_atom_order(molecule): + from .._core import prepare_sponge_atom_order + + return tuple(int(index) for index in prepare_sponge_atom_order(molecule)) + + def _capture_source_atom_ids(molecule, source_atom_ids): if source_atom_ids is None: return None diff --git a/tests/test_release_024.py b/tests/test_release_025.py similarity index 99% rename from tests/test_release_024.py rename to tests/test_release_025.py index e36d0a5..e669db0 100644 --- a/tests/test_release_024.py +++ b/tests/test_release_025.py @@ -18,7 +18,7 @@ def test_release_version_and_compatibility_target(): metadata = tomllib.loads( (ROOT / "pyproject.toml").read_text(encoding="utf-8") )["project"] - assert metadata["version"] == XpongeCPP.__version__ == "0.2.4" + assert metadata["version"] == XpongeCPP.__version__ == "0.2.5" assert "Xponge-origin 1.7b9" in metadata["description"] assert metadata["requires-python"] == ">=3.10,<3.13" diff --git a/tests/test_xpongecpp_api.py b/tests/test_xpongecpp_api.py index fabd4b9..2c2e36a 100644 --- a/tests/test_xpongecpp_api.py +++ b/tests/test_xpongecpp_api.py @@ -1114,6 +1114,34 @@ def test_save_sponge_input_reorders_linked_residue_components_for_export(tmp_pat assert (tmp_path / "linked_resname.txt").read_text().splitlines() == ["3", "FAR", "WAT", "LIG"] +def test_save_sponge_input_mapping_tracks_reordered_linked_components(tmp_path): + Xponge.register_tip3p() + mol = Xponge.load_mol2(StringIO(CUSTOM_MOL2_TEXT)) | Xponge.load_mol2(StringIO(MOL2_TEXT)) + + residue_counts = [len(residue.atoms) for residue in mol.residues] + source_ids = tuple(f"source:{index}" for index, _atom in enumerate(mol.atoms)) + mol.add_residue_link(mol.residues[0].name2atom("O1"), mol.residues[2].name2atom("O")) + + saved, mapping = Xponge.Save_SPONGE_Input( + mol, + prefix="linked_mapping", + dirname=str(tmp_path), + source_atom_ids=source_ids, + return_mapping=True, + ) + + far_end = residue_counts[0] + ligand_end = far_end + residue_counts[1] + expected_source_ids = ( + source_ids[:far_end] + + source_ids[ligand_end:] + + source_ids[far_end:ligand_end] + ) + assert saved is mol + assert [residue.name for residue in saved.residues] == ["FAR", "WAT", "LIG"] + assert tuple(record["source_atom_id"] for record in mapping) == expected_source_ids + + def test_save_sponge_input_reorders_coordination_components_for_export(tmp_path): Xponge.register_tip3p() mol = Xponge.load_mol2(StringIO(CUSTOM_MOL2_TEXT)) | Xponge.load_mol2(StringIO(MOL2_TEXT)) From 6aec887a21e82f683ba9a45acda0f591ebb605c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8E=E6=B5=A9?= Date: Tue, 18 Aug 2026 09:12:42 +0800 Subject: [PATCH 08/11] fix(qm): detect available backend on Windows --- .github/workflows/build-packages.yml | 2 +- .github/workflows/publish-pypi.yml | 2 +- README.md | 9 +++--- RELEASE_NOTES.md | 14 ++++++++ docs/installation.md | 13 ++++---- docs/installation.zh-CN.md | 13 ++++---- ...xponge-vs-xpongecpp-architecture-status.md | 2 +- pyproject.toml | 6 ++-- src/XpongeCPP/__init__.py | 2 +- src/XpongeCPP/assign/resp.py | 11 ++----- src/XpongeCPP/qm/scheduler.py | 25 ++++++++++----- tests/test_assign_charge_models.py | 32 +++++++++++++------ tests/test_release_025.py | 6 ++-- 13 files changed, 84 insertions(+), 53 deletions(-) diff --git a/.github/workflows/build-packages.yml b/.github/workflows/build-packages.yml index 4d7d934..b440981 100644 --- a/.github/workflows/build-packages.yml +++ b/.github/workflows/build-packages.yml @@ -16,7 +16,7 @@ env: CIBW_TEST_COMMAND: >- python -c "import importlib.util, XpongeCPP, Xponge, XpongeCPP.io_bundle, XpongeCPP.metal_assignment; - assert XpongeCPP.__version__ == '0.2.5'; + assert XpongeCPP.__version__ == '0.2.6'; assert not hasattr(XpongeCPP, 'MC' + 'PB'); assert importlib.util.find_spec('XpongeCPP.' + ('MC' + 'PB').lower()) is None" CIBW_BEFORE_ALL_LINUX: >- diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 2c7cbd1..1ee7f61 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -12,7 +12,7 @@ env: CIBW_TEST_COMMAND: >- python -c "import importlib.util, XpongeCPP, Xponge, XpongeCPP.io_bundle, XpongeCPP.metal_assignment; - assert XpongeCPP.__version__ == '0.2.5'; + assert XpongeCPP.__version__ == '0.2.6'; assert not hasattr(XpongeCPP, 'MC' + 'PB'); assert importlib.util.find_spec('XpongeCPP.' + ('MC' + 'PB').lower()) is None" CIBW_BEFORE_ALL_LINUX: >- diff --git a/README.md b/README.md index c1b36e5..0b3790f 100644 --- a/README.md +++ b/README.md @@ -143,10 +143,10 @@ XpongeCPP.save_sponge_input_raw(molecule, "system", "inputs") XpongeCPP.save_sponge_input_bundle(molecule, "system", "inputs") ``` -RESP supports a multi-backend strategy: +RESP automatically selects the first available backend: -- default backend: `PySCF` -- optional backend: `Psi4` +- preferred backend: `PySCF` +- fallback backend: `Psi4` - shared dispatch layer: `XpongeCPP.qm` Example: @@ -161,7 +161,8 @@ qm.run_scf(assign, backend="pyscf") qm.optimize_geometry(assign, backend="pyscf") ``` -For Windows RESP workflows, install `Psi4` separately and select it explicitly: +Windows does not install PySCF automatically. If a compatible PySCF is +available, it is selected; otherwise install Psi4 separately as the fallback: ```bash conda install -c conda-forge psi4 diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index c538879..2a45954 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,17 @@ +# XpongeCPP 0.2.6 + +Compatibility target: Xponge-origin 1.7b10. + +This parity release changes QM backend selection from an operating-system rule +to runtime capability detection. XpongeCPP now prefers an installed PySCF +backend, falls back to Psi4 when PySCF is absent, and reports a clear error +when neither backend is available. Windows packaging continues to skip PySCF; +users may supply a compatible PySCF installation or install Psi4 separately. + +Regression coverage verifies PySCF preference, Psi4 fallback, and the +no-backend error path. Windows wheel CI remains a packaging smoke test; it does +not claim native Windows PySCF support. + # XpongeCPP 0.2.5 Compatibility target: Xponge-origin 1.7b9. diff --git a/docs/installation.md b/docs/installation.md index 196a418..b8753b2 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -55,15 +55,16 @@ python -c "import Xponge.forcefield.amber.ff19sb; from Xponge.forcefield.special ## RESP 后端说明 -当前 RESP 电荷计算支持多后端策略: +当前 RESP 电荷计算会自动选择第一个可用的后端: -- 默认后端:`PySCF` -- 可选后端:`Psi4` +- 优先后端:`PySCF` +- 回退后端:`Psi4` 推荐使用方式: -- Linux / macOS:默认直接使用 `PySCF` -- Windows:安装 `Psi4`,并在 RESP 调用时显式指定 `backend="psi4"` +- 所有平台:若已安装可用的 `PySCF`,则优先使用它 +- PySCF 不可用时:自动回退到 `Psi4` +- 两者都不可用时:在开始 QM 前给出明确错误 示例: @@ -72,7 +73,7 @@ assign.calculate_charge("resp", backend="pyscf") assign.calculate_charge("resp", backend="psi4") ``` -如果是在 Windows 上做 RESP,推荐安装顺序是: +Windows 不会自动安装 PySCF;如果没有自行提供兼容的 PySCF,推荐安装 Psi4: ```bash conda install -c conda-forge psi4 diff --git a/docs/installation.zh-CN.md b/docs/installation.zh-CN.md index 717864a..4ab489e 100644 --- a/docs/installation.zh-CN.md +++ b/docs/installation.zh-CN.md @@ -68,15 +68,16 @@ python -c "import Xponge.forcefield.amber.ff19sb; from Xponge.forcefield.special ## RESP 后端说明 -当前 RESP 电荷计算采用多后端策略: +当前 RESP 电荷计算会自动选择第一个可用的后端: -- 默认后端:`PySCF` -- 可选后端:`Psi4` +- 优先后端:`PySCF` +- 回退后端:`Psi4` 推荐用法: -- Linux / macOS:默认直接使用 `PySCF` -- Windows:安装 `Psi4`,并在 RESP 调用时显式指定 `backend="psi4"` +- 所有平台:若已安装可用的 `PySCF`,则优先使用它 +- PySCF 不可用时:自动回退到 `Psi4` +- 两者都不可用时:在开始 QM 前给出明确错误 示例: @@ -85,7 +86,7 @@ assign.calculate_charge("resp", backend="pyscf") assign.calculate_charge("resp", backend="psi4") ``` -如果你是在 Windows 上使用 RESP,推荐安装方式是: +Windows 不会自动安装 PySCF;如果没有自行提供兼容的 PySCF,推荐安装 Psi4: ```bash conda install -c conda-forge psi4 diff --git a/docs/xponge-vs-xpongecpp-architecture-status.md b/docs/xponge-vs-xpongecpp-architecture-status.md index 201def5..ec4b3af 100644 --- a/docs/xponge-vs-xpongecpp-architecture-status.md +++ b/docs/xponge-vs-xpongecpp-architecture-status.md @@ -110,7 +110,7 @@ XpongeCPP | `AssignRule` custom Python registry | Supported | User custom rules run in Python and do not affect built-in C++ hot paths. | | TPACM4 | Supported | C++ implementation with regression tests. | | Gasteiger | Supported with optional dependency | Uses RDKit when installed. | -| RESP | Partial | `PySCF` remains the default backend; optional `Psi4` routing, Windows-facing install hints, and Python/C++ RESP-core parity tests exist. Real-fixture regressions pass under `PySCF`, while `Psi4` coverage is currently guarded by optional-dependency tests rather than mandatory CI. Full large-case and all-parameter Xponge parity is not complete. | +| RESP | Partial | The QM scheduler prefers an available `PySCF` backend and falls back to `Psi4`; Windows packaging still does not install PySCF automatically. Python/C++ RESP-core parity tests exist. Real-fixture regressions pass under `PySCF`, while `Psi4` coverage is currently guarded by optional-dependency tests rather than mandatory CI. Full large-case and all-parameter Xponge parity is not complete. | | pH model | Partial | Common phenol/carboxyl/alcohol behavior exists, including reference-backed typing checks and protonation/deprotonation coverage in both directions for the current supported chemistry classes. Full original edge coverage is not complete. | | PubChem real network behavior | Partial | Signature and dependency behavior are present; live network regression is opt-in. | | CIF symmetry/crystal expansion | Partial | Basic cell/fractional coordinate support exists, including reference-backed fractional-coordinate cases plus richer non-orthogonal symmetry-basis coverage. Full original CIF crystallographic behavior is not complete. | diff --git a/pyproject.toml b/pyproject.toml index b837ae7..12a664d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,13 +4,13 @@ build-backend = "scikit_build_core.build" [project] name = "XpongeCPP" -version = "0.2.5" -description = "C++ core rewrite of Xponge workflows, compatible with Xponge-origin 1.7b9" +version = "0.2.6" +description = "C++ core rewrite of Xponge workflows, compatible with Xponge-origin 1.7b10" readme = "README.md" requires-python = ">=3.10,<3.13" authors = [{ name = "The XpongeCPP contributors" }] license = { text = "Apache License 2.0" } -keywords = ["molecular-dynamics", "molecular-mechanics", "Xponge-origin-1.7b9"] +keywords = ["molecular-dynamics", "molecular-mechanics", "Xponge-origin-1.7b10"] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Science/Research", diff --git a/src/XpongeCPP/__init__.py b/src/XpongeCPP/__init__.py index f2dd773..0fa0d93 100644 --- a/src/XpongeCPP/__init__.py +++ b/src/XpongeCPP/__init__.py @@ -193,7 +193,7 @@ def _get_molecule_charge(molecule): from .legacy_types import _LegacyResidueTypeHandle from .template_ops import load_mol2 -__version__ = "0.2.5" +__version__ = "0.2.6" __mokda_backend__ = "xpongecpp" __implementation_version__ = __version__ pi = np.pi diff --git a/src/XpongeCPP/assign/resp.py b/src/XpongeCPP/assign/resp.py index d124fed..c27ba39 100644 --- a/src/XpongeCPP/assign/resp.py +++ b/src/XpongeCPP/assign/resp.py @@ -6,7 +6,6 @@ from __future__ import annotations import time -import sys from ..helper import Xprint, set_global_alternative_names from ..qm import scheduler as qm_scheduler @@ -86,15 +85,9 @@ def _build_backend_payload(backend_module, assign, resolved_basis, charge, spin, def _legacy_backend_import_or_hint(backend_name, exc): message = str(exc) - if backend_name == "pyscf" and sys.platform.startswith("win"): + if backend_name == "psi4": message += ( - " On Windows, install Psi4 via conda-forge or the official Psi4 " - "installer and call calculate_charge('resp', backend='psi4', ...)." - ) - elif backend_name == "psi4": - message += ( - " On Windows, Psi4 is not installed through pip by default; " - "install it via conda-forge or the official Psi4 installer and retry." + " Install Psi4 via conda-forge or the official Psi4 installer and retry." ) raise ImportError(message) from exc diff --git a/src/XpongeCPP/qm/scheduler.py b/src/XpongeCPP/qm/scheduler.py index ab5d794..ba5ec3d 100644 --- a/src/XpongeCPP/qm/scheduler.py +++ b/src/XpongeCPP/qm/scheduler.py @@ -2,7 +2,7 @@ from __future__ import annotations -import sys +from importlib.util import find_spec from .backends import psi4_backend, pyscf_backend from ._esp_memory import normalize_chunk_policy, normalize_safety_factor, parse_memory_limit_bytes @@ -31,10 +31,21 @@ }) +def _backend_available(backend_name): + try: + return find_spec(backend_name) is not None + except (ImportError, ValueError): + return False + + def default_backend_name(): - if sys.platform.startswith("win"): - return "psi4" - return "pyscf" + for backend_name in ("pyscf", "psi4"): + if _backend_available(backend_name): + return backend_name + raise QMBackendImportError( + "Neither PySCF nor Psi4 is installed; quantum chemistry features are " + "unavailable. Install PySCF (preferred) or Psi4 before retrying." + ) def normalize_backend_name(backend): @@ -49,10 +60,8 @@ def normalize_backend_name(backend): def backend_import_or_hint(backend_name, exc): message = str(exc) - if backend_name == "pyscf" and sys.platform.startswith("win"): - message += " On Windows, install Psi4 via conda-forge or the official Psi4 installer and call calculate_charge('resp', backend='psi4', ...)." - elif backend_name == "psi4": - message += " On Windows, Psi4 is not installed through pip by default; install it via conda-forge or the official Psi4 installer and retry." + if backend_name == "psi4": + message += " Install Psi4 via conda-forge or the official Psi4 installer and retry." raise QMBackendImportError(message) from exc diff --git a/tests/test_assign_charge_models.py b/tests/test_assign_charge_models.py index d9f01cf..5d2e53c 100644 --- a/tests/test_assign_charge_models.py +++ b/tests/test_assign_charge_models.py @@ -327,7 +327,7 @@ def test_resp_rejects_unknown_core(): resp_module.resp_fit(assignment, core="unknown") -def test_resp_windows_hint_mentions_psi4(monkeypatch): +def test_resp_pyscf_import_error_is_not_platform_specific(monkeypatch): assignment = _assignment("water", ["O", "H", "H"], [(0, 1, 1), (0, 2, 1)]) class FailingBackend: @@ -336,9 +336,7 @@ def build_backend_payload(assign, basis, charge, spin, opt): raise ImportError("PySCF is required for RESP charge calculation") monkeypatch.setitem(resp_module._BACKEND_MODULES, "pyscf", FailingBackend) - monkeypatch.setattr(resp_module.sys, "platform", "win32") - - with pytest.raises(ImportError, match="install Psi4"): + with pytest.raises(ImportError, match="PySCF is required"): resp_module.resp_fit(assignment, backend="pyscf") @@ -356,9 +354,7 @@ def build_backend_payload(assign, basis, charge, spin, opt): resp_module.resp_fit(assignment, backend="psi4") -def test_qm_scheduler_windows_psi4_hint_mentions_external_install(monkeypatch): - monkeypatch.setattr(qm_scheduler.sys, "platform", "win32") - +def test_qm_scheduler_psi4_hint_mentions_external_install(): with pytest.raises(ImportError, match="official Psi4 installer"): qm_scheduler.backend_import_or_hint("psi4", ImportError("Psi4 is required")) @@ -370,16 +366,32 @@ def test_qm_scheduler_exposes_known_backends(): qm_get_backend("unknown") -def test_qm_scheduler_default_backend_matches_platform(monkeypatch): - monkeypatch.setattr(qm_scheduler.sys, "platform", "linux") +def test_qm_scheduler_default_backend_prefers_pyscf(monkeypatch): + monkeypatch.setattr(qm_scheduler, "find_spec", lambda name: object()) assert qm_scheduler.normalize_backend_name(None) == "pyscf" assert qm_get_backend(None).name == "pyscf" - monkeypatch.setattr(qm_scheduler.sys, "platform", "win32") + +def test_qm_scheduler_default_backend_falls_back_to_psi4(monkeypatch): + monkeypatch.setattr( + qm_scheduler, + "find_spec", + lambda name: object() if name == "psi4" else None, + ) assert qm_scheduler.normalize_backend_name(None) == "psi4" assert qm_get_backend(None).name == "psi4" +def test_qm_scheduler_default_backend_errors_when_none_installed(monkeypatch): + monkeypatch.setattr(qm_scheduler, "find_spec", lambda name: None) + + with pytest.raises( + qm_scheduler.QMBackendImportError, + match="Neither PySCF nor Psi4 is installed", + ): + qm_scheduler.normalize_backend_name(None) + + def test_qm_scheduler_runs_pyscf_scf_and_esp_smoke(): assignment = Xponge.get_assignment_from_mol2(str(FORMAMIDE_RESP_MOL2), total_charge="sum") scf_result = qm_run_scf( diff --git a/tests/test_release_025.py b/tests/test_release_025.py index e669db0..c25d4f6 100644 --- a/tests/test_release_025.py +++ b/tests/test_release_025.py @@ -1,4 +1,4 @@ -"""Release-contract checks for the Xponge-origin 1.7b9 alignment.""" +"""Release-contract checks for the Xponge-origin 1.7b10 alignment.""" from __future__ import annotations @@ -18,8 +18,8 @@ def test_release_version_and_compatibility_target(): metadata = tomllib.loads( (ROOT / "pyproject.toml").read_text(encoding="utf-8") )["project"] - assert metadata["version"] == XpongeCPP.__version__ == "0.2.5" - assert "Xponge-origin 1.7b9" in metadata["description"] + assert metadata["version"] == XpongeCPP.__version__ == "0.2.6" + assert "Xponge-origin 1.7b10" in metadata["description"] assert metadata["requires-python"] == ">=3.10,<3.13" From d8820cc8557591c84c3809ae3e662540d64b8b40 Mon Sep 17 00:00:00 2001 From: Xiaoxuan Yu Date: Tue, 18 Aug 2026 23:47:16 +0800 Subject: [PATCH 09/11] feat(protocol): add typed virtual atoms --- src/XpongeCPP/__init__.py | 2 + src/XpongeCPP/io_bundle/__init__.py | 2 + src/XpongeCPP/io_bundle/protocol.py | 87 ++++++++++++++++++++++++++++- tests/test_bundle_protocol.py | 30 ++++++++++ 4 files changed, 120 insertions(+), 1 deletion(-) diff --git a/src/XpongeCPP/__init__.py b/src/XpongeCPP/__init__.py index 0fa0d93..a305a80 100644 --- a/src/XpongeCPP/__init__.py +++ b/src/XpongeCPP/__init__.py @@ -94,6 +94,7 @@ ProtocolSITS, ProtocolSoftWall, ProtocolSteering, + ProtocolVirtualAtom, SpongeProtocol, save_sponge_input_bundle, ) @@ -333,6 +334,7 @@ def load_parameter_from_ffitp(filename, folder, reset=True): "ProtocolSITS", "ProtocolSoftWall", "ProtocolSteering", + "ProtocolVirtualAtom", "save_pdb", "save_gro", "save_mol2", diff --git a/src/XpongeCPP/io_bundle/__init__.py b/src/XpongeCPP/io_bundle/__init__.py index 4c9eec7..97630ac 100644 --- a/src/XpongeCPP/io_bundle/__init__.py +++ b/src/XpongeCPP/io_bundle/__init__.py @@ -46,6 +46,7 @@ ProtocolSITS, ProtocolSoftWall, ProtocolSteering, + ProtocolVirtualAtom, SpongeProtocol, ) from .reverse_converter import ( @@ -86,6 +87,7 @@ "ProtocolSITS", "ProtocolSoftWall", "ProtocolSteering", + "ProtocolVirtualAtom", "ReverseConversionManifest", "SpongeProtocol", "UnverifiedBundlePairError", diff --git a/src/XpongeCPP/io_bundle/protocol.py b/src/XpongeCPP/io_bundle/protocol.py index 3c662b1..715504b 100644 --- a/src/XpongeCPP/io_bundle/protocol.py +++ b/src/XpongeCPP/io_bundle/protocol.py @@ -42,6 +42,7 @@ class ProtocolCollectiveVariable: name: str type: str atom_indices: tuple[int, ...] = () + atom_refs: tuple[int | str, ...] = () parameters: Mapping[str, Any] = field(default_factory=dict) period: tuple[float, ...] = () sigma: tuple[float, ...] = () @@ -54,6 +55,17 @@ class ProtocolCollectiveVariable: max_padding: float | None = None +@dataclass(frozen=True) +class ProtocolVirtualAtom: + """One named center used as an atom by collective variables.""" + + name: str + type: str + atom_indices: tuple[int, ...] + weight: tuple[float, ...] = () + enabled: bool = True + + @dataclass(frozen=True) class ProtocolDistanceConstraints: """Extra distance-constraint pairs used by the runtime constraint module.""" @@ -180,6 +192,7 @@ class SpongeProtocol: """Composable native protocol attached to a serialized XPONGE system.""" collective_variables: tuple[ProtocolCollectiveVariable, ...] = () + virtual_atoms: tuple[ProtocolVirtualAtom, ...] = () distance_constraints: tuple[ProtocolDistanceConstraints, ...] = () positional_restraints: tuple[ProtocolPositionalRestraint, ...] = () cv_restraints: tuple[ProtocolCVRestraint, ...] = () @@ -216,6 +229,7 @@ def add_protocol_to_bundle( ) cvs = tuple(protocol.collective_variables) + virtual_atoms = tuple(protocol.virtual_atoms) constraints = tuple(protocol.distance_constraints) positional = tuple(protocol.positional_restraints) cv_restraints = tuple(protocol.cv_restraints) @@ -226,6 +240,7 @@ def add_protocol_to_bundle( soft_walls = tuple(protocol.soft_walls) _validate_protocol( cvs, + virtual_atoms, constraints, positional, cv_restraints, @@ -237,6 +252,8 @@ def add_protocol_to_bundle( atom_count=atom_count, ) + for virtual_atom in virtual_atoms: + _write_virtual_atom(builder, virtual_atom) for cv in cvs: _write_cv(builder, cv) for constraint in constraints: @@ -272,6 +289,7 @@ def add_protocol_to_bundle( def _validate_protocol( cvs: tuple[ProtocolCollectiveVariable, ...], + virtual_atoms: tuple[ProtocolVirtualAtom, ...], constraints: tuple[ProtocolDistanceConstraints, ...], positional: tuple[ProtocolPositionalRestraint, ...], cv_restraints: tuple[ProtocolCVRestraint, ...], @@ -286,19 +304,75 @@ def _validate_protocol( if atom_count <= 0: raise BundleValidationError("a native protocol requires a positive atom count") _require_unique_names(cvs, "collective variable") + _require_unique_names(virtual_atoms, "virtual atom") _require_unique_names(constraints, "distance constraint") _require_unique_names(positional + cv_restraints, "restraint") _require_unique_names(metadynamics, "metadynamics") cv_by_name = {cv.name: cv for cv in cvs if cv.enabled} + virtual_atom_names = {item.name for item in virtual_atoms if item.enabled} + overlapping_names = {cv.name for cv in cvs} & {item.name for item in virtual_atoms} + if overlapping_names: + raise BundleValidationError( + f"collective variables and virtual atoms must use distinct names: {sorted(overlapping_names)}" + ) + for item in virtual_atoms: + _validate_name(item.name, "virtual atom") + if item.type not in {"center", "center_of_mass"}: + raise BundleValidationError( + f"virtual atom {item.name!r} type must be 'center' or 'center_of_mass'" + ) + if not item.atom_indices: + raise BundleValidationError(f"virtual atom {item.name!r} requires atom_indices") + _validate_atom_indices(item.atom_indices, atom_count, f"virtual atom {item.name!r}") + if item.type == "center": + _validate_vector_length( + item.weight, + len(item.atom_indices), + f"virtual atom {item.name!r} weight", + required=True, + ) + _require_finite(item.weight, f"virtual atom {item.name!r} weight") + elif item.weight: + raise BundleValidationError( + f"virtual atom {item.name!r} center_of_mass must not define weight" + ) for cv in cvs: _validate_name(cv.name, "collective variable") + if cv.name == "virtual_atom": + raise BundleValidationError( + "collective variable name 'virtual_atom' is reserved by /cv/virtual_atom" + ) _validate_name(cv.type, f"collective variable {cv.name!r} type") if cv.dimension != 1: raise BundleValidationError( f"collective variable {cv.name!r} dimension must be 1 for the current SPONGE runtime" ) + if cv.atom_indices and cv.atom_refs: + raise BundleValidationError( + f"collective variable {cv.name!r} atom_indices and atom_refs are mutually exclusive" + ) _validate_atom_indices(cv.atom_indices, atom_count, f"collective variable {cv.name!r}") + physical_atom_refs = [item for item in cv.atom_refs if isinstance(item, (int, np.integer))] + invalid_atom_refs = [item for item in cv.atom_refs if not isinstance(item, (str, int, np.integer))] + if invalid_atom_refs: + raise BundleValidationError( + f"collective variable {cv.name!r} atom_refs must contain atom indices or virtual atom names" + ) + normalized_atom_refs = tuple(str(item) for item in cv.atom_refs) + if len(normalized_atom_refs) != len(set(normalized_atom_refs)): + raise BundleValidationError( + f"collective variable {cv.name!r} atom_refs must be unique" + ) + _validate_atom_indices(physical_atom_refs, atom_count, f"collective variable {cv.name!r}") + missing_atom_refs = [ + name for name in cv.atom_refs if isinstance(name, str) and name not in virtual_atom_names + ] + if missing_atom_refs: + raise BundleValidationError( + f"collective variable {cv.name!r} references missing or disabled virtual atoms: " + f"{missing_atom_refs}" + ) _validate_vector_length(cv.period, cv.dimension, f"collective variable {cv.name!r} period") _validate_vector_length(cv.sigma, cv.dimension, f"collective variable {cv.name!r} sigma") if cv.sigma and any(value <= 0 or not np.isfinite(value) for value in cv.sigma): @@ -308,7 +382,7 @@ def _validate_protocol( if cv.reference_coordinates: _validate_xyz( cv.reference_coordinates, - len(cv.atom_indices), + len(cv.atom_indices or cv.atom_refs), f"collective variable {cv.name!r} reference coordinates", ) for key, value in cv.parameters.items(): @@ -655,6 +729,8 @@ def _write_cv(builder: BundleBuilder, cv: ProtocolCollectiveVariable) -> None: _add_scalar(builder, root + "/enabled_default", int(cv.enabled), np.int32) if cv.atom_indices: _add_array(builder, root + "/atom_indices", cv.atom_indices, np.int32) + if cv.atom_refs: + _add_array(builder, root + "/atom_refs", tuple(str(item) for item in cv.atom_refs), object) if cv.period: _add_array(builder, root + "/period", cv.period, np.float32) if cv.sigma: @@ -683,6 +759,15 @@ def _write_cv(builder: BundleBuilder, cv: ProtocolCollectiveVariable) -> None: ) +def _write_virtual_atom(builder: BundleBuilder, virtual_atom: ProtocolVirtualAtom) -> None: + root = f"/cv/virtual_atom/{virtual_atom.name}" + _add_scalar(builder, root + "/type", virtual_atom.type) + _add_scalar(builder, root + "/enabled_default", int(virtual_atom.enabled), np.int32) + _add_array(builder, root + "/atom_indices", virtual_atom.atom_indices, np.int32) + if virtual_atom.weight: + _add_array(builder, root + "/weight", virtual_atom.weight, np.float32) + + def _write_constraint(builder: BundleBuilder, constraint: ProtocolDistanceConstraints) -> None: root = f"/constraint/{constraint.name}" _add_scalar(builder, root + "/schema_version", 1, np.int64) diff --git a/tests/test_bundle_protocol.py b/tests/test_bundle_protocol.py index 9c4409c..92edc85 100644 --- a/tests/test_bundle_protocol.py +++ b/tests/test_bundle_protocol.py @@ -138,6 +138,36 @@ def test_invalid_protocol_does_not_publish_partial_bundle(tmp_path): assert not list(tmp_path.glob("invalid_*")) +def test_protocol_writes_typed_virtual_atom_arrays_and_cv_references(tmp_path): + molecule = _peptide() + protocol = Xponge.SpongeProtocol( + virtual_atoms=( + Xponge.ProtocolVirtualAtom( + name="center", + type="center", + atom_indices=(0, 1), + weight=(0.25, 0.75), + ), + ), + collective_variables=( + Xponge.ProtocolCollectiveVariable( + name="distance_cv", + type="distance", + atom_refs=("center", 2), + ), + ), + ) + + Xponge.save_sponge_input_bundle(molecule, "virtual", tmp_path, protocol=protocol) + + with h5py.File(tmp_path / "virtual_protocol.spgp.h5", "r") as handle: + assert _text(handle["/cv/virtual_atom/center/type"]) == "center" + assert handle["/cv/virtual_atom/center/atom_indices"][...].tolist() == [0, 1] + assert handle["/cv/virtual_atom/center/weight"][...].tolist() == pytest.approx([0.25, 0.75]) + assert handle["/cv/distance_cv/atom_refs"].asstr()[...].tolist() == ["center", "2"] + assert int(handle["/protocol/cv_count"][()]) == 1 + + @pytest.mark.parametrize("prefix", ["../escape", ".", "subdir/.."]) def test_protocol_saver_rejects_unsafe_prefixes(tmp_path, prefix): with pytest.raises(BundlePathError, match="prefix|escapes"): From 240222a13bc712fbb7466dbd8c6ce77bd6c77be9 Mon Sep 17 00:00:00 2001 From: Xiaoxuan Yu Date: Tue, 18 Aug 2026 23:50:15 +0800 Subject: [PATCH 10/11] test(qm): port Windows backend coverage from master Completes the integration-branch port of 9a7ce58 without replaying its obsolete 0.1.7 release metadata. --- pixi.toml | 2 +- tests/test_assign_charge_models.py | 22 +++++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/pixi.toml b/pixi.toml index 1a8f2eb..eb1155a 100644 --- a/pixi.toml +++ b/pixi.toml @@ -28,4 +28,4 @@ pubchempy = "*" install-dev = "python -m pip install -e . --force-reinstall --no-cache-dir" test = { cmd = "pytest -q", depends-on = ["install-dev"] } test-assign-full = { cmd = "pytest tests/test_assign_charge_models.py tests/test_gaff_assign_migration.py tests/test_amber_fep_workflow.py::test_merge_dual_topology_can_derive_match_map_from_assign_when_rdkit_is_available -q", depends-on = ["install-dev"] } -test-resp = { cmd = "pytest tests/test_assign_charge_models.py::test_resp_uses_pyscf_backend_or_reports_missing_dependency -q", depends-on = ["install-dev"] } +test-resp = { cmd = "pytest tests/test_assign_charge_models.py::test_resp_uses_available_backend_or_reports_missing_dependency -q", depends-on = ["install-dev"] } diff --git a/tests/test_assign_charge_models.py b/tests/test_assign_charge_models.py index 5d2e53c..599ca71 100644 --- a/tests/test_assign_charge_models.py +++ b/tests/test_assign_charge_models.py @@ -197,10 +197,22 @@ def fake_import(name, *args, **kwargs): assignment.calculate_charge("resp") -def test_resp_defaults_to_platform_backend(monkeypatch): +@pytest.mark.parametrize( + ("available_backends", "expected_backend"), + [ + ({"pyscf", "psi4"}, "pyscf"), + ({"psi4"}, "psi4"), + ], +) +def test_resp_defaults_to_available_backend(monkeypatch, available_backends, expected_backend): assignment = _assignment("water", ["O", "H", "H"], [(0, 1, 1), (0, 2, 1)]) calls = [] - default_backend = qm_scheduler.normalize_backend_name(None) + + monkeypatch.setattr( + qm_scheduler, + "find_spec", + lambda name: object() if name in available_backends else None, + ) class FakeBackend: @staticmethod @@ -220,7 +232,7 @@ def compute_esp_on_grid(payload, grids, *, memory_limit=None, chunk_policy="auto calls.append(("esp", len(grids), memory_limit, chunk_policy, safety_factor)) return np.zeros(len(grids)) - monkeypatch.setitem(resp_module._BACKEND_MODULES, default_backend, FakeBackend) + monkeypatch.setitem(resp_module._BACKEND_MODULES, expected_backend, FakeBackend) monkeypatch.setattr(resp_module.resp_core, "get_mk_grid", lambda *args, **kwargs: __import__("numpy").zeros((2, 3))) monkeypatch.setattr(resp_module.resp_core, "fit_resp_from_esp", lambda *args, **kwargs: [0.0, 0.0, 0.0]) @@ -1037,12 +1049,12 @@ def test_save_as_mol2_atomtype_argument_and_equal_atoms_api(tmp_path): assert any(set(group) == {0, 1} for group in groups) -def test_resp_uses_pyscf_backend_or_reports_missing_dependency(): +def test_resp_uses_available_backend_or_reports_missing_dependency(): water = _assignment("water", ["O", "H", "H"], [(0, 1, 1), (0, 2, 1)]) try: water.calculate_charge("resp", basis="sto-3g", charge=0, grid_density=1, grid_cell_layer=1, only_esp=True) except ImportError as exc: - assert "PySCF" in str(exc) + assert "PySCF" in str(exc) or "Psi4" in str(exc) return assert len(water.charges) == 3 assert math.isclose(sum(water.charges), 0.0, abs_tol=1e-5) From 0266bb5853396cc577a217c6981bdaa308d8ff97 Mon Sep 17 00:00:00 2001 From: Xiaoxuan Yu Date: Wed, 19 Aug 2026 00:05:10 +0800 Subject: [PATCH 11/11] Fix release test path in CI --- .github/workflows/build-packages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-packages.yml b/.github/workflows/build-packages.yml index b440981..fbe3047 100644 --- a/.github/workflows/build-packages.yml +++ b/.github/workflows/build-packages.yml @@ -122,7 +122,7 @@ jobs: - name: Run Xponge-origin compatibility parity cohorts run: >- python -m pytest -q - tests/test_release_023.py + tests/test_release_025.py tests/test_origin_resp_linear_constraints.py tests/test_origin_io_bundle.py tests/test_origin_io_bundle_reverse.py