htpolynet 2.5.0__tar.gz → 2.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (338) hide show
  1. {htpolynet-2.5.0 → htpolynet-2.6.0}/CHANGELOG.md +67 -6
  2. {htpolynet-2.5.0 → htpolynet-2.6.0}/PKG-INFO +1 -1
  3. {htpolynet-2.5.0 → htpolynet-2.6.0}/ROADMAP.md +24 -18
  4. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/configs/configs-for-run.rst +1 -1
  5. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/postcure-repair.rst +36 -0
  6. {htpolynet-2.5.0 → htpolynet-2.6.0}/pyproject.toml +1 -1
  7. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/core/runtime.py +9 -0
  8. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/cure/curecontroller.py +47 -0
  9. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/repair/cyanate_cap.py +191 -28
  10. htpolynet-2.6.0/tests/unit/test_cap_placement.py +115 -0
  11. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_completion_bias.py +65 -1
  12. {htpolynet-2.5.0 → htpolynet-2.6.0}/.claude/settings.json +0 -0
  13. {htpolynet-2.5.0 → htpolynet-2.6.0}/.claude/skills/htpolynet/SKILL.md +0 -0
  14. {htpolynet-2.5.0 → htpolynet-2.6.0}/.envrc +0 -0
  15. {htpolynet-2.5.0 → htpolynet-2.6.0}/.github/workflows/conda-forge-sync.yml +0 -0
  16. {htpolynet-2.5.0 → htpolynet-2.6.0}/.github/workflows/docker.yml +0 -0
  17. {htpolynet-2.5.0 → htpolynet-2.6.0}/.github/workflows/release.yaml +0 -0
  18. {htpolynet-2.5.0 → htpolynet-2.6.0}/.github/workflows/test.yml +0 -0
  19. {htpolynet-2.5.0 → htpolynet-2.6.0}/.gitignore +0 -0
  20. {htpolynet-2.5.0 → htpolynet-2.6.0}/.readthedocs.yaml +0 -0
  21. {htpolynet-2.5.0 → htpolynet-2.6.0}/CITATION.cff +0 -0
  22. {htpolynet-2.5.0 → htpolynet-2.6.0}/CLAUDE.md +0 -0
  23. {htpolynet-2.5.0 → htpolynet-2.6.0}/LICENSE +0 -0
  24. {htpolynet-2.5.0 → htpolynet-2.6.0}/MANIFEST.in +0 -0
  25. {htpolynet-2.5.0 → htpolynet-2.6.0}/README.md +0 -0
  26. {htpolynet-2.5.0 → htpolynet-2.6.0}/docker/Dockerfile +0 -0
  27. {htpolynet-2.5.0 → htpolynet-2.6.0}/docker/compose.yml +0 -0
  28. {htpolynet-2.5.0 → htpolynet-2.6.0}/docker/docker-entrypoint.sh +0 -0
  29. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/Makefile +0 -0
  30. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/README.rst +0 -0
  31. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/make.bat +0 -0
  32. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/requirements.txt +0 -0
  33. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/_static/.gitkeep +0 -0
  34. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/changelog.rst +0 -0
  35. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/conf.py +0 -0
  36. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/0-liquid-styrene/configuration.rst +0 -0
  37. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/0-liquid-styrene/index.rst +0 -0
  38. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/0-liquid-styrene/introduction.rst +0 -0
  39. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/0-liquid-styrene/monomer.rst +0 -0
  40. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/0-liquid-styrene/postsim.rst +0 -0
  41. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/0-liquid-styrene/results.rst +0 -0
  42. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/0-liquid-styrene/run.rst +0 -0
  43. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/configuration.rst +0 -0
  44. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/index.rst +0 -0
  45. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/introduction.rst +0 -0
  46. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/monomer.rst +0 -0
  47. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/pics/STY.png +0 -0
  48. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/pics/STYCC.png +0 -0
  49. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/pics/buildtraces.png +0 -0
  50. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/pics/cure_info.png +0 -0
  51. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/pics/densification-density.png +0 -0
  52. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/pics/final-box.png +0 -0
  53. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/pics/reaction_network.png +0 -0
  54. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/pics/sty-coloring.tcl +0 -0
  55. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/pics/sty-cured.png +0 -0
  56. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/pics/sty-detail.png +0 -0
  57. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/pics/sty-liq.png +0 -0
  58. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/pics/styrene-polymerization.png +0 -0
  59. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/postsim.rst +0 -0
  60. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/reactions.rst +0 -0
  61. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/results.rst +0 -0
  62. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/run.rst +0 -0
  63. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/configuration.rst +0 -0
  64. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/index.rst +0 -0
  65. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/introduction.rst +0 -0
  66. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/BPA.png +0 -0
  67. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/GMA.png +0 -0
  68. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/HIE.png +0 -0
  69. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/buildtraces.png +0 -0
  70. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/cure_info.png +0 -0
  71. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/densification-density.png +0 -0
  72. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/four_dimers.eps +0 -0
  73. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/four_dimers.fig +0 -0
  74. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/four_dimers.png +0 -0
  75. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/gma-sty-coloring.tcl +0 -0
  76. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/gma-sty-cured.png +0 -0
  77. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/gma-sty-detail.png +0 -0
  78. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/gma-sty-liq.png +0 -0
  79. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/p1-traces.png +0 -0
  80. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/reaction_network.png +0 -0
  81. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/vesys.eps +0 -0
  82. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/vesys.fig +0 -0
  83. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/vesys.png +0 -0
  84. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/postsim.rst +0 -0
  85. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/reactions.rst +0 -0
  86. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/results.rst +0 -0
  87. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/run.rst +0 -0
  88. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/configuration.rst +0 -0
  89. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/index.rst +0 -0
  90. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/introduction.rst +0 -0
  91. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/monomers.rst +0 -0
  92. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/DGE-epoxy.png +0 -0
  93. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/DGE-labelled.png +0 -0
  94. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/PAC-2d.png +0 -0
  95. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/PAC-labelled.png +0 -0
  96. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/buildtraces.png +0 -0
  97. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/cure_info.png +0 -0
  98. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/densification-density.png +0 -0
  99. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/dge-pac-coloring.tcl +0 -0
  100. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/dge-pac-cured.png +0 -0
  101. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/dge-pac-detail.png +0 -0
  102. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/dge-pac-liq.png +0 -0
  103. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/dgesys.eps +0 -0
  104. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/dgesys.fig +0 -0
  105. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/dgesys.png +0 -0
  106. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/equil-rho_v_ns.png +0 -0
  107. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/postsim-typical.png +0 -0
  108. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/prod-e.png +0 -0
  109. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/prod-equil-rho_v_ns.png +0 -0
  110. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/prod-rho_v_ns.png +0 -0
  111. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/prod-tg.png +0 -0
  112. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/r1.png +0 -0
  113. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/r2.png +0 -0
  114. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/r3.png +0 -0
  115. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/reaction_network.png +0 -0
  116. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/rho_v_ns.png +0 -0
  117. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/short-e.png +0 -0
  118. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/short-tg.png +0 -0
  119. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/postsim.rst +0 -0
  120. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/reactions.rst +0 -0
  121. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/results.rst +0 -0
  122. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/run.rst +0 -0
  123. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/configuration.rst +0 -0
  124. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/index.rst +0 -0
  125. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/introduction.rst +0 -0
  126. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/monomers.rst +0 -0
  127. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/pics/buildtraces.png +0 -0
  128. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/pics/cure_info.png +0 -0
  129. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/pics/densification-density.png +0 -0
  130. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/pics/dfa-fde-coloring.tcl +0 -0
  131. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/pics/dfa-fde-cured.png +0 -0
  132. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/pics/dfa-fde-detail.png +0 -0
  133. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/pics/dfa-fde-liq.png +0 -0
  134. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/pics/reaction_network.png +0 -0
  135. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/postsim.rst +0 -0
  136. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/reactions.rst +0 -0
  137. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/results.rst +0 -0
  138. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/run.rst +0 -0
  139. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/configuration.rst +0 -0
  140. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/index.rst +0 -0
  141. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/introduction.rst +0 -0
  142. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/monomers.rst +0 -0
  143. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/pics/buildtraces.png +0 -0
  144. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/pics/cure_info.png +0 -0
  145. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/pics/densification-density.png +0 -0
  146. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/pics/htpb-coloring.tcl +0 -0
  147. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/pics/htpb-ipdi-cured.png +0 -0
  148. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/pics/htpb-ipdi-detail.png +0 -0
  149. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/pics/htpb-ipdi-liq.png +0 -0
  150. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/pics/reaction_network.png +0 -0
  151. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/postsim.rst +0 -0
  152. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/reactions.rst +0 -0
  153. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/results.rst +0 -0
  154. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/run.rst +0 -0
  155. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/configuration.rst +0 -0
  156. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/index.rst +0 -0
  157. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/introduction.rst +0 -0
  158. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/monomers.rst +0 -0
  159. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/pics/badcy-coloring.tcl +0 -0
  160. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/pics/badcy-cured.png +0 -0
  161. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/pics/badcy-detail.png +0 -0
  162. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/pics/badcy-liq.png +0 -0
  163. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/pics/buildtraces.png +0 -0
  164. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/pics/cure_info.png +0 -0
  165. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/pics/densification-density.png +0 -0
  166. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/pics/reaction_network.png +0 -0
  167. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/postsim.rst +0 -0
  168. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/reactions.rst +0 -0
  169. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/results.rst +0 -0
  170. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/run.rst +0 -0
  171. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/index.rst +0 -0
  172. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/htpolynetpackage.rst +0 -0
  173. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/index.rst +0 -0
  174. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/install.rst +0 -0
  175. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/references/index.rst +0 -0
  176. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/references/mol2-reference.pdf +0 -0
  177. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/references.bib +0 -0
  178. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/release-history.rst +0 -0
  179. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/CURE.odg +0 -0
  180. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/bond_filter.odg +0 -0
  181. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/building-a-system.rst +0 -0
  182. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/configs/configs-for-analyze.rst +0 -0
  183. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/configs/configs-for-postsim.rst +0 -0
  184. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/configuration-files.rst +0 -0
  185. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/container-usage.rst +0 -0
  186. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/flow1.odg +0 -0
  187. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/flow1yaml.odg +0 -0
  188. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/hydrogenated.eps +0 -0
  189. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/hydrogenated.fig +0 -0
  190. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/index.rst +0 -0
  191. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/molecular-structure-inputs.rst +0 -0
  192. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/pics/STY.png +0 -0
  193. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/pics/STYCC.png +0 -0
  194. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/pics/TypicalUsageFlow.png +0 -0
  195. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/pics/bond_filter.png +0 -0
  196. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/pics/chemdoodle-2dsketcher-emb.png +0 -0
  197. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/pics/chemdoodle-2dsketcher-styrene.png +0 -0
  198. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/pics/hydrogenated.png +0 -0
  199. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/pics/ring_pierce/ring_pierce_cases.png +0 -0
  200. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/pics/ring_pierce/ring_pierce_linkcell.png +0 -0
  201. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/program-flow.rst +0 -0
  202. {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/usage.rst +0 -0
  203. {htpolynet-2.5.0 → htpolynet-2.6.0}/scripts/check-conda-sync.py +0 -0
  204. {htpolynet-2.5.0 → htpolynet-2.6.0}/scripts/release.sh +0 -0
  205. {htpolynet-2.5.0 → htpolynet-2.6.0}/scripts/render-detail.sh +0 -0
  206. {htpolynet-2.5.0 → htpolynet-2.6.0}/scripts/render-detail.tcl +0 -0
  207. {htpolynet-2.5.0 → htpolynet-2.6.0}/scripts/render-snapshot.sh +0 -0
  208. {htpolynet-2.5.0 → htpolynet-2.6.0}/scripts/run_all_examples.sh +0 -0
  209. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/__init__.py +0 -0
  210. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/analysis/__init__.py +0 -0
  211. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/analysis/analyze.py +0 -0
  212. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/analysis/plot.py +0 -0
  213. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/analysis/postsim.py +0 -0
  214. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/analysis/utils.py +0 -0
  215. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/cli.py +0 -0
  216. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/core/__init__.py +0 -0
  217. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/core/bondtemplate.py +0 -0
  218. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/core/configuration.py +0 -0
  219. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/core/coordinates.py +0 -0
  220. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/core/molecule.py +0 -0
  221. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/core/paramcache.py +0 -0
  222. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/core/projectfilesystem.py +0 -0
  223. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/core/topocoord.py +0 -0
  224. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/core/topology.py +0 -0
  225. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/cure/__init__.py +0 -0
  226. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/cure/chain.py +0 -0
  227. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/cure/expandreactions.py +0 -0
  228. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/cure/reaction.py +0 -0
  229. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/external/__init__.py +0 -0
  230. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/external/ambertools.py +0 -0
  231. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/external/command.py +0 -0
  232. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/external/gromacs.py +0 -0
  233. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/external/slurm.py +0 -0
  234. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/external/smiles_input.py +0 -0
  235. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/external/software.py +0 -0
  236. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/geometry/__init__.py +0 -0
  237. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/geometry/bondlist.py +0 -0
  238. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/geometry/lattice.py +0 -0
  239. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/geometry/linkcell.py +0 -0
  240. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/geometry/matrix4.py +0 -0
  241. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/geometry/ring.py +0 -0
  242. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/io/__init__.py +0 -0
  243. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/io/gro.py +0 -0
  244. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/io/mol2.py +0 -0
  245. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/io/pdb.py +0 -0
  246. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/repair/__init__.py +0 -0
  247. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/repair/topology_surgery.py +0 -0
  248. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/README.md +0 -0
  249. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/__init__.py +0 -0
  250. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/claude/SKILL.md +0 -0
  251. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/example_depot/0-liquid-styrene.yaml +0 -0
  252. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/example_depot/1-polystyrene.yaml +0 -0
  253. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/example_depot/2-bisgma-styrene-thermoset.yaml +0 -0
  254. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/example_depot/3-pacm-dgeba-epoxy-thermoset.yaml +0 -0
  255. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/example_depot/4-dfda-fde-epoxy-thermoset.yaml +0 -0
  256. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/example_depot/5-htpb-ipdi.yaml +0 -0
  257. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/example_depot/6-cyanate-ester.yaml +0 -0
  258. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/mdp/README.md +0 -0
  259. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/mdp/drag-min.mdp +0 -0
  260. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/mdp/drag-npt.mdp +0 -0
  261. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/mdp/drag-nvt.mdp +0 -0
  262. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/mdp/min.mdp +0 -0
  263. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/mdp/npt.mdp +0 -0
  264. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/mdp/nvt.mdp +0 -0
  265. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/mdp/relax-min.mdp +0 -0
  266. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/mdp/relax-npt.mdp +0 -0
  267. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/mdp/relax-nvt.mdp +0 -0
  268. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/mdp/single-molecule-min.mdp +0 -0
  269. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/mdp/single-molecule-nvt.mdp +0 -0
  270. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/inputs/DFA.pdb +0 -0
  271. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/inputs/DGE.mol2 +0 -0
  272. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/inputs/EMB.mol2 +0 -0
  273. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/inputs/FDE.pdb +0 -0
  274. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/inputs/GMA.mol2 +0 -0
  275. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/inputs/PAC.mol2 +0 -0
  276. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/inputs/STY.mol2 +0 -0
  277. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/make-monomers.sh +0 -0
  278. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/pics/DFA.png +0 -0
  279. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/pics/DGE.png +0 -0
  280. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/pics/EMB.png +0 -0
  281. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/pics/FDE.png +0 -0
  282. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/pics/GMA.png +0 -0
  283. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/pics/PAC.png +0 -0
  284. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/pics/STY.png +0 -0
  285. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/sample-inputs/DFA.pdb +0 -0
  286. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/sample-inputs/DGE.mol2 +0 -0
  287. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/sample-inputs/EMB.mol2 +0 -0
  288. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/sample-inputs/FDE.pdb +0 -0
  289. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/sample-inputs/GMA.mol2 +0 -0
  290. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/sample-inputs/PAC.mol2 +0 -0
  291. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/sample-inputs/STY.mol2 +0 -0
  292. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/tcl/readbonds.tcl +0 -0
  293. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/tcl/readgrx.tcl +0 -0
  294. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/tcl/render.tcl +0 -0
  295. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/utils/__init__.py +0 -0
  296. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/utils/banner.py +0 -0
  297. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/utils/checkpoint.py +0 -0
  298. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/utils/dataframetools.py +0 -0
  299. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/utils/inputcheck.py +0 -0
  300. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/utils/logsetup.py +0 -0
  301. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/utils/profiling.py +0 -0
  302. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/utils/stringthings.py +0 -0
  303. {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/utils/vmd_viz.py +0 -0
  304. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/__init__.py +0 -0
  305. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/conftest.py +0 -0
  306. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/__init__.py +0 -0
  307. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/fixtures/config1.gro +0 -0
  308. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/fixtures/config1.top +0 -0
  309. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/fixtures/config2.gro +0 -0
  310. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/fixtures/config2.top +0 -0
  311. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/fixtures/items31.edr +0 -0
  312. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/fixtures/items43.edr +0 -0
  313. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/fixtures/items45.edr +0 -0
  314. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/fixtures/short.mdp +0 -0
  315. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_bondtemplate.py +0 -0
  316. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_chain.py +0 -0
  317. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_configuration.py +0 -0
  318. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_dataframetools.py +0 -0
  319. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_gpu_usability.py +0 -0
  320. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_gromacs_get_energy_menu.py +0 -0
  321. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_gromacs_gmx_energy_trace.py +0 -0
  322. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_inputcheck.py +0 -0
  323. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_linkcell_pierce.py +0 -0
  324. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_paramcache.py +0 -0
  325. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_paramcache_ambertools.py +0 -0
  326. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_parameterize_react.py +0 -0
  327. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_plot_smoke.py +0 -0
  328. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_projectfilesystem.py +0 -0
  329. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_repair_conversion.py +0 -0
  330. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_resources.py +0 -0
  331. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_ring.py +0 -0
  332. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_ring_pierce_figs.py +0 -0
  333. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_setup_claude.py +0 -0
  334. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_slurm_script.py +0 -0
  335. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_smiles_input.py +0 -0
  336. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_software_provenance.py +0 -0
  337. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_topology/test.top +0 -0
  338. {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_topology.py +0 -0
@@ -7,6 +7,58 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.6.0] - 2026-08-26
11
+
12
+ ### Fixed
13
+
14
+ - **Transferred `-C#N` caps are no longer placed blind, which is what has been
15
+ killing low-conversion builds.** The repair stage relocates a cap by
16
+ putting it along the bridge oxygen's old O-H vector, with no regard for
17
+ what is already there. In a box at polymer density that direction is
18
+ usually occupied: in a synthetic 13000-atom box at 93 atoms/nm³, blind
19
+ placement put 91 of 158 caps within 0.10 nm of a neighbour and the worst at
20
+ 0.008 nm -- effectively superimposed, which is the step-0 Lennard-Jones
21
+ term of order 1e15 that no minimization recovers from.
22
+
23
+ The cap is now still tried along the O-H vector first, so a cap in open
24
+ space lands exactly where it always did; only when that direction is
25
+ occupied does the driver search the sphere for the clearest one, holding
26
+ both bond lengths fixed so only the orientation moves. Caps already placed
27
+ are part of the neighbourhood the next one sees. On the same synthetic box
28
+ no cap lands closer than 0.128 nm, at either 158 or 378 transfers.
29
+
30
+ This matters more the lower the conversion, because the number of caps to
31
+ place is an identity -- `total reactive sites - bonds formed` -- so it rises
32
+ as conversion falls. `completion_bias` does not reduce it.
33
+
34
+ - **The stage now says how many fragments it transferred, and complains about
35
+ the ones it could not place well.** The transfer count is the quantity that
36
+ predicts whether repair survives and it was buried in one mid-log INFO line;
37
+ it is now reported next to the conversion, along with the tightest placement
38
+ achieved. A cap that could not reach `cap_min_clearance` (new, default
39
+ 0.15 nm) in any direction is named, with its oxygen, while that is still
40
+ cheap to act on -- the alternative was working back to cap placement from a
41
+ GROMACS internal error at step 0.
42
+
43
+ ### Added
44
+
45
+ - **`completion_bias` now says so when it is ranking the wrong side of the
46
+ reaction.** The bias ranks on the `B` reactant, because htpolynet's A2+B3
47
+ idiom puts the crosslinker there. Declare the crosslinker as `A` instead
48
+ and the bias quietly starts completing the *bridges* -- a different claim
49
+ about which partly-reacted species is a reactive intermediate, and almost
50
+ certainly not the one that was wanted. The first cure iteration now
51
+ compares the initial functionality of the two sides and warns if the `A`
52
+ side is the more functional one.
53
+
54
+ Worth recording why the obvious detector does not work: looking for an
55
+ all-zero bias key catches nothing, because a difunctional bridge
56
+ accumulates reactions perfectly well and the key is non-zero. What
57
+ separates the two cases is which side carries more reactive sites, and that
58
+ is readable straight off the atom dataframe -- forming a bond decrements an
59
+ atom's `z` and increments its `nreactions` in the same operation, so their
60
+ sum is conserved and reads the same at iteration 0 as at the end.
61
+
10
62
  ## [2.5.0] - 2026-08-26
11
63
 
12
64
  ### Added
@@ -48,12 +100,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
48
100
  either side of the setting must be compared at matched crosslinker
49
101
  conversion, not matched `desired_conversion`.
50
102
 
51
- It also nearly empties the repair stage, which is worth having on its own
52
- terms: repair has been seen to blow up at low conversion, where it places
53
- hundreds of caps and transfers hundreds of fragments and the cap-placement
54
- geometry produces atom overlaps that a step-0 minimization cannot recover
55
- from. Completing crosslinkers instead of decorating them leaves it almost
56
- nothing to do.
103
+ It also reduces the repair stage's dismantling work: at a bond conversion
104
+ of 0.90 the driver dismantles roughly 24 crosslinkers instead of 58, and
105
+ places 72 caps instead of 174.
106
+
107
+ **Corrected 2026-08-26, after this section was published:** the original
108
+ text here claimed the bias also relieves the cap-placement blowup that has
109
+ killed builds at low conversion. It does not. Only *transferred* caps are
110
+ placed geometrically -- `_place_cyn_along` runs solely where
111
+ `bonded_o is None` -- and the number of those is an identity,
112
+ `total_sites - bonds`, with no term for how the bonds are distributed. So
113
+ the bias cannot change it at a matched bond conversion, and at a matched
114
+ crosslinker conversion it makes it *worse*, because it reaches that
115
+ conversion by forming fewer bonds and every bond not formed is a fragment
116
+ that must be transferred. The blowup is a separate defect in
117
+ `repair/cyanate_cap.py`; see `ROADMAP.md`.
57
118
 
58
119
  - **A CUDA image, published as `ghcr.io/cameronabrams/htpolynet:cuda`.** The
59
120
  only image until now installed conda-forge's default linux-64 Gromacs,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: htpolynet
3
- Version: 2.5.0
3
+ Version: 2.6.0
4
4
  Summary: Automated MD System Builder for Amorphous Network Polymers
5
5
  Project-URL: Source, https://github.com/cameronabrams/htpolynet
6
6
  Project-URL: Documentation, https://htpolynet.readthedocs.io/
@@ -171,12 +171,16 @@ Coverage as of the last measurement: **38.8%** overall.
171
171
  by how many bonds their `B`-side residue already carries, because
172
172
  htpolynet's A2+B3 idiom puts the multifunctional crosslinker in the `B`
173
173
  position -- as example 6 does. A user who declares the crosslinker as `A`
174
- gets no bias at all, silently, because every candidate scores zero on a
175
- difunctional bridge and the ordering collapses back to distance. The
176
- generalization is small (rank on the `A` side, or on the sum of both) but
177
- each variant is a different physical claim about which partly-reacted
178
- species is an intermediate, and none of them has been run. Do it when
179
- someone has a chemistry that needs it, and make them say which side.
174
+ gets the bias pointed at their *bridges* instead: not a no-op, a different
175
+ claim about which partly-reacted species is a reactive intermediate. That
176
+ case is now warned about at the first cure iteration, by comparing the
177
+ initial functionality of the two sides, so it is visible rather than silent
178
+ -- but it is still not *served*. The generalization is small (rank on the
179
+ `A` side, or on the sum of both) and each variant is a different physical
180
+ claim, none of them run. Do it when someone has a chemistry that needs it,
181
+ and make them say which side rather than inferring it from functionality:
182
+ the warning's heuristic is good enough to flag a probable mistake and not
183
+ good enough to silently redirect the ranking on.
180
184
 
181
185
  - **Nobody has run example 6 with `completion_bias` on.** The ranking is
182
186
  unit-tested -- ordering, tie-breaks, missing residues, the fallback when a
@@ -190,18 +194,20 @@ Coverage as of the last measurement: **38.8%** overall.
190
194
  truncation and that is where to look. Until someone runs it, the directive
191
195
  is documented as untested at scale.
192
196
 
193
- - **The repair stage's cap placement does not scale to low conversion.**
194
- At a bond conversion of 0.90 the triazine-to-cyanate driver places on the
195
- order of a hundred caps; at 0.30 it places hundreds and transfers hundreds
196
- of fragments, and runs have died at the repair NVT with a step-0 potential
197
- energy around 7e15 dominated by Lennard-Jones -- atom overlap from cap
198
- placement. It is packing-dependent rather than systematic: siblings at the
199
- same conversion survive. `completion_bias` makes the stage nearly empty
200
- and so hides this, but the geometry is still wrong for a crowded box, and
201
- a sub-gel-point study that wants the unbiased ranking will hit it again.
202
- The fix is in `repair/cyanate_cap.py::_place_cyn_along` and the greedy
203
- matcher around it: place against the local neighbourhood rather than along
204
- the old O-H vector alone, or minimize incrementally as caps are placed.
197
+ - **Cap placement is greedy and never backtracks.** Transferred `-C#N`
198
+ fragments are now placed against the local neighbourhood rather than blindly
199
+ along the old O-H vector, which removes the catastrophic overlaps: in a
200
+ synthetic box at polymer density, blind placement put 91 of 158 caps within
201
+ 0.10 nm of a neighbour and the worst at 0.008 nm, while the neighbourhood
202
+ search puts none below 0.128 nm. What it does not do is relax. Each cap is
203
+ placed once, in the clearest direction available *at that moment*, and later
204
+ caps must work around it; in a genuinely over-packed box a residue of
205
+ sub-target placements remains (17 of 158, 33 of 378 in the same synthetic
206
+ test) and is reported rather than fixed. The next lever is minimizing
207
+ incrementally as caps land, or placing in order of how constrained each site
208
+ is rather than in match order. Do it if the reported clearance warnings turn
209
+ out to correlate with builds that still die -- the instrumentation to decide
210
+ that now exists, and did not before.
205
211
 
206
212
  - **`bdf.loc[:abs_max]` takes one bond more than the limit.** In
207
213
  `curecontroller.py::_searchbonds`, the truncation that applies
@@ -228,7 +228,7 @@ In this section we show all subdirectives for each of the five main directives i
228
228
 
229
229
  The ``min_bonds_per_iteration`` knob batches small late-stage finds together: instead of accepting whichever 1-2 bonds happen to lie within the initial 0.5 nm radius and immediately moving on to relax + equilibrate, the search grows the radius until at least 10 (the default) eligible bonds have been gathered. On the DGEBA/PACM example, raising it from 1 to 10 cuts the cure iteration count from 41 to 15; raising further to 20 only buys one more iteration. The default of 10 is roughly the diminishing-returns sweet spot.
230
230
 
231
- ``completion_bias`` changes which bonds a CURE iteration chooses, and with it what ``desired_conversion`` means physically, so it is off by default and every shipped example runs without it. Candidates are ranked by separation alone by default. Separation is uncorrelated with how many bonds a crosslinker already carries, so in an A2+B3 system the bonds spread evenly across every ``B`` in the box rather than finishing any of them: at a bond conversion of 0.90, a trifunctional crosslinker placed at random is complete with probability 0.90\ :sup:`3`, about 0.73. Turning ``completion_bias`` on makes the number of bonds already on the candidate's ``B``-side residue the primary sort key, with separation as the tie-break inside each group, so a crosslinker with two of three sites filled is finished before an untouched one is started. Nothing else about the search changes -- same radius growth, same dragging and relaxation, same probabilities, same cycle handling.
231
+ ``completion_bias`` changes which bonds a CURE iteration chooses, and with it what ``desired_conversion`` means physically, so it is off by default and every shipped example runs without it. Candidates are ranked by separation alone by default. Separation is uncorrelated with how many bonds a crosslinker already carries, so in an A2+B3 system the bonds spread evenly across every ``B`` in the box rather than finishing any of them: at a bond conversion of 0.90, a trifunctional crosslinker placed at random is complete with probability 0.90\ :sup:`3`, about 0.73. Turning ``completion_bias`` on makes the number of bonds already on the candidate's ``B``-side residue the primary sort key, with separation as the tie-break inside each group, so a crosslinker with two of three sites filled is finished before an untouched one is started. The ``B`` side is where htpolynet's A2+B3 idiom puts the crosslinker; declare it as ``A`` instead and the bias completes your *bridges*, so the first cure iteration compares the two sides' functionality and warns if the more functional one is on ``A``. Nothing else about the search changes -- same radius growth, same dragging and relaxation, same probabilities, same cycle handling.
232
232
 
233
233
  Which conversion this matters for depends on the chemistry. Where a partly-reacted crosslinker is a real, stable species, the unbiased ranking is the more faithful one. Where it is a reactive intermediate -- a cyclotrimerizing cyanate ester, whose triazine ring either closes or does not -- the biased ranking is, and the difference shows up directly in what :ref:`postcure repair <postcure_repair>` has to dismantle. Note that the two settings reach different structures at the same ``desired_conversion``: a config that yields a crosslinker conversion of 0.76 at ``desired_conversion: 0.90`` unbiased will yield about 0.90 biased. Comparisons across the setting have to be made at matched crosslinker conversion, not matched ``desired_conversion``.
234
234
 
@@ -153,6 +153,7 @@ Spec fields
153
153
  cap_residue: CYN
154
154
  cap_template: BPA~O1-C1~CYN
155
155
  cap_search_radius: 0.6 # nm
156
+ cap_min_clearance: 0.15 # nm
156
157
 
157
158
  * ``crosslinker`` — names the trifunctional residue to be inspected.
158
159
  ``ring_carbon_atoms`` / ``ring_nitrogen_atoms`` give the
@@ -169,6 +170,17 @@ Spec fields
169
170
  types / charges / bonded interactions get spliced in for each
170
171
  bridge-O-to-cap-C bond. ``cap_template`` is the ``product:`` field
171
172
  of a ``stage: repair`` reaction declared elsewhere in the YAML.
173
+ * ``cap_min_clearance`` — how much room (nm) a transferred cap tries
174
+ to leave itself. The cap is first placed along the bridge-O's old
175
+ O-H vector, as it always was; if that direction is already occupied
176
+ the driver searches the sphere for the clearest direction, holding
177
+ both bond lengths fixed so only the orientation moves, and stops as
178
+ soon as it reaches this clearance. Default 0.15 nm, which is about
179
+ what a steepest-descent minimization absorbs and which is actually
180
+ achievable in a box at polymer density — 0.22 nm is not, and asking
181
+ for it would flag every cap. When no direction reaches the target,
182
+ the driver says so and names the worst offenders; see
183
+ :ref:`what repair reports <postcure_repair_reporting>`.
172
184
  * ``cap_search_radius`` — radius (nm) within which a free-cap
173
185
  fragment looks for an unreacted bridge-O. Expanded up to 10× on
174
186
  miss; falls back to globally nearest as a last resort. Atom
@@ -322,6 +334,30 @@ directory, for reading across a series of runs:
322
334
  n_dismantled: 64
323
335
  crosslinker_conversion: 0.7333333333333333
324
336
 
337
+ The stage also reports how many **cap fragments it transferred**, and
338
+ how tightly they landed::
339
+
340
+ Repair transferred 158 cap fragments; tightest placement 0.128 nm
341
+
342
+ That count is worth watching, because it is the quantity that predicts
343
+ whether this stage survives. It is an identity — every bond the cure
344
+ did not form leaves a bridge site unreacted, and every unreacted
345
+ bridge site receives one transferred fragment::
346
+
347
+ transferred fragments = total reactive sites - bonds formed
348
+
349
+ so it *rises as conversion falls*, and a low-conversion build can be
350
+ placing several hundred. Each one is a two-atom group being dropped
351
+ into an already-dense box. If the following minimization dies with a
352
+ Lennard-Jones term of order 1e15, this is where to look, and the
353
+ warning about caps that could not reach ``cap_min_clearance`` names
354
+ the specific oxygens involved.
355
+
356
+ Note that this count depends only on the bond conversion, not on how
357
+ the bonds are distributed, so ``completion_bias`` does not reduce it.
358
+ At a *matched crosslinker conversion* the bias in fact increases it,
359
+ because it reaches that conversion by forming fewer bonds.
360
+
325
361
  To *raise* the crosslinker conversion rather than merely report it,
326
362
  see the ``CURE.controls.completion_bias`` directive in
327
363
  :ref:`the run-configuration reference <cure.controls>`: it makes the
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "htpolynet"
7
- version = "2.5.0"
7
+ version = "2.6.0"
8
8
  authors = [
9
9
  { name="Cameron F Abrams", email="cfa22@drexel.edu" },
10
10
  ]
@@ -523,6 +523,15 @@ class Runtime:
523
523
  if bond_conversion is not None:
524
524
  msg += f'; the cure reported a bond conversion of {bond_conversion:.3f}'
525
525
  my_logger(msg, logger.info)
526
+ # the transfer count is what predicts whether this stage survives,
527
+ # so it belongs next to the conversion rather than mid-log
528
+ if st.get('n_transferred'):
529
+ tmsg = f'Repair transferred {st["n_transferred"]} cap fragments'
530
+ if st.get('min_clearance_nm') is not None:
531
+ tmsg += f'; tightest placement {st["min_clearance_nm"]:.3f} nm'
532
+ if st.get('n_below_target'):
533
+ tmsg += f'; {st["n_below_target"]} below the clearance target'
534
+ my_logger(tmsg, logger.info)
526
535
  try:
527
536
  with open('repair-summary.yaml', 'w') as f:
528
537
  yaml.dump(record, f, default_flow_style=False)
@@ -38,6 +38,22 @@ def residue_reaction_counts(adf):
38
38
  """
39
39
  return adf.groupby('resNum')['nreactions'].sum()
40
40
 
41
+ def residue_functionality(adf):
42
+ """Counts, per residue, how many reactive sites it started with.
43
+
44
+ Forming a bond decrements an atom's 'z' and increments its 'nreactions' in
45
+ the same operation, so their sum is conserved per atom for the whole cure.
46
+ Summed over a residue it is that residue's initial functionality, and it
47
+ reads the same at iteration 0 as at the end.
48
+
49
+ Args:
50
+ adf (pandas.DataFrame): the global atom dataframe; must carry the 'resNum', 'z' and 'nreactions' attributes
51
+
52
+ Returns:
53
+ pandas.Series: initial reactive sites, indexed by residue number
54
+ """
55
+ return adf.groupby('resNum')[['z','nreactions']].sum().sum(axis=1)
56
+
41
57
  def rank_bond_candidates(bdf,reaction_counts=None):
42
58
  """Orders bond candidates ahead of the greedy one-bond-per-residue downselection.
43
59
 
@@ -212,6 +228,9 @@ class CureController:
212
228
  self.dicts[k][kk]=vv
213
229
 
214
230
  self.chain_manager=None
231
+ # once-per-run: has the completion bias been checked against which side
232
+ # of the reaction actually carries the more functional residue?
233
+ self.bias_side_checked=False
215
234
  self.dragging_enabled=False
216
235
  d=self.dicts['drag']
217
236
  if (d['nstages']>0 or d['increment']>0.0) and d['limit']>0.0:
@@ -636,6 +655,33 @@ class CureController:
636
655
  return None
637
656
  return residue_reaction_counts(adf)
638
657
 
658
+ def _check_bias_side(self,adf,bdf):
659
+ """Warns, once per run, if the completion bias is ranking the less functional side.
660
+
661
+ The bias ranks on the ``B`` reactant because htpolynet's A2+B3 idiom
662
+ puts the crosslinker there. Declare the crosslinker as ``A`` instead
663
+ and the bias silently starts completing the *bridges*, which is a
664
+ different claim about which species is a reactive intermediate and
665
+ almost certainly not the one that was wanted. It cannot be caught by
666
+ looking for an all-zero bias key -- a difunctional bridge accumulates
667
+ reactions perfectly well, so the key is non-zero and the ranking looks
668
+ healthy. What distinguishes the two cases is which side is the more
669
+ functional one, and that is fixed at setup.
670
+
671
+ Args:
672
+ adf (pandas.DataFrame): the global atom dataframe
673
+ bdf (pandas.DataFrame): the bond candidates for this iteration
674
+ """
675
+ if self.bias_side_checked: return
676
+ if not all(c in adf.columns for c in ('z','nreactions')): return
677
+ self.bias_side_checked=True
678
+ func=residue_functionality(adf)
679
+ a=bdf['ri'].map(func).dropna()
680
+ b=bdf['rj'].map(func).dropna()
681
+ if a.empty or b.empty: return
682
+ if a.max()>b.max():
683
+ logger.warning(f'completion_bias ranks candidates on the B-side residue, but the A side is the more functional one here ({int(a.max())} reactive sites vs {int(b.max())}). The bias will complete B-side residues, which is probably not what was intended; declare the crosslinker as the second reactant to bias toward it.')
684
+
639
685
  # TODO: move to searchbonds.by; split into make_candidates() and apply_filters()
640
686
  def _searchbonds(self,TC:TopoCoord,RL:ReactionList,MD:MoleculeDict,stage=reaction_stage.cure,abs_max=0,apply_probabilities=False,reentry=False):
641
687
  """Manages the search for bonds.
@@ -749,6 +795,7 @@ class CureController:
749
795
  reaction_counts=self._completion_bias_counts(adf)
750
796
  bdf=rank_bond_candidates(bdf,reaction_counts)
751
797
  if reaction_counts is not None:
798
+ self._check_bias_side(adf,bdf)
752
799
  nrx=bdf['rj'].map(reaction_counts).fillna(0).astype(int)
753
800
  logger.debug(f'Completion bias: {int((nrx>0).sum())} of {bdf.shape[0]} candidates extend an already-reacted B-side residue (most bonds already carried: {int(nrx.max())})')
754
801
  bdf['allowed']=[True for x in range(bdf.shape[0])]
@@ -106,6 +106,10 @@ def _place_cyn_along(bpa_o_xyz, bpa_o_h_xyz, oc_len=0.136, cn_len=0.116):
106
106
  direction from BPA-O toward the (about-to-be-deleted) H, so the new
107
107
  cap sticks out along the original O-H bond.
108
108
 
109
+ This is the unconditioned placement: it is where the cap goes if that
110
+ direction happens to be empty, and it is blind to whether it is. See
111
+ :func:`_choose_cap_placement`, which uses it as its first candidate.
112
+
109
113
  Lengths in nm: standard -O-C ester ~1.36 A, -C#N ~1.16 A.
110
114
  Returns (C_xyz, N_xyz).
111
115
  """
@@ -122,12 +126,140 @@ def _place_cyn_along(bpa_o_xyz, bpa_o_h_xyz, oc_len=0.136, cn_len=0.116):
122
126
  return c_xyz, n_xyz
123
127
 
124
128
 
129
+ _PLACEMENT_LOCALITY = 0.8
130
+ """nm: an atom farther than this from the target oxygen cannot constrain a cap
131
+ that reaches only ~0.25 nm from it, so it is not scored against."""
132
+
133
+
134
+ def _report_placements(placements, target):
135
+ """Say how well the transferred caps landed, and complain about the tight ones.
136
+
137
+ The transfer count is the quantity that predicts whether this stage will
138
+ survive, so it is stated plainly rather than left to be inferred, and a
139
+ placement that could not reach the target clearance is named while it is
140
+ still cheap to act on -- the alternative is reading it back out of a
141
+ Lennard-Jones term at step 0.
142
+
143
+ Args:
144
+ placements (list): one dict per transferred cap, with 'o' and 'clearance'
145
+ target (float): the clearance in nm that was being aimed for
146
+
147
+ Returns:
148
+ dict: n_transferred, n_direction_searched, n_below_target, min_clearance_nm
149
+ """
150
+ if not placements:
151
+ return {'n_transferred': 0, 'n_direction_searched': 0,
152
+ 'n_below_target': 0, 'min_clearance_nm': None}
153
+ gaps = np.array([p['clearance'] for p in placements], dtype=float)
154
+ searched = sum(1 for p in placements if not p['kept_o_h_direction'])
155
+ below = [p for p in placements if p['clearance'] < target]
156
+ logger.info(
157
+ f'triazine_to_cyanate_cap: placed {len(placements)} transferred caps '
158
+ f'({searched} needed a direction search); clearance min/median '
159
+ f'{gaps.min():.3f}/{float(np.median(gaps)):.3f} nm against a target of {target:.3f}'
160
+ )
161
+ if below:
162
+ worst = sorted(below, key=lambda p: p['clearance'])[:5]
163
+ detail = ', '.join(f'O {int(p["o"])} at {p["clearance"]:.3f} nm' for p in worst)
164
+ logger.warning(
165
+ f'triazine_to_cyanate_cap: {len(below)} of {len(placements)} transferred '
166
+ f'caps could not reach {target:.3f} nm of clearance in any direction '
167
+ f'(worst: {detail}). The box is too crowded here for this many caps; '
168
+ f'if the following minimization fails with a huge Lennard-Jones term, '
169
+ f'this is why.'
170
+ )
171
+ return {
172
+ 'n_transferred': len(placements),
173
+ 'n_direction_searched': searched,
174
+ 'n_below_target': len(below),
175
+ 'min_clearance_nm': float(gaps.min()),
176
+ }
177
+
178
+
179
+ def _sphere_directions(n=48):
180
+ """``n`` roughly uniform unit vectors on the sphere, deterministically.
181
+
182
+ A Fibonacci spiral: no randomness, so a rebuild of the same system places
183
+ its caps identically.
184
+
185
+ Args:
186
+ n (int): how many directions to generate
187
+
188
+ Returns:
189
+ numpy.ndarray: (n, 3) array of unit vectors
190
+ """
191
+ k = np.arange(n) + 0.5
192
+ phi = np.arccos(1.0 - 2.0 * k / n)
193
+ theta = np.pi * (1.0 + 5.0 ** 0.5) * k
194
+ return np.stack([np.cos(theta) * np.sin(phi),
195
+ np.sin(theta) * np.sin(phi),
196
+ np.cos(phi)], axis=1)
197
+
198
+
199
+ def _clearance(c_xyz, n_xyz, neighbors, box):
200
+ """Smallest distance from either cap atom to any neighbouring atom.
201
+
202
+ Args:
203
+ c_xyz (numpy.ndarray): position of the cap carbon
204
+ n_xyz (numpy.ndarray): position of the cap nitrogen
205
+ neighbors (numpy.ndarray): (m, 3) positions to stay clear of
206
+ box (numpy.ndarray): orthorhombic box lengths, for minimum image
207
+
208
+ Returns:
209
+ float: the smallest such distance in nm, or inf if there are no neighbours
210
+ """
211
+ if neighbors is None or len(neighbors) == 0:
212
+ return float('inf')
213
+ return float(min(_min_image_dist(c_xyz, neighbors, box).min(),
214
+ _min_image_dist(n_xyz, neighbors, box).min()))
215
+
216
+
217
+ def _choose_cap_placement(o_xyz, ref_xyz, neighbors, box, oc_len=0.136,
218
+ cn_len=0.116, target=0.15, n_directions=48):
219
+ """Place a transferred -C#N group in the clearest direction available.
220
+
221
+ The old O-H vector is tried first and kept if it is clear enough, so a
222
+ cap in open space lands exactly where it always did. Only when that
223
+ direction is occupied does this search the sphere -- which is the case
224
+ that used to produce a step-0 Lennard-Jones term around 1e15 and a
225
+ minimization that could not recover. Bond lengths are held fixed; only
226
+ the direction moves, so the chemistry is unchanged.
227
+
228
+ Args:
229
+ o_xyz (numpy.ndarray): position of the bridge oxygen the cap attaches to
230
+ ref_xyz (numpy.ndarray): position defining the preferred direction, normally the O's about-to-be-deleted H
231
+ neighbors (numpy.ndarray): (m, 3) positions the cap must avoid
232
+ box (numpy.ndarray): orthorhombic box lengths, for minimum image
233
+ oc_len (float): O-C bond length in nm
234
+ cn_len (float): C#N bond length in nm
235
+ target (float): clearance in nm that counts as good enough to stop searching; the default 0.15 is about what a steepest-descent minimization absorbs without trouble, and is achievable in a box at polymer density -- 0.22 is not, and asking for it would flag every cap
236
+ n_directions (int): how many directions to try when the preferred one is occupied
237
+
238
+ Returns:
239
+ tuple: (C position, N position, achieved clearance in nm, whether the preferred direction was kept)
240
+ """
241
+ o = np.asarray(o_xyz, dtype=float)
242
+ c_xyz, n_xyz = _place_cyn_along(o, ref_xyz, oc_len=oc_len, cn_len=cn_len)
243
+ best = _clearance(c_xyz, n_xyz, neighbors, box)
244
+ if best >= target:
245
+ return c_xyz, n_xyz, best, True
246
+ for u in _sphere_directions(n_directions):
247
+ cc = o + u * oc_len
248
+ nn = cc + u * cn_len
249
+ gap = _clearance(cc, nn, neighbors, box)
250
+ if gap > best:
251
+ best, c_xyz, n_xyz = gap, cc, nn
252
+ if best >= target:
253
+ break
254
+ return c_xyz, n_xyz, best, False
255
+
256
+
125
257
  # ---------------------------------------------------------------------------
126
258
  # Main driver
127
259
  # ---------------------------------------------------------------------------
128
260
 
129
261
 
130
- def _completion_stats(residue_name, n_crosslinkers, n_incomplete, log=True):
262
+ def _completion_stats(residue_name, n_crosslinkers, n_incomplete, log=True, placement=None):
131
263
  """Summarize crosslinker completion and log it.
132
264
 
133
265
  A crosslinker survives this repair only if every one of its sites is
@@ -142,6 +274,7 @@ def _completion_stats(residue_name, n_crosslinkers, n_incomplete, log=True):
142
274
  n_crosslinkers (int): number of crosslinker residues in the system
143
275
  n_incomplete (int): how many of them are being dismantled
144
276
  log (bool): emit the summary message; False when there is nothing to summarize
277
+ placement (dict): cap-placement summary from :func:`_report_placements`, merged into the result
145
278
 
146
279
  Returns:
147
280
  dict: n_crosslinkers, n_complete, n_dismantled, crosslinker_conversion
@@ -154,13 +287,16 @@ def _completion_stats(residue_name, n_crosslinkers, n_incomplete, log=True):
154
287
  f'{residue_name} residues are complete and survive repair; '
155
288
  f'crosslinker conversion {chi:.3f}'
156
289
  )
157
- return {
290
+ out = {
158
291
  'residue': residue_name,
159
292
  'n_crosslinkers': n_crosslinkers,
160
293
  'n_complete': n_complete,
161
294
  'n_dismantled': n_incomplete,
162
295
  'crosslinker_conversion': chi,
163
296
  }
297
+ if placement:
298
+ out.update(placement)
299
+ return out
164
300
 
165
301
 
166
302
  def triazine_to_cyanate_cap(TC, moldict, spec, reactions):
@@ -287,34 +423,60 @@ def triazine_to_cyanate_cap(TC, moldict, spec, reactions):
287
423
  ts.reassign_residue(TC, [cap['c'], cap['n']], cap_residue_name, new_resnum)
288
424
  ts.set_atom_attributes(TC, cap['c'], atomName='C1')
289
425
  ts.set_atom_attributes(TC, cap['n'], atomName='N1')
290
- cap_records.append({'c': cap['c'], 'n': cap['n'], 'o': target_o, 'resnum': new_resnum})
426
+ cap_records.append({'c': cap['c'], 'n': cap['n'], 'o': target_o,
427
+ 'resnum': new_resnum, 'free': cap['bonded_o'] is None})
291
428
  new_resnum += 1
292
429
 
293
430
  # 3d. for free-cap donations: relocate the CYN atoms next to their target O
294
- for rec in cap_records:
295
- cap_in_plans = next(
296
- (c for p in incomplete_plans for c in p['caps']
297
- if c['c'] == rec['c'] and c['n'] == rec['n']),
298
- None,
299
- )
300
- if cap_in_plans is None or cap_in_plans['bonded_o'] is not None:
301
- continue
302
- o_idx = rec['o']
303
- o_xyz = ts.positions(TC, [o_idx])[0]
304
- # use the (about-to-be-deleted) H on the O as the direction reference
305
- o_h_idx = _find_bonded_hydrogen(TC, o_idx)
306
- if o_h_idx is None:
307
- # no H to use as reference (shouldn't happen for a still-reactive O,
308
- # but if it does, just push the cap along +x by the rest length)
309
- ref_xyz = o_xyz + np.array([1.0, 0.0, 0.0])
310
- else:
311
- ref_xyz = ts.positions(TC, [o_h_idx])[0]
312
- h_to_delete.add(int(o_h_idx))
313
- c_xyz, n_xyz = _place_cyn_along(o_xyz, ref_xyz)
314
- ts.set_atom_attributes(TC, rec['c'],
315
- posX=float(c_xyz[0]), posY=float(c_xyz[1]), posZ=float(c_xyz[2]))
316
- ts.set_atom_attributes(TC, rec['n'],
317
- posX=float(n_xyz[0]), posY=float(n_xyz[1]), posZ=float(n_xyz[2]))
431
+ #
432
+ # This is the phase that fails catastrophically when it fails at all. A
433
+ # cap dropped blindly along the old O-H vector can land on top of a
434
+ # neighbour, and the resulting step-0 Lennard-Jones term -- of order 1e15
435
+ # kJ/mol -- is not something the following minimization recovers from.
436
+ # The count of caps to place here is an identity, (total reactive sites -
437
+ # bonds formed), so it rises as conversion falls: a low-conversion build
438
+ # places hundreds and is where this has been seen to kill runs.
439
+ #
440
+ # So each cap is placed against its local neighbourhood, and every cap
441
+ # already placed is part of the neighbourhood the next one sees.
442
+ free_records = [rec for rec in cap_records if rec['free']]
443
+ placements = []
444
+ placement_summary = None
445
+ if free_records:
446
+ box = TC.Coordinates.box.diagonal()
447
+ target_clearance = float(spec.get('cap_min_clearance', 0.15))
448
+ # the O's H is consumed in forming the cap; collect them all up front
449
+ # so that a cap placed early does not dodge an atom about to vanish
450
+ for rec in free_records:
451
+ h = _find_bonded_hydrogen(TC, rec['o'])
452
+ rec['o_h'] = h
453
+ if h is not None:
454
+ h_to_delete.add(int(h))
455
+ moving = {int(rec['c']) for rec in free_records} | {int(rec['n']) for rec in free_records}
456
+ A = TC.Coordinates.A
457
+ going = moving | {int(x) for x in h_to_delete}
458
+ background = A.loc[~A['globalIdx'].isin(going),
459
+ ['posX', 'posY', 'posZ']].to_numpy(dtype=float)
460
+ for rec in free_records:
461
+ o_xyz = ts.positions(TC, [rec['o']])[0]
462
+ if rec['o_h'] is None:
463
+ # no H to take a direction from (shouldn't happen for a
464
+ # still-reactive O); any direction will do as a starting guess
465
+ ref_xyz = o_xyz + np.array([1.0, 0.0, 0.0])
466
+ else:
467
+ ref_xyz = ts.positions(TC, [rec['o_h']])[0]
468
+ # only atoms near this O can constrain a group that reaches
469
+ # 0.25 nm from it; the rest are not worth scoring against
470
+ local = background[_min_image_dist(o_xyz, background, box) < _PLACEMENT_LOCALITY]
471
+ c_xyz, n_xyz, gap, kept = _choose_cap_placement(
472
+ o_xyz, ref_xyz, local, box, target=target_clearance)
473
+ ts.set_atom_attributes(TC, rec['c'],
474
+ posX=float(c_xyz[0]), posY=float(c_xyz[1]), posZ=float(c_xyz[2]))
475
+ ts.set_atom_attributes(TC, rec['n'],
476
+ posX=float(n_xyz[0]), posY=float(n_xyz[1]), posZ=float(n_xyz[2]))
477
+ background = np.vstack([background, c_xyz, n_xyz])
478
+ placements.append({'o': rec['o'], 'clearance': gap, 'kept_o_h_direction': kept})
479
+ placement_summary = _report_placements(placements, target_clearance)
318
480
 
319
481
  # ---- Phase 4: splice template params (forms BPA-O-C bond with full
320
482
  # angle/dihedral/pair entries from the cap_template molecule) ----
@@ -362,7 +524,8 @@ def triazine_to_cyanate_cap(TC, moldict, spec, reactions):
362
524
  msg='triazine_to_cyanate_cap post-deletion rebalance',
363
525
  )
364
526
 
365
- return _completion_stats(cl['residue'], len(cl_residues), len(incomplete_plans))
527
+ return _completion_stats(cl['residue'], len(cl_residues), len(incomplete_plans),
528
+ placement=placement_summary)
366
529
 
367
530
 
368
531
  # ---------------------------------------------------------------------------