CodeEntropy 1.1.0__tar.gz → 2.1.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 (227) hide show
  1. {codeentropy-1.1.0 → codeentropy-2.1.0}/.github/workflows/daily.yaml +1 -1
  2. {codeentropy-1.1.0 → codeentropy-2.1.0}/.github/workflows/pr.yaml +2 -2
  3. {codeentropy-1.1.0 → codeentropy-2.1.0}/.github/workflows/release.yaml +13 -6
  4. {codeentropy-1.1.0 → codeentropy-2.1.0}/.github/workflows/weekly-regression.yaml +1 -2
  5. {codeentropy-1.1.0 → codeentropy-2.1.0}/CITATION.cff +2 -2
  6. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/__init__.py +1 -1
  7. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/config/argparse.py +20 -14
  8. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/config/runtime.py +3 -3
  9. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/core/logging.py +2 -3
  10. codeentropy-2.1.0/CodeEntropy/entropy/configurational.py +86 -0
  11. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/entropy/graph.py +13 -7
  12. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/entropy/nodes/aggregate.py +9 -4
  13. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/entropy/nodes/configurational.py +17 -25
  14. codeentropy-2.1.0/CodeEntropy/entropy/nodes/orientational.py +84 -0
  15. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/entropy/nodes/vibrational.py +31 -14
  16. codeentropy-2.1.0/CodeEntropy/entropy/orientational.py +117 -0
  17. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/entropy/vibrational.py +36 -4
  18. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/entropy/water.py +9 -8
  19. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/entropy/workflow.py +42 -18
  20. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/levels/axes.py +7 -7
  21. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/levels/dihedrals.py +138 -98
  22. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/levels/forces.py +18 -13
  23. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/levels/frame_dag.py +11 -11
  24. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/levels/hierarchy.py +7 -8
  25. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/levels/level_dag.py +22 -18
  26. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/levels/linalg.py +4 -5
  27. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/levels/mda.py +8 -9
  28. codeentropy-2.1.0/CodeEntropy/levels/neighbors.py +251 -0
  29. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/levels/nodes/accumulators.py +12 -11
  30. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/levels/nodes/beads.py +12 -11
  31. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/levels/nodes/conformations.py +28 -13
  32. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/levels/nodes/covariance.py +45 -45
  33. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/levels/nodes/detect_levels.py +5 -5
  34. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/levels/nodes/detect_molecules.py +3 -3
  35. codeentropy-2.1.0/CodeEntropy/levels/nodes/find_neighbors.py +93 -0
  36. codeentropy-2.1.0/CodeEntropy/levels/search.py +241 -0
  37. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/molecules/grouping.py +7 -7
  38. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/results/reporter.py +37 -38
  39. {codeentropy-1.1.0 → codeentropy-2.1.0}/PKG-INFO +6 -4
  40. {codeentropy-1.1.0 → codeentropy-2.1.0}/README.md +3 -2
  41. {codeentropy-1.1.0 → codeentropy-2.1.0}/conda-recipe/meta.yaml +11 -6
  42. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/conf.py +3 -4
  43. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/config.yaml +4 -1
  44. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/getting_started.rst +17 -4
  45. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/science.rst +33 -6
  46. {codeentropy-1.1.0 → codeentropy-2.1.0}/pyproject.toml +3 -2
  47. codeentropy-2.1.0/tests/regression/baselines/benzaldehyde.json +45 -0
  48. codeentropy-1.1.0/tests/regression/baselines/benzaldehyde.json → codeentropy-2.1.0/tests/regression/baselines/benzaldehyde_rad.json +13 -11
  49. codeentropy-2.1.0/tests/regression/baselines/benzene.json +45 -0
  50. codeentropy-1.1.0/tests/regression/baselines/benzene.json → codeentropy-2.1.0/tests/regression/baselines/benzene_rad.json +13 -11
  51. codeentropy-2.1.0/tests/regression/baselines/cyclohexane.json +45 -0
  52. codeentropy-1.1.0/tests/regression/baselines/cyclohexane.json → codeentropy-2.1.0/tests/regression/baselines/cylcohexane_rad.json +13 -11
  53. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/regression/baselines/dna.json +16 -13
  54. codeentropy-2.1.0/tests/regression/baselines/ethyl-acetate.json +45 -0
  55. codeentropy-2.1.0/tests/regression/baselines/ethyl-acetate_rad.json +45 -0
  56. codeentropy-2.1.0/tests/regression/baselines/methane.json +42 -0
  57. codeentropy-2.1.0/tests/regression/baselines/methane_rad.json +42 -0
  58. codeentropy-2.1.0/tests/regression/baselines/methanol.json +45 -0
  59. codeentropy-1.1.0/tests/regression/baselines/methanol.json → codeentropy-2.1.0/tests/regression/baselines/methanol_rad.json +13 -11
  60. codeentropy-2.1.0/tests/regression/baselines/octonal_rad.json +45 -0
  61. codeentropy-2.1.0/tests/regression/baselines/octonol.json +45 -0
  62. codeentropy-2.1.0/tests/regression/baselines/water.json +42 -0
  63. codeentropy-2.1.0/tests/regression/baselines/water_rad.json +42 -0
  64. codeentropy-2.1.0/tests/regression/configs/benzaldehyde/config.yaml +14 -0
  65. {codeentropy-1.1.0/tests/regression/configs/benzaldehyde → codeentropy-2.1.0/tests/regression/configs/benzaldehyde_rad}/config.yaml +2 -0
  66. codeentropy-2.1.0/tests/regression/configs/benzene/config.yaml +14 -0
  67. {codeentropy-1.1.0/tests/regression/configs/benzene → codeentropy-2.1.0/tests/regression/configs/benzene_rad}/config.yaml +2 -0
  68. codeentropy-2.1.0/tests/regression/configs/cyclohexane/config.yaml +14 -0
  69. {codeentropy-1.1.0/tests/regression/configs/cyclohexane → codeentropy-2.1.0/tests/regression/configs/cyclohexane_rad}/config.yaml +2 -0
  70. codeentropy-2.1.0/tests/regression/configs/ethyl-acetate/config.yaml +14 -0
  71. {codeentropy-1.1.0/tests/regression/configs/ethyl-acetate → codeentropy-2.1.0/tests/regression/configs/ethyl-acetate_rad}/config.yaml +2 -0
  72. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/regression/configs/methane/config.yaml +4 -2
  73. codeentropy-2.1.0/tests/regression/configs/methane_rad/config.yaml +14 -0
  74. codeentropy-2.1.0/tests/regression/configs/methanol/config.yaml +14 -0
  75. {codeentropy-1.1.0/tests/regression/configs/methanol → codeentropy-2.1.0/tests/regression/configs/methanol_rad}/config.yaml +2 -0
  76. codeentropy-2.1.0/tests/regression/configs/octonol/config.yaml +14 -0
  77. {codeentropy-1.1.0/tests/regression/configs/octonol → codeentropy-2.1.0/tests/regression/configs/octonol_rad}/config.yaml +2 -0
  78. codeentropy-2.1.0/tests/regression/configs/water/config.yaml +14 -0
  79. codeentropy-2.1.0/tests/regression/configs/water_rad/config.yaml +14 -0
  80. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/regression/helpers.py +8 -9
  81. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/regression/test_regression.py +9 -8
  82. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/core/logging/test_export_console.py +1 -1
  83. codeentropy-2.1.0/tests/unit/CodeEntropy/entropy/nodes/test_orientational_node.py +39 -0
  84. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/entropy/nodes/test_vibrational_node.py +16 -7
  85. codeentropy-2.1.0/tests/unit/CodeEntropy/entropy/test_configurational_edges.py +12 -0
  86. codeentropy-2.1.0/tests/unit/CodeEntropy/entropy/test_configurational_entropy_math.py +25 -0
  87. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/entropy/test_graph.py +2 -0
  88. codeentropy-2.1.0/tests/unit/CodeEntropy/entropy/test_orientational_entropy.py +34 -0
  89. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/entropy/test_vibrational_entropy_math.py +11 -2
  90. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/entropy/test_workflow.py +23 -11
  91. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/levels/nodes/test_conformations_node.py +1 -1
  92. codeentropy-2.1.0/tests/unit/CodeEntropy/levels/nodes/test_find_neighbors.py +33 -0
  93. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/levels/test_dihedrals.py +28 -9
  94. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/levels/test_forces_force_torque_calculator.py +29 -0
  95. codeentropy-2.1.0/tests/unit/CodeEntropy/levels/test_neighbors.py +489 -0
  96. codeentropy-2.1.0/tests/unit/CodeEntropy/levels/test_search.py +277 -0
  97. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/results/test_reporter.py +1 -2
  98. codeentropy-1.1.0/CodeEntropy/entropy/configurational.py +0 -266
  99. codeentropy-1.1.0/CodeEntropy/entropy/orientational.py +0 -158
  100. codeentropy-1.1.0/tests/regression/baselines/ethyl-acetate.json +0 -43
  101. codeentropy-1.1.0/tests/regression/baselines/methane.json +0 -40
  102. codeentropy-1.1.0/tests/regression/baselines/octonol.json +0 -43
  103. codeentropy-1.1.0/tests/unit/CodeEntropy/entropy/test_configurational_edges.py +0 -90
  104. codeentropy-1.1.0/tests/unit/CodeEntropy/entropy/test_configurational_entropy_math.py +0 -120
  105. codeentropy-1.1.0/tests/unit/CodeEntropy/entropy/test_orientational_entropy.py +0 -54
  106. {codeentropy-1.1.0 → codeentropy-2.1.0}/.github/CONTRIBUTING.md +0 -0
  107. {codeentropy-1.1.0 → codeentropy-2.1.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  108. {codeentropy-1.1.0 → codeentropy-2.1.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  109. {codeentropy-1.1.0 → codeentropy-2.1.0}/.github/ISSUE_TEMPLATE/documentation.md +0 -0
  110. {codeentropy-1.1.0 → codeentropy-2.1.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  111. {codeentropy-1.1.0 → codeentropy-2.1.0}/.github/ISSUE_TEMPLATE/general_report.md +0 -0
  112. {codeentropy-1.1.0 → codeentropy-2.1.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  113. {codeentropy-1.1.0 → codeentropy-2.1.0}/.github/renovate.json +0 -0
  114. {codeentropy-1.1.0 → codeentropy-2.1.0}/.github/workflows/update-copyright-years-in-license-file.yaml +0 -0
  115. {codeentropy-1.1.0 → codeentropy-2.1.0}/.github/workflows/weekly-docs.yaml +0 -0
  116. {codeentropy-1.1.0 → codeentropy-2.1.0}/.gitignore +0 -0
  117. {codeentropy-1.1.0 → codeentropy-2.1.0}/.pre-commit-config.yaml +0 -0
  118. {codeentropy-1.1.0 → codeentropy-2.1.0}/CODE_OF_CONDUCT.md +0 -0
  119. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/__main__.py +0 -0
  120. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/cli.py +0 -0
  121. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/config/__init__.py +0 -0
  122. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/core/__init__.py +0 -0
  123. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/entropy/__init__.py +0 -0
  124. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/entropy/nodes/__init__.py +0 -0
  125. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/levels/__init__.py +0 -0
  126. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/levels/nodes/__init__.py +0 -0
  127. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/molecules/__init__.py +0 -0
  128. {codeentropy-1.1.0 → codeentropy-2.1.0}/CodeEntropy/results/__init__.py +0 -0
  129. {codeentropy-1.1.0 → codeentropy-2.1.0}/LICENSE +0 -0
  130. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/Makefile +0 -0
  131. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/README.md +0 -0
  132. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/_static/README.md +0 -0
  133. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/_static/custom.css +0 -0
  134. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/_static/logos/biosim-codeentropy_logo_dark.png +0 -0
  135. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/_static/logos/biosim-codeentropy_logo_light.png +0 -0
  136. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/_templates/README.md +0 -0
  137. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.cli.rst +0 -0
  138. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.config.argparse.rst +0 -0
  139. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.config.rst +0 -0
  140. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.config.runtime.rst +0 -0
  141. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.core.logging.rst +0 -0
  142. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.core.rst +0 -0
  143. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.entropy.configurational.rst +0 -0
  144. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.entropy.graph.rst +0 -0
  145. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.entropy.nodes.aggregate.rst +0 -0
  146. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.entropy.nodes.configurational.rst +0 -0
  147. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.entropy.nodes.rst +0 -0
  148. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.entropy.nodes.vibrational.rst +0 -0
  149. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.entropy.orientational.rst +0 -0
  150. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.entropy.rst +0 -0
  151. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.entropy.vibrational.rst +0 -0
  152. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.entropy.water.rst +0 -0
  153. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.entropy.workflow.rst +0 -0
  154. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.levels.axes.rst +0 -0
  155. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.levels.dihedrals.rst +0 -0
  156. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.levels.forces.rst +0 -0
  157. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.levels.frame_dag.rst +0 -0
  158. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.levels.hierarchy.rst +0 -0
  159. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.levels.level_dag.rst +0 -0
  160. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.levels.linalg.rst +0 -0
  161. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.levels.mda.rst +0 -0
  162. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.levels.nodes.accumulators.rst +0 -0
  163. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.levels.nodes.beads.rst +0 -0
  164. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.levels.nodes.conformations.rst +0 -0
  165. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.levels.nodes.covariance.rst +0 -0
  166. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.levels.nodes.detect_levels.rst +0 -0
  167. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.levels.nodes.detect_molecules.rst +0 -0
  168. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.levels.nodes.rst +0 -0
  169. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.levels.rst +0 -0
  170. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.molecules.grouping.rst +0 -0
  171. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.molecules.rst +0 -0
  172. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.results.reporter.rst +0 -0
  173. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.results.rst +0 -0
  174. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/CodeEntropy.rst +0 -0
  175. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/api/modules.rst +0 -0
  176. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/developer_guide.rst +0 -0
  177. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/faq.rst +0 -0
  178. {codeentropy-1.1.0 → codeentropy-2.1.0}/docs/index.rst +0 -0
  179. {codeentropy-1.1.0 → codeentropy-2.1.0}/readthedocs.yml +0 -0
  180. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/__init__.py +0 -0
  181. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/pytest.ini +0 -0
  182. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/regression/configs/dna/config.yaml +0 -0
  183. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/regression/conftest.py +0 -0
  184. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/cli/test___main__.py +0 -0
  185. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/cli/test_cli.py +0 -0
  186. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/config/argparse/conftest.py +0 -0
  187. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/config/argparse/test_argparse_build_parser.py +0 -0
  188. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/config/argparse/test_argparse_load_config.py +0 -0
  189. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/config/argparse/test_argparse_resolve.py +0 -0
  190. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/config/argparse/test_argparse_str2bool.py +0 -0
  191. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/config/argparse/test_argparse_validate_inputs.py +0 -0
  192. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/config/runtime/conftest.py +0 -0
  193. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/config/runtime/test_build_universe_branches.py +0 -0
  194. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/config/runtime/test_citation_data.py +0 -0
  195. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/config/runtime/test_create_job_folder.py +0 -0
  196. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/config/runtime/test_print_args_table.py +0 -0
  197. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/config/runtime/test_required_args.py +0 -0
  198. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/config/runtime/test_run_entropy_workflow_branches.py +0 -0
  199. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/config/runtime/test_runtime_properties.py +0 -0
  200. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/config/runtime/test_show_splash.py +0 -0
  201. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/config/runtime/test_unit_conversions.py +0 -0
  202. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/config/runtime/test_universe_io.py +0 -0
  203. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/core/logging/conftest.py +0 -0
  204. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/core/logging/test_configure_loggers.py +0 -0
  205. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/core/logging/test_error_filter.py +0 -0
  206. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/core/logging/test_get_console_singleton.py +0 -0
  207. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/core/logging/test_handlers_setup.py +0 -0
  208. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/core/logging/test_set_level.py +0 -0
  209. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/entropy/conftest.py +0 -0
  210. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/entropy/nodes/test_aggregate_node.py +0 -0
  211. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/entropy/nodes/test_configurational_node.py +0 -0
  212. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/entropy/test_water_entropy.py +0 -0
  213. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/levels/conftest.py +0 -0
  214. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/levels/nodes/test_build_beads_node.py +0 -0
  215. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/levels/nodes/test_detect_levels_node.py +0 -0
  216. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/levels/nodes/test_detect_molecules_node.py +0 -0
  217. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/levels/nodes/test_frame_covariance_node.py +0 -0
  218. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/levels/nodes/test_init_covariance_accumulators_node.py +0 -0
  219. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/levels/test_axes.py +0 -0
  220. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/levels/test_frame_graph.py +0 -0
  221. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/levels/test_hierarchy_builder.py +0 -0
  222. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/levels/test_level_dag_orchestration.py +0 -0
  223. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/levels/test_level_dag_reduce.py +0 -0
  224. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/levels/test_level_dag_reduction.py +0 -0
  225. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/levels/test_linalg_matrix_utils.py +0 -0
  226. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/levels/test_mda_universe_operations.py +0 -0
  227. {codeentropy-1.1.0 → codeentropy-2.1.0}/tests/unit/CodeEntropy/molecules/test_grouping.py +0 -0
@@ -35,4 +35,4 @@ jobs:
35
35
  python -m pip install -e .[testing]
36
36
 
37
37
  - name: Pytest (unit) • ${{ matrix.os }} • py${{ matrix.python-version }}
38
- run: python -m pytest tests/unit -q
38
+ run: python -m pytest tests/unit
@@ -33,7 +33,7 @@ jobs:
33
33
  python -m pip install -e .[testing]
34
34
 
35
35
  - name: Pytest (unit) • ${{ matrix.os }}, ${{ matrix.python-version }}
36
- run: python -m pytest tests/unit -q
36
+ run: python -m pytest tests/unit
37
37
 
38
38
  regression-quick:
39
39
  name: Regression (quick)
@@ -62,7 +62,7 @@ jobs:
62
62
  python -m pip install -e .[testing]
63
63
 
64
64
  - name: Pytest (regression quick)
65
- run: python -m pytest tests/regression -q
65
+ run: python -m pytest tests/regression
66
66
 
67
67
  - name: Upload artifacts (failure)
68
68
  if: failure()
@@ -160,32 +160,39 @@ jobs:
160
160
 
161
161
  - name: Set version in conda recipe
162
162
  run: |
163
- sed -i -E "s/^{% set version = \".*\" %}$/{% set version = \"${{ github.event.inputs.version }}\" %}/" conda-recipe/meta.yaml
163
+ sed -i "s/^{% set version = \".*\" %}$/{% set version = \"${{ github.event.inputs.version }}\" %}/" conda-recipe/meta.yaml
164
+ grep '{% set version' conda-recipe/meta.yaml
164
165
 
165
- - name: Setup Conda
166
+ - name: Setup Conda (conda-forge only)
166
167
  uses: conda-incubator/setup-miniconda@v3
167
168
  with:
168
169
  miniforge-variant: Miniforge3
169
170
  python-version: "3.12"
170
- auto-activate-base: false
171
+ auto-activate: false
172
+ conda-remove-defaults: true
173
+ channels: conda-forge
174
+ channel-priority: strict
171
175
 
172
176
  - name: Install build tools
173
177
  shell: bash -l {0}
174
178
  run: |
175
179
  conda install -y conda-build anaconda-client
176
- conda config --add channels conda-forge
177
- conda config --set channel_priority strict
178
180
  conda config --set anaconda_upload no
179
181
 
180
182
  - name: Build package
181
183
  shell: bash -l {0}
184
+ env:
185
+ PIP_NO_INDEX: "0"
182
186
  run: |
187
+ PKG_PATH=$(conda build conda-recipe --output)
188
+ echo "PKG_PATH=$PKG_PATH" >> "$GITHUB_ENV"
183
189
  conda build conda-recipe
190
+ test -f "$PKG_PATH"
191
+ echo "Built: $PKG_PATH"
184
192
 
185
193
  - name: Upload to Anaconda
186
194
  shell: bash -l {0}
187
195
  env:
188
196
  ANACONDA_API_TOKEN: ${{ secrets.ANACONDA_TOKEN }}
189
197
  run: |
190
- PKG_PATH=$(conda build conda-recipe --output)
191
198
  anaconda -t "$ANACONDA_API_TOKEN" upload "$PKG_PATH" --user CCPBioSim --force
@@ -13,7 +13,6 @@ jobs:
13
13
  regression:
14
14
  name: Regression tests (including slow)
15
15
  runs-on: ubuntu-24.04
16
- timeout-minutes: 180
17
16
  steps:
18
17
  - name: Checkout repo
19
18
  uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
@@ -36,7 +35,7 @@ jobs:
36
35
  pip install -e .[testing]
37
36
 
38
37
  - name: Run regression test suite
39
- run: pytest tests/regression -q --run-slow
38
+ run: pytest tests/regression --run-slow
40
39
 
41
40
  - name: Upload regression artifacts on failure
42
41
  if: failure()
@@ -76,5 +76,5 @@ keywords:
76
76
  - biomolecular simulations
77
77
  - protein flexibility
78
78
  license: MIT
79
- version: 1.1.0
80
- date-released: '2026-02-27'
79
+ version: 2.1.0
80
+ date-released: '2026-03-26'
@@ -8,4 +8,4 @@ entropy estimates, supporting a wide range of applications in molecular simulati
8
8
  and statistical mechanics.
9
9
  """
10
10
 
11
- __version__ = "1.1.0"
11
+ __version__ = "2.1.0"
@@ -25,7 +25,7 @@ import glob
25
25
  import logging
26
26
  import os
27
27
  from dataclasses import dataclass
28
- from typing import Any, Dict, Optional, Set
28
+ from typing import Any
29
29
 
30
30
  import yaml
31
31
 
@@ -48,11 +48,11 @@ class ArgSpec:
48
48
  help: str
49
49
  default: Any = None
50
50
  type: Any = None
51
- action: Optional[str] = None
52
- nargs: Optional[str] = None
51
+ action: str | None = None
52
+ nargs: str | None = None
53
53
 
54
54
 
55
- ARG_SPECS: Dict[str, ArgSpec] = {
55
+ ARG_SPECS: dict[str, ArgSpec] = {
56
56
  "top_traj_file": ArgSpec(
57
57
  type=str,
58
58
  nargs="+",
@@ -145,6 +145,12 @@ ARG_SPECS: Dict[str, ArgSpec] = {
145
145
  default=True,
146
146
  help="Use bonded axes to rotate forces for UA level vibrational entropies",
147
147
  ),
148
+ "search_type": ArgSpec(
149
+ type=str,
150
+ default="RAD",
151
+ help="Type of neighbor search to use."
152
+ "Default RAD; grid search is also available",
153
+ ),
148
154
  }
149
155
 
150
156
 
@@ -159,7 +165,7 @@ class ConfigResolver:
159
165
  - validating trajectory-related numeric parameters
160
166
  """
161
167
 
162
- def __init__(self, arg_specs: Optional[Dict[str, ArgSpec]] = None) -> None:
168
+ def __init__(self, arg_specs: dict[str, ArgSpec] | None = None) -> None:
163
169
  """Initialize the manager.
164
170
 
165
171
  Args:
@@ -168,7 +174,7 @@ class ConfigResolver:
168
174
  """
169
175
  self._arg_specs = dict(arg_specs or ARG_SPECS)
170
176
 
171
- def load_config(self, directory_path: str) -> Dict[str, Any]:
177
+ def load_config(self, directory_path: str) -> dict[str, Any]:
172
178
  """Load the first YAML config file found in a directory.
173
179
 
174
180
  The current behavior matches your existing workflow:
@@ -188,7 +194,7 @@ class ConfigResolver:
188
194
 
189
195
  config_path = yaml_files[0]
190
196
  try:
191
- with open(config_path, "r", encoding="utf-8") as file:
197
+ with open(config_path, encoding="utf-8") as file:
192
198
  config = yaml.safe_load(file) or {"run1": {}}
193
199
  logger.info("Loaded configuration from: %s", config_path)
194
200
  return config
@@ -253,7 +259,7 @@ class ConfigResolver:
253
259
  )
254
260
  continue
255
261
 
256
- kwargs: Dict[str, Any] = {}
262
+ kwargs: dict[str, Any] = {}
257
263
  if spec.type is not None:
258
264
  kwargs["type"] = spec.type
259
265
  if spec.default is not None:
@@ -266,7 +272,7 @@ class ConfigResolver:
266
272
  return parser
267
273
 
268
274
  def resolve(
269
- self, args: argparse.Namespace, run_config: Optional[Dict[str, Any]]
275
+ self, args: argparse.Namespace, run_config: dict[str, Any] | None
270
276
  ) -> argparse.Namespace:
271
277
  """Merge CLI arguments with YAML configuration and adjust logging level.
272
278
 
@@ -306,8 +312,8 @@ class ConfigResolver:
306
312
 
307
313
  @staticmethod
308
314
  def _detect_cli_overrides(
309
- args_dict: Dict[str, Any], default_dict: Dict[str, Any]
310
- ) -> Set[str]:
315
+ args_dict: dict[str, Any], default_dict: dict[str, Any]
316
+ ) -> set[str]:
311
317
  """Detect which args were explicitly overridden in the CLI.
312
318
 
313
319
  Args:
@@ -322,8 +328,8 @@ class ConfigResolver:
322
328
  def _apply_yaml_defaults(
323
329
  self,
324
330
  args: argparse.Namespace,
325
- run_config: Dict[str, Any],
326
- cli_provided: Set[str],
331
+ run_config: dict[str, Any],
332
+ cli_provided: set[str],
327
333
  ) -> None:
328
334
  """Apply YAML values onto args for keys not provided by CLI.
329
335
 
@@ -336,7 +342,7 @@ class ConfigResolver:
336
342
  if yaml_value is None or key in cli_provided:
337
343
  continue
338
344
  if key in self._arg_specs:
339
- logger.debug("Using YAML value for %s: %s", key, yaml_value)
345
+ logger.debug(f"Using YAML value for {key}: {yaml_value}")
340
346
  setattr(args, key, yaml_value)
341
347
 
342
348
  def _ensure_defaults(self, args: argparse.Namespace) -> None:
@@ -18,7 +18,7 @@ from __future__ import annotations
18
18
  import logging
19
19
  import os
20
20
  import pickle
21
- from typing import Any, Dict, Optional
21
+ from typing import Any
22
22
 
23
23
  import MDAnalysis as mda
24
24
  import requests
@@ -112,7 +112,7 @@ class CodeEntropyRunner:
112
112
 
113
113
  return new_folder_path
114
114
 
115
- def load_citation_data(self) -> Optional[Dict[str, Any]]:
115
+ def load_citation_data(self) -> dict[str, Any] | None:
116
116
  """Load CITATION.cff from GitHub.
117
117
 
118
118
  If the request fails (offline, blocked, etc.), returns None.
@@ -335,7 +335,7 @@ class CodeEntropyRunner:
335
335
  kcal_units = args.kcal_force_units
336
336
 
337
337
  if forcefile is None:
338
- logger.debug("Loading Universe with %s and %s", tprfile, trrfile)
338
+ logger.debug(f"Loading Universe with {tprfile} and {trrfile}")
339
339
  return mda.Universe(tprfile, trrfile, format=fileformat)
340
340
 
341
341
  return universe_operations.merge_forces(
@@ -16,7 +16,6 @@ from __future__ import annotations
16
16
 
17
17
  import logging
18
18
  import os
19
- from typing import Dict, Optional
20
19
 
21
20
  from rich.console import Console
22
21
  from rich.logging import RichHandler
@@ -55,7 +54,7 @@ class LoggingConfig:
55
54
  handlers: Mapping of handler name to handler instance.
56
55
  """
57
56
 
58
- _console: Optional[Console] = None
57
+ _console: Console | None = None
59
58
 
60
59
  @classmethod
61
60
  def get_console(cls) -> Console:
@@ -80,7 +79,7 @@ class LoggingConfig:
80
79
 
81
80
  self.level = level
82
81
  self.console = self.get_console()
83
- self.handlers: Dict[str, logging.Handler] = {}
82
+ self.handlers: dict[str, logging.Handler] = {}
84
83
 
85
84
  self._setup_handlers()
86
85
 
@@ -0,0 +1,86 @@
1
+ """Conformational entropy utilities.
2
+
3
+ This module provides:
4
+
5
+ - Assignment of discrete conformational states for a single dihedral time series.
6
+ - Computation of conformational entropy from a sequence of state labels.
7
+
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import logging
13
+ from typing import Any
14
+
15
+ import numpy as np
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class ConformationalEntropy:
21
+ """Compute conformational entropy from states information."""
22
+
23
+ _GAS_CONST: float = 8.3144598484848
24
+
25
+ def __init__(self) -> None:
26
+ """Math-only engine.
27
+
28
+ This class assigns conformational states and computes conformational entropy.
29
+ It does not depend on the workflow runner, universe, grouping, or reporting.
30
+ """
31
+ pass
32
+
33
+ def conformational_entropy_calculation(self, states: Any) -> float:
34
+ """Compute conformational entropy for a sequence of state labels.
35
+
36
+ Entropy is computed as:
37
+
38
+ S = -R * sum_i p_i * ln(p_i)
39
+
40
+ where p_i is the observed probability of state i in ``states``.
41
+
42
+ Args:
43
+ states: Sequence/array of discrete state labels. Empty/None yields 0.0.
44
+ number_frames: Frame count metadata.
45
+
46
+ Returns:
47
+ float: Conformational entropy in J/mol/K.
48
+ """
49
+ arr = self._to_1d_array(states)
50
+ if arr is None or arr.size == 0:
51
+ return 0.0
52
+
53
+ values, counts = np.unique(arr, return_counts=True)
54
+ total_count = int(np.sum(counts))
55
+ if total_count <= 0 or values.size <= 1:
56
+ return 0.0
57
+
58
+ probs = counts.astype(float) / float(total_count)
59
+ probs = probs[probs > 0.0]
60
+
61
+ s_conf = -self._GAS_CONST * float(np.sum(probs * np.log(probs)))
62
+ logger.debug(f"Total conformational entropy: {s_conf}")
63
+ return s_conf
64
+
65
+ @staticmethod
66
+ def _to_1d_array(states: Any) -> np.ndarray | None:
67
+ """Convert a state sequence into a 1D numpy array.
68
+
69
+ Args:
70
+ states: Input sequence/array.
71
+
72
+ Returns:
73
+ 1D numpy array, or None if input is not usable.
74
+ """
75
+ if states is None:
76
+ return None
77
+
78
+ if isinstance(states, np.ndarray):
79
+ arr = states.reshape(-1)
80
+ else:
81
+ try:
82
+ arr = np.asarray(list(states)).reshape(-1)
83
+ except TypeError:
84
+ return None
85
+
86
+ return arr
@@ -15,18 +15,19 @@ from __future__ import annotations
15
15
 
16
16
  import logging
17
17
  from dataclasses import dataclass
18
- from typing import Any, Dict
18
+ from typing import Any
19
19
 
20
20
  import networkx as nx
21
21
 
22
22
  from CodeEntropy.entropy.nodes.aggregate import AggregateEntropyNode
23
23
  from CodeEntropy.entropy.nodes.configurational import ConfigurationalEntropyNode
24
+ from CodeEntropy.entropy.nodes.orientational import OrientationalEntropyNode
24
25
  from CodeEntropy.entropy.nodes.vibrational import VibrationalEntropyNode
25
26
 
26
27
  logger = logging.getLogger(__name__)
27
28
 
28
29
 
29
- SharedData = Dict[str, Any]
30
+ SharedData = dict[str, Any]
30
31
 
31
32
 
32
33
  @dataclass(frozen=True)
@@ -57,9 +58,9 @@ class EntropyGraph:
57
58
  def __init__(self) -> None:
58
59
  """Initialize an empty entropy graph."""
59
60
  self._graph: nx.DiGraph = nx.DiGraph()
60
- self._nodes: Dict[str, Any] = {}
61
+ self._nodes: dict[str, Any] = {}
61
62
 
62
- def build(self) -> "EntropyGraph":
63
+ def build(self) -> EntropyGraph:
63
64
  """Populate the graph with the standard entropy workflow.
64
65
 
65
66
  Returns:
@@ -68,10 +69,15 @@ class EntropyGraph:
68
69
  specs = (
69
70
  NodeSpec("vibrational_entropy", VibrationalEntropyNode()),
70
71
  NodeSpec("configurational_entropy", ConfigurationalEntropyNode()),
72
+ NodeSpec("orientational_entropy", OrientationalEntropyNode()),
71
73
  NodeSpec(
72
74
  "aggregate_entropy",
73
75
  AggregateEntropyNode(),
74
- deps=("vibrational_entropy", "configurational_entropy"),
76
+ deps=(
77
+ "vibrational_entropy",
78
+ "configurational_entropy",
79
+ "orientational_entropy",
80
+ ),
75
81
  ),
76
82
  )
77
83
 
@@ -82,7 +88,7 @@ class EntropyGraph:
82
88
 
83
89
  def execute(
84
90
  self, shared_data: SharedData, *, progress: object | None = None
85
- ) -> Dict[str, Any]:
91
+ ) -> dict[str, Any]:
86
92
  """Execute the entropy graph in topological order.
87
93
 
88
94
  Nodes are executed in dependency order (topological sort). Each node reads
@@ -105,7 +111,7 @@ class EntropyGraph:
105
111
  Raises:
106
112
  KeyError: If a node name is missing from the internal node registry.
107
113
  """
108
- results: Dict[str, Any] = {}
114
+ results: dict[str, Any] = {}
109
115
 
110
116
  for node_name in nx.topological_sort(self._graph):
111
117
  node = self._nodes[node_name]
@@ -2,10 +2,11 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from collections.abc import Mapping, MutableMapping
5
6
  from dataclasses import dataclass
6
- from typing import Any, Dict, Mapping, MutableMapping, Optional
7
+ from typing import Any
7
8
 
8
- EntropyResults = Dict[str, Any]
9
+ EntropyResults = dict[str, Any]
9
10
 
10
11
 
11
12
  @dataclass(frozen=True, slots=True)
@@ -25,11 +26,12 @@ class AggregateEntropyNode:
25
26
 
26
27
  vibrational_key: str = "vibrational_entropy"
27
28
  configurational_key: str = "configurational_entropy"
29
+ orientational_key: str = "orientational_entropy"
28
30
  output_key: str = "entropy_results"
29
31
 
30
32
  def run(
31
33
  self, shared_data: MutableMapping[str, Any], **_: Any
32
- ) -> Dict[str, EntropyResults]:
34
+ ) -> dict[str, EntropyResults]:
33
35
  """Run the aggregation step.
34
36
 
35
37
  Args:
@@ -69,10 +71,13 @@ class AggregateEntropyNode:
69
71
  "configurational_entropy": self._get_optional(
70
72
  shared_data, self.configurational_key
71
73
  ),
74
+ "orientational_entropy": self._get_optional(
75
+ shared_data, self.orientational_key
76
+ ),
72
77
  }
73
78
 
74
79
  @staticmethod
75
- def _get_optional(shared_data: Mapping[str, Any], key: str) -> Optional[Any]:
80
+ def _get_optional(shared_data: Mapping[str, Any], key: str) -> Any | None:
76
81
  """Fetch an optional value from shared data.
77
82
 
78
83
  Args:
@@ -3,16 +3,9 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import logging
6
+ from collections.abc import Iterable, Mapping, MutableMapping, Sequence
6
7
  from typing import (
7
8
  Any,
8
- Dict,
9
- Iterable,
10
- Mapping,
11
- MutableMapping,
12
- Optional,
13
- Sequence,
14
- Tuple,
15
- Union,
16
9
  )
17
10
 
18
11
  import numpy as np
@@ -23,8 +16,8 @@ logger = logging.getLogger(__name__)
23
16
 
24
17
  GroupId = int
25
18
  ResidueId = int
26
- StateKey = Tuple[GroupId, ResidueId]
27
- StateSequence = Union[Sequence[Any], np.ndarray]
19
+ StateKey = tuple[GroupId, ResidueId]
20
+ StateSequence = Sequence[Any] | np.ndarray
28
21
 
29
22
 
30
23
  class ConfigurationalEntropyNode:
@@ -36,7 +29,7 @@ class ConfigurationalEntropyNode:
36
29
  Results are written back into ``shared_data["configurational_entropy"]``.
37
30
  """
38
31
 
39
- def run(self, shared_data: MutableMapping[str, Any], **_: Any) -> Dict[str, Any]:
32
+ def run(self, shared_data: MutableMapping[str, Any], **_: Any) -> dict[str, Any]:
40
33
  """Execute configurational entropy calculation.
41
34
 
42
35
  Args:
@@ -58,7 +51,7 @@ class ConfigurationalEntropyNode:
58
51
  ce = self._build_entropy_engine()
59
52
 
60
53
  fragments = universe.atoms.fragments
61
- results: Dict[int, Dict[str, float]] = {}
54
+ results: dict[int, dict[str, float]] = {}
62
55
 
63
56
  for group_id, mol_ids in groups.items():
64
57
  results[group_id] = {"ua": 0.0, "res": 0.0, "poly": 0.0}
@@ -104,9 +97,9 @@ class ConfigurationalEntropyNode:
104
97
 
105
98
  def _get_state_containers(
106
99
  self, shared_data: Mapping[str, Any]
107
- ) -> Tuple[
108
- Dict[StateKey, StateSequence],
109
- Union[Dict[GroupId, StateSequence], Sequence[Optional[StateSequence]]],
100
+ ) -> tuple[
101
+ dict[StateKey, StateSequence],
102
+ dict[GroupId, StateSequence] | Sequence[StateSequence | None],
110
103
  ]:
111
104
  """Retrieve conformational state containers.
112
105
 
@@ -144,7 +137,7 @@ class ConfigurationalEntropyNode:
144
137
  residues: Iterable[Any],
145
138
  states_ua: Mapping[StateKey, StateSequence],
146
139
  n_frames: int,
147
- reporter: Optional[Any],
140
+ reporter: Any | None,
148
141
  ) -> float:
149
142
  """Compute united atom entropy for a group.
150
143
 
@@ -163,7 +156,7 @@ class ConfigurationalEntropyNode:
163
156
 
164
157
  for res_id, res in enumerate(residues):
165
158
  states = states_ua.get((group_id, res_id))
166
- val = self._entropy_or_zero(ce, states, n_frames)
159
+ val = self._entropy_or_zero(ce, states)
167
160
  total += val
168
161
 
169
162
  if reporter is not None:
@@ -186,29 +179,28 @@ class ConfigurationalEntropyNode:
186
179
  *,
187
180
  ce: ConformationalEntropy,
188
181
  group_id: int,
189
- states_res: Union[Dict[int, StateSequence], Sequence[Optional[StateSequence]]],
182
+ states_res: dict[int, StateSequence] | Sequence[StateSequence | None],
190
183
  n_frames: int,
191
184
  ) -> float:
