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.
- {htpolynet-2.5.0 → htpolynet-2.6.0}/CHANGELOG.md +67 -6
- {htpolynet-2.5.0 → htpolynet-2.6.0}/PKG-INFO +1 -1
- {htpolynet-2.5.0 → htpolynet-2.6.0}/ROADMAP.md +24 -18
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/configs/configs-for-run.rst +1 -1
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/postcure-repair.rst +36 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/pyproject.toml +1 -1
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/core/runtime.py +9 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/cure/curecontroller.py +47 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/repair/cyanate_cap.py +191 -28
- htpolynet-2.6.0/tests/unit/test_cap_placement.py +115 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_completion_bias.py +65 -1
- {htpolynet-2.5.0 → htpolynet-2.6.0}/.claude/settings.json +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/.claude/skills/htpolynet/SKILL.md +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/.envrc +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/.github/workflows/conda-forge-sync.yml +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/.github/workflows/docker.yml +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/.github/workflows/release.yaml +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/.github/workflows/test.yml +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/.gitignore +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/.readthedocs.yaml +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/CITATION.cff +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/CLAUDE.md +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/LICENSE +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/MANIFEST.in +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/README.md +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docker/Dockerfile +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docker/compose.yml +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docker/docker-entrypoint.sh +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/Makefile +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/README.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/make.bat +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/requirements.txt +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/_static/.gitkeep +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/changelog.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/conf.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/0-liquid-styrene/configuration.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/0-liquid-styrene/index.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/0-liquid-styrene/introduction.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/0-liquid-styrene/monomer.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/0-liquid-styrene/postsim.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/0-liquid-styrene/results.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/0-liquid-styrene/run.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/configuration.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/index.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/introduction.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/monomer.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/pics/STY.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/pics/STYCC.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/pics/buildtraces.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/pics/cure_info.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/pics/densification-density.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/pics/final-box.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/pics/reaction_network.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/pics/sty-coloring.tcl +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/pics/sty-cured.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/pics/sty-detail.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/pics/sty-liq.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/pics/styrene-polymerization.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/postsim.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/reactions.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/results.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/1-polystyrene/run.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/configuration.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/index.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/introduction.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/BPA.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/GMA.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/HIE.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/buildtraces.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/cure_info.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/densification-density.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/four_dimers.eps +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/four_dimers.fig +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/four_dimers.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/gma-sty-coloring.tcl +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/gma-sty-cured.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/gma-sty-detail.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/gma-sty-liq.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/p1-traces.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/reaction_network.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/vesys.eps +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/vesys.fig +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/pics/vesys.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/postsim.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/reactions.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/results.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/2-bisgma-styrene-thermoset/run.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/configuration.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/index.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/introduction.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/monomers.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/DGE-epoxy.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/DGE-labelled.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/PAC-2d.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/PAC-labelled.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/buildtraces.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/cure_info.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/densification-density.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/dge-pac-coloring.tcl +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/dge-pac-cured.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/dge-pac-detail.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/dge-pac-liq.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/dgesys.eps +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/dgesys.fig +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/dgesys.png +0 -0
- {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
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/postsim-typical.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/prod-e.png +0 -0
- {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
- {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
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/prod-tg.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/r1.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/r2.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/r3.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/reaction_network.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/rho_v_ns.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/short-e.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/pics/short-tg.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/postsim.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/reactions.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/results.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/3-pacm-dgeba-epoxy-thermoset/run.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/configuration.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/index.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/introduction.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/monomers.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/pics/buildtraces.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/pics/cure_info.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/pics/densification-density.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/pics/dfa-fde-coloring.tcl +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/pics/dfa-fde-cured.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/pics/dfa-fde-detail.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/pics/dfa-fde-liq.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/pics/reaction_network.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/postsim.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/reactions.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/results.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/4-dfda-fde-epoxy-thermoset/run.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/configuration.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/index.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/introduction.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/monomers.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/pics/buildtraces.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/pics/cure_info.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/pics/densification-density.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/pics/htpb-coloring.tcl +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/pics/htpb-ipdi-cured.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/pics/htpb-ipdi-detail.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/pics/htpb-ipdi-liq.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/pics/reaction_network.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/postsim.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/reactions.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/results.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/5-htpb-ipdi/run.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/configuration.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/index.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/introduction.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/monomers.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/pics/badcy-coloring.tcl +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/pics/badcy-cured.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/pics/badcy-detail.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/pics/badcy-liq.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/pics/buildtraces.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/pics/cure_info.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/pics/densification-density.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/pics/reaction_network.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/postsim.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/reactions.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/results.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/6-cyanate-ester/run.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/example-tutorials/index.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/htpolynetpackage.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/index.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/install.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/references/index.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/references/mol2-reference.pdf +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/references.bib +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/release-history.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/CURE.odg +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/bond_filter.odg +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/building-a-system.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/configs/configs-for-analyze.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/configs/configs-for-postsim.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/configuration-files.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/container-usage.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/flow1.odg +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/flow1yaml.odg +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/hydrogenated.eps +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/hydrogenated.fig +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/index.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/molecular-structure-inputs.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/pics/STY.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/pics/STYCC.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/pics/TypicalUsageFlow.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/pics/bond_filter.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/pics/chemdoodle-2dsketcher-emb.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/pics/chemdoodle-2dsketcher-styrene.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/pics/hydrogenated.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/pics/ring_pierce/ring_pierce_cases.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/pics/ring_pierce/ring_pierce_linkcell.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/program-flow.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/docs/source/user-guide/usage.rst +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/scripts/check-conda-sync.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/scripts/release.sh +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/scripts/render-detail.sh +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/scripts/render-detail.tcl +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/scripts/render-snapshot.sh +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/scripts/run_all_examples.sh +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/__init__.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/analysis/__init__.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/analysis/analyze.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/analysis/plot.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/analysis/postsim.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/analysis/utils.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/cli.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/core/__init__.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/core/bondtemplate.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/core/configuration.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/core/coordinates.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/core/molecule.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/core/paramcache.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/core/projectfilesystem.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/core/topocoord.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/core/topology.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/cure/__init__.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/cure/chain.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/cure/expandreactions.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/cure/reaction.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/external/__init__.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/external/ambertools.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/external/command.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/external/gromacs.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/external/slurm.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/external/smiles_input.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/external/software.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/geometry/__init__.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/geometry/bondlist.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/geometry/lattice.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/geometry/linkcell.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/geometry/matrix4.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/geometry/ring.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/io/__init__.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/io/gro.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/io/mol2.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/io/pdb.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/repair/__init__.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/repair/topology_surgery.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/README.md +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/__init__.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/claude/SKILL.md +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/example_depot/0-liquid-styrene.yaml +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/example_depot/1-polystyrene.yaml +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/example_depot/2-bisgma-styrene-thermoset.yaml +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/example_depot/3-pacm-dgeba-epoxy-thermoset.yaml +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/example_depot/4-dfda-fde-epoxy-thermoset.yaml +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/example_depot/5-htpb-ipdi.yaml +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/example_depot/6-cyanate-ester.yaml +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/mdp/README.md +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/mdp/drag-min.mdp +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/mdp/drag-npt.mdp +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/mdp/drag-nvt.mdp +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/mdp/min.mdp +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/mdp/npt.mdp +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/mdp/nvt.mdp +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/mdp/relax-min.mdp +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/mdp/relax-npt.mdp +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/mdp/relax-nvt.mdp +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/mdp/single-molecule-min.mdp +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/mdp/single-molecule-nvt.mdp +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/inputs/DFA.pdb +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/inputs/DGE.mol2 +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/inputs/EMB.mol2 +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/inputs/FDE.pdb +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/inputs/GMA.mol2 +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/inputs/PAC.mol2 +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/inputs/STY.mol2 +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/make-monomers.sh +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/pics/DFA.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/pics/DGE.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/pics/EMB.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/pics/FDE.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/pics/GMA.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/pics/PAC.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/pics/STY.png +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/sample-inputs/DFA.pdb +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/sample-inputs/DGE.mol2 +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/sample-inputs/EMB.mol2 +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/sample-inputs/FDE.pdb +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/sample-inputs/GMA.mol2 +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/sample-inputs/PAC.mol2 +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/molecules/sample-inputs/STY.mol2 +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/tcl/readbonds.tcl +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/tcl/readgrx.tcl +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/resources/tcl/render.tcl +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/utils/__init__.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/utils/banner.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/utils/checkpoint.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/utils/dataframetools.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/utils/inputcheck.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/utils/logsetup.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/utils/profiling.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/utils/stringthings.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/src/htpolynet/utils/vmd_viz.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/__init__.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/conftest.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/__init__.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/fixtures/config1.gro +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/fixtures/config1.top +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/fixtures/config2.gro +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/fixtures/config2.top +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/fixtures/items31.edr +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/fixtures/items43.edr +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/fixtures/items45.edr +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/fixtures/short.mdp +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_bondtemplate.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_chain.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_configuration.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_dataframetools.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_gpu_usability.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_gromacs_get_energy_menu.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_gromacs_gmx_energy_trace.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_inputcheck.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_linkcell_pierce.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_paramcache.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_paramcache_ambertools.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_parameterize_react.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_plot_smoke.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_projectfilesystem.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_repair_conversion.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_resources.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_ring.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_ring_pierce_figs.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_setup_claude.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_slurm_script.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_smiles_input.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_software_provenance.py +0 -0
- {htpolynet-2.5.0 → htpolynet-2.6.0}/tests/unit/test_topology/test.top +0 -0
- {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
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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.
|
|
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
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
- **
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
|
@@ -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
|
-
|
|
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,
|
|
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
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
# ---------------------------------------------------------------------------
|