mlmm-toolkit 0.2.2.dev0__py3-none-any.whl
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.
- hessian_ff/__init__.py +50 -0
- hessian_ff/analytical_hessian.py +609 -0
- hessian_ff/constants.py +46 -0
- hessian_ff/forcefield.py +339 -0
- hessian_ff/loaders.py +608 -0
- hessian_ff/native/Makefile +8 -0
- hessian_ff/native/__init__.py +28 -0
- hessian_ff/native/analytical_hessian.py +88 -0
- hessian_ff/native/analytical_hessian_ext.cpp +258 -0
- hessian_ff/native/bonded.py +82 -0
- hessian_ff/native/bonded_ext.cpp +640 -0
- hessian_ff/native/loader.py +349 -0
- hessian_ff/native/nonbonded.py +118 -0
- hessian_ff/native/nonbonded_ext.cpp +1150 -0
- hessian_ff/prmtop_parmed.py +23 -0
- hessian_ff/system.py +107 -0
- hessian_ff/terms/__init__.py +14 -0
- hessian_ff/terms/angle.py +73 -0
- hessian_ff/terms/bond.py +44 -0
- hessian_ff/terms/cmap.py +406 -0
- hessian_ff/terms/dihedral.py +141 -0
- hessian_ff/terms/nonbonded.py +209 -0
- hessian_ff/tests/__init__.py +0 -0
- hessian_ff/tests/conftest.py +75 -0
- hessian_ff/tests/data/small/complex.parm7 +1346 -0
- hessian_ff/tests/data/small/complex.pdb +125 -0
- hessian_ff/tests/data/small/complex.rst7 +63 -0
- hessian_ff/tests/test_coords_input.py +44 -0
- hessian_ff/tests/test_energy_force.py +49 -0
- hessian_ff/tests/test_hessian.py +137 -0
- hessian_ff/tests/test_smoke.py +18 -0
- hessian_ff/tests/test_validation.py +40 -0
- hessian_ff/workflows.py +889 -0
- mlmm/__init__.py +36 -0
- mlmm/__main__.py +7 -0
- mlmm/_version.py +34 -0
- mlmm/add_elem_info.py +374 -0
- mlmm/advanced_help.py +91 -0
- mlmm/align_freeze_atoms.py +601 -0
- mlmm/all.py +3535 -0
- mlmm/bond_changes.py +231 -0
- mlmm/bool_compat.py +223 -0
- mlmm/cli.py +574 -0
- mlmm/cli_utils.py +166 -0
- mlmm/default_group.py +337 -0
- mlmm/defaults.py +467 -0
- mlmm/define_layer.py +526 -0
- mlmm/dft.py +1041 -0
- mlmm/energy_diagram.py +253 -0
- mlmm/extract.py +2213 -0
- mlmm/fix_altloc.py +464 -0
- mlmm/freq.py +1406 -0
- mlmm/harmonic_constraints.py +140 -0
- mlmm/hessian_cache.py +44 -0
- mlmm/hessian_calc.py +174 -0
- mlmm/irc.py +638 -0
- mlmm/mlmm_calc.py +2262 -0
- mlmm/mm_parm.py +945 -0
- mlmm/oniom_export.py +1983 -0
- mlmm/oniom_import.py +457 -0
- mlmm/opt.py +1742 -0
- mlmm/path_opt.py +1353 -0
- mlmm/path_search.py +2299 -0
- mlmm/preflight.py +88 -0
- mlmm/py.typed +1 -0
- mlmm/pysis_runner.py +45 -0
- mlmm/scan.py +1047 -0
- mlmm/scan2d.py +1226 -0
- mlmm/scan3d.py +1265 -0
- mlmm/scan_common.py +184 -0
- mlmm/summary_log.py +736 -0
- mlmm/trj2fig.py +448 -0
- mlmm/tsopt.py +2871 -0
- mlmm/utils.py +2309 -0
- mlmm/xtb_embedcharge_correction.py +475 -0
- mlmm_toolkit-0.2.2.dev0.dist-info/METADATA +1159 -0
- mlmm_toolkit-0.2.2.dev0.dist-info/RECORD +372 -0
- mlmm_toolkit-0.2.2.dev0.dist-info/WHEEL +5 -0
- mlmm_toolkit-0.2.2.dev0.dist-info/entry_points.txt +2 -0
- mlmm_toolkit-0.2.2.dev0.dist-info/licenses/LICENSE +674 -0
- mlmm_toolkit-0.2.2.dev0.dist-info/top_level.txt +4 -0
- pysisyphus/Geometry.py +1667 -0
- pysisyphus/LICENSE +674 -0
- pysisyphus/TableFormatter.py +63 -0
- pysisyphus/TablePrinter.py +74 -0
- pysisyphus/__init__.py +12 -0
- pysisyphus/calculators/AFIR.py +452 -0
- pysisyphus/calculators/AnaPot.py +20 -0
- pysisyphus/calculators/AnaPot2.py +48 -0
- pysisyphus/calculators/AnaPot3.py +12 -0
- pysisyphus/calculators/AnaPot4.py +20 -0
- pysisyphus/calculators/AnaPotBase.py +337 -0
- pysisyphus/calculators/AnaPotCBM.py +25 -0
- pysisyphus/calculators/AtomAtomTransTorque.py +154 -0
- pysisyphus/calculators/CFOUR.py +250 -0
- pysisyphus/calculators/Calculator.py +844 -0
- pysisyphus/calculators/CerjanMiller.py +24 -0
- pysisyphus/calculators/Composite.py +123 -0
- pysisyphus/calculators/ConicalIntersection.py +171 -0
- pysisyphus/calculators/DFTBp.py +430 -0
- pysisyphus/calculators/DFTD3.py +66 -0
- pysisyphus/calculators/DFTD4.py +84 -0
- pysisyphus/calculators/Dalton.py +61 -0
- pysisyphus/calculators/Dimer.py +681 -0
- pysisyphus/calculators/Dummy.py +20 -0
- pysisyphus/calculators/EGO.py +76 -0
- pysisyphus/calculators/EnergyMin.py +224 -0
- pysisyphus/calculators/ExternalPotential.py +264 -0
- pysisyphus/calculators/FakeASE.py +35 -0
- pysisyphus/calculators/FourWellAnaPot.py +28 -0
- pysisyphus/calculators/FreeEndNEBPot.py +39 -0
- pysisyphus/calculators/Gaussian09.py +18 -0
- pysisyphus/calculators/Gaussian16.py +726 -0
- pysisyphus/calculators/HardSphere.py +159 -0
- pysisyphus/calculators/IDPPCalculator.py +49 -0
- pysisyphus/calculators/IPIClient.py +133 -0
- pysisyphus/calculators/IPIServer.py +234 -0
- pysisyphus/calculators/LEPSBase.py +24 -0
- pysisyphus/calculators/LEPSExpr.py +139 -0
- pysisyphus/calculators/LennardJones.py +80 -0
- pysisyphus/calculators/MOPAC.py +219 -0
- pysisyphus/calculators/MullerBrownSympyPot.py +51 -0
- pysisyphus/calculators/MultiCalc.py +85 -0
- pysisyphus/calculators/NFK.py +45 -0
- pysisyphus/calculators/OBabel.py +87 -0
- pysisyphus/calculators/ONIOMv2.py +1129 -0
- pysisyphus/calculators/ORCA.py +893 -0
- pysisyphus/calculators/ORCA5.py +6 -0
- pysisyphus/calculators/OpenMM.py +88 -0
- pysisyphus/calculators/OpenMolcas.py +281 -0
- pysisyphus/calculators/OverlapCalculator.py +908 -0
- pysisyphus/calculators/Psi4.py +218 -0
- pysisyphus/calculators/PyPsi4.py +37 -0
- pysisyphus/calculators/PySCF.py +341 -0
- pysisyphus/calculators/PyXTB.py +73 -0
- pysisyphus/calculators/QCEngine.py +106 -0
- pysisyphus/calculators/Rastrigin.py +22 -0
- pysisyphus/calculators/Remote.py +76 -0
- pysisyphus/calculators/Rosenbrock.py +15 -0
- pysisyphus/calculators/SocketCalc.py +97 -0
- pysisyphus/calculators/TIP3P.py +111 -0
- pysisyphus/calculators/TransTorque.py +161 -0
- pysisyphus/calculators/Turbomole.py +965 -0
- pysisyphus/calculators/VRIPot.py +37 -0
- pysisyphus/calculators/WFOWrapper.py +333 -0
- pysisyphus/calculators/WFOWrapper2.py +341 -0
- pysisyphus/calculators/XTB.py +418 -0
- pysisyphus/calculators/__init__.py +81 -0
- pysisyphus/calculators/cosmo_data.py +139 -0
- pysisyphus/calculators/parser.py +150 -0
- pysisyphus/color.py +19 -0
- pysisyphus/config.py +133 -0
- pysisyphus/constants.py +65 -0
- pysisyphus/cos/AdaptiveNEB.py +230 -0
- pysisyphus/cos/ChainOfStates.py +725 -0
- pysisyphus/cos/FreeEndNEB.py +25 -0
- pysisyphus/cos/FreezingString.py +103 -0
- pysisyphus/cos/GrowingChainOfStates.py +71 -0
- pysisyphus/cos/GrowingNT.py +309 -0
- pysisyphus/cos/GrowingString.py +508 -0
- pysisyphus/cos/NEB.py +189 -0
- pysisyphus/cos/SimpleZTS.py +64 -0
- pysisyphus/cos/__init__.py +22 -0
- pysisyphus/cos/stiffness.py +199 -0
- pysisyphus/drivers/__init__.py +17 -0
- pysisyphus/drivers/afir.py +855 -0
- pysisyphus/drivers/barriers.py +271 -0
- pysisyphus/drivers/birkholz.py +138 -0
- pysisyphus/drivers/cluster.py +318 -0
- pysisyphus/drivers/diabatization.py +133 -0
- pysisyphus/drivers/merge.py +368 -0
- pysisyphus/drivers/merge_mol2.py +322 -0
- pysisyphus/drivers/opt.py +375 -0
- pysisyphus/drivers/perf.py +91 -0
- pysisyphus/drivers/pka.py +52 -0
- pysisyphus/drivers/precon_pos_rot.py +669 -0
- pysisyphus/drivers/rates.py +480 -0
- pysisyphus/drivers/replace.py +219 -0
- pysisyphus/drivers/scan.py +212 -0
- pysisyphus/drivers/spectrum.py +166 -0
- pysisyphus/drivers/thermo.py +31 -0
- pysisyphus/dynamics/Gaussian.py +103 -0
- pysisyphus/dynamics/__init__.py +20 -0
- pysisyphus/dynamics/colvars.py +136 -0
- pysisyphus/dynamics/driver.py +297 -0
- pysisyphus/dynamics/helpers.py +256 -0
- pysisyphus/dynamics/lincs.py +105 -0
- pysisyphus/dynamics/mdp.py +364 -0
- pysisyphus/dynamics/rattle.py +121 -0
- pysisyphus/dynamics/thermostats.py +128 -0
- pysisyphus/dynamics/wigner.py +266 -0
- pysisyphus/elem_data.py +3473 -0
- pysisyphus/exceptions.py +2 -0
- pysisyphus/filtertrj.py +69 -0
- pysisyphus/helpers.py +623 -0
- pysisyphus/helpers_pure.py +649 -0
- pysisyphus/init_logging.py +50 -0
- pysisyphus/intcoords/Bend.py +69 -0
- pysisyphus/intcoords/Bend2.py +25 -0
- pysisyphus/intcoords/BondedFragment.py +32 -0
- pysisyphus/intcoords/Cartesian.py +41 -0
- pysisyphus/intcoords/CartesianCoords.py +140 -0
- pysisyphus/intcoords/Coords.py +56 -0
- pysisyphus/intcoords/DLC.py +197 -0
- pysisyphus/intcoords/DistanceFunction.py +34 -0
- pysisyphus/intcoords/DummyImproper.py +70 -0
- pysisyphus/intcoords/DummyTorsion.py +72 -0
- pysisyphus/intcoords/LinearBend.py +105 -0
- pysisyphus/intcoords/LinearDisplacement.py +80 -0
- pysisyphus/intcoords/OutOfPlane.py +59 -0
- pysisyphus/intcoords/PrimTypes.py +286 -0
- pysisyphus/intcoords/Primitive.py +137 -0
- pysisyphus/intcoords/RedundantCoords.py +659 -0
- pysisyphus/intcoords/RobustTorsion.py +59 -0
- pysisyphus/intcoords/Rotation.py +147 -0
- pysisyphus/intcoords/Stretch.py +31 -0
- pysisyphus/intcoords/Torsion.py +101 -0
- pysisyphus/intcoords/Torsion2.py +25 -0
- pysisyphus/intcoords/Translation.py +45 -0
- pysisyphus/intcoords/__init__.py +61 -0
- pysisyphus/intcoords/augment_bonds.py +126 -0
- pysisyphus/intcoords/derivatives.py +10512 -0
- pysisyphus/intcoords/eval.py +80 -0
- pysisyphus/intcoords/exceptions.py +37 -0
- pysisyphus/intcoords/findiffs.py +48 -0
- pysisyphus/intcoords/generate_derivatives.py +414 -0
- pysisyphus/intcoords/helpers.py +235 -0
- pysisyphus/intcoords/logging_conf.py +10 -0
- pysisyphus/intcoords/mp_derivatives.py +10836 -0
- pysisyphus/intcoords/setup.py +962 -0
- pysisyphus/intcoords/setup_fast.py +176 -0
- pysisyphus/intcoords/update.py +272 -0
- pysisyphus/intcoords/valid.py +89 -0
- pysisyphus/interpolate/Geodesic.py +93 -0
- pysisyphus/interpolate/IDPP.py +55 -0
- pysisyphus/interpolate/Interpolator.py +116 -0
- pysisyphus/interpolate/LST.py +70 -0
- pysisyphus/interpolate/Redund.py +152 -0
- pysisyphus/interpolate/__init__.py +9 -0
- pysisyphus/interpolate/helpers.py +34 -0
- pysisyphus/io/__init__.py +22 -0
- pysisyphus/io/aomix.py +178 -0
- pysisyphus/io/cjson.py +24 -0
- pysisyphus/io/crd.py +101 -0
- pysisyphus/io/cube.py +220 -0
- pysisyphus/io/fchk.py +184 -0
- pysisyphus/io/hdf5.py +49 -0
- pysisyphus/io/hessian.py +72 -0
- pysisyphus/io/mol2.py +146 -0
- pysisyphus/io/molden.py +293 -0
- pysisyphus/io/orca.py +189 -0
- pysisyphus/io/pdb.py +269 -0
- pysisyphus/io/psf.py +79 -0
- pysisyphus/io/pubchem.py +31 -0
- pysisyphus/io/qcschema.py +34 -0
- pysisyphus/io/sdf.py +29 -0
- pysisyphus/io/xyz.py +61 -0
- pysisyphus/io/zmat.py +175 -0
- pysisyphus/irc/DWI.py +108 -0
- pysisyphus/irc/DampedVelocityVerlet.py +134 -0
- pysisyphus/irc/Euler.py +22 -0
- pysisyphus/irc/EulerPC.py +345 -0
- pysisyphus/irc/GonzalezSchlegel.py +187 -0
- pysisyphus/irc/IMKMod.py +164 -0
- pysisyphus/irc/IRC.py +878 -0
- pysisyphus/irc/IRCDummy.py +10 -0
- pysisyphus/irc/Instanton.py +307 -0
- pysisyphus/irc/LQA.py +53 -0
- pysisyphus/irc/ModeKill.py +136 -0
- pysisyphus/irc/ParamPlot.py +53 -0
- pysisyphus/irc/RK4.py +36 -0
- pysisyphus/irc/__init__.py +31 -0
- pysisyphus/irc/initial_displ.py +219 -0
- pysisyphus/linalg.py +411 -0
- pysisyphus/line_searches/Backtracking.py +88 -0
- pysisyphus/line_searches/HagerZhang.py +184 -0
- pysisyphus/line_searches/LineSearch.py +232 -0
- pysisyphus/line_searches/StrongWolfe.py +108 -0
- pysisyphus/line_searches/__init__.py +9 -0
- pysisyphus/line_searches/interpol.py +15 -0
- pysisyphus/modefollow/NormalMode.py +40 -0
- pysisyphus/modefollow/__init__.py +10 -0
- pysisyphus/modefollow/davidson.py +199 -0
- pysisyphus/modefollow/lanczos.py +95 -0
- pysisyphus/optimizers/BFGS.py +99 -0
- pysisyphus/optimizers/BacktrackingOptimizer.py +113 -0
- pysisyphus/optimizers/ConjugateGradient.py +98 -0
- pysisyphus/optimizers/CubicNewton.py +75 -0
- pysisyphus/optimizers/FIRE.py +113 -0
- pysisyphus/optimizers/HessianOptimizer.py +1176 -0
- pysisyphus/optimizers/LBFGS.py +228 -0
- pysisyphus/optimizers/LayerOpt.py +411 -0
- pysisyphus/optimizers/MicroOptimizer.py +169 -0
- pysisyphus/optimizers/NCOptimizer.py +90 -0
- pysisyphus/optimizers/Optimizer.py +1084 -0
- pysisyphus/optimizers/PreconLBFGS.py +260 -0
- pysisyphus/optimizers/PreconSteepestDescent.py +7 -0
- pysisyphus/optimizers/QuickMin.py +74 -0
- pysisyphus/optimizers/RFOptimizer.py +181 -0
- pysisyphus/optimizers/RSA.py +99 -0
- pysisyphus/optimizers/StabilizedQNMethod.py +248 -0
- pysisyphus/optimizers/SteepestDescent.py +23 -0
- pysisyphus/optimizers/StringOptimizer.py +173 -0
- pysisyphus/optimizers/__init__.py +41 -0
- pysisyphus/optimizers/closures.py +301 -0
- pysisyphus/optimizers/cls_map.py +58 -0
- pysisyphus/optimizers/exceptions.py +6 -0
- pysisyphus/optimizers/gdiis.py +280 -0
- pysisyphus/optimizers/guess_hessians.py +311 -0
- pysisyphus/optimizers/hessian_updates.py +355 -0
- pysisyphus/optimizers/poly_fit.py +285 -0
- pysisyphus/optimizers/precon.py +153 -0
- pysisyphus/optimizers/restrict_step.py +24 -0
- pysisyphus/pack.py +172 -0
- pysisyphus/peakdetect.py +948 -0
- pysisyphus/plot.py +1031 -0
- pysisyphus/run.py +2106 -0
- pysisyphus/socket_helper.py +74 -0
- pysisyphus/stocastic/FragmentKick.py +132 -0
- pysisyphus/stocastic/Kick.py +81 -0
- pysisyphus/stocastic/Pipeline.py +303 -0
- pysisyphus/stocastic/__init__.py +21 -0
- pysisyphus/stocastic/align.py +127 -0
- pysisyphus/testing.py +96 -0
- pysisyphus/thermo.py +156 -0
- pysisyphus/trj.py +824 -0
- pysisyphus/tsoptimizers/RSIRFOptimizer.py +56 -0
- pysisyphus/tsoptimizers/RSPRFOptimizer.py +182 -0
- pysisyphus/tsoptimizers/TRIM.py +59 -0
- pysisyphus/tsoptimizers/TSHessianOptimizer.py +463 -0
- pysisyphus/tsoptimizers/__init__.py +23 -0
- pysisyphus/wavefunction/Basis.py +239 -0
- pysisyphus/wavefunction/DIIS.py +76 -0
- pysisyphus/wavefunction/__init__.py +25 -0
- pysisyphus/wavefunction/build_ext.py +42 -0
- pysisyphus/wavefunction/cart2sph.py +190 -0
- pysisyphus/wavefunction/diabatization.py +304 -0
- pysisyphus/wavefunction/excited_states.py +435 -0
- pysisyphus/wavefunction/gen_ints.py +1811 -0
- pysisyphus/wavefunction/helpers.py +104 -0
- pysisyphus/wavefunction/ints/__init__.py +0 -0
- pysisyphus/wavefunction/ints/boys.py +193 -0
- pysisyphus/wavefunction/ints/boys_table_N_64_xasym_27.1_step_0.01.npy +0 -0
- pysisyphus/wavefunction/ints/cart_gto3d.py +176 -0
- pysisyphus/wavefunction/ints/coulomb3d.py +25928 -0
- pysisyphus/wavefunction/ints/diag_quadrupole3d.py +10036 -0
- pysisyphus/wavefunction/ints/dipole3d.py +8762 -0
- pysisyphus/wavefunction/ints/int2c2e3d.py +7198 -0
- pysisyphus/wavefunction/ints/int3c2e3d_sph.py +65040 -0
- pysisyphus/wavefunction/ints/kinetic3d.py +8240 -0
- pysisyphus/wavefunction/ints/ovlp3d.py +3777 -0
- pysisyphus/wavefunction/ints/quadrupole3d.py +15054 -0
- pysisyphus/wavefunction/ints/self_ovlp3d.py +198 -0
- pysisyphus/wavefunction/localization.py +458 -0
- pysisyphus/wavefunction/multipole.py +159 -0
- pysisyphus/wavefunction/normalization.py +36 -0
- pysisyphus/wavefunction/pop_analysis.py +134 -0
- pysisyphus/wavefunction/shells.py +1171 -0
- pysisyphus/wavefunction/wavefunction.py +504 -0
- pysisyphus/wrapper/__init__.py +11 -0
- pysisyphus/wrapper/exceptions.py +2 -0
- pysisyphus/wrapper/jmol.py +120 -0
- pysisyphus/wrapper/mwfn.py +169 -0
- pysisyphus/wrapper/packmol.py +71 -0
- pysisyphus/xyzloader.py +168 -0
- pysisyphus/yaml_mods.py +45 -0
- thermoanalysis/LICENSE +674 -0
- thermoanalysis/QCData.py +244 -0
- thermoanalysis/__init__.py +0 -0
- thermoanalysis/config.py +3 -0
- thermoanalysis/constants.py +20 -0
- thermoanalysis/thermo.py +1011 -0
mlmm/trj2fig.py
ADDED
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
# mlmm/trj2fig.py
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Energy-profile utility: extract energies from XYZ trajectories and export Plotly figures / CSV.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
mlmm trj2fig -i traj.xyz -o energy.png energy.csv --unit kcal
|
|
8
|
+
|
|
9
|
+
For detailed documentation, see: docs/trj2fig.md
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import csv
|
|
16
|
+
import re
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import List, Optional, Sequence, Tuple
|
|
19
|
+
|
|
20
|
+
import click
|
|
21
|
+
import plotly.graph_objs as go
|
|
22
|
+
from ase import Atoms
|
|
23
|
+
from ase.io import read
|
|
24
|
+
from pysisyphus.constants import AU2EV, AU2KCALPERMOL
|
|
25
|
+
|
|
26
|
+
AXIS_WIDTH = 3 # axis and tick thickness
|
|
27
|
+
FONT_SIZE = 18 # tick-label font size
|
|
28
|
+
AXIS_TITLE_SIZE = 20 # axis-title font size
|
|
29
|
+
LINE_WIDTH = 2 # curve width
|
|
30
|
+
MARKER_SIZE = 6 # marker size
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# ---------------------------------------------------------------------
|
|
34
|
+
# File helpers
|
|
35
|
+
# ---------------------------------------------------------------------
|
|
36
|
+
def read_energies_xyz(fname: Path | str) -> List[float]:
|
|
37
|
+
"""
|
|
38
|
+
Extract Hartree energies from the second-line comment of each XYZ frame.
|
|
39
|
+
"""
|
|
40
|
+
energies: List[float] = []
|
|
41
|
+
with open(fname, encoding="utf-8") as fh:
|
|
42
|
+
while (hdr := fh.readline()):
|
|
43
|
+
try:
|
|
44
|
+
nat = int(hdr.strip())
|
|
45
|
+
except ValueError: # reached a non-XYZ header
|
|
46
|
+
break
|
|
47
|
+
comment = fh.readline().strip()
|
|
48
|
+
m = re.search(r"(-?\d+(?:\.\d+)?)", comment)
|
|
49
|
+
if not m:
|
|
50
|
+
raise RuntimeError(f"Energy not found in comment: {comment}")
|
|
51
|
+
energies.append(float(m.group(1)))
|
|
52
|
+
for _ in range(nat): # skip coordinates
|
|
53
|
+
fh.readline()
|
|
54
|
+
if not energies:
|
|
55
|
+
raise RuntimeError(f"No energy data in {fname}")
|
|
56
|
+
return energies
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def recompute_energies(
|
|
60
|
+
traj_path: Path,
|
|
61
|
+
charge: Optional[int],
|
|
62
|
+
multiplicity: Optional[int],
|
|
63
|
+
) -> List[float]:
|
|
64
|
+
try:
|
|
65
|
+
import torch
|
|
66
|
+
from fairchem.core import pretrained_mlip
|
|
67
|
+
from fairchem.core.datasets import data_list_collater
|
|
68
|
+
from fairchem.core.datasets.atomic_data import AtomicData
|
|
69
|
+
except Exception as exc:
|
|
70
|
+
raise RuntimeError(
|
|
71
|
+
"Energy recomputation requires fairchem-core and torch."
|
|
72
|
+
) from exc
|
|
73
|
+
|
|
74
|
+
frames_obj = read(traj_path, index=":", format="xyz")
|
|
75
|
+
frames = [frames_obj] if isinstance(frames_obj, Atoms) else list(frames_obj)
|
|
76
|
+
if not frames:
|
|
77
|
+
raise RuntimeError(f"No frames found in {traj_path}")
|
|
78
|
+
|
|
79
|
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
|
80
|
+
predictor = pretrained_mlip.get_predict_unit("uma-s-1p1", device=str(device))
|
|
81
|
+
predictor.model.eval()
|
|
82
|
+
backbone = getattr(getattr(predictor.model, "module", predictor.model), "backbone", None)
|
|
83
|
+
uma_max_neigh = getattr(backbone, "max_neighbors", None)
|
|
84
|
+
uma_radius = getattr(backbone, "cutoff", None)
|
|
85
|
+
|
|
86
|
+
q = int(charge if charge is not None else 0)
|
|
87
|
+
mult = int(multiplicity if multiplicity is not None else 1)
|
|
88
|
+
energies_h: List[float] = []
|
|
89
|
+
for atoms in frames:
|
|
90
|
+
atoms.info.update({"charge": q, "spin": mult})
|
|
91
|
+
data = AtomicData.from_ase(
|
|
92
|
+
atoms,
|
|
93
|
+
max_neigh=uma_max_neigh,
|
|
94
|
+
radius=uma_radius,
|
|
95
|
+
r_edges=False,
|
|
96
|
+
).to(device)
|
|
97
|
+
data.dataset = "omol"
|
|
98
|
+
batch = data_list_collater([data], otf_graph=True).to(device)
|
|
99
|
+
with torch.no_grad():
|
|
100
|
+
res = predictor.predict(batch)
|
|
101
|
+
energy_ev = float(res["energy"].squeeze().detach().item())
|
|
102
|
+
energies_h.append(energy_ev / AU2EV)
|
|
103
|
+
return energies_h
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# ---------------------------------------------------------------------
|
|
107
|
+
# Transformation
|
|
108
|
+
# ---------------------------------------------------------------------
|
|
109
|
+
def _parse_reference_spec(spec: str | None) -> str | int | None:
|
|
110
|
+
"""
|
|
111
|
+
Normalize the reference specification:
|
|
112
|
+
- "init" (case-insensitive) -> "init"
|
|
113
|
+
- "none"/"null" -> None
|
|
114
|
+
- integer-like string -> int
|
|
115
|
+
"""
|
|
116
|
+
if spec is None:
|
|
117
|
+
return "init"
|
|
118
|
+
s = str(spec).strip()
|
|
119
|
+
lower = s.lower()
|
|
120
|
+
if lower in {"none", "null"}:
|
|
121
|
+
return None
|
|
122
|
+
if lower == "init":
|
|
123
|
+
return "init"
|
|
124
|
+
try:
|
|
125
|
+
return int(s)
|
|
126
|
+
except ValueError:
|
|
127
|
+
raise ValueError(
|
|
128
|
+
f'Invalid -r/--reference: {spec!r}. Use "init", "None", or an integer index.'
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _resolve_reference_index(
|
|
133
|
+
n_frames: int, ref_spec: str | int | None, reverse_x: bool
|
|
134
|
+
) -> Tuple[Optional[int], bool]:
|
|
135
|
+
"""
|
|
136
|
+
Decide the reference index and whether to compute a ΔE series.
|
|
137
|
+
|
|
138
|
+
Returns (reference_index or None, is_delta)
|
|
139
|
+
"""
|
|
140
|
+
if ref_spec is None:
|
|
141
|
+
return None, False # absolute energies
|
|
142
|
+
if ref_spec == "init":
|
|
143
|
+
idx = 0 if not reverse_x else n_frames - 1
|
|
144
|
+
return idx, True
|
|
145
|
+
# integer index
|
|
146
|
+
idx = int(ref_spec)
|
|
147
|
+
if idx < 0 or idx >= n_frames:
|
|
148
|
+
raise IndexError(f"Reference index {idx} out of range (0..{n_frames-1}).")
|
|
149
|
+
return idx, True
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def transform_series(
|
|
153
|
+
energies_hartree: Sequence[float],
|
|
154
|
+
ref_spec_raw: str | None,
|
|
155
|
+
unit: str,
|
|
156
|
+
reverse_x: bool,
|
|
157
|
+
) -> Tuple[List[float], str, bool]:
|
|
158
|
+
"""
|
|
159
|
+
Compute the y-series and its axis label.
|
|
160
|
+
|
|
161
|
+
Returns (values, ylabel, is_delta)
|
|
162
|
+
"""
|
|
163
|
+
ref_spec = _parse_reference_spec(ref_spec_raw)
|
|
164
|
+
ref_idx, is_delta = _resolve_reference_index(len(energies_hartree), ref_spec, reverse_x)
|
|
165
|
+
|
|
166
|
+
scale = AU2KCALPERMOL if unit == "kcal" else 1.0
|
|
167
|
+
if is_delta:
|
|
168
|
+
base = energies_hartree[ref_idx] # type: ignore[index]
|
|
169
|
+
values = [float((e - base) * scale) for e in energies_hartree]
|
|
170
|
+
ylabel = f"ΔE ({'kcal/mol' if unit == 'kcal' else 'hartree'})"
|
|
171
|
+
else:
|
|
172
|
+
values = [float(e * scale) for e in energies_hartree]
|
|
173
|
+
ylabel = f"E ({'kcal/mol' if unit == 'kcal' else 'hartree'})"
|
|
174
|
+
|
|
175
|
+
return values, ylabel, is_delta
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
# ---------------------------------------------------------------------
|
|
179
|
+
# Plotting
|
|
180
|
+
# ---------------------------------------------------------------------
|
|
181
|
+
def _axis_template() -> dict:
|
|
182
|
+
return dict(
|
|
183
|
+
showline=True,
|
|
184
|
+
linewidth=AXIS_WIDTH,
|
|
185
|
+
linecolor="#1C1C1C",
|
|
186
|
+
mirror=True,
|
|
187
|
+
ticks="inside",
|
|
188
|
+
tickwidth=AXIS_WIDTH,
|
|
189
|
+
tickcolor="#1C1C1C",
|
|
190
|
+
tickfont=dict(size=FONT_SIZE, color="#1C1C1C"),
|
|
191
|
+
gridcolor="lightgrey",
|
|
192
|
+
gridwidth=0.5,
|
|
193
|
+
zeroline=False,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def build_figure(delta_or_abs: Sequence[float], ylabel: str, reverse_x: bool) -> go.Figure:
|
|
198
|
+
"""
|
|
199
|
+
Build a Plotly figure without a title.
|
|
200
|
+
"""
|
|
201
|
+
fig = go.Figure(
|
|
202
|
+
go.Scatter(
|
|
203
|
+
x=list(range(len(delta_or_abs)))),
|
|
204
|
+
)
|
|
205
|
+
fig.data[0].update(
|
|
206
|
+
y=list(delta_or_abs),
|
|
207
|
+
mode="lines+markers",
|
|
208
|
+
marker=dict(size=MARKER_SIZE),
|
|
209
|
+
line=dict(shape="spline", smoothing=1.0, width=LINE_WIDTH),
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
xaxis_conf = _axis_template() | {
|
|
213
|
+
"title": dict(text="Frame", font=dict(size=AXIS_TITLE_SIZE, color="#1C1C1C"))
|
|
214
|
+
}
|
|
215
|
+
if reverse_x:
|
|
216
|
+
xaxis_conf["autorange"] = "reversed"
|
|
217
|
+
|
|
218
|
+
fig.update_layout(
|
|
219
|
+
xaxis=xaxis_conf,
|
|
220
|
+
yaxis=_axis_template() | {
|
|
221
|
+
"title": dict(text=ylabel, font=dict(size=AXIS_TITLE_SIZE, color="#1C1C1C"))
|
|
222
|
+
},
|
|
223
|
+
plot_bgcolor="white",
|
|
224
|
+
paper_bgcolor="white",
|
|
225
|
+
margin=dict(l=80, r=40, t=40, b=80),
|
|
226
|
+
)
|
|
227
|
+
return fig
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def save_outputs(
|
|
231
|
+
outs: Sequence[Path],
|
|
232
|
+
fig: Optional[go.Figure],
|
|
233
|
+
energies: Sequence[float],
|
|
234
|
+
values: Sequence[float],
|
|
235
|
+
unit: str,
|
|
236
|
+
is_delta: bool,
|
|
237
|
+
) -> None:
|
|
238
|
+
"""
|
|
239
|
+
Write all requested outputs.
|
|
240
|
+
"""
|
|
241
|
+
for out in outs:
|
|
242
|
+
ext = out.suffix.lower()
|
|
243
|
+
if ext == ".csv":
|
|
244
|
+
write_csv(out, energies, values, unit, is_delta)
|
|
245
|
+
elif ext == ".html":
|
|
246
|
+
assert fig is not None
|
|
247
|
+
fig.write_html(out)
|
|
248
|
+
click.echo(f"[trj2fig] Saved figure -> {out}")
|
|
249
|
+
elif ext in {".png", ".jpg", ".jpeg", ".pdf", ".svg"}:
|
|
250
|
+
assert fig is not None
|
|
251
|
+
kw = {"engine": "kaleido"}
|
|
252
|
+
if ext == ".png":
|
|
253
|
+
kw["scale"] = 2 # high-resolution PNG
|
|
254
|
+
fig.write_image(out, **kw)
|
|
255
|
+
click.echo(f"[trj2fig] Saved figure -> {out}")
|
|
256
|
+
else:
|
|
257
|
+
raise ValueError(f"Unsupported format: {ext}")
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def write_csv(
|
|
261
|
+
out: Path,
|
|
262
|
+
energies_hartree: Sequence[float],
|
|
263
|
+
series: Sequence[float],
|
|
264
|
+
unit: str,
|
|
265
|
+
is_delta: bool,
|
|
266
|
+
) -> None:
|
|
267
|
+
"""
|
|
268
|
+
Save energies (hartree) and ΔE/E series to CSV.
|
|
269
|
+
"""
|
|
270
|
+
colname = (f"delta_{unit}" if is_delta else f"energy_{unit}")
|
|
271
|
+
with out.open("w", newline="", encoding="utf-8") as fh:
|
|
272
|
+
w = csv.writer(fh)
|
|
273
|
+
w.writerow(["frame", "energy_hartree", colname])
|
|
274
|
+
for i, (eh, y) in enumerate(zip(energies_hartree, series)):
|
|
275
|
+
w.writerow([i, f"{eh:.8f}", f"{y:.6f}"])
|
|
276
|
+
click.echo(f"[trj2fig] Saved CSV -> {out}")
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
# ---------------------------------------------------------------------
|
|
280
|
+
# CLI (argparse)
|
|
281
|
+
# ---------------------------------------------------------------------
|
|
282
|
+
def parse_cli() -> argparse.Namespace:
|
|
283
|
+
p = argparse.ArgumentParser(
|
|
284
|
+
prog="trj2fig",
|
|
285
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
286
|
+
description="Plot ΔE or E from an XYZ trajectory and export a figure and/or CSV (no title).",
|
|
287
|
+
)
|
|
288
|
+
p.add_argument("-i", "--input", required=True, help="XYZ trajectory file")
|
|
289
|
+
p.add_argument(
|
|
290
|
+
"-o",
|
|
291
|
+
"--out",
|
|
292
|
+
nargs="+",
|
|
293
|
+
default=["energy.png"],
|
|
294
|
+
help="Output file(s) [.png/.html/.svg/.pdf/.csv]. Multiple names allowed.",
|
|
295
|
+
)
|
|
296
|
+
p.add_argument("--unit", choices=["kcal", "hartree"], default="kcal", help="Energy unit")
|
|
297
|
+
p.add_argument(
|
|
298
|
+
"-r",
|
|
299
|
+
"--reference",
|
|
300
|
+
default="init",
|
|
301
|
+
help='Reference: "init" (initial frame; last frame if --reverse-x), "None" (absolute E), or an integer index.',
|
|
302
|
+
)
|
|
303
|
+
p.add_argument(
|
|
304
|
+
"-q",
|
|
305
|
+
"--charge",
|
|
306
|
+
type=int,
|
|
307
|
+
required=False,
|
|
308
|
+
help="Total charge. Recompute energies when supplied.",
|
|
309
|
+
)
|
|
310
|
+
p.add_argument(
|
|
311
|
+
"-m",
|
|
312
|
+
"--multiplicity",
|
|
313
|
+
type=int,
|
|
314
|
+
required=False,
|
|
315
|
+
help="Spin multiplicity (2S+1). Recompute energies when supplied.",
|
|
316
|
+
)
|
|
317
|
+
p.add_argument(
|
|
318
|
+
"--reverse-x",
|
|
319
|
+
action="store_true",
|
|
320
|
+
help="Reverse the x-axis (last frame on the left).",
|
|
321
|
+
)
|
|
322
|
+
return p.parse_args()
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def run_trj2fig(
|
|
326
|
+
input_path: Path,
|
|
327
|
+
outs: Sequence[Path],
|
|
328
|
+
unit: str,
|
|
329
|
+
reference: str,
|
|
330
|
+
reverse_x: bool,
|
|
331
|
+
charge: Optional[int] = None,
|
|
332
|
+
multiplicity: Optional[int] = None,
|
|
333
|
+
) -> None:
|
|
334
|
+
traj = input_path.expanduser().resolve()
|
|
335
|
+
if not traj.is_file():
|
|
336
|
+
raise FileNotFoundError(traj)
|
|
337
|
+
|
|
338
|
+
if charge is None and multiplicity is None:
|
|
339
|
+
energies = read_energies_xyz(traj)
|
|
340
|
+
else:
|
|
341
|
+
click.echo("[trj2fig] Recomputing energies with UMA model ...")
|
|
342
|
+
energies = recompute_energies(traj, charge, multiplicity)
|
|
343
|
+
values, ylabel, is_delta = transform_series(energies, reference, unit, reverse_x)
|
|
344
|
+
|
|
345
|
+
need_plot = any(Path(o).suffix.lower() != ".csv" for o in outs)
|
|
346
|
+
fig = build_figure(values, ylabel, reverse_x) if need_plot else None
|
|
347
|
+
|
|
348
|
+
out_paths = [Path(o).expanduser().resolve() for o in outs]
|
|
349
|
+
save_outputs(out_paths, fig, energies, values, unit, is_delta)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def main() -> None:
|
|
353
|
+
args = parse_cli()
|
|
354
|
+
run_trj2fig(
|
|
355
|
+
Path(args.input),
|
|
356
|
+
args.out,
|
|
357
|
+
args.unit,
|
|
358
|
+
args.reference,
|
|
359
|
+
args.reverse_x,
|
|
360
|
+
args.charge,
|
|
361
|
+
args.multiplicity,
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
# ---------------------------------------------------------------------
|
|
366
|
+
# Click wrapper for package CLI integration
|
|
367
|
+
# ---------------------------------------------------------------------
|
|
368
|
+
@click.command(
|
|
369
|
+
name="trj2fig",
|
|
370
|
+
help="Plot ΔE or E from an XYZ trajectory and export figure/CSV.",
|
|
371
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
372
|
+
)
|
|
373
|
+
@click.option(
|
|
374
|
+
"-i",
|
|
375
|
+
"--input",
|
|
376
|
+
"input_path", # explicit internal argument name
|
|
377
|
+
required=True,
|
|
378
|
+
type=click.Path(exists=True, dir_okay=False, path_type=Path),
|
|
379
|
+
help="XYZ trajectory file",
|
|
380
|
+
)
|
|
381
|
+
@click.option(
|
|
382
|
+
"-o",
|
|
383
|
+
"--out",
|
|
384
|
+
"outs",
|
|
385
|
+
multiple=True, # allow repeating -o
|
|
386
|
+
default=(), # default is empty (we inject the fallback later)
|
|
387
|
+
type=click.Path(dir_okay=False, path_type=Path),
|
|
388
|
+
help="Output file(s). You can repeat -o, and/or list extra filenames after options "
|
|
389
|
+
"(.png/.html/.svg/.pdf/.csv). If nothing is given, defaults to energy.png.",
|
|
390
|
+
)
|
|
391
|
+
@click.argument(
|
|
392
|
+
"extra_outs", # also accept extra filenames provided positionally after options
|
|
393
|
+
nargs=-1,
|
|
394
|
+
type=click.Path(dir_okay=False, path_type=Path),
|
|
395
|
+
)
|
|
396
|
+
@click.option(
|
|
397
|
+
"--unit",
|
|
398
|
+
type=click.Choice(["kcal", "hartree"]),
|
|
399
|
+
default="kcal",
|
|
400
|
+
help="Energy unit.",
|
|
401
|
+
)
|
|
402
|
+
@click.option(
|
|
403
|
+
"-r",
|
|
404
|
+
"--reference",
|
|
405
|
+
default="init",
|
|
406
|
+
help='Reference: "init" (initial frame; last frame if --reverse-x), "None" (absolute E), or an integer index.',
|
|
407
|
+
)
|
|
408
|
+
@click.option(
|
|
409
|
+
"-q",
|
|
410
|
+
"--charge",
|
|
411
|
+
type=int,
|
|
412
|
+
default=None,
|
|
413
|
+
help="Total charge. Recompute energies when supplied.",
|
|
414
|
+
)
|
|
415
|
+
@click.option(
|
|
416
|
+
"-m",
|
|
417
|
+
"--multiplicity",
|
|
418
|
+
type=int,
|
|
419
|
+
default=None,
|
|
420
|
+
help="Spin multiplicity (2S+1). Recompute energies when supplied.",
|
|
421
|
+
)
|
|
422
|
+
@click.option(
|
|
423
|
+
"--reverse-x/--no-reverse-x",
|
|
424
|
+
"reverse_x",
|
|
425
|
+
default=False,
|
|
426
|
+
show_default=True,
|
|
427
|
+
help="Reverse the x-axis (last frame on the left).",
|
|
428
|
+
)
|
|
429
|
+
def cli(
|
|
430
|
+
input_path: Path,
|
|
431
|
+
outs: Tuple[Path, ...],
|
|
432
|
+
extra_outs: Tuple[Path, ...],
|
|
433
|
+
unit: str,
|
|
434
|
+
reference: str,
|
|
435
|
+
charge: Optional[int],
|
|
436
|
+
multiplicity: Optional[int],
|
|
437
|
+
reverse_x: bool,
|
|
438
|
+
) -> None:
|
|
439
|
+
# Combine outputs from -o with positional filenames that follow the options
|
|
440
|
+
all_outs: List[Path] = list(outs) + list(extra_outs)
|
|
441
|
+
if not all_outs:
|
|
442
|
+
# Use the default when nothing is specified
|
|
443
|
+
all_outs = [Path("energy.png")]
|
|
444
|
+
run_trj2fig(input_path, all_outs, unit, reference, reverse_x, charge, multiplicity)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
if __name__ == "__main__":
|
|
448
|
+
main()
|