192
185
  """Compute residue-level entropy for a group."""
193
186
  group_states = self._get_group_states(states_res, group_id)
194
- return self._entropy_or_zero(ce, group_states, n_frames)
187
+ return self._entropy_or_zero(ce, group_states)
195
188
 
196
189
  def _entropy_or_zero(
197
190
  self,
198
191
  ce: ConformationalEntropy,
199
- states: Optional[StateSequence],
200
- n_frames: int,
192
+ states: StateSequence | None,
201
193
  ) -> float:
202
194
  """Return entropy value or zero if no state data exists."""
203
195
  if not self._has_state_data(states):
204
196
  return 0.0
205
- return float(ce.conformational_entropy_calculation(states, n_frames))
197
+ return float(ce.conformational_entropy_calculation(states))
206
198
 
207
199
  @staticmethod
208
200
  def _get_group_states(
209
- states_res: Union[Dict[int, StateSequence], Sequence[Optional[StateSequence]]],
201
+ states_res: dict[int, StateSequence] | Sequence[StateSequence | None],
210
202
  group_id: int,
211
- ) -> Optional[StateSequence]:
203
+ ) -> StateSequence | None:
212
204
  """Fetch group states from container."""
213
205
  if isinstance(states_res, dict):
214
206
  return states_res.get(group_id)
