pm3-rs-python 0.1.2__tar.gz → 0.2.3__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.
- pm3_rs_python-0.2.3/CHANGELOG.md +692 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/Cargo.lock +1 -1
- pm3_rs_python-0.2.3/Cargo.toml +44 -0
- pm3_rs_python-0.2.3/PKG-INFO +246 -0
- pm3_rs_python-0.2.3/README.md +212 -0
- pm3_rs_python-0.2.3/docs/divide-and-conquer.md +268 -0
- pm3_rs_python-0.2.3/docs/pbc.md +667 -0
- pm3_rs_python-0.2.3/docs/python-api.md +573 -0
- pm3_rs_python-0.2.3/docs/rust-api.md +326 -0
- pm3_rs_python-0.2.3/docs/scope.md +162 -0
- pm3_rs_python-0.2.3/pyproject.toml +91 -0
- pm3_rs_python-0.2.3/python/pm3_rs/__init__.py +59 -0
- pm3_rs_python-0.2.3/python/pm3_rs/_native.pyi +273 -0
- pm3_rs_python-0.2.3/python/pm3_rs/ase.py +964 -0
- pm3_rs_python-0.2.3/python/pm3_rs/cli.py +31 -0
- pm3_rs_python-0.2.3/python/pm3_rs/native.py +1059 -0
- pm3_rs_python-0.2.3/python/pm3_rs/py.typed +0 -0
- pm3_rs_python-0.2.3/src/bin/pm3_rs.rs +12 -0
- pm3_rs_python-0.2.3/src/cell.rs +616 -0
- pm3_rs_python-0.2.3/src/cli.rs +1579 -0
- pm3_rs_python-0.2.3/src/cmatrix.rs +342 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/corrections/d3.rs +82 -7
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/corrections/h4.rs +27 -5
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/corrections/hx.rs +12 -1
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/corrections/mod.rs +84 -5
- pm3_rs_python-0.2.3/src/corrections/periodic.rs +477 -0
- pm3_rs_python-0.2.3/src/dc/derivatives.rs +311 -0
- pm3_rs_python-0.2.3/src/dc/mod.rs +53 -0
- pm3_rs_python-0.2.3/src/dc/partition.rs +407 -0
- pm3_rs_python-0.2.3/src/dc/pattern.rs +423 -0
- pm3_rs_python-0.2.3/src/dc/scf.rs +1158 -0
- pm3_rs_python-0.2.3/src/densitydiis.rs +208 -0
- pm3_rs_python-0.2.3/src/dipole.rs +354 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/fock.rs +144 -8
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/gradient.rs +100 -1
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/hamiltonian.rs +60 -2
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/hessian.rs +595 -64
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/integrals_d.rs +4 -4
- pm3_rs_python-0.2.3/src/ir.rs +616 -0
- pm3_rs_python-0.2.3/src/lib.rs +106 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/linalg.rs +1 -6
- pm3_rs_python-0.2.3/src/molden.rs +487 -0
- pm3_rs_python-0.2.3/src/neighbor.rs +467 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/optimizer.rs +12 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/overlap.rs +178 -29
- pm3_rs_python-0.2.3/src/pbc/berry.rs +401 -0
- pm3_rs_python-0.2.3/src/pbc/born.rs +190 -0
- pm3_rs_python-0.2.3/src/pbc/dfpt.rs +4819 -0
- pm3_rs_python-0.2.3/src/pbc/dielectric.rs +360 -0
- pm3_rs_python-0.2.3/src/pbc/ewald.rs +3211 -0
- pm3_rs_python-0.2.3/src/pbc/ewald_hessian.rs +608 -0
- pm3_rs_python-0.2.3/src/pbc/ewald_reference.rs +406 -0
- pm3_rs_python-0.2.3/src/pbc/finite_field.rs +648 -0
- pm3_rs_python-0.2.3/src/pbc/gamma.rs +2239 -0
- pm3_rs_python-0.2.3/src/pbc/gradient.rs +989 -0
- pm3_rs_python-0.2.3/src/pbc/hessian.rs +979 -0
- pm3_rs_python-0.2.3/src/pbc/kernel.rs +520 -0
- pm3_rs_python-0.2.3/src/pbc/kpoints.rs +662 -0
- pm3_rs_python-0.2.3/src/pbc/kscf.rs +1958 -0
- pm3_rs_python-0.2.3/src/pbc/lo_to.rs +163 -0
- pm3_rs_python-0.2.3/src/pbc/mod.rs +98 -0
- pm3_rs_python-0.2.3/src/pbc/multipole.rs +864 -0
- pm3_rs_python-0.2.3/src/pbc/optimize.rs +567 -0
- pm3_rs_python-0.2.3/src/pbc/phased.rs +1709 -0
- pm3_rs_python-0.2.3/src/pbc/phonon.rs +365 -0
- pm3_rs_python-0.2.3/src/pbc/screen.rs +628 -0
- pm3_rs_python-0.2.3/src/python.rs +1948 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/scf.rs +218 -47
- pm3_rs_python-0.2.3/src/special.rs +280 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/system.rs +71 -6
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/tests/api_surface.rs +99 -13
- pm3_rs_python-0.2.3/tests/crystals.rs +438 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/tests/molecules.rs +190 -0
- pm3_rs_python-0.2.3/tests/pbc_berry.rs +313 -0
- pm3_rs_python-0.2.3/tests/pbc_born_charges.rs +256 -0
- pm3_rs_python-0.2.3/tests/pbc_dfpt_options.rs +272 -0
- pm3_rs_python-0.2.3/tests/pbc_dielectric.rs +367 -0
- pm3_rs_python-0.2.3/tests/pbc_finite_field.rs +366 -0
- pm3_rs_python-0.2.3/tests/pbc_lo_to.rs +243 -0
- pm3_rs_python-0.2.3/tests/pbc_phonon.rs +229 -0
- pm3_rs_python-0.2.3/tests/pbc_uhf_response.rs +258 -0
- pm3_rs_python-0.2.3/tests/test_ase_npt.py +351 -0
- pm3_rs_python-0.2.3/tests/test_periodic_python_api.py +782 -0
- pm3_rs_python-0.2.3/tests/test_python_api.py +908 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/third_party/mopac/LICENSE +176 -176
- pm3_rs_python-0.1.2/CHANGELOG.md +0 -101
- pm3_rs_python-0.1.2/Cargo.toml +0 -33
- pm3_rs_python-0.1.2/PKG-INFO +0 -160
- pm3_rs_python-0.1.2/README.md +0 -146
- pm3_rs_python-0.1.2/docs/python-api.md +0 -172
- pm3_rs_python-0.1.2/docs/rust-api.md +0 -168
- pm3_rs_python-0.1.2/docs/scope.md +0 -46
- pm3_rs_python-0.1.2/examples/ammonia.xyz +0 -6
- pm3_rs_python-0.1.2/examples/bench102.xyz +0 -104
- pm3_rs_python-0.1.2/examples/ethanol.xyz +0 -11
- pm3_rs_python-0.1.2/examples/formaldehyde.xyz +0 -6
- pm3_rs_python-0.1.2/examples/h2s.xyz +0 -5
- pm3_rs_python-0.1.2/examples/hcl.xyz +0 -4
- pm3_rs_python-0.1.2/examples/methane.xyz +0 -7
- pm3_rs_python-0.1.2/examples/methyl_radical.xyz +0 -6
- pm3_rs_python-0.1.2/examples/ticl4.xyz +0 -7
- pm3_rs_python-0.1.2/examples/water.xyz +0 -5
- pm3_rs_python-0.1.2/examples/water_distorted.xyz +0 -5
- pm3_rs_python-0.1.2/pyproject.toml +0 -21
- pm3_rs_python-0.1.2/python/pm3_rs/__init__.py +0 -27
- pm3_rs_python-0.1.2/python/pm3_rs/ase.py +0 -175
- pm3_rs_python-0.1.2/python/pm3_rs/native.py +0 -175
- pm3_rs_python-0.1.2/src/bin/pm3_rs.rs +0 -255
- pm3_rs_python-0.1.2/src/lib.rs +0 -60
- pm3_rs_python-0.1.2/src/python.rs +0 -376
- pm3_rs_python-0.1.2/tests/test_python_api.py +0 -370
- pm3_rs_python-0.1.2/tools/extract_d3_data.py +0 -131
- pm3_rs_python-0.1.2/tools/extract_pm3_params.py +0 -196
- pm3_rs_python-0.1.2/tools/extract_report.md +0 -9
- pm3_rs_python-0.1.2/tools/oracle/ALL_ELEMENTS_FORCE_RESULTS.json +0 -1035
- pm3_rs_python-0.1.2/tools/oracle/ALL_ELEMENTS_RESULTS.json +0 -1035
- pm3_rs_python-0.1.2/tools/oracle/HEAVY_HALOGEN_FD005_RESULTS.json +0 -66
- pm3_rs_python-0.1.2/tools/oracle/PM3_VALIDATION.md +0 -114
- pm3_rs_python-0.1.2/tools/oracle/all_element_validation.py +0 -413
- pm3_rs_python-0.1.2/tools/oracle/molecules/ammonia.xyz +0 -6
- pm3_rs_python-0.1.2/tools/oracle/molecules/ar2.xyz +0 -4
- pm3_rs_python-0.1.2/tools/oracle/molecules/capped_methyl.xyz +0 -7
- pm3_rs_python-0.1.2/tools/oracle/molecules/formaldehyde.xyz +0 -6
- pm3_rs_python-0.1.2/tools/oracle/molecules/gdf3.xyz +0 -6
- pm3_rs_python-0.1.2/tools/oracle/molecules/h2s.xyz +0 -5
- pm3_rs_python-0.1.2/tools/oracle/molecules/hcl.xyz +0 -4
- pm3_rs_python-0.1.2/tools/oracle/molecules/heh_plus.xyz +0 -4
- pm3_rs_python-0.1.2/tools/oracle/molecules/methane.xyz +0 -7
- pm3_rs_python-0.1.2/tools/oracle/molecules/methyl_radical.xyz +0 -6
- pm3_rs_python-0.1.2/tools/oracle/molecules/ph3.xyz +0 -6
- pm3_rs_python-0.1.2/tools/oracle/molecules/sf6.xyz +0 -9
- pm3_rs_python-0.1.2/tools/oracle/molecules/sih4.xyz +0 -7
- pm3_rs_python-0.1.2/tools/oracle/molecules/ticl4.xyz +0 -7
- pm3_rs_python-0.1.2/tools/oracle/molecules/water_dimer.xyz +0 -8
- pm3_rs_python-0.1.2/tools/oracle/molecules/water_minus.xyz +0 -6
- pm3_rs_python-0.1.2/tools/oracle/molecules/water_plus.xyz +0 -6
- pm3_rs_python-0.1.2/tools/oracle/pair_sweep.py +0 -216
- pm3_rs_python-0.1.2/tools/oracle/run_mopac.py +0 -244
- pm3_rs_python-0.1.2/tools/oracle/sweep.py +0 -134
- pm3_rs_python-0.1.2/tools/verify_pair_params.py +0 -86
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/LICENSE +0 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/THIRD_PARTY_NOTICES.md +0 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/basis.rs +0 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/constants.rs +0 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/data/d3_c6_reference.csv +0 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/data/d3_r0ab.csv +0 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/data/d3_radii.csv +0 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/data/element_data.csv +0 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/data/pm3_global.csv +0 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/data/pm3_pair_parameters.csv +0 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/data/pm3_parameters.csv +0 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/data/pm3_sparkles.csv +0 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/data_tables.rs +0 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/dual.rs +0 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/dual2.rs +0 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/error.rs +0 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/frame.rs +0 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/integrals.rs +0 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/math.rs +0 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/onecenter.rs +0 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/overlap_numeric.rs +0 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/params.rs +0 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/repulsion.rs +0 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/src/rotations.rs +0 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/tests/test_python.py +0 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/third_party/dftd3/NOTICE +0 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/third_party/h_bonds4/NOTICE +0 -0
- {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.3}/third_party/pyseqm/NOTICE +0 -0
|
@@ -0,0 +1,692 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.3 — 2026-09-03
|
|
4
|
+
|
|
5
|
+
One performance fix, found by running real crystals rather than by reading code,
|
|
6
|
+
and two reporting defects it turned up on the way.
|
|
7
|
+
|
|
8
|
+
### Faster
|
|
9
|
+
|
|
10
|
+
- **Born charges and Γ-point phonons are ~2.4× faster**, and the numbers are
|
|
11
|
+
unchanged. The long-range term of the bare perturbation ran two lattice sums
|
|
12
|
+
over the site pairs *inside* the per-degree-of-freedom loop — `6N` sums where
|
|
13
|
+
two suffice. The displacements do not depend on the Cartesian axis at all, so
|
|
14
|
+
each was recomputed three times per atom for nothing, and across atoms the
|
|
15
|
+
sets are disjoint slices of one site-by-site table. It is now built once
|
|
16
|
+
(`pbc::dfpt::LongRangeKernels`).
|
|
17
|
+
|
|
18
|
+
At 12 atoms a Born-charge run went from 62.2 s to 26.3 s; the Rust suite's
|
|
19
|
+
periodic tests roughly halved.
|
|
20
|
+
|
|
21
|
+
Worth recording how it was found, because three earlier attempts missed it.
|
|
22
|
+
Replacing the `3N` coupled-perturbed solves with three by the interchange
|
|
23
|
+
theorem changed the wall clock by **nothing**; hoisting the neighbour list out
|
|
24
|
+
of the bare perturbations changed **nothing**; parallelising the three field
|
|
25
|
+
solves changed **nothing**. Timing the pieces put 85% of the run in one
|
|
26
|
+
untimed call. The lesson is in `examples/born_profile.rs`, which decomposes
|
|
27
|
+
the cost with public entry points that differ in one term each.
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- **`Pm3Error::ScfNotConverged` always reported `error: NaN`.** It was
|
|
32
|
+
hardcoded at all four sites — the Γ path, the k-point path and both molecular
|
|
33
|
+
paths — so the field meant to say how far off a run was never did, and `NaN`
|
|
34
|
+
reads as a numerical blow-up when the truth was usually a slow tail. Diamond's
|
|
35
|
+
conventional cell reports `3.854e-4` against a `1e-7` tolerance; it was not
|
|
36
|
+
diverging at all.
|
|
37
|
+
- **A failed Γ-point SCF never mentioned the Γ margin**, which is usually the
|
|
38
|
+
cause. Below it one k-point cannot represent the cell and no amount of damping
|
|
39
|
+
helps. The message now gives the margin and says to use a k-mesh or a larger
|
|
40
|
+
supercell. Measured on NaCl's conventional cell, where the Γ answer and the
|
|
41
|
+
4×4×4 one differ by **421 eV**.
|
|
42
|
+
|
|
43
|
+
### Housekeeping
|
|
44
|
+
|
|
45
|
+
- `cargo fmt` across the tree: 0.2.2 shipped 26 files that rustfmt disagreed
|
|
46
|
+
with, all of them new or edited in that release.
|
|
47
|
+
|
|
48
|
+
## 0.2.2 — 2026-09-03
|
|
49
|
+
|
|
50
|
+
The response properties a dynamical matrix was already most of the way to, and a
|
|
51
|
+
Berry phase to check them from outside. The largest fix is none of those: a
|
|
52
|
+
coupled-perturbed solver that had been returning unconverged responses under a
|
|
53
|
+
converged label, which every analytic Hessian and infrared intensity this crate
|
|
54
|
+
has produced was built on.
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
|
|
58
|
+
- **Born effective charges** (`pbc::born`, `pm3_rs.born_charges`, CLI `born`,
|
|
59
|
+
`PM3.get_born_charges`), reported with the acoustic sum-rule residual rather
|
|
60
|
+
than with the rule imposed.
|
|
61
|
+
- **Polarizability and the dielectric tensors** (`pbc::dielectric`,
|
|
62
|
+
`pm3_rs.dielectric`, CLI `dielectric`, `PM3.get_dielectric`): `ε∞` for a
|
|
63
|
+
fully periodic cell, `α` in every dimensionality, and — with `include_ionic`
|
|
64
|
+
/ `--static` — the static tensor `ε₀`. `skipped_modes` says how much of the
|
|
65
|
+
ionic sum is missing, since at an unrelaxed geometry the answer is incomplete
|
|
66
|
+
rather than merely odd.
|
|
67
|
+
- **LO–TO splitting** (`pbc::lo_to`, `phonons(lo_to_direction=)`,
|
|
68
|
+
`--lo-to`). The coefficient was not transcribed: it was fixed against this
|
|
69
|
+
crate's own finite-`q` DFPT in the `q → 0` limit, where the ratio converges to
|
|
70
|
+
1 as `q²` (0.56 → 0.89 → 0.973).
|
|
71
|
+
- **Supercell force constants and phonon dispersion** (`pbc::phonon`,
|
|
72
|
+
`ForceConstants`, `pm3_rs.phonon_bands`, CLI `phonon-bands`,
|
|
73
|
+
`PM3.get_phonon_bands`). One Hessian buys the whole band structure. `Φ(T)`
|
|
74
|
+
lives in a sorted vector, not a hash map: the Fourier sum's accumulation order
|
|
75
|
+
is otherwise run-dependent, which in a near-degenerate mode is not a
|
|
76
|
+
last-digit effect.
|
|
77
|
+
- **Berry-phase polarization** (`pbc::berry`, `pm3_rs.berry_polarization`, CLI
|
|
78
|
+
`berry`, `PM3.get_berry_polarization`), as an independent check on the above
|
|
79
|
+
rather than as a feature in itself.
|
|
80
|
+
- **A finite electric field along a periodic direction** (`pbc::finite_field`,
|
|
81
|
+
`pm3_rs.finite_field`, CLI `finite-field`, `PM3.get_finite_field`), by the
|
|
82
|
+
Nunes–Gonze electric enthalpy `F = E − Ω 𝓔·P`. `𝓔·R` is not lattice-periodic
|
|
83
|
+
along a periodic axis, so `H − 𝓔·R` has no ground state there; a field
|
|
84
|
+
orthogonal to every lattice vector still goes through `Pm3Options::field`.
|
|
85
|
+
Reproduces the CPHF polarizability to 1 part in 10⁴. Restricted, gapped, 3D
|
|
86
|
+
only, and there is no force.
|
|
87
|
+
- **`DfptOptions` / `LongRange` / `DfptResult`**, `force_constants_at_q`,
|
|
88
|
+
`frequencies_at_q`, and the response density (`PhononResponse`) everything
|
|
89
|
+
above contracts.
|
|
90
|
+
- **The Mermin electronic free energy**: `KpointResult::entropy_ts_ev` and
|
|
91
|
+
`free_energy_ev`, and ASE's `free_energy`, so
|
|
92
|
+
`get_potential_energy(force_consistent=True)` works. Documented in four places
|
|
93
|
+
as *not* a Gibbs energy — no zero-point energy, no vibrational partition
|
|
94
|
+
function, no `pV`, no nuclear entropy.
|
|
95
|
+
- **`divide_and_conquer_forces`**, `periodic_hessian`, `magnetization`,
|
|
96
|
+
`smearing_ev`, CLI `--reference`, and the mode vectors and masses
|
|
97
|
+
`frequencies` and `dynamical_matrix` had been withholding.
|
|
98
|
+
|
|
99
|
+
### Fixed
|
|
100
|
+
|
|
101
|
+
- **CPHF returned unconverged responses as converged.** All four paths ran to an
|
|
102
|
+
iteration limit and returned `Ok`. Adding the check failed eight existing
|
|
103
|
+
tests, which exposed the cause: `hessian.rs`'s own DIIS divided `B_ij` by each
|
|
104
|
+
residual's magnitude, replacing the constraint `Σc = 1` with a different one.
|
|
105
|
+
The correct implementation was already in `scf.rs`. Water's response went from
|
|
106
|
+
160 passes to **3** (residual `8.1e-17`); the Rust suite from 38.7 s to 31.4 s.
|
|
107
|
+
- **`D(q)` diverged as `1/q²`** in a polar cell — the acoustic sum rule read
|
|
108
|
+
220.1 at `q = 0.0125`, and water in a 10 Bohr cell put its lowest mode at
|
|
109
|
+
−4705 cm⁻¹. The `G = 0` term of the bare long-range perturbation was scaled by
|
|
110
|
+
the displaced atom's own charge rather than the cell's, so `Σ_a Q_a = 0` never
|
|
111
|
+
cancelled it. Fixed by excluding the macroscopic field from the
|
|
112
|
+
self-consistent response and restoring it analytically; the term restored is
|
|
113
|
+
exactly `pbc::lo_to`. No existing DFPT test ran below `q = 0.2`.
|
|
114
|
+
- **`dynamical_matrix_on_mesh` built its skeleton from the Γ density**, so
|
|
115
|
+
passing a mesh corrected the response and left the dominant term wrong.
|
|
116
|
+
- **ASE ran two SCF calculations per step and stitched them together**, taking
|
|
117
|
+
energy and forces from an unsmeared one and charges from a smeared one — on a
|
|
118
|
+
metal, different states. Now one call; the Python suite went 99 s → 51 s.
|
|
119
|
+
- **UHF CPHF had no DIIS**, so the hardest case was the one without acceleration.
|
|
120
|
+
- **Open-shell HOMO/LUMO ignored the β spectrum.**
|
|
121
|
+
- **`stress_tol` crossed the Python boundary without its unit conversion**,
|
|
122
|
+
leaving the periodic optimizer 6.75× looser than documented and returning
|
|
123
|
+
relaxed cells with `converged=True`.
|
|
124
|
+
- **`long_range_cutoff` was silently ignored by periodic divide-and-conquer**,
|
|
125
|
+
and `--kpts` by three CLI paths that cannot sample a mesh. Both now refuse.
|
|
126
|
+
- **ASE's result cache was not invalidated by attribute changes**, so setting
|
|
127
|
+
`atoms.calc.charge = 1` returned the neutral energy.
|
|
128
|
+
- **`vibrational_analysis` divided by zero mass** for MOPAC's sparkles.
|
|
129
|
+
|
|
130
|
+
### Fixed in the new code, by cross-checks rather than by review
|
|
131
|
+
|
|
132
|
+
- **The field operator was half its correct size.** `M = i λ (W₊ − W₋) C†` is
|
|
133
|
+
one-sided — `M|v⟩ = 0` for a virtual `v` — so it holds the whole
|
|
134
|
+
virtual-occupied block and none of the occupied-virtual one. The conventional
|
|
135
|
+
`½(M + M†)` therefore halves the block a linear response is made of. Measured
|
|
136
|
+
at a ratio of `0.5001` against the CPHF polarizability; `M + M†` gives
|
|
137
|
+
`1.0003`.
|
|
138
|
+
- **The finite field computed polarization only along the axes the field
|
|
139
|
+
touched.** Those are not the axes that carry polarization: a zero field came
|
|
140
|
+
back with an electronic polarization of exactly zero. `resolved` now reports
|
|
141
|
+
which axes the mesh could see.
|
|
142
|
+
- **The CLI handed `run_finite_field` a negated field.** `--field` is stored
|
|
143
|
+
with the sign that makes `E = E₀ + μ·F` hold for the molecular `−𝓔·r`
|
|
144
|
+
coupling. Nothing failed — the loop converged and `∂P/∂𝓔` was backwards.
|
|
145
|
+
|
|
146
|
+
### Wiring
|
|
147
|
+
|
|
148
|
+
A layer-by-layer audit found four features present in some layers and not
|
|
149
|
+
others, the same shape of gap as the "DFPT is Rust-only" report:
|
|
150
|
+
`berry_polarization` was Rust-only, supercell dispersion was CLI-only, the
|
|
151
|
+
finite field reached neither ASE nor the CLI, and LO–TO was unreachable from the
|
|
152
|
+
CLI. All four are now in every layer.
|
|
153
|
+
|
|
154
|
+
### Measured, and not fixed
|
|
155
|
+
|
|
156
|
+
- **The correction Hessian is still `O(N⁴)`.** Bounding the D3/H4 sums at the
|
|
157
|
+
radii the periodic path uses was tried and reverted: it cost `2.5e-4` eV at
|
|
158
|
+
375 atoms and bought nothing, the log-log slope staying at **1.959**. The sums
|
|
159
|
+
are `for i { for j { if r > cutoff { continue } } }`, so a radius bounds the
|
|
160
|
+
range and not the work — and 30 Bohr encloses ~2000 atoms of liquid water,
|
|
161
|
+
more than any system measured. See `examples/correction_cutoff.rs` and
|
|
162
|
+
`examples/correction_scaling.rs`.
|
|
163
|
+
- **Divide-and-conquer is at slope 1.16**, against an acceptance criterion of
|
|
164
|
+
1.15, over 360 to 2880 atoms — 86× over full diagonalization at 2880 atoms, at
|
|
165
|
+
28 µeV per atom, a figure flat in system size.
|
|
166
|
+
- **A cliff at the Γ-point margin.** Crossing `DEFAULT_SHORT_RANGE_CUTOFF`
|
|
167
|
+
(14 Bohr) steps a water box's energy by **35 eV** between 7.4080 and 7.4085 Å.
|
|
168
|
+
Documented behaviour rather than a defect — below the margin the SCF converges
|
|
169
|
+
cleanly to a well-defined wrong answer — but the size of the step is not
|
|
170
|
+
obvious from that sentence, and a test fixture of this crate's own was sitting
|
|
171
|
+
on the wrong side of it. See `examples/cell_continuity.rs`.
|
|
172
|
+
|
|
173
|
+
### Two Berry-phase conventions worth writing down
|
|
174
|
+
|
|
175
|
+
Both were derived against this crate's own gauge rather than taken from a
|
|
176
|
+
published form, because getting either wrong returns a plausible number rather
|
|
177
|
+
than an error.
|
|
178
|
+
|
|
179
|
+
- **No closure factor on the last link.** `bloch_fock` carries the phase on the
|
|
180
|
+
lattice translation alone, so `H(k + G) = H(k)` exactly and the coefficients at
|
|
181
|
+
`k₀ + G` *are* those at `k₀`. An extra `e^{−iG·τ}` put fluorine's Born charge
|
|
182
|
+
at `+21.8 e` against a true `−0.33`.
|
|
183
|
+
- **The sign of the electronic term.** With `e^{−ib·τ}` in the overlap, the
|
|
184
|
+
textbook `−(e/Ω) φ a` counts that sign twice. Fixed by the single-orbital
|
|
185
|
+
limit; getting it wrong gives `+14.4`.
|
|
186
|
+
|
|
187
|
+
With both right, the Berry and CPHF Born charges differ by `0.147 e` — and
|
|
188
|
+
removing the intra-atomic `dd` moment the phase omits (`PM3_BORN_NO_DD=1`)
|
|
189
|
+
collapses that to `1.9e-4`. The two formalisms agree on everything except one
|
|
190
|
+
identified physical term.
|
|
191
|
+
|
|
192
|
+
## 0.2.1 — 2026-09-01
|
|
193
|
+
|
|
194
|
+
Phonons everywhere the Γ point already worked, an external electric field, and
|
|
195
|
+
the wavefunction outputs that go with it. Three defects that had shipped in
|
|
196
|
+
0.2.0 are fixed; all of them were invisible at `q = 0`, which is where every
|
|
197
|
+
test that could have caught them ran.
|
|
198
|
+
|
|
199
|
+
### Added
|
|
200
|
+
|
|
201
|
+
- **Analytic stress for a slab.** Every periodic dimensionality now reports one:
|
|
202
|
+
a chain its axis, a slab its two in-plane components, a crystal all nine, with
|
|
203
|
+
exact zeros in the non-periodic directions. `relax` relaxes the cell vectors
|
|
204
|
+
that exist and leaves a slab's vacuum thickness alone; only an isolated cell
|
|
205
|
+
refuses, having no strain rather than an underived derivative.
|
|
206
|
+
- **A uniform external electric field** (`Pm3Options::field`), molecular only —
|
|
207
|
+
energy, analytic gradient and analytic Hessian. Validated against MOPAC's own
|
|
208
|
+
`FIELD=` keyword to all eight digits MOPAC prints. Refused under periodic
|
|
209
|
+
boundary conditions, where `−f·r` is not lattice-periodic.
|
|
210
|
+
- **A shared dipole operator** (`pm3_rs::dipole`). The reported dipole and the
|
|
211
|
+
field coupling are the same matrix, which is what makes `μ = −∂E/∂F` hold by
|
|
212
|
+
construction rather than by coincidence.
|
|
213
|
+
- **Molden wavefunction output** (`pm3_rs::molden`, the CLI's `molden` command,
|
|
214
|
+
`write_molden` in both Python layers). The coefficients are the raw ZDO ones,
|
|
215
|
+
which is MOPAC's own `VECTORS`/`GRAPHF` convention and makes the comparison
|
|
216
|
+
against it direct; the Slater functions are expanded in Gaussians derived at
|
|
217
|
+
run time by a regularized linear least squares rather than transcribed from a
|
|
218
|
+
table, to a measured overlap deficit of `2e-5` at worst.
|
|
219
|
+
- **β orbitals on `Pm3Result`** (`mo_energies_beta`, `mo_coeff_beta`, `n_beta`),
|
|
220
|
+
which an unrestricted Molden file needs and which the UHF Hessian was
|
|
221
|
+
re-diagonalizing to recover.
|
|
222
|
+
- **Infrared intensities** (`pm3_rs::ir`): the dipole-derivative tensor
|
|
223
|
+
`∂μ/∂R` from **three** coupled-perturbed solves rather than `3N`, by the
|
|
224
|
+
interchange theorem, with the nuclear term the electronic part alone would
|
|
225
|
+
miss; and from it the per-mode spectrum in km/mol, translations and rotations
|
|
226
|
+
projected out of the mass-weighted modes. RHF and UHF, both finite-difference
|
|
227
|
+
checked. The km/mol conversion is computed from its SI inputs rather than
|
|
228
|
+
transcribed, and checked against the published 974.88.
|
|
229
|
+
- **Phonons for a spin-polarized cell.** The response is written over a list of
|
|
230
|
+
spin channels rather than as a pair of code paths: a closed shell is one
|
|
231
|
+
channel holding the total density at half exchange strength with two electrons
|
|
232
|
+
per state, an open shell two channels each holding its own at full strength
|
|
233
|
+
with one. Those are the same number when the spins are equal, which keeps the
|
|
234
|
+
restricted path at its old cost — one diagonalization per k-point, not two —
|
|
235
|
+
and pins it, every closed-shell value being unchanged. Validated against
|
|
236
|
+
central differences of the periodic UHF force, which shares none of the
|
|
237
|
+
response machinery.
|
|
238
|
+
- **The classical corrections at finite `q`.** `D(q)` carries the D3/H4/X terms
|
|
239
|
+
instead of refusing them. Their contribution is the bilinear form
|
|
240
|
+
`Σ_{T,T'} e^{−iq·T} e^{+iq·T'} ∂²E_cell/∂x_{(κ,T)}∂x_{(κ',T')}`, obtained by
|
|
241
|
+
scaling each cluster entry's displacement by its own weight and taking a
|
|
242
|
+
forward-mode second derivative of the whole cluster energy — never of a pair,
|
|
243
|
+
so D3's coordination-number coupling and H4's donor–hydrogen–acceptor triples
|
|
244
|
+
come along. Four real evaluations per entry, `Dual2` being real.
|
|
245
|
+
- **Every new feature reaches every API.** The external field, the phonons at a
|
|
246
|
+
wavevector, the band structure and the variable-cell relaxation are now
|
|
247
|
+
callable from `pm3_rs`, `pm3_rs.native`, the ASE calculator and (for the
|
|
248
|
+
field) the CLI's `--field`; `divide_and_conquer` gained the ASE accessor it
|
|
249
|
+
never had. The Python layers take the field in **volts per Angstrom**, the unit
|
|
250
|
+
MOPAC's own `FIELD=` keyword uses. `dipole`, `ir`, `molden` and `pbc::dfpt`
|
|
251
|
+
are re-exported from the crate root and pinned by `tests/api_surface.rs`,
|
|
252
|
+
which covered none of them.
|
|
253
|
+
- **The divide-and-conquer SCF's memory is linear in the system.** The eight
|
|
254
|
+
`nao × nao` matrices the loop held — two densities, two Fock matrices, four
|
|
255
|
+
workspaces — live on the sparsity pattern now. Every one of them was already
|
|
256
|
+
read back only through that pattern, and the subsystem gather looks exactly
|
|
257
|
+
where some subsystem holds both orbitals, so nothing outside it was ever
|
|
258
|
+
consulted: no number changes, and `a_reaching_buffer_reproduces_the_full_result_exactly`
|
|
259
|
+
still holds to the bit. The Fock build writes into the pattern directly instead
|
|
260
|
+
of filling an array to have most of it ignored. Measured on a chain of waters,
|
|
261
|
+
8 → 128 molecules: dense grows 0.14 → 36.0 MiB, a log-log slope of exactly
|
|
262
|
+
2.00; on the pattern, 0.141 → 5.06 MiB, slope **1.06**.
|
|
263
|
+
- **`pm3_rs::dipole` and `pbc::dfpt` reach Python.** `dipole(...)` returns the
|
|
264
|
+
operator, the centre of mass it is taken about, and the `3 × 3N` derivative
|
|
265
|
+
tensor — three coupled-perturbed solves, not `3N`, and no Hessian, which is
|
|
266
|
+
what separates it from `ir_spectrum`. `dynamical_matrix(...)` returns `D(q)`
|
|
267
|
+
itself rather than only the frequencies it is diagonalized to, with the
|
|
268
|
+
Hermitian defect it was assembled at. Both reach `pm3_rs`, `pm3_rs.native` and
|
|
269
|
+
the ASE calculator.
|
|
270
|
+
- **The heavy ASE accessors are cached.** They were already lazy — nothing but
|
|
271
|
+
energy, forces, charges and dipole is computed in a `calculate` cycle — but
|
|
272
|
+
each recomputed on every ask, so a caller who wanted frequencies and then an
|
|
273
|
+
infrared spectrum paid for two Hessians and got no warning. Each now keeps its
|
|
274
|
+
most recent result against the geometry and parameters it was computed at, and
|
|
275
|
+
the phonon and dynamical-matrix caches are keyed on the wavevector and mesh as
|
|
276
|
+
well, so a dispersion sweep neither reuses the wrong answer nor accumulates
|
|
277
|
+
every matrix it built.
|
|
278
|
+
- **A multipole tree for the isolated far field.** The point-multipole sum past
|
|
279
|
+
the 80 Bohr handover was quadratic in atoms; it now runs over an octree whose
|
|
280
|
+
nodes carry the combined moments of everything beneath them, so a distant group
|
|
281
|
+
is one term rather than many. The translation up the tree is exact — a node's
|
|
282
|
+
moments *are* its clouds', shifted — so the only error is the expansion's own
|
|
283
|
+
truncation, which a test checks against the definition rather than against the
|
|
284
|
+
recursion that produced it.
|
|
285
|
+
|
|
286
|
+
A node is accepted only when `d − s > MULTIPOLE_RADIUS`, so by the triangle
|
|
287
|
+
inequality every cloud inside it was already far by the pairwise rule and **the
|
|
288
|
+
near field is exactly the set it was**. The second condition is an *absolute*
|
|
289
|
+
error bound, not a Barnes–Hut opening angle: the pairwise rule it has to match
|
|
290
|
+
is absolute, and a fixed angle is far looser where it matters — `θ = 0.3` moved
|
|
291
|
+
the energy by 1.5 meV against a 50 µeV tolerance. Measured by counting terms
|
|
292
|
+
rather than by a clock, since this machine runs other work: over a block
|
|
293
|
+
growing 125 → 1000 clouds the far-field count grows with a log-log slope of
|
|
294
|
+
**1.58** against the pairwise sum's exact 2.00, doing 18% of the pairwise work
|
|
295
|
+
at the larger size.
|
|
296
|
+
- **An independent check on the phased lattice sum.** `ewald_phased` shipped in
|
|
297
|
+
0.2.0 with four tests, all of which compare it against itself.
|
|
298
|
+
`ewald_reference::direct_phased_sum` compares it against a direct sum over
|
|
299
|
+
whole cells that shares none of its algebra — value, gradient and Hessian, to
|
|
300
|
+
better than `1e-6` at a generic interior wavevector.
|
|
301
|
+
|
|
302
|
+
- **Phonons off Γ in one and two dimensions.** The phased lattice sum, the phased
|
|
303
|
+
Parry slab and the phased direct chain now all exist, so a slab or a wire has a
|
|
304
|
+
dynamical matrix at any wavevector in its periodic subspace — a component along
|
|
305
|
+
a non-periodic axis is refused rather than quietly summed. `slab_kernel` gained
|
|
306
|
+
its second `z` derivative (and its first unit test), and `ewald_atom_hessian`
|
|
307
|
+
covers 1D and 2D through the phased sum at `q = 0` instead of a second
|
|
308
|
+
implementation.
|
|
309
|
+
|
|
310
|
+
### Fixed
|
|
311
|
+
|
|
312
|
+
- **`D(q)` was not Hermitian**, by an amount that was exactly zero at `q = 0` and
|
|
313
|
+
grew linearly with `q` — a tenth of an eV/Bohr² out of twenty-five by
|
|
314
|
+
`q = (0.2, 0, 0)`. The first-order density's contribution to the multipole
|
|
315
|
+
charges read only the lower triangle and doubled it. That is the same number as
|
|
316
|
+
the symmetrized form for a ground-state density, which is real and symmetric,
|
|
317
|
+
and a different number for a complex first-order one; it also stopped the
|
|
318
|
+
charge map being the adjoint of the potential map, which is what Hermiticity
|
|
319
|
+
rests on. Every identity test in the file ran on the rigid-ion matrix, so none
|
|
320
|
+
of them could see it.
|
|
321
|
+
- **The response solve diverged at a general wavevector and said nothing.** It
|
|
322
|
+
reached `1e30` in its two hundred passes and returned what it was holding. Two
|
|
323
|
+
changes: it now refuses rather than returns, and it extrapolates over a history
|
|
324
|
+
instead of substituting the equation into itself. The response is linear, so
|
|
325
|
+
the plain iteration converges only where the spectral radius of `χ₀K` is below
|
|
326
|
+
one, and at a general `q` it is not; damping only rescales that eigenvalue.
|
|
327
|
+
Extrapolation solves the linear system on the Krylov subspace the history
|
|
328
|
+
spans, which does not care. Every wavevector probed now converges to `1e-10`.
|
|
329
|
+
- **The response summed the time-reversal-irreducible mesh.** The ground state may
|
|
330
|
+
— `P(−k) = P(k)*` — and the response may not: time reversal maps the coupled
|
|
331
|
+
pair `(k, k+q)` onto a pair at `−q`, so doubling the irreducible weights is the
|
|
332
|
+
wrong sum wherever `q ≠ −q`. The response now regenerates the full mesh, while
|
|
333
|
+
still sharing the potential the reduced SCF converged.
|
|
334
|
+
- **The external field was half-wired into three paths.** The open-shell
|
|
335
|
+
skeleton (`skeleton_fock_ov_spin`) never carried the field's first derivative,
|
|
336
|
+
so a UHF Hessian in a field came back symmetric, with six near-zero modes, and
|
|
337
|
+
short by the cross term `Tr[(∂P/∂R)(∂H'/∂R)]` — measured at `3e-3 eV/Bohr²`
|
|
338
|
+
for the methyl radical, sixteen times the finite-difference tolerance. The
|
|
339
|
+
divide-and-conquer energy omitted the field's nuclear half `−Σ_A Z_A R_A·f`,
|
|
340
|
+
and its gradient omitted `−q_A f`. `analytic_gradient` dropped the field
|
|
341
|
+
entirely. The screened long-range path and the periodic path now refuse a
|
|
342
|
+
field rather than ignore one — including on an isolated cell, where the field
|
|
343
|
+
is well-defined but that machinery still does not carry it.
|
|
344
|
+
- **Every periodic image was given the reference cell's coordination-number
|
|
345
|
+
response.** `cn[image] = cn[parent]` is exact at `Γ`, where all copies move
|
|
346
|
+
alike, and wrong under a phased displacement, where the image's neighbours
|
|
347
|
+
move by different amounts than its parent's do. It was therefore invisible to
|
|
348
|
+
every test that existed: at `q = 0` it is not an approximation. An image is
|
|
349
|
+
now given its own coordination number wherever the cluster is wide enough
|
|
350
|
+
around it for that number to be right — a triangle-inequality test against the
|
|
351
|
+
cluster radius — and the parent's beyond. Restoring the blanket copy fails the
|
|
352
|
+
supercell folding test by four thousand times its tolerance.
|
|
353
|
+
- **The phased reciprocal sum carried a conjugated phase**, `e^{+i(G+q)·d}` where
|
|
354
|
+
Poisson summation gives `e^{−i(G+q)·d}`, which made `D(q)` wrong in its
|
|
355
|
+
imaginary parts at a generic interior `q` in 3D. Every existing test was
|
|
356
|
+
structurally blind to it: the oracle contraction is identically real over `±d`
|
|
357
|
+
pairs, the folding test's `q = b/2` makes the shifted set negation-symmetric,
|
|
358
|
+
and the internal identities conjugate both halves together. Pinned now by the
|
|
359
|
+
α-independence of `Im Φ_q` and by `Φ_q(d+T₀) = e^{−iq·T₀} Φ_q(d)`.
|
|
360
|
+
- **The shipped Python is now ASCII.** Sixteen docstrings could not be printed on
|
|
361
|
+
a legacy-codepage console: the package imported and computed correctly, and
|
|
362
|
+
`help()` on it raised `UnicodeEncodeError` on Japanese, Chinese or Korean
|
|
363
|
+
Windows. Installing was never affected — verified by building the sdist and
|
|
364
|
+
installing it under a forced `cp932` locale — but a documented API surface was
|
|
365
|
+
unusable. Two tests keep it that way.
|
|
366
|
+
- **The divide-and-conquer radii disagreed between layers by a factor of 1.9.**
|
|
367
|
+
`DcOptions` defaults to 6.0 and 9.0 *Bohr*; the Python signature took Ångström
|
|
368
|
+
and defaulted to 6.0 and 9.0, so a caller who omitted the argument silently got
|
|
369
|
+
subsystems nearly twice the intended size. The CLI and `pm3_rs.native` were
|
|
370
|
+
right; the extension's own defaults are now the same radii.
|
|
371
|
+
- **`long_range_cutoff` was unreachable from `import pm3_rs`** — the whole
|
|
372
|
+
linear-scaling path existed and could not be switched on.
|
|
373
|
+
- **`phonons` alone had no `reference` argument**, so an open-shell cell could
|
|
374
|
+
not be asked for. ASE's forwarding is now by keyword, since adding the argument
|
|
375
|
+
immediately exposed a positional call handing `method` to `reference`.
|
|
376
|
+
- The type stub declared `optimize(max_iter, gtol)`, arguments that never
|
|
377
|
+
existed. A test now compares every stub signature against the extension, and
|
|
378
|
+
another asserts `pm3_rs.native` can pass every argument the extension accepts.
|
|
379
|
+
|
|
380
|
+
## 0.2.0 — 2026-08-27
|
|
381
|
+
|
|
382
|
+
Periodic boundary conditions. Molecular results are unchanged except for one
|
|
383
|
+
bug fix, noted below.
|
|
384
|
+
|
|
385
|
+
### Added — periodic boundary conditions
|
|
386
|
+
|
|
387
|
+
- `Cell` on `Molecule`, covering 1D chains, 2D slabs, and 3D crystals. The
|
|
388
|
+
non-periodic directions never enter the measure, the reciprocal basis, or the
|
|
389
|
+
stress, so a slab's vacuum thickness cannot affect a result.
|
|
390
|
+
- Γ-point periodic SCF (`pbc::gamma::run_gamma`), RHF and UHF, with analytic
|
|
391
|
+
forces and analytic stress (`pbc::gradient::periodic_gradient`) and
|
|
392
|
+
fixed- or variable-cell relaxation (`pbc::optimize::relax`).
|
|
393
|
+
- Ewald electrostatics with the dimension dependence confined to the reciprocal
|
|
394
|
+
sum: 3D tinfoil, exact 2D Parry slab, a cell-grouped 1D direct sum, and a
|
|
395
|
+
plain owner-excluded pair sum at zero dimensions. Charged cells are supported
|
|
396
|
+
in every dimensionality — a uniform neutralizing background in 3D and 2D, a
|
|
397
|
+
neutralizing line charge in 1D — and the neutralizer's potential enters the
|
|
398
|
+
Fock matrix rather than only correcting the energy afterwards.
|
|
399
|
+
- D3, H4, X, and the simple hydrogen-bond correction are lattice summed,
|
|
400
|
+
including the D3 coordination number over images.
|
|
401
|
+
- `PeriodicResult::gamma_margin` reports the Γ-point validity condition — see
|
|
402
|
+
`docs/pbc.md`. A cell narrower than the exchange cutoff converges cleanly to
|
|
403
|
+
an answer that is wrong by tens of eV, and nothing else reveals it.
|
|
404
|
+
- **k-point sampling** (`pbc::kscf::run_kpoints`), RHF and UHF, neutral and
|
|
405
|
+
charged: Γ-centred Monkhorst–Pack meshes with exact time-reversal reduction,
|
|
406
|
+
a global Fermi level with optional Fermi–Dirac smearing, fixed or free
|
|
407
|
+
magnetization, and band structures along a path. Sampling more than one `k`
|
|
408
|
+
is what lets `P(0, T)` decay with `T`, which lifts the Γ-point cell-width
|
|
409
|
+
condition above.
|
|
410
|
+
- New `cmatrix` module: complex Hermitian matrices and eigensolver. ZDO makes
|
|
411
|
+
`S(k) = I`, so no generalized eigenproblem is needed.
|
|
412
|
+
- **Γ-point analytic Hessian and phonons** (`pbc::hessian`), with the Ewald
|
|
413
|
+
second derivative (`pbc::ewald_hessian`) and the periodic CPHF response. The
|
|
414
|
+
acoustic sum rule holds before enforcement, so enforcement cleans up rounding
|
|
415
|
+
rather than hiding a misplaced term.
|
|
416
|
+
- **Divide and conquer** (`dc`), molecular and Γ-point periodic, RHF and UHF:
|
|
417
|
+
Yang–Lee partitioning with a single global chemical potential, plus forces and
|
|
418
|
+
stress from the partitioned density.
|
|
419
|
+
- **Zero dimensions as a member of the same family** (`Cell::isolated`). An
|
|
420
|
+
isolated system is the case with no images, where the lattice sum is a plain
|
|
421
|
+
owner-excluded pair sum. Running a molecule through the periodic path gives it
|
|
422
|
+
the crystal's `O(N)` near field — neighbour-list pair tables plus a
|
|
423
|
+
point-charge model outside the cutoff — instead of a dense `O(N²)` pair cache.
|
|
424
|
+
It reproduces molecular PM3 to 3 µeV per atom with the default switch, and to
|
|
425
|
+
`1e-8` eV total with the switch pushed past the molecule; the error is
|
|
426
|
+
intensive, staying put from 12 to 288 atoms rather than accumulating.
|
|
427
|
+
- **A linear-scaling near field for molecular divide and conquer**
|
|
428
|
+
(`DcOptions::long_range_cutoff`, `None` by default). Same split at zero
|
|
429
|
+
dimensions. Measured at 960 atoms: 7.0× faster than full diagonalization
|
|
430
|
+
against 3.2× for the dense path, with the log-log slope down from 2.03 to
|
|
431
|
+
1.27. Left off by default because the switch costs a measured 28 µeV per atom,
|
|
432
|
+
about a hundred times the divide-and-conquer truncation at the default buffer.
|
|
433
|
+
- **A multipole far field for isolated systems.** Beyond 80 Bohr two atoms now
|
|
434
|
+
interact through their net charge, dipole and second moment rather than through
|
|
435
|
+
all six hundred and twenty-five of their auxiliary site pairs, with the
|
|
436
|
+
potential carried back out to the sites by a Taylor expansion of matching
|
|
437
|
+
order. The self-consistent field also stops computing site gradients it never
|
|
438
|
+
reads. Together these took the Fock build at 960 atoms from 2.52 s to 1.22 s
|
|
439
|
+
and the whole run from 4.9 s to 3.0 s, with no measurable change in the answer
|
|
440
|
+
— the divide-and-conquer error stays at 28.2 µeV/atom, and `pbc::ewald`'s own
|
|
441
|
+
test puts the collapse at 8 µeV over forty-eight atoms.
|
|
442
|
+
`PM3_DC_PROFILE=1` prints the loop's split into Fock build, subsystem solves
|
|
443
|
+
and density assembly.
|
|
444
|
+
- **Analytic stress in 1D.** 1D is summed directly rather than through
|
|
445
|
+
reciprocal space, so its virial is the ordinary pair virial and comes free
|
|
446
|
+
with the gradients. Only the axial component exists; the projection onto the
|
|
447
|
+
strains a cell actually has now happens once, for every dimensionality, rather
|
|
448
|
+
than being left to each sum.
|
|
449
|
+
- **Charged 1D cells**, at Γ and at any k mesh, through a uniform neutralizing
|
|
450
|
+
line charge. The subtraction is in terms of the physical extent summed rather
|
|
451
|
+
than the image count — `H_N + ln(L/L₀)` with `L₀` a fixed reference — which is
|
|
452
|
+
what makes it size-consistent. `Q² H_N / L` alone gives each cell a stable,
|
|
453
|
+
plausible energy while a chain and its own doubled cell disagree by eV.
|
|
454
|
+
- **Sparkles, point atoms and `d` shells in the periodic path.** An atom with no
|
|
455
|
+
orbitals has no electronic multipoles, so its realization is the nucleus it
|
|
456
|
+
already has; a `d` element's realization comes from MNDO-d's own multipole
|
|
457
|
+
table, which has only `l ≤ 2` and so is carried in full rather than truncated.
|
|
458
|
+
(PM3 itself parameterizes all forty-two of its elements on an s/p basis, so
|
|
459
|
+
the `d` path is unreachable through the standard tables and its test builds
|
|
460
|
+
its own subject.)
|
|
461
|
+
- **DFPT at arbitrary `q`** (`pbc::dfpt`), with the phased lattice sum it needs
|
|
462
|
+
(`pbc::phased`): the dynamical matrix and phonon frequencies at any
|
|
463
|
+
wavevector, from the primitive cell, at a cost that does not depend on `q`.
|
|
464
|
+
The electrons' response is included, and it is most of the answer — on water
|
|
465
|
+
it turns a rigid-ion force constant of 5.96 eV/Bohr² into 0.56.
|
|
466
|
+
`rigid_ion_dynamical_matrix` gives the fixed-density part alone.
|
|
467
|
+
|
|
468
|
+
Validated at two levels. `D(0)` against `pbc::hessian::periodic_hessian`, an
|
|
469
|
+
independent implementation with no phases in it, itself checked against finite
|
|
470
|
+
differences; and `D(q)` by folding, a doubled cell's Γ-point force constants
|
|
471
|
+
holding the primitive cell's `D(0)` and `D(zone boundary)` between them. Each
|
|
472
|
+
constituent is separately checked against something built differently, which
|
|
473
|
+
is what made three real bugs findable rather than merely visible: a lattice
|
|
474
|
+
sum that skipped same-atom images, a phased kernel with no Ewald self term,
|
|
475
|
+
and an exchange handed a total density where it wanted a spin one.
|
|
476
|
+
|
|
477
|
+
The response samples one k-point, `Γ`, paired with `q` — the sampling the
|
|
478
|
+
ground state used, carrying the same `gamma_margin` condition. 3D, closed
|
|
479
|
+
shell, plain PM3.
|
|
480
|
+
- New `docs/pbc.md` and `docs/divide-and-conquer.md`.
|
|
481
|
+
|
|
482
|
+
### Added — interfaces
|
|
483
|
+
|
|
484
|
+
- Python: `periodic_single_point`, `periodic_forces`, `phonons` and
|
|
485
|
+
`divide_and_conquer`, all taking a cell in Ångström.
|
|
486
|
+
- ASE: the calculator switches to the periodic path from `atoms.pbc`, adds
|
|
487
|
+
`stress` to `implemented_properties` (6-component Voigt, eV/ų) and gains
|
|
488
|
+
`get_phonons`. A molecule or a slab raises on `get_stress()` rather than
|
|
489
|
+
returning zeros, because zeros would be a claim rather than an absence.
|
|
490
|
+
- CLI: `--cell`, `--pbc`, `--kpts`, `--dc`, `--dc-core`, and the `stress`,
|
|
491
|
+
`phonons` and `bands` subcommands. A Γ-point run that violates the validity
|
|
492
|
+
condition prints a warning naming it.
|
|
493
|
+
- Packaging: PEP 639 licence metadata, classifiers, project URLs, keywords,
|
|
494
|
+
a `py.typed` marker and `_native.pyi` stubs, and GitHub Actions workflows for
|
|
495
|
+
CI and for wheels published through PyPI Trusted Publishing.
|
|
496
|
+
- **`pip install` puts the `pm3-rs` command on your path.** The CLI moved out of
|
|
497
|
+
`src/bin` and into the library, taking its argument vector rather than reading
|
|
498
|
+
the process environment, so a PyO3 wrapper can hand it `sys.argv`. `pip` and
|
|
499
|
+
`cargo install` give the identical interface rather than two that drift.
|
|
500
|
+
Verified by installing the source distribution into a clean virtualenv,
|
|
501
|
+
compiling from scratch, and running the installed command.
|
|
502
|
+
|
|
503
|
+
### Fixed
|
|
504
|
+
|
|
505
|
+
- **XYZ files with a byte-order mark.** Notepad and PowerShell's
|
|
506
|
+
`-Encoding utf8` both put one in front of the atom count, which produced
|
|
507
|
+
`invalid XYZ atom count: 3` on a file whose first line was visibly `3`. A
|
|
508
|
+
leading mark is now stripped; a second one is still an error, because that is a
|
|
509
|
+
malformed file rather than a Windows editor.
|
|
510
|
+
- **Charged-system dipole origin.** The dipole was referenced to the coordinate
|
|
511
|
+
origin rather than to the centre of mass, so for any system with a net charge
|
|
512
|
+
it depended on where the molecule sat: translating NH₄⁺ by 5 Å moved its
|
|
513
|
+
dipole from 0 to 24.02 D. It now matches MOPAC's `dipole.F90` (centre of mass,
|
|
514
|
+
with `+`/`−` point atoms carrying zero mass) for all 60 oracle cases to ~1e-6 D.
|
|
515
|
+
Neutral systems are unaffected, the dipole being origin-independent there.
|
|
516
|
+
- `erfc` was computed as `1 − erf`, which cancels catastrophically: 1.16e-8
|
|
517
|
+
relative error at `x = 3.9`. It now uses a continued fraction above `x = 2`,
|
|
518
|
+
giving 3.9e-14 against scipy over 157 points. (New code only; no molecular
|
|
519
|
+
result used `erfc`.)
|
|
520
|
+
- The DIIS coefficient solve normalizes its Gram matrix before the pivot test.
|
|
521
|
+
The threshold was absolute while `⟨E_i,E_j⟩ ~ ‖E‖²` shrinks quadratically, so
|
|
522
|
+
a converging run could be handed a matrix of ~1e-10 entries and get back
|
|
523
|
+
wildly amplified coefficients — DIIS converging nicely and then walking back
|
|
524
|
+
out into a limit cycle. The coefficients are invariant under the scaling, so
|
|
525
|
+
no converged result changes.
|
|
526
|
+
|
|
527
|
+
- **The Γ-point periodic energy belonged to no state.** It was reported as
|
|
528
|
+
`½(P·H + P·F)` with `F` the *extrapolated* Fock — a combination of history
|
|
529
|
+
matrices — paired with the freshly diagonalized density, while the density
|
|
530
|
+
actually returned was the damped and extrapolated one. Three different
|
|
531
|
+
objects. The convergence test could not see it: a stable set of DIIS weights
|
|
532
|
+
makes a wrong energy stop moving as convincingly as a right one. Worst case
|
|
533
|
+
measured, 1.03 eV on a chain of 92 water molecules; usually far below
|
|
534
|
+
tolerance, which is why it survived. The k-point path was checked for the same
|
|
535
|
+
defect and does not have it — its accelerator extrapolates the density, not
|
|
536
|
+
the Fock.
|
|
537
|
+
- **The `B` auxiliary integrals lost seven digits just above `|x| = 0.5`.**
|
|
538
|
+
MOPAC's closed-form recursion multiplies its cancellation by `k/|x|` at every
|
|
539
|
+
step, and its power series stops at `0.5`; at `x = −0.51`, `B₉` was off by a
|
|
540
|
+
relative `5e-7` against the integral itself. Below `1e-6` the `x → 0` branch
|
|
541
|
+
returned constants, so a differentiating scalar got a zero derivative where
|
|
542
|
+
`dB₁/dx = −2/3`. Both go away by summing the defining series directly out to
|
|
543
|
+
`|x| = 3` — absolutely convergent, every term independent of every other,
|
|
544
|
+
exact at `x = 0` including its derivative — and the `x → 0` special case
|
|
545
|
+
disappears rather than being widened. Every frozen MOPAC value is unchanged.
|
|
546
|
+
- **The Slater overlap went NaN beyond about 500 Bohr.** `A_k` carries
|
|
547
|
+
`e^{−r(ζ_a+ζ_b)/2}` and `B_k` carries `e^{+r|ζ_a−ζ_b|/2}`; the overlap is their
|
|
548
|
+
product, which is tiny, but the two factors separately are `0` and `∞`, and
|
|
549
|
+
`B` overflows first. For an O–H pair that happens at 501 Bohr, and `0 · ∞` then
|
|
550
|
+
propagated silently through `H_core`. This affects **any** calculation
|
|
551
|
+
containing a 500 Bohr separation, not only a periodic or partitioned one; it
|
|
552
|
+
surfaced here because divide-and-conquer was the first thing run on a system
|
|
553
|
+
that large. The overlap is now cut where its own decaying factor is below
|
|
554
|
+
`1e-130`, so no overlap that could matter is affected and every frozen MOPAC
|
|
555
|
+
value is unchanged.
|
|
556
|
+
|
|
557
|
+
### Performance
|
|
558
|
+
|
|
559
|
+
- **The periodic SCF now uses the molecular path's accelerator.** Plain CDIIS
|
|
560
|
+
interpolates the Fock with unconstrained weights and has no notion of the
|
|
561
|
+
energy going down, so only damping held it and how much damping is enough
|
|
562
|
+
grows with the system: on a chain of identical waters it converged at 56
|
|
563
|
+
molecules, failed at 58, converged again at 66, then failed at every size
|
|
564
|
+
beyond. Restricted periodic runs now use A-DIIS until the commutator is small
|
|
565
|
+
and CDIIS after, sharing `crate::scf`'s history rather than a second copy.
|
|
566
|
+
Every size converges, in 25 iterations where damped CDIIS needed 110.
|
|
567
|
+
- **The Ewald sum caches everything that depends on the geometry** — the
|
|
568
|
+
reciprocal enumeration, the neighbour list, and the `cos(G·r)`/`sin(G·r)` of
|
|
569
|
+
every site against every `G`. An SCF changes only the charges. Capped at
|
|
570
|
+
256 MiB, above which the trigonometry is recomputed.
|
|
571
|
+
- **Divide and conquer no longer does `O(N²)` work on its own structural
|
|
572
|
+
zeros.** A partitioned density is zero wherever no subsystem holds both
|
|
573
|
+
orbitals — the approximation the method makes, not a rounding effect. DIIS,
|
|
574
|
+
damping, the RMS change and the energy trace now run over a recorded sparsity
|
|
575
|
+
pattern, and every matrix the loop touches is allocated once. At 960 atoms the
|
|
576
|
+
DIIS history alone fell from 7.17 s to 0.19 s and the whole run from 25.5 s to
|
|
577
|
+
6.5 s, with the converged energies unchanged to every printed digit.
|
|
578
|
+
- **The ASE calculator computes forces unasked.** ASE requests one property at a
|
|
579
|
+
time and re-enters `calculate` for each, so naming only what was asked
|
|
580
|
+
converged the same SCF two or three times per MD step. With the Ewald cache, a
|
|
581
|
+
periodic single point went from 250 ms to 53 ms and an MD step from ~520 ms to
|
|
582
|
+
74 ms.
|
|
583
|
+
- Periodic SCF: CDIIS on the `[F, P]` commutator, replacing plain damping
|
|
584
|
+
(about 38 iterations → a dozen, each costing a full lattice sum).
|
|
585
|
+
- Periodic UHF starts from core-Hamiltonian orbitals occupied to the two aufbau
|
|
586
|
+
counts rather than a spin-scaled atomic-density guess. The scaled guess starts
|
|
587
|
+
inside the spin-symmetric subspace, which for the methyl radical contains a
|
|
588
|
+
stationary point 4.8 eV above the UHF minimum: damping needed ~25 wasted
|
|
589
|
+
cycles to escape it and CDIIS converged straight onto it. Methyl in a cell
|
|
590
|
+
now takes 9 iterations rather than 96, and reaches the correct solution.
|
|
591
|
+
- The Ewald reciprocal sums enumerate one member of each `±G` pair and double.
|
|
592
|
+
Every quantity they produce is even under `G → −G`.
|
|
593
|
+
|
|
594
|
+
## 0.1.2
|
|
595
|
+
|
|
596
|
+
Performance and memory release. No change to any computed PM3 quantity: the
|
|
597
|
+
MOPAC v23.2.5 oracle regressions (heats of formation, charges, gradients,
|
|
598
|
+
optimized geometries, frequencies, special atoms, Sparkles) are unchanged.
|
|
599
|
+
|
|
600
|
+
### Performance
|
|
601
|
+
|
|
602
|
+
- The two-center Fock build — the `O(N²)` part of every SCF and CPHF iteration —
|
|
603
|
+
now runs batched-parallel over atom pairs instead of in a single serial loop.
|
|
604
|
+
- The two-center Coulomb term is contracted as two packed mat-vecs over the
|
|
605
|
+
`(μν)`/`(λσ)` orbital-pair indices rather than a four-index loop: 100 instead
|
|
606
|
+
of 256 multiply-adds per sp/sp pair, with identical arithmetic.
|
|
607
|
+
- The SCF accelerator keeps its `⟨E_i,E_j⟩` and `⟨D_i,F_j⟩` Gram matrices
|
|
608
|
+
incrementally, evaluating only the new row and column each iteration. The
|
|
609
|
+
A-DIIS difference matrices `D_i − D_n` / `F_j − F_n` are no longer
|
|
610
|
+
materialized, removing `2 × depth` full `nao × nao` temporaries per iteration.
|
|
611
|
+
- `[F,P]` is formed with one matrix product instead of two (`F` and `P` are
|
|
612
|
+
symmetric, so `PF = (FP)ᵀ`), and is skipped entirely when no accelerator runs.
|
|
613
|
+
- The two-electron pair table is a single flat buffer instead of a `Vec<Vec<_>>`,
|
|
614
|
+
removing one heap allocation per packed row and making each row contiguous.
|
|
615
|
+
- The classical-correction (D3/H4/X) Hessian off-diagonal loop runs on rayon.
|
|
616
|
+
- Elementwise reductions (`frobenius_dot`, RMS density change, history
|
|
617
|
+
combination) go parallel above 2^18 elements.
|
|
618
|
+
- `symmetric_eigen` skips building a permutation when `faer` already returns
|
|
619
|
+
ascending eigenvalues.
|
|
620
|
+
- 900-atom water cluster (nao = 1800), 16 cores: 35.7 s → 17.7 s wall.
|
|
621
|
+
|
|
622
|
+
### Memory
|
|
623
|
+
|
|
624
|
+
- The pair cache no longer retains the electron–core attraction blocks
|
|
625
|
+
`e1b`/`e2a` (2 × 81 `f64` per pair). They are consumed while `H_core` is
|
|
626
|
+
assembled and never referenced again; holding them cost 1.3 KiB per pair
|
|
627
|
+
(3.7 GiB for a 2400-atom system) for no benefit.
|
|
628
|
+
- New `Pm3Options::integral_memory_mb` (env `PM3_MAX_PAIR_CACHE_MB`, default
|
|
629
|
+
4096 MiB): the `O(N²)` pair cache size is computed in closed form before the
|
|
630
|
+
allocation and reported as a `ResourceLimit` error if it exceeds the budget.
|
|
631
|
+
- New `Pm3Options::scf_memory_mb` (default 512 MiB): bounds the SCF accelerator
|
|
632
|
+
history. The DIIS depth is reduced to fit, and A-DIIS degrades to CDIIS (which
|
|
633
|
+
needs no density history) rather than exceeding the budget.
|
|
634
|
+
- 900-atom water cluster peak resident set: ≈ 1.4 GiB → 0.75 GiB.
|
|
635
|
+
|
|
636
|
+
### Documented-API verification
|
|
637
|
+
|
|
638
|
+
Every code block and stated guarantee in `README.md`, `docs/rust-api.md`, and
|
|
639
|
+
`docs/python-api.md` is now executed as a test, and the CLI is exercised over
|
|
640
|
+
every documented subcommand, flag, and error path.
|
|
641
|
+
|
|
642
|
+
- `tests/api_surface.rs` (new, 15 tests) — the Rust surface: constructors,
|
|
643
|
+
`Pm3Options` fields and defaults, the `Pm3Result`/`GradientResult`/
|
|
644
|
+
`VibrationalModes`/`OptResult` field lists, unit conventions, `Variant::parse`,
|
|
645
|
+
reference selection, the memory budgets, and the documented error variants.
|
|
646
|
+
- `tests/test_python_api.py` (new, 19 tests) — `pm3_rs.native` dict keys and
|
|
647
|
+
Hartree↔eV / Bohr↔Å conversions, the `pm3_rs.ase.PM3` calculator (ASE units,
|
|
648
|
+
lazy Hessian, every accessor), and both documented example blocks.
|
|
649
|
+
|
|
650
|
+
Fixed along the way:
|
|
651
|
+
|
|
652
|
+
- **`pm3_rs.ase.PM3`**: every accessor (`get_potential_energy`, `get_forces`,
|
|
653
|
+
`get_gradient`, `get_hessian`, `get_frequencies`) raised
|
|
654
|
+
`AttributeError: 'NoneType' object has no attribute 'get_atomic_numbers'` when
|
|
655
|
+
called with no argument before the calculator had been bound to a structure.
|
|
656
|
+
They now raise a `RuntimeError` that names the three ways to fix it.
|
|
657
|
+
- `docs/python-api.md`: the cation example was labelled "forced UHF doublet" but
|
|
658
|
+
passed `multiplicity=1` (NH4+ is a closed-shell singlet), and its
|
|
659
|
+
`nh4_positions` was never defined, so the block could not be run as printed.
|
|
660
|
+
Corrected, given a runnable geometry, and extended with an open-shell example.
|
|
661
|
+
- `docs/python-api.md`: documented what `atoms=None` means for the ASE
|
|
662
|
+
accessors — assigning `atoms.calc = PM3()` does not bind the structure.
|
|
663
|
+
|
|
664
|
+
### Documentation
|
|
665
|
+
|
|
666
|
+
- Removed the claims that this crate is derived from or structured after sibling
|
|
667
|
+
Rust projects; the PM3 Hamiltonian, parameters, and derivations are documented
|
|
668
|
+
against MOPAC v23.2.5 and the primary literature.
|
|
669
|
+
- `third_party/pyseqm/NOTICE` added — `THIRD_PARTY_NOTICES.md` referenced a
|
|
670
|
+
`third_party/pyseqm/LICENSE` that was not present.
|
|
671
|
+
- Corrected the MOPAC reference values quoted in the `scf.rs` unit-test comments
|
|
672
|
+
for water and the methyl radical; they did not match the asserted PM3 values.
|
|
673
|
+
- `docs/rust-api.md`: documented the three memory budgets and corrected the
|
|
674
|
+
description of the Hessian `step` argument.
|
|
675
|
+
|
|
676
|
+
## 0.1.1
|
|
677
|
+
|
|
678
|
+
Initial `pm3-rs` release, backed by the PM3 Hamiltonian and the MOPAC v23.2.5
|
|
679
|
+
PM3 parameter tables.
|
|
680
|
+
|
|
681
|
+
- Rust implementation; linear algebra via `faer`, no BLAS/LAPACK dependency.
|
|
682
|
+
- RHF/UHF energies, heats of formation, charges, dipoles, analytic gradients,
|
|
683
|
+
CPHF/UCPHF Hessians, optimization, and frequencies.
|
|
684
|
+
- PM3-D3, PM3-D3H4, and PM3-D3H4X correction variants.
|
|
685
|
+
- MOPAC special atoms `Cb`, `+`, `-`, and La-Lu trivalent Sparkles.
|
|
686
|
+
- Rust library `pm3_rs`, CLI `pm3_rs_cli`, and Python distribution
|
|
687
|
+
`pm3-rs-python` with native and ASE APIs.
|
|
688
|
+
- MOPAC v23.2.5 oracle regressions for closed-shell molecules, an open-shell
|
|
689
|
+
radical, a Sparkle complex, point charges, gradients, optimization, and
|
|
690
|
+
frequencies.
|
|
691
|
+
- Configurable Hessian workspace budget and bounded integral-cache construction.
|
|
692
|
+
- Release profile uses fat LTO with one code-generation unit.
|