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.
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/PKG-INFO +18 -9
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/_version.py +2 -2
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/api.py +29 -0
- chemparseplot-1.12.0/chemparseplot/contracts/__init__.py +1 -0
- chemparseplot-1.12.0/chemparseplot/contracts/load_outcome.py +34 -0
- chemparseplot-1.12.0/chemparseplot/parse/benchmark.py +185 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/eon/_trajectory_common.py +3 -2
- chemparseplot-1.12.0/chemparseplot/parse/eon/con_io.py +233 -0
- chemparseplot-1.12.0/chemparseplot/parse/eon/con_trajectory.py +137 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/eon/neb.py +13 -2
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/eon/stitch.py +31 -6
- chemparseplot-1.12.0/chemparseplot/parse/mlneb.py +89 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/neb_utils.py +8 -1
- chemparseplot-1.12.0/chemparseplot/parse/surrogate/__init__.py +64 -0
- chemparseplot-1.12.0/chemparseplot/parse/surrogate/cases.py +120 -0
- chemparseplot-1.12.0/chemparseplot/parse/surrogate/gpr_optim.py +831 -0
- chemparseplot-1.12.0/chemparseplot/parse/surrogate/mlneb.py +212 -0
- chemparseplot-1.12.0/chemparseplot/parse/surrogate/model.py +381 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/trajectory/hdf5.py +2 -2
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/trajectory/neb.py +1 -1
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/plot/__init__.py +2 -0
- chemparseplot-1.12.0/chemparseplot/plot/benchmark.py +454 -0
- chemparseplot-1.12.0/chemparseplot/plot/con.py +186 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/plot/disconnectivity.py +6 -16
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/plot/landscape.py +26 -17
- chemparseplot-1.12.0/chemparseplot/plot/mlneb.py +70 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/plot/neb.py +87 -14
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/plot/optimization.py +40 -24
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/plot/plumed.py +2 -1
- chemparseplot-1.12.0/chemparseplot/plot/provenance.py +122 -0
- chemparseplot-1.12.0/chemparseplot/plot/reaction.py +247 -0
- chemparseplot-1.12.0/chemparseplot/plot/rings.py +944 -0
- chemparseplot-1.12.0/chemparseplot/plot/surrogate.py +2765 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/pyproject.toml +21 -11
- chemparseplot-1.12.0/tests/fixtures/restart_exact.con +26 -0
- chemparseplot-1.12.0/tests/fixtures/restart_exact_vel.con +35 -0
- chemparseplot-1.12.0/tests/fixtures/surrogate/cases/baselines.csv +4 -0
- chemparseplot-1.12.0/tests/fixtures/surrogate/cases/cases.csv +6 -0
- chemparseplot-1.12.0/tests/fixtures/surrogate/mlneb/evaluated.traj +0 -0
- chemparseplot-1.12.0/tests/fixtures/surrogate/mlneb/ml_summary.txt +6 -0
- chemparseplot-1.12.0/tests/fixtures/surrogate/mlneb/ml_time.txt +6 -0
- chemparseplot-1.12.0/tests/fixtures/surrogate/mlneb/predicted.traj +0 -0
- chemparseplot-1.12.0/tests/fixtures/surrogate/record/baker/25_hcnh2/band/band.h5 +0 -0
- chemparseplot-1.12.0/tests/fixtures/surrogate/record/baker/25_hcnh2/band/obs-archive/kernel.txt +7 -0
- chemparseplot-1.12.0/tests/fixtures/surrogate/record/baker/25_hcnh2/result.json +35 -0
- chemparseplot-1.12.0/tests/fixtures/surrogate/record/baker/25_hcnh2/saddle/pos.con +20 -0
- chemparseplot-1.12.0/tests/fixtures/surrogate/record/birkholz/16_oxirane/result.json +34 -0
- chemparseplot-1.12.0/tests/fixtures/surrogate/record/birkholz/16_oxirane/saddle/mlflow.run.json +1 -0
- chemparseplot-1.12.0/tests/fixtures/surrogate/record/mlruns/606078088088587/7eaf5653201fe6b92f3e3831bead3063/metrics/dimer.curvature +42 -0
- chemparseplot-1.12.0/tests/fixtures/surrogate/record/mlruns/606078088088587/7eaf5653201fe6b92f3e3831bead3063/metrics/dimer.force +42 -0
- chemparseplot-1.12.0/tests/fixtures/surrogate/record/mlruns/606078088088587/7eaf5653201fe6b92f3e3831bead3063/metrics/dimer.measured_curvature +2 -0
- chemparseplot-1.12.0/tests/fixtures/surrogate/record/mlruns/606078088088587/7eaf5653201fe6b92f3e3831bead3063/metrics/dimer.oracle_calls +42 -0
- chemparseplot-1.12.0/tests/fixtures/surrogate/record/mlruns/606078088088587/7eaf5653201fe6b92f3e3831bead3063/metrics/dimer.surrogate_curvature +2 -0
- chemparseplot-1.12.0/tests/fixtures/surrogate/scaling/breakdown.csv +25 -0
- chemparseplot-1.12.0/tests/fixtures/surrogate/scaling/components.csv +13 -0
- chemparseplot-1.12.0/tests/fixtures/surrogate/scaling/counts.csv +19 -0
- chemparseplot-1.12.0/tests/fixtures/surrogate/scaling/pop.csv +7 -0
- chemparseplot-1.12.0/tests/fixtures/surrogate/scaling/wall.csv +37 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/_con_fixtures.py +20 -0
- chemparseplot-1.12.0/tests/parse/test_con_io.py +317 -0
- chemparseplot-1.12.0/tests/parse/test_con_trajectory.py +114 -0
- chemparseplot-1.12.0/tests/parse/test_con_units_and_corpus.py +130 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_dimer_trajectory.py +9 -9
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_min_trajectory.py +12 -11
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_neb_stitch.py +46 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_neb_utils.py +8 -0
- chemparseplot-1.12.0/tests/parse/test_readcon_boundary.py +168 -0
- chemparseplot-1.12.0/tests/parse/test_surrogate_gpr_optim.py +195 -0
- chemparseplot-1.12.0/tests/parse/test_surrogate_mlneb.py +49 -0
- chemparseplot-1.12.0/tests/plot/data/sexithiophene.sdf +458 -0
- chemparseplot-1.12.0/tests/plot/test_benchmark.py +126 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/plot/test_landscape_disconnectivity.py +4 -12
- chemparseplot-1.12.0/tests/plot/test_mlneb.py +48 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/plot/test_optimization.py +37 -0
- chemparseplot-1.12.0/tests/plot/test_primitive_rings.py +107 -0
- chemparseplot-1.12.0/tests/plot/test_provenance.py +57 -0
- chemparseplot-1.12.0/tests/plot/test_reaction.py +70 -0
- chemparseplot-1.12.0/tests/plot/test_ring_track.py +207 -0
- chemparseplot-1.12.0/tests/plot/test_ring_updater.py +22 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/plot/test_strip_rendering.py +37 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/plot/test_surface_auto_thin.py +12 -10
- chemparseplot-1.12.0/tests/plot/test_surrogate_plots.py +1018 -0
- chemparseplot-1.12.0/tests/test_load_outcome.py +14 -0
- chemparseplot-1.10.2/chemparseplot/parse/eon/con_io.py +0 -136
- chemparseplot-1.10.2/tests/parse/test_con_io.py +0 -237
- chemparseplot-1.10.2/tests/parse/test_readcon_boundary.py +0 -80
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/.gitignore +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/LICENSE +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/__init__.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/__init__.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/chemgp_hdf5.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/chemgp_jsonl.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/converter.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/eon/__init__.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/eon/dimer_trajectory.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/eon/frame_series.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/eon/gprd.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/eon/min_trajectory.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/eon/minimization.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/eon/saddle_search.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/file_.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/grammar/__init__.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/grammar/_deps.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/grammar/orca_text.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/grammar/xyz.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/orca/__init__.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/orca/_opi.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/orca/geomscan.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/orca/neb/__init__.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/orca/neb/interp.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/orca/neb/opi_parser.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/patterns.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/plumed.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/projection.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/sella/saddle_search.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/trajectory/__init__.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/parse/types.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/plot/chemgp.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/plot/geomscan.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/plot/structs.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/plot/theme.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/units.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/chemparseplot/util.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/readme.md +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/__init__.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/_optional_imports.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/conftest.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/fixtures/h2mol.xyz +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/fixtures/orca_energy_coords_snippet.out +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/fixtures_geomscan_snippet.txt +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/__init__.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/grammar/__init__.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/grammar/test_grammar_deps.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/grammar/test_orca_text_grammar.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/grammar/test_xyz_grammar.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/orca/test_geomscan.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/orca/test_interp.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_chemgp_hdf5.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_chemgp_jsonl_types.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_converter.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_eon_neb_types.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_eon_trajectory_common.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_frame_series.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_minimization_result.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_orca_public_api.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_parser_types.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_patterns.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_plumed.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_projection.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_saddle_results.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_sella_saddle.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_trajectory_hdf5.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/parse/test_trajectory_neb.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/plot/__init__.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/plot/test_chemgp_utils.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/plot/test_neb_helpers.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/plot/test_neb_renderers.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/plot/test_orca_neb_payload.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/plot/test_projection_refactor.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/plot/test_structs_units.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/test_coverage_batch.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/test_full_coverage.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/test_public_api.py +0 -0
- {chemparseplot-1.10.2 → chemparseplot-1.12.0}/tests/tutorials/test_chemparseplot.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: chemparseplot
|
|
3
|
-
Version: 1.
|
|
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:
|
|
32
|
-
Requires-Dist:
|
|
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.
|
|
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.
|
|
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.
|
|
85
|
+
Requires-Dist: xyzrender>=0.3.8; extra == 'test'
|
|
78
86
|
Provides-Extra: xyzrender
|
|
79
|
-
Requires-Dist:
|
|
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.
|
|
22
|
-
__version_tuple__ = version_tuple = (1,
|
|
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
|
-
|
|
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
|
|
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)
|