@@ -217,7 +209,7 @@ class ConfigurationalEntropyNode:
217
209
  return None
218
210
 
219
211
  @staticmethod
220
- def _has_state_data(states: Optional[StateSequence]) -> bool:
212
+ def _has_state_data(states: StateSequence | None) -> bool:
221
213
  """Check if state container has usable data."""
222
214
  if states is None:
223
215
  return False
@@ -0,0 +1,84 @@
1
+ """Node for computing orientational entropy from neighbors."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from collections.abc import MutableMapping, Sequence
7
+ from typing import (
8
+ Any,
9
+ )
10
+
11
+ import numpy as np
12
+
13
+ from CodeEntropy.entropy.orientational import OrientationalEntropy
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ GroupId = int
18
+ ResidueId = int
19
+ StateKey = tuple[GroupId, ResidueId]
20
+ StateSequence = Sequence[Any] | np.ndarray
21
+
22
+
23
+ class OrientationalEntropyNode:
24
+ """Compute orientational entropy using precomputed neighbors and symmetry.
25
+
26
+ This node reads number of neighbors and symmetry from ``shared_data`` and
27
+ computes entropy contributions at the molecular (highest) level.
28
+
29
+ Results are written back into ``shared_data["orientational_entropy"]``.
30
+ """
31
+
32
+ def run(self, shared_data: MutableMapping[str, Any], **_: Any) -> dict[str, Any]:
33
+ """Execute orientational entropy calculation.
34
+
35
+ Args:
36
+ shared_data: Shared workflow state dictionary.
37
+
38
+ Returns:
39
+ Dictionary containing orientational entropy results.
40
+
41
+ Raises:
42
+ KeyError: If required keys are missing.
43
+ """
44
+ groups = shared_data["groups"]
45
+ levels = shared_data["levels"]
46
+ neighbors = shared_data["neighbors"]
47
+ symmetry_number = shared_data["symmetry_number"]
48
+ linear = shared_data["linear"]
49
+ reporter = shared_data.get("reporter")
50
+
51
+ oe = self._build_entropy_engine()
52
+
53
+ results: dict[int, float] = {}
54
+
55
+ for group_id, mol_ids in groups.items():
56
+ results[group_id] = 0
57
+ if not mol_ids:
58
+ continue
59
+ rep_mol_id = mol_ids[0]
60
+ highest_level = levels[rep_mol_id][-1]
61
+
62
+ neighbor = neighbors[group_id]
63
+ symmetry = symmetry_number[group_id]
64
+ line = linear[group_id]
65
+
66
+ result_value = oe.calculate(
67
+ neighbor,
68
+ symmetry,
69
+ line,
70
+ )
71
+ results[group_id] = result_value
72
+
73
+ if reporter is not None:
74
+ reporter.add_results_data(
75
+ group_id, highest_level, "Orientational", result_value
76
+ )
77
+
78
+ shared_data["orientational_entropy"] = results
79
+
80
+ return {"orientational_entropy": results}
81
+
82
+ def _build_entropy_engine(self) -> OrientationalEntropy:
83
+ """Create the entropy calculation engine."""
84
+ return OrientationalEntropy()