chemparseplot 1.10.2__tar.gz → 1.12.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 (164) hide show
  1. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/PKG-INFO +18 -9
  2. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/_version.py +2 -2
  3. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/api.py +29 -0
  4. chemparseplot-1.12.0/chemparseplot/contracts/__init__.py +1 -0
  5. chemparseplot-1.12.0/chemparseplot/contracts/load_outcome.py +34 -0
  6. chemparseplot-1.12.0/chemparseplot/parse/benchmark.py +185 -0
  7. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/eon/_trajectory_common.py +3 -2
  8. chemparseplot-1.12.0/chemparseplot/parse/eon/con_io.py +233 -0
  9. chemparseplot-1.12.0/chemparseplot/parse/eon/con_trajectory.py +137 -0
  10. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/eon/neb.py +13 -2
  11. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/eon/stitch.py +31 -6
  12. chemparseplot-1.12.0/chemparseplot/parse/mlneb.py +89 -0
  13. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/neb_utils.py +8 -1
  14. chemparseplot-1.12.0/chemparseplot/parse/surrogate/__init__.py +64 -0
  15. chemparseplot-1.12.0/chemparseplot/parse/surrogate/cases.py +120 -0
  16. chemparseplot-1.12.0/chemparseplot/parse/surrogate/gpr_optim.py +831 -0
  17. chemparseplot-1.12.0/chemparseplot/parse/surrogate/mlneb.py +212 -0
  18. chemparseplot-1.12.0/chemparseplot/parse/surrogate/model.py +381 -0
  19. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/trajectory/hdf5.py +2 -2
  20. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/trajectory/neb.py +1 -1
  21. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/plot/__init__.py +2 -0
  22. chemparseplot-1.12.0/chemparseplot/plot/benchmark.py +454 -0
  23. chemparseplot-1.12.0/chemparseplot/plot/con.py +186 -0
  24. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/plot/disconnectivity.py +6 -16
  25. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/plot/landscape.py +26 -17
  26. chemparseplot-1.12.0/chemparseplot/plot/mlneb.py +70 -0
  27. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/plot/neb.py +87 -14
  28. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/plot/optimization.py +40 -24
  29. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/plot/plumed.py +2 -1
  30. chemparseplot-1.12.0/chemparseplot/plot/provenance.py +122 -0
  31. chemparseplot-1.12.0/chemparseplot/plot/reaction.py +247 -0
  32. chemparseplot-1.12.0/chemparseplot/plot/rings.py +944 -0
  33. chemparseplot-1.12.0/chemparseplot/plot/surrogate.py +2765 -0
  34. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/pyproject.toml +21 -11
  35. chemparseplot-1.12.0/tests/fixtures/restart_exact.con +26 -0
  36. chemparseplot-1.12.0/tests/fixtures/restart_exact_vel.con +35 -0
  37. chemparseplot-1.12.0/tests/fixtures/surrogate/cases/baselines.csv +4 -0
  38. chemparseplot-1.12.0/tests/fixtures/surrogate/cases/cases.csv +6 -0
  39. chemparseplot-1.12.0/tests/fixtures/surrogate/mlneb/evaluated.traj +0 -0
  40. chemparseplot-1.12.0/tests/fixtures/surrogate/mlneb/ml_summary.txt +6 -0
  41. chemparseplot-1.12.0/tests/fixtures/surrogate/mlneb/ml_time.txt +6 -0
  42. chemparseplot-1.12.0/tests/fixtures/surrogate/mlneb/predicted.traj +0 -0
  43. chemparseplot-1.12.0/tests/fixtures/surrogate/record/baker/25_hcnh2/band/band.h5 +0 -0
  44. chemparseplot-1.12.0/tests/fixtures/surrogate/record/baker/25_hcnh2/band/obs-archive/kernel.txt +7 -0
  45. chemparseplot-1.12.0/tests/fixtures/surrogate/record/baker/25_hcnh2/result.json +35 -0
  46. chemparseplot-1.12.0/tests/fixtures/surrogate/record/baker/25_hcnh2/saddle/pos.con +20 -0
  47. chemparseplot-1.12.0/tests/fixtures/surrogate/record/birkholz/16_oxirane/result.json +34 -0
  48. chemparseplot-1.12.0/tests/fixtures/surrogate/record/birkholz/16_oxirane/saddle/mlflow.run.json +1 -0
  49. chemparseplot-1.12.0/tests/fixtures/surrogate/record/mlruns/606078088088587/7eaf5653201fe6b92f3e3831bead3063/metrics/dimer.curvature +42 -0
  50. chemparseplot-1.12.0/tests/fixtures/surrogate/record/mlruns/606078088088587/7eaf5653201fe6b92f3e3831bead3063/metrics/dimer.force +42 -0
  51. chemparseplot-1.12.0/tests/fixtures/surrogate/record/mlruns/606078088088587/7eaf5653201fe6b92f3e3831bead3063/metrics/dimer.measured_curvature +2 -0
  52. chemparseplot-1.12.0/tests/fixtures/surrogate/record/mlruns/606078088088587/7eaf5653201fe6b92f3e3831bead3063/metrics/dimer.oracle_calls +42 -0
  53. chemparseplot-1.12.0/tests/fixtures/surrogate/record/mlruns/606078088088587/7eaf5653201fe6b92f3e3831bead3063/metrics/dimer.surrogate_curvature +2 -0
  54. chemparseplot-1.12.0/tests/fixtures/surrogate/scaling/breakdown.csv +25 -0
  55. chemparseplot-1.12.0/tests/fixtures/surrogate/scaling/components.csv +13 -0
  56. chemparseplot-1.12.0/tests/fixtures/surrogate/scaling/counts.csv +19 -0
  57. chemparseplot-1.12.0/tests/fixtures/surrogate/scaling/pop.csv +7 -0
  58. chemparseplot-1.12.0/tests/fixtures/surrogate/scaling/wall.csv +37 -0
  59. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/_con_fixtures.py +20 -0
  60. chemparseplot-1.12.0/tests/parse/test_con_io.py +317 -0
  61. chemparseplot-1.12.0/tests/parse/test_con_trajectory.py +114 -0
  62. chemparseplot-1.12.0/tests/parse/test_con_units_and_corpus.py +130 -0
  63. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_dimer_trajectory.py +9 -9
  64. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_min_trajectory.py +12 -11
  65. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_neb_stitch.py +46 -0
  66. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_neb_utils.py +8 -0
  67. chemparseplot-1.12.0/tests/parse/test_readcon_boundary.py +168 -0
  68. chemparseplot-1.12.0/tests/parse/test_surrogate_gpr_optim.py +195 -0
  69. chemparseplot-1.12.0/tests/parse/test_surrogate_mlneb.py +49 -0
  70. chemparseplot-1.12.0/tests/plot/data/sexithiophene.sdf +458 -0
  71. chemparseplot-1.12.0/tests/plot/test_benchmark.py +126 -0
  72. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/plot/test_landscape_disconnectivity.py +4 -12
  73. chemparseplot-1.12.0/tests/plot/test_mlneb.py +48 -0
  74. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/plot/test_optimization.py +37 -0
  75. chemparseplot-1.12.0/tests/plot/test_primitive_rings.py +107 -0
  76. chemparseplot-1.12.0/tests/plot/test_provenance.py +57 -0
  77. chemparseplot-1.12.0/tests/plot/test_reaction.py +70 -0
  78. chemparseplot-1.12.0/tests/plot/test_ring_track.py +207 -0
  79. chemparseplot-1.12.0/tests/plot/test_ring_updater.py +22 -0
  80. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/plot/test_strip_rendering.py +37 -0
  81. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/plot/test_surface_auto_thin.py +12 -10
  82. chemparseplot-1.12.0/tests/plot/test_surrogate_plots.py +1018 -0
  83. chemparseplot-1.12.0/tests/test_load_outcome.py +14 -0
  84. chemparseplot-1.10.2/chemparseplot/parse/eon/con_io.py +0 -136
  85. chemparseplot-1.10.2/tests/parse/test_con_io.py +0 -237
  86. chemparseplot-1.10.2/tests/parse/test_readcon_boundary.py +0 -80
  87. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/.gitignore +0 -0
  88. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/LICENSE +0 -0
  89. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/__init__.py +0 -0
  90. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/__init__.py +0 -0
  91. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/chemgp_hdf5.py +0 -0
  92. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/chemgp_jsonl.py +0 -0
  93. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/converter.py +0 -0
  94. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/eon/__init__.py +0 -0
  95. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/eon/dimer_trajectory.py +0 -0
  96. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/eon/frame_series.py +0 -0
  97. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/eon/gprd.py +0 -0
  98. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/eon/min_trajectory.py +0 -0
  99. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/eon/minimization.py +0 -0
  100. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/eon/saddle_search.py +0 -0
  101. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/file_.py +0 -0
  102. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/grammar/__init__.py +0 -0
  103. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/grammar/_deps.py +0 -0
  104. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/grammar/orca_text.py +0 -0
  105. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/grammar/xyz.py +0 -0
  106. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/orca/__init__.py +0 -0
  107. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/orca/_opi.py +0 -0
  108. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/orca/geomscan.py +0 -0
  109. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/orca/neb/__init__.py +0 -0
  110. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/orca/neb/interp.py +0 -0
  111. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/orca/neb/opi_parser.py +0 -0
  112. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/patterns.py +0 -0
  113. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/plumed.py +0 -0
  114. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/projection.py +0 -0
  115. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/sella/saddle_search.py +0 -0
  116. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/trajectory/__init__.py +0 -0
  117. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/types.py +0 -0
  118. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/plot/chemgp.py +0 -0
  119. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/plot/geomscan.py +0 -0
  120. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/plot/structs.py +0 -0
  121. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/plot/theme.py +0 -0
  122. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/units.py +0 -0
  123. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/util.py +0 -0
  124. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/readme.md +0 -0
  125. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/__init__.py +0 -0
  126. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/_optional_imports.py +0 -0
  127. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/conftest.py +0 -0
  128. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/fixtures/h2mol.xyz +0 -0
  129. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/fixtures/orca_energy_coords_snippet.out +0 -0
  130. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/fixtures_geomscan_snippet.txt +0 -0
  131. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/__init__.py +0 -0
  132. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/grammar/__init__.py +0 -0
  133. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/grammar/test_grammar_deps.py +0 -0
  134. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/grammar/test_orca_text_grammar.py +0 -0
  135. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/grammar/test_xyz_grammar.py +0 -0
  136. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/orca/test_geomscan.py +0 -0
  137. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/orca/test_interp.py +0 -0
  138. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_chemgp_hdf5.py +0 -0
  139. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_chemgp_jsonl_types.py +0 -0
  140. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_converter.py +0 -0
  141. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_eon_neb_types.py +0 -0
  142. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_eon_trajectory_common.py +0 -0
  143. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_frame_series.py +0 -0
  144. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_minimization_result.py +0 -0
  145. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_orca_public_api.py +0 -0
  146. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_parser_types.py +0 -0
  147. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_patterns.py +0 -0
  148. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_plumed.py +0 -0
  149. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_projection.py +0 -0
  150. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_saddle_results.py +0 -0
  151. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_sella_saddle.py +0 -0
  152. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_trajectory_hdf5.py +0 -0
  153. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_trajectory_neb.py +0 -0
  154. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/plot/__init__.py +0 -0
  155. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/plot/test_chemgp_utils.py +0 -0
  156. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/plot/test_neb_helpers.py +0 -0
  157. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/plot/test_neb_renderers.py +0 -0
  158. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/plot/test_orca_neb_payload.py +0 -0
  159. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/plot/test_projection_refactor.py +0 -0
  160. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/plot/test_structs_units.py +0 -0
  161. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/test_coverage_batch.py +0 -0
  162. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/test_full_coverage.py +0 -0
  163. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/test_public_api.py +0 -0
  164. {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/tutorials/test_chemparseplot.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: chemparseplot
3
- Version: 1.10.2
3
+ Version: 1.12.0
4
4
  Summary: Parsers and plotting tools for computational chemistry
5
5
  Project-URL: Documentation, https://chemparseplot.rgoswami.me
6
6
  Project-URL: Issues, https://github.com/HaoZeke/chemparseplot/issues
@@ -13,7 +13,6 @@ Keywords: compchem,parser,plot
13
13
  Classifier: Development Status :: 4 - Beta
14
14
  Classifier: Programming Language :: Python
15
15
  Classifier: Programming Language :: Python :: 3 :: Only
16
- Classifier: Programming Language :: Python :: 3.10
17
16
  Classifier: Programming Language :: Python :: 3.11
18
17
  Classifier: Programming Language :: Python :: 3.12
19
18
  Classifier: Programming Language :: Python :: Implementation :: CPython
@@ -28,8 +27,11 @@ Requires-Dist: matplotlib>=3.8.2; extra == 'all'
28
27
  Requires-Dist: orca-pi>=0.1.0; extra == 'all'
29
28
  Requires-Dist: parsimonious>=0.10.0; extra == 'all'
30
29
  Requires-Dist: polars>=0.20; extra == 'all'
31
- Requires-Dist: readcon>=0.13.1; extra == 'all'
32
- Requires-Dist: xyzrender>=0.1.2; extra == 'all'
30
+ Requires-Dist: pydseamslib>=2.11.1; extra == 'all'
31
+ Requires-Dist: readcon-db>=0.2.0; extra == 'all'
32
+ Requires-Dist: readcon>=0.17.1; extra == 'all'
33
+ Requires-Dist: resvg-py>=0.5.0; extra == 'all'
34
+ Requires-Dist: xyzrender>=0.3.8; extra == 'all'
33
35
  Provides-Extra: doc
34
36
  Requires-Dist: mdit-py-plugins>=0.3.4; extra == 'doc'
35
37
  Requires-Dist: myst-nb>=1; extra == 'doc'
@@ -51,7 +53,8 @@ Provides-Extra: neb
51
53
  Requires-Dist: ase>=3.22; extra == 'neb'
52
54
  Requires-Dist: h5py>=3.0; extra == 'neb'
53
55
  Requires-Dist: polars>=0.20; extra == 'neb'
54
- Requires-Dist: readcon>=0.13.1; extra == 'neb'
56
+ Requires-Dist: readcon-db>=0.2.0; extra == 'neb'
57
+ Requires-Dist: readcon>=0.17.1; extra == 'neb'
55
58
  Provides-Extra: opi
56
59
  Requires-Dist: orca-pi>=0.1.0; extra == 'opi'
57
60
  Provides-Extra: plot
@@ -62,6 +65,9 @@ Requires-Dist: build>=0.10; extra == 'release'
62
65
  Requires-Dist: towncrier>=24.8.0; extra == 'release'
63
66
  Requires-Dist: twine>=4.0; extra == 'release'
64
67
  Requires-Dist: wheel>=0.40; extra == 'release'
68
+ Provides-Extra: rings
69
+ Requires-Dist: pydseamslib>=2.11.1; extra == 'rings'
70
+ Requires-Dist: xyzrender>=0.3.8; extra == 'rings'
65
71
  Provides-Extra: test
66
72
  Requires-Dist: ase>=3.22; extra == 'test'
67
73
  Requires-Dist: h5py>=3.0; extra == 'test'
@@ -69,14 +75,17 @@ Requires-Dist: jax[cpu]>=0.4; extra == 'test'
69
75
  Requires-Dist: matplotlib>=3.8.2; extra == 'test'
70
76
  Requires-Dist: parsimonious>=0.10.0; extra == 'test'
71
77
  Requires-Dist: polars>=0.20; extra == 'test'
78
+ Requires-Dist: pydseamslib>=2.11.1; extra == 'test'
72
79
  Requires-Dist: pytest-cov>=4.1.0; extra == 'test'
73
80
  Requires-Dist: pytest>=7.4.3; extra == 'test'
74
- Requires-Dist: readcon>=0.13.1; extra == 'test'
81
+ Requires-Dist: readcon-db>=0.2.0; extra == 'test'
82
+ Requires-Dist: readcon>=0.17.1; extra == 'test'
75
83
  Requires-Dist: rgpycrumbs; extra == 'test'
76
84
  Requires-Dist: scipy>=1.11; extra == 'test'
77
- Requires-Dist: xyzrender>=0.1.3; extra == 'test'
85
+ Requires-Dist: xyzrender>=0.3.8; extra == 'test'
78
86
  Provides-Extra: xyzrender
79
- Requires-Dist: xyzrender>=0.1.2; extra == 'xyzrender'
87
+ Requires-Dist: resvg-py>=0.5.0; extra == 'xyzrender'
88
+ Requires-Dist: xyzrender>=0.3.8; extra == 'xyzrender'
80
89
  Description-Content-Type: text/markdown
81
90
 
82
91
 
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '1.10.2'
22
- __version_tuple__ = version_tuple = (1, 10, 2)
21
+ __version__ = version = '1.12.0'
22
+ __version_tuple__ = version_tuple = (1, 12, 0)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -46,9 +46,12 @@ __all__ = [
46
46
  "energy_quantity",
47
47
  "extract_orca_geomscan_energy",
48
48
  "grammar_available",
49
+ "load_trajectory",
49
50
  "normalize_energy_unit",
50
51
  "parse_orca_final_energy",
51
52
  "parse_xyz",
53
+ "plot_con_overlay",
54
+ "plot_con_overview",
52
55
  "suite_pins",
53
56
  ]
54
57
 
@@ -124,6 +127,32 @@ def extract_orca_geomscan_energy(
124
127
  return extract_energy_data(data, energy_type)
125
128
 
126
129
 
130
+ def load_trajectory(path: str):
131
+ """Load a ``.con`` / ``.convel`` file, or an ASE trajectory of another suffix.
132
+
133
+ ``.con`` goes through :mod:`chemparseplot.parse.eon.con_io`. ``fmax`` comes
134
+ from ``frame.frc`` when a forces section is present. The extxyz reader in
135
+ :mod:`chemparseplot.parse.trajectory.neb` is a different function.
136
+ """
137
+ from chemparseplot.parse.eon.con_trajectory import load_trajectory as _load
138
+
139
+ return _load(path)
140
+
141
+
142
+ def plot_con_overview(*args, **kwargs):
143
+ """Draw one loaded trajectory. This does not parse a file."""
144
+ from chemparseplot.plot.con import plot_con_overview as _plot
145
+
146
+ return _plot(*args, **kwargs)
147
+
148
+
149
+ def plot_con_overlay(*args, **kwargs):
150
+ """Draw several loaded trajectories on one axes. This does not parse."""
151
+ from chemparseplot.plot.con import plot_con_overlay as _plot
152
+
153
+ return _plot(*args, **kwargs)
154
+
155
+
127
156
  def suite_pins() -> dict[str, str]:
128
157
  """Return suite package pins from rgpkgs config + env (soft on old hub).
129
158
 
@@ -0,0 +1 @@
1
+ """Readers for control-plane contracts."""
@@ -0,0 +1,34 @@
1
+ """Decode an eOn Outcome JSON record.
2
+
3
+ The record is job type and status. Trajectories stay readcon frames.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ from collections.abc import Mapping
10
+ from typing import Any
11
+
12
+ _FIELDS = ("jobType", "statusCode", "statusText")
13
+
14
+
15
+ def load_outcome(payload: str | Mapping[str, Any]) -> dict[str, Any]:
16
+ """Return job type and status from the outcome JSON debug form."""
17
+ if isinstance(payload, str):
18
+ data = json.loads(payload)
19
+ else:
20
+ data = dict(payload)
21
+ if not isinstance(data, dict):
22
+ message = "outcome payload must be an object"
23
+ raise TypeError(message)
24
+ if "positions" in data or "trajectory" in data:
25
+ message = "trajectories stay readcon"
26
+ raise ValueError(message)
27
+ missing = [key for key in _FIELDS if key not in data]
28
+ if missing:
29
+ raise KeyError(",".join(missing))
30
+ return {
31
+ "jobType": str(data["jobType"]),
32
+ "statusCode": int(data["statusCode"]),
33
+ "statusText": str(data["statusText"]),
34
+ }
@@ -0,0 +1,185 @@
1
+ """Paired search costs and complete reaction outcomes from campaign tables."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import csv
6
+ import math
7
+ from pathlib import Path
8
+
9
+
10
+ def load_benchmark_exports(paths, panels):
11
+ """Read verified cells.csv exports without silently dropping failed cases.
12
+
13
+ ``panels`` maps each panel name to its expected number of reactions. Costs
14
+ include initialization when the export provides that count. A repeated GP
15
+ reference must agree, including its result hash and source revision.
16
+ """
17
+ records, references = {}, {}
18
+
19
+ def flag(value):
20
+ if value not in ("True", "False"):
21
+ raise ValueError(f"Invalid acceptance: {value!r}")
22
+ return value == "True"
23
+
24
+ def number(value):
25
+ if value in (None, ""):
26
+ return None
27
+ result = float(value)
28
+ if not math.isfinite(result) or result < 0:
29
+ msg = "Costs must be finite and nonnegative"
30
+ raise ValueError(msg)
31
+ return result
32
+
33
+ for path in paths:
34
+ with Path(path).open(newline="") as stream:
35
+ for raw in csv.DictReader(stream):
36
+ panel, method, label = raw["set"], raw["method"], raw["label"]
37
+ if panel not in panels or method == "gpridge":
38
+ raise ValueError(f"Unexpected export group: {panel}/{method}")
39
+ key = panel, method, label
40
+ if key in records:
41
+ raise ValueError(f"Duplicate measurement: {key}")
42
+ records[key] = {
43
+ "set": panel,
44
+ "method": method,
45
+ "label": label,
46
+ "accepted": flag(raw["accepted"]),
47
+ "search_calls": number(
48
+ raw.get("search_calls_with_initialization", raw["search_calls"])
49
+ ),
50
+ "search_wall_s": number(raw["search_wall_s"]),
51
+ "error": raw.get("error", ""),
52
+ "run_sha256": raw["run_sha256"],
53
+ }
54
+ reference = {
55
+ "set": panel,
56
+ "method": "gpridge",
57
+ "label": label,
58
+ "accepted": flag(raw["gp_accepted"]),
59
+ "search_calls": number(raw["gp_search_calls"]),
60
+ "search_wall_s": number(raw["gp_search_wall_s"]),
61
+ "run_sha256": raw["gp_result_sha256"],
62
+ "source_revision": raw["gp_revision"],
63
+ }
64
+ refkey = panel, "gpridge", label
65
+ if refkey in references and references[refkey] != reference:
66
+ raise ValueError(f"Conflicting reference: {refkey}")
67
+ references[refkey] = reference
68
+ records.update(references)
69
+ for panel, count in panels.items():
70
+ expected = {label for p, m, label in references if p == panel}
71
+ if len(expected) != count:
72
+ raise ValueError(f"Incomplete reference panel: {panel}")
73
+ for method in {m for p, m, _ in records if p == panel}:
74
+ labels = {label for p, m, label in records if p == panel and m == method}
75
+ if labels != expected:
76
+ raise ValueError(f"Incomplete campaign: {panel}/{method}")
77
+ for row in records.values():
78
+ if row["accepted"] and any(
79
+ row[k] is None or row[k] <= 0 for k in ("search_calls", "search_wall_s")
80
+ ):
81
+ msg = "Accepted searches require positive measured costs"
82
+ raise ValueError(msg)
83
+ return list(records.values())
84
+
85
+
86
+ def paired_costs(rows, panel, method, reference="gpridge"):
87
+ """Ratio of total costs on the exact intersection of accepted reactions."""
88
+ selected = {
89
+ r["label"]: r for r in rows if r["set"] == panel and r["method"] == method
90
+ }
91
+ refs = {r["label"]: r for r in rows if r["set"] == panel and r["method"] == reference}
92
+ if set(selected) != set(refs):
93
+ msg = "Methods must contain the same complete reaction panel"
94
+ raise ValueError(msg)
95
+ labels = sorted(
96
+ k for k in selected if selected[k]["accepted"] and refs[k]["accepted"]
97
+ )
98
+ result = {
99
+ "labels": labels,
100
+ "accepted": sum(r["accepted"] for r in selected.values()),
101
+ "reference_accepted": sum(r["accepted"] for r in refs.values()),
102
+ "total": len(refs),
103
+ }
104
+ for metric in ("search_calls", "search_wall_s"):
105
+ a = sum(selected[k][metric] for k in labels)
106
+ b = sum(refs[k][metric] for k in labels)
107
+ result[metric] = {
108
+ "method_total": a,
109
+ "reference_total": b,
110
+ "ratio": a / b if labels else None,
111
+ }
112
+ return result
113
+
114
+
115
+ def performance_ratios(rows, panel, methods, metric):
116
+ """Return cost/best accepted cost; failed checks retain their denominator."""
117
+ selected = [r for r in rows if r["set"] == panel and r["method"] in methods]
118
+ group = {(r["method"], r["label"]): r for r in selected}
119
+ if len(group) != len(selected):
120
+ msg = "Duplicate reaction in performance profile"
121
+ raise ValueError(msg)
122
+ labels = sorted({r["label"] for r in selected})
123
+ if not labels or any((m, label) not in group for m in methods for label in labels):
124
+ msg = "Methods must contain the same complete reaction set"
125
+ raise ValueError(msg)
126
+ ratios = {m: [] for m in methods}
127
+ for label in labels:
128
+ costs = {}
129
+ for method in methods:
130
+ row = group[method, label]
131
+ if row["accepted"]:
132
+ value = row[metric]
133
+ if not math.isfinite(value) or value <= 0:
134
+ msg = "Accepted performance costs must be finite and positive"
135
+ raise ValueError(msg)
136
+ costs[method] = value
137
+ best = min(costs.values(), default=None)
138
+ for method in methods:
139
+ ratios[method].append(costs[method] / best if method in costs else None)
140
+ return {"labels": labels, "ratios": ratios}
141
+
142
+
143
+ def cost_frontier(rows, panel, methods, *, exclude=("plain",)):
144
+ """Mean costs on the same successful cases, retaining full-panel acceptance.
145
+
146
+ Equal points both belong to the frontier. A method dominates another only
147
+ when neither cost is larger and at least one is strictly smaller.
148
+ """
149
+ checked = performance_ratios(rows, panel, methods, "search_calls")
150
+ performance_ratios(rows, panel, methods, "search_wall_s")
151
+ labels = checked["labels"]
152
+ group = {(r["method"], r["label"]): r for r in rows if r["set"] == panel}
153
+ locators = [m for m in methods if m not in exclude]
154
+ common = [k for k in labels if all(group[m, k]["accepted"] for m in locators)]
155
+ points = []
156
+ if locators and common:
157
+ for method in locators:
158
+ points.append(
159
+ {
160
+ "method": method,
161
+ **{
162
+ f"mean_{key}": sum(group[method, k][key] for k in common)
163
+ / len(common)
164
+ for key in ("search_calls", "search_wall_s")
165
+ },
166
+ }
167
+ )
168
+ for point in points:
169
+ point["nondominated"] = not any(
170
+ all(
171
+ other[key] <= point[key]
172
+ for key in ("mean_search_calls", "mean_search_wall_s")
173
+ )
174
+ and any(
175
+ other[key] < point[key]
176
+ for key in ("mean_search_calls", "mean_search_wall_s")
177
+ )
178
+ for other in points
179
+ )
180
+ return {
181
+ "common_accepted_labels": common if locators else [],
182
+ "points": points,
183
+ "acceptance": {m: sum(group[m, k]["accepted"] for k in labels) for m in methods},
184
+ "cases": len(labels),
185
+ }
@@ -124,9 +124,10 @@ def metadata_value(frame: object, key: str) -> Any:
124
124
  """Return a typed metadata value from a readcon frame."""
125
125
 
126
126
  if key == "frame_index":
127
- return frame.frame_index
127
+ value = frame.metadata.get(key)
128
+ return frame.frame_index if value is None else value
128
129
  if key == "energy":
129
- return frame.energy
130
+ return con_io.frame_energy_ev(frame)
130
131
  return frame.metadata.get(key)
131
132
 
132
133
 
@@ -0,0 +1,233 @@
1
+ # SPDX-FileCopyrightText: 2023-present Rohit Goswami <rog32@hi.is>
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+ """Public readcon-backed CON/convel helpers for eOn parsers.
5
+
6
+ All ``.con`` / ``.convel`` I/O in chemparseplot goes through this module.
7
+ ``readcon`` stays an optional extra (``chemparseplot[neb]``). Import errors
8
+ surface only when these helpers are called. Writers omit precision and leave
9
+ canonical off, which is RoundTrip on ``readcon>=0.17.1``. Corpus selection
10
+ opens stores read-only and decodes selected frames as they are consumed.
11
+
12
+ ```{versionadded} 1.8.1
13
+ ```
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from collections.abc import Iterator, Sequence
19
+ from copy import deepcopy
20
+ from pathlib import Path
21
+ from typing import Any
22
+
23
+ import numpy as np
24
+
25
+ __all__ = [
26
+ "CON_SUFFIXES",
27
+ "frame_energy_ev",
28
+ "frame_positions_angstrom",
29
+ "frame_with_energy",
30
+ "frame_with_positions",
31
+ "is_con_path",
32
+ "iter_con_frames",
33
+ "iter_corpus_frames",
34
+ "read_con_as_ase",
35
+ "read_con_frames",
36
+ "read_first_atoms",
37
+ "read_first_frame",
38
+ "write_atoms_as_con",
39
+ "write_con_frames",
40
+ ]
41
+
42
+ CON_SUFFIXES = (".con", ".convel")
43
+
44
+ _READCON_HINT = (
45
+ "readcon is required for CON I/O. Install with: "
46
+ "pip install 'chemparseplot[neb]' (readcon>=0.17.1)"
47
+ )
48
+
49
+
50
+ def is_con_path(path: str | Path) -> bool:
51
+ """Return True when *path* looks like an eOn CON/convel file."""
52
+ return Path(path).suffix.lower() in CON_SUFFIXES
53
+
54
+
55
+ def _readcon():
56
+ try:
57
+ import readcon
58
+ except ImportError as exc: # pragma: no cover - exercised when extra missing
59
+ raise ImportError(_READCON_HINT) from exc
60
+ return readcon
61
+
62
+
63
+ def _metadata_dict(frame: Any) -> dict[str, Any]:
64
+ metadata = deepcopy(frame.metadata)
65
+ if hasattr(metadata, "items"):
66
+ return {str(key): value for key, value in metadata.items()}
67
+ return dict(metadata or {})
68
+
69
+
70
+ def _clone_frame(frame: Any) -> Any:
71
+ """Copy a frame through the ``ConFrame`` constructor so Atom sections travel."""
72
+ readcon = _readcon()
73
+ return readcon.ConFrame(
74
+ frame.cell,
75
+ frame.angles,
76
+ list(frame.atoms),
77
+ frame.prebox_header,
78
+ frame.postbox_header,
79
+ _metadata_dict(frame),
80
+ )
81
+
82
+
83
+ def read_con_frames(path: str | Path) -> list[Any]:
84
+ """Read all frames from a CON/convel file as ``readcon.ConFrame`` objects."""
85
+ readcon = _readcon()
86
+ return list(readcon.read_con(str(path)))
87
+
88
+
89
+ def iter_con_frames(path: str | Path) -> Iterator[Any]:
90
+ """Yield CON frames without retaining the complete trajectory."""
91
+ yield from _readcon().iter_con(str(path))
92
+
93
+
94
+ def iter_corpus_frames(
95
+ path: str | Path,
96
+ *,
97
+ trajectory: int | None = None,
98
+ require_energy: bool = False,
99
+ require_forces: bool = False,
100
+ ) -> Iterator[Any]:
101
+ """Yield selected stored frames in trajectory and frame-index order.
102
+
103
+ The corpus is opened read-only. Frame decoding is lazy; selection retains
104
+ only the trajectory and frame keys, including for compressed stores.
105
+ """
106
+ from readcon_db import ConCorpus
107
+
108
+ corpus = ConCorpus(str(path), readonly=True)
109
+ keys = corpus.select(
110
+ traj_id=trajectory, require_energy=require_energy, require_forces=require_forces
111
+ )
112
+ for trajectory_id, frame_index in sorted(keys):
113
+ yield corpus.get_frame(trajectory_id, frame_index)
114
+
115
+
116
+ def _energy_unit(frame: Any) -> str:
117
+ return frame.metadata.get("units", {}).get("energy", "eV")
118
+
119
+
120
+ def _unit_factor(source: str, target: str) -> float:
121
+ if source == target:
122
+ return 1.0
123
+ from readcon_db import unit_conversion_factor
124
+
125
+ return unit_conversion_factor(source, target)
126
+
127
+
128
+ def frame_energy_ev(frame: Any) -> float | None:
129
+ """Return the stored total energy in eV without evaluating a potential."""
130
+ value = frame.metadata.get("energy")
131
+ if value is None:
132
+ value = getattr(frame, "energy", None)
133
+ if value is None:
134
+ return None
135
+ return float(value) * _unit_factor(_energy_unit(frame), "eV")
136
+
137
+
138
+ def read_con_as_ase(path: str | Path) -> list:
139
+ """Read all frames from a CON/convel file as ASE ``Atoms`` objects."""
140
+ readcon = _readcon()
141
+ return list(readcon.read_con_as_ase(str(path)))
142
+
143
+
144
+ def read_first_frame(path: str | Path) -> Any:
145
+ """Read the first CON frame and stop. Later frames are not parsed."""
146
+ readcon = _readcon()
147
+ return readcon.read_first_frame(str(path))
148
+
149
+
150
+ def read_first_atoms(path: str | Path):
151
+ """Read the first frame of a CON file as an ASE ``Atoms`` object."""
152
+ return read_first_frame(path).to_ase()
153
+
154
+
155
+ def frame_positions_angstrom(frame: Any) -> np.ndarray:
156
+ """Return Cartesian positions in Angstrom using the declared length unit."""
157
+ source = frame.metadata.get("units", {}).get("length", "angstrom")
158
+ return np.asarray(frame.xyz, dtype=float) * _unit_factor(source, "angstrom")
159
+
160
+
161
+ def frame_with_energy(frame: Any, energy: float) -> Any:
162
+ """Return a ``ConFrame`` copy with the supplied total energy in eV.
163
+
164
+ The stored number uses the frame's declared energy unit. Geometry, atom
165
+ sections, and their unit declarations are retained. ``ConFrame.set_energy``
166
+ validates that the stored value is finite.
167
+ """
168
+ clone = _clone_frame(frame)
169
+ value = float(energy) * _unit_factor("eV", _energy_unit(frame))
170
+ setter = getattr(clone, "set_energy", None)
171
+ if not callable(setter):
172
+ raise ImportError(_READCON_HINT)
173
+ setter(value)
174
+ return clone
175
+
176
+
177
+ def frame_with_positions(frame: Any, positions: Any) -> Any:
178
+ """Return a ``ConFrame`` copy with Cartesian positions replaced.
179
+
180
+ Each ``Atom`` row is copied and only ``x`` / ``y`` / ``z`` are written.
181
+ ``fixed``, ``atom_id``, ``mass``, velocities, forces, displacements, and
182
+ spreads stay. That is correct for an unmoved slice or a pure translation.
183
+ It is the wrong helper for ``displacement.con``: a rotation or a reorder
184
+ must drop those sections and let eOn recompute them.
185
+ """
186
+ coords = np.asarray(positions, dtype=np.float64)
187
+ n_atoms = len(frame.atoms)
188
+ if coords.shape != (n_atoms, 3):
189
+ msg = f"positions must have shape ({n_atoms}, 3), got {tuple(coords.shape)}"
190
+ raise ValueError(msg)
191
+ clone = _clone_frame(frame)
192
+ for atom, xyz in zip(clone.atoms, coords, strict=True):
193
+ atom.x = xyz[0].item()
194
+ atom.y = xyz[1].item()
195
+ atom.z = xyz[2].item()
196
+ return clone
197
+
198
+
199
+ def write_con_frames(path: str | Path, frames: Sequence[Any]) -> Path:
200
+ """Write ``ConFrame`` objects to *path* via ``readcon.write_con``.
201
+
202
+ Precision is omitted and canonical stays off. There is no corpus write.
203
+ """
204
+ readcon = _readcon()
205
+ out = Path(path)
206
+ out.parent.mkdir(parents=True, exist_ok=True)
207
+ readcon.write_con(str(out), list(frames))
208
+ return out
209
+
210
+
211
+ def write_atoms_as_con(
212
+ path: str | Path,
213
+ atoms_list: Sequence[Any],
214
+ *,
215
+ energies: Sequence[float | None] | None = None,
216
+ ) -> Path:
217
+ """Convert ASE ``Atoms`` that were never a ``ConFrame`` and write CON.
218
+
219
+ Optional *energies* (eV) are attached per frame with
220
+ :func:`frame_with_energy`. ``from_ase`` drops displacements and spreads.
221
+ ASE tags become atom ids only when they are distinct and non-negative;
222
+ otherwise ids are sequential. Two atoms of one symbol whose masses
223
+ disagree past the reader tolerance fail at ``build``. Precision is
224
+ omitted and canonical stays off.
225
+ """
226
+ readcon = _readcon()
227
+ frames = []
228
+ for idx, atoms in enumerate(atoms_list):
229
+ frame = readcon.ConFrame.from_ase(atoms)
230
+ if energies is not None and idx < len(energies) and energies[idx] is not None:
231
+ frame = frame_with_energy(frame, float(energies[idx]))
232
+ frames.append(frame)
233
+ return write_con_frames(path, frames)