Skip to content

Nuclear Gradients - #124

Closed
maxscheurer wants to merge 25 commits into
masterfrom
gradients
Closed

Nuclear Gradients#124
maxscheurer wants to merge 25 commits into
masterfrom
gradients

Conversation

@maxscheurer

@maxscheurer maxscheurer commented Jan 13, 2021

Copy link
Copy Markdown
Member

Implemented together with @Drrehn.

Features

  • MP2
  • ADC(0)
  • ADC(1)
  • ADC(2)
  • ADC(2)-x
  • ADC(3)
  • CVS-ADC(1) (by @iubr)
  • CVS-ADC(2) (by @iubr)
  • CVS-ADC(2)-x (by @iubr)

ToDo

@lgtm-com

lgtm-com Bot commented Jan 13, 2021

Copy link
Copy Markdown

This pull request introduces 1 alert when merging 6b92a09 into 6e7ac34 - view on LGTM.com

new alerts:

  • 1 for Unused local variable

@lgtm-com

lgtm-com Bot commented Jan 13, 2021

Copy link
Copy Markdown

This pull request introduces 1 alert when merging 7136d03 into 6e7ac34 - view on LGTM.com

new alerts:

  • 1 for Unused local variable

Comment thread adcc/AmplitudeVector.py Outdated
@mfherbst

Copy link
Copy Markdown
Member

Awesome 👍

@lgtm-com

lgtm-com Bot commented Jan 16, 2021

Copy link
Copy Markdown

This pull request introduces 1 alert when merging 844cc1c into 6e7ac34 - view on LGTM.com

new alerts:

  • 1 for Unused local variable

@lgtm-com

lgtm-com Bot commented Jan 16, 2021

Copy link
Copy Markdown

This pull request introduces 1 alert when merging 10980bf into 6e7ac34 - view on LGTM.com

new alerts:

  • 1 for Unused local variable

@lgtm-com

lgtm-com Bot commented Jan 17, 2021

Copy link
Copy Markdown

This pull request introduces 1 alert when merging 3f82618 into 6e7ac34 - view on LGTM.com

new alerts:

  • 1 for Unused local variable

@lgtm-com

lgtm-com Bot commented Jan 19, 2021

Copy link
Copy Markdown

This pull request introduces 1 alert when merging b92ee1b into 041cdcb - view on LGTM.com

new alerts:

  • 1 for Unused local variable

@lgtm-com

lgtm-com Bot commented Feb 21, 2021

Copy link
Copy Markdown

This pull request introduces 1 alert when merging 1a3c802 into 041cdcb - view on LGTM.com

new alerts:

  • 1 for Unused local variable

@maxscheurer
maxscheurer force-pushed the gradients branch 2 times, most recently from 1717c34 to 54a9181 Compare June 3, 2021 12:54
@lgtm-com

lgtm-com Bot commented Jun 3, 2021

Copy link
Copy Markdown

This pull request introduces 1 alert when merging 54a9181 into e4ca2ed - view on LGTM.com

new alerts:

  • 1 for Unused local variable

@maxscheurer
maxscheurer force-pushed the gradients branch 2 times, most recently from 6e67a9d to 365cd5a Compare June 3, 2021 13:57
@lgtm-com

lgtm-com Bot commented Dec 14, 2021

Copy link
Copy Markdown

This pull request introduces 4 alerts when merging 92e7535 into e4ca2ed - view on LGTM.com

new alerts:

  • 3 for Unused import
  • 1 for Unused local variable

@lgtm-com

lgtm-com Bot commented Dec 14, 2021

Copy link
Copy Markdown

This pull request introduces 4 alerts when merging df675fa into e4ca2ed - view on LGTM.com

new alerts:

  • 3 for Unused import
  • 1 for Unused local variable

@mfherbst

Copy link
Copy Markdown
Member

Of course tests need to work and all, but I wonder if a good first aim is to just get the things already implemented merged into master and release a new version?

@maxscheurer

Copy link
Copy Markdown
Member Author

The tests are horrible to implement... ensuring highly converged eigenvectors is difficult 😞 but in principle all the implemented gradient methods are correct 👍🏻

@iubr

iubr commented Dec 15, 2021

Copy link
Copy Markdown

Not sure if this is a good suggestion, but maybe we could run a couple of small molecules and save the results in a text file for pytest to compare agains (this is more or less how it's done in veloxchem). I can try out the cvs-adc gradients and find an optimal/good enough setup (I guess the question is of comparing analytical vs. numerical gradients)

@maxscheurer

Copy link
Copy Markdown
Member Author

The generation of finite-difference reference data works fine already, for both generic and CVS-ADC 😊👍🏻

@mfherbst

Copy link
Copy Markdown
Member

The tests are horrible to implement... ensuring highly converged eigenvectors is difficult

Can't you lower to tolerance or introduce perturbations in the molecule / orbitals to ensure they are sufficiently distinct (to avoid rotations in invariant subspaces etc.)

@maxscheurer

Copy link
Copy Markdown
Member Author

You mean the convergence tolerance/atol for asserts?

You mean geometry perturbations to break symmetries? Yes, I can try that for sure 👍🏻

@mfherbst

Copy link
Copy Markdown
Member

You mean the convergence tolerance/atol for asserts?

Yes. It's not amazing, but it will at least catch forgotten factors of two or switched signs.

@maxscheurer

Copy link
Copy Markdown
Member Author

@iubr, the CVS-ADC2-x don't seem to be correct right now, at least I get an error for H2O of ~1e-5 (compared to 5-point finite differences). Maybe I made some mistake while cleaning up the code... Which accuracy could you obtain in your tests? I'll try to run your original branch again at some point to get to the bottom of this.

@iubr

iubr commented Dec 18, 2021

Copy link
Copy Markdown

The ~1e-5 is similar to what I get with my code (in comparison to the 2-point finite difference numerical gradient). I checked it today with a 5-point finite difference and it is a little smaller, but still same order (1e-5). So if there is a bug, it would be in my module as well.
I had discussed this difference with Patrick and we had concluded that it could be due to the tolerance/convergence threshold for the adc step (I couldn't go better than 1e-6 because of the requirement for the HF reference). Of course, this might not be the right explanation for the difference. It's, of course, possible that there is still an undiscovered bug or mistake somewhere in the code/equations. I'll make another check next week. Let's see.

@maxscheurer

Copy link
Copy Markdown
Member Author

I had discussed this difference with Patrick and we had concluded that it could be due to the tolerance/convergence threshold for the adc step

I converge the SCF to 1e-12, and the ADC procedure is converged to 1e-10 in the energy. Given the fact that all other gradient methods so far can achieve at least 1e-7 accuracy, I'm convinced that one can achieve the same for CVS-ADC2-x . I'll try to add canonical ADC2-x gradients now and see how accurate one can get.

@iubr

iubr commented Dec 19, 2021

Copy link
Copy Markdown

Thanks a lot!

maxscheurer and others added 15 commits October 23, 2024 22:22
lgtm and coveralls (hopefully)

more fdiff

fix
hijacked AmplitudeVector for CVS-ADC orbital response.

CVS-ADC0 working!

cvs-adc1 working
added cvs-adc2 2PDMs,started orbital response (need to check if the RHS is correct)

cvs-adc2 gradient working.

cvs-adc2x working
… vs. cvs-adc rhs.

fixed 1/sqrt(2)

additional comments, RHS

switched to using symmetrise and antisymmetrise to construct density matrices
@maxscheurer

maxscheurer commented May 29, 2026

Copy link
Copy Markdown
Member Author

Performance Optimization: 2-RDM / ERI Derivative Contraction (PySCF Backend)

Problem

The current gradient implementation has two major memory bottlenecks in correlated_gradient:

  1. Full AO transform of the 2-RDM: g2_total.to_ao_basis() produces two full (nao, nao, nao, nao) arrays
  2. Full ERI derivative pre-computation: mol.intor('int2e_ip1', aosym='s1') → shape (3, nao, nao, nao, nao)

Peak memory: ~5 × N⁴_AO × 8 bytes (at nao=100 → ~4 GB, at nao=150 → ~20 GB).

How PySCF handles this (MP2/CCSD gradients)

PySCF uses a two-part strategy that avoids full N⁴ tensors entirely:

Part 1: Correlated 2-RDM → shell-batched direct contraction

  • Half-transforms the 2-RDM: only virtual→AO, keeping occupied indices in MO basis. For MP2: (nocc, nocc, nao, nao) instead of (nao, nao, nao, nao).
  • Shell-batched streaming of int2e_ip1 (memory-capped, with aosym='s2kl' for kl permutational symmetry).
  • Completes the remaining occ→AO transform on-the-fly within the shell loop.
  • Direct einsum contraction of the ERI shell-slice with the density batch → accumulate gradient.

Part 2: Separable 1-RDM contributions → J/K gradient machinery

  • HF and orbital-response parts of the 2-RDM that factorize as products of 1-RDMs can be handled via _vhf.direct_mapdm (C-level integral-driven J/K with screening).
  • PySCF builds vhf (J-K response) piggybacking on the same shell loop for "free".
  • The correlated 2-RDM blocks (t2, u·u type) cannot use J/K — they require direct 4-index contraction.

Proposed Optimization Strategy

New method: PyScfGradientProvider.correlated_gradient_direct(g2_mo, g1, w, refstate) that accepts the block-sparse TwoParticleDensityMatrix in MO basis.

Combined approach — block-wise half-transform + shell-batched ERI contraction:

Block type Strategy
oovv (MP2 t2-like) Half-transform vv→AO → (nocc², nao²), then complete occ→AO on-the-fly in shell loop
ovov, ooov Half-transform the virtual indices → (nocc·nvir, nao) or similar, stream similarly
oooo Tiny (nocc⁴), can be fully transformed cheaply
ovvv, vvvv Large, but only appear at ADC(2)/ADC(3); will need batched strategies
HF oooo + orbital-response blocks Factor as 1-RDM products → use get_jk gradient calls

Memory target: O(nocc² × nao²) + O(shell_batch × nao³) instead of O(nao⁴).

Implementation Plan

  1. Start with MP2 (only oovv block non-zero in g2a) as proof of concept
  2. Keep existing naive correlated_gradient for validation
  3. Generalize to ADC(1) (adds ovov + orbital-response separable blocks)
  4. Then extend to ADC(2)/ADC(3) with all blocks

Reference

  • PySCF MP2 gradient: pyscf/grad/mp2.pygrad_elec(), _shell_prange()
  • PySCF CCSD gradient: pyscf/grad/ccsd.py_rdm2_mo2ao() (HDF5-backed half-transform)
  • adcc gradient: adcc/gradients/__init__.py, adcc/backends/pyscf.py
  • Theory: Rehn, Dreuw et al., J. Chem. Phys. 150, 164102 (2019), DOI: 10.1063/1.5085117

@maxscheurer maxscheurer mentioned this pull request Jun 14, 2026
7 tasks
* chore: open PR for issue 214

* implement PySCF gradient memory contraction and profiling script

* remove unused shell-batched gradient path

* extract spin sub-blocks for direct gradient AO-pair transform

Add a Tensor.export_block primitive that exports a dense rank-N
sub-block over a half-open per-axis window without materialising the
full tensor, mirroring the btod_export scatter (inverse permutation)
so it stays correct on antisymmetric same-space blocks.

Rewrite to_ao_pair_density to extract only the spin sub-block each
spin case needs via export_block and contract it with the compacted
non-zero-row coefficients, instead of densifying full zero-padded TPDM
blocks and full rank-4 AO tensors. Spin sub-blocks are pre-extracted
once and reused across pair chunks; the packed density is written once
per chunk to keep the out-of-core path I/O-efficient.

Matches the full-AO path to machine precision (RHF/UHF/CVS, cc-pVDZ/
pVTZ, memory and hdf5 storage). Peak RSS at cc-pVTZ drops ~25 percent
(in-memory) to ~37 percent (out-of-core), with the win growing with
basis size. Adds export_block and spin-subblock regression tests.

* address review findings: bound hdf5 memory, drop outcore alias, remove dead code

- hdf5 storage now picks a bounded default pair_chunk_size so the
  in-memory working buffer no longer grows to the full (nao, nao, npair)
- remove the undocumented 'outcore' alias; validate storage against an
  explicit {memory, hdf5} set with a matching error message
- guard the hdf5 cleanup so it cannot mask an in-flight exception
- drop dead code: numpy export_block fallback, redundant provider
  ao_pair_density_from_dense wrapper, unused _empty_gradient keys,
  unused cblk_size in Tensor::export_block
- fold duplicated direct/exchange spin-case loops; drop redundant copy;
  hoist loop-invariant np.any(offdiag)
- fix flake8 blank-line/E402 violations
- add tests for hdf5-vs-memory equivalence, scratch cleanup, bounded
  default chunk, and the new validation guards

* reframe TPDM AO-pair packing docstrings as backend-neutral

Describe the lower-triangular symmetric AO-pair packing as a generic
convention rather than a PySCF assumption; PySCF aosym="s2kl" is now
mentioned only as one concrete consumer example.

* rename gradient TEI->ERI and add nuclear gradients docs

Rename the two-electron-integral terminology to ERI throughout the
nuclear gradient API to match adcc's established convention: the
eri_contraction / eri_shell_chunk_size / eri_pair_chunk_size /
eri_pair_density_storage kwargs of nuclear_gradient, the PySCF
_contract_eri_with_packed_density method and Gradient["ERI"] key, and
the Psi4 Gradient["ERI"] key. Tests and the profiling script follow.

Add a Nuclear gradients documentation page (usage-first, followed by a
theory and algorithm section), a supported-methods table, and citations
to Rehn and Dreuw (2019) and Brumboiu et al. (2021).

* bump test data to 0.7.4 to fix download

The 0.7.2 test-data path on the upstream server is no longer available,
causing the testdata download in conftest.py to fail at pytest collection
time (and thus all CI test jobs). Pull update_testdata.sh (SOURCE bump to
0.7.4) and the regenerated SHA256SUMS from master.

* fix: add missing return in GradientComponents.total and expand gradient contraction tests
* chore: open PR for issue 219

* Merge origin/master into gradients branch

Integrate the master refactor (StateView/ElectronicStates restructure,
libadcc/ -> libadcc_src/ rename, pyproject.toml migration) into the
gradients feature branch.

Key changes for gradient code compatibility:
- Add forwarding properties (ground_state, reference_state, method) on
  Excitation for gradient code access
- Add total_energy property to ElectronicStates and StateView using the
  modern MethodLevel enum pattern
- Use OneParticleDensity (not OneParticleOperator) for gradient density
  matrices — PR 200 separated these with different AO transforms
  (densities: C^T D C, operators: S C^T D C S)
- Use OperatorSymmetry.NOSYMMETRY for CVS density matrices that require
  non-canonical block assignments (co/oc), with explicit transpose blocks
- Update excitation_property_keys blacklist to exclude forwarding props
- Modernize method.level usage to method.level.to_int() in tests
- Regenerate libadcc.pyi with export_block signature

Closes #219

* add backend-free total_energy and forwarding-property tests

Addresses review findings 2 and 3 on PR 224:
- test_total_energy (adc0/adc2) independently validates the total_energy
  formula across both MethodLevel branches and the Excitation/StateView
  delegation, instead of comparing against a self-generated snapshot.
- test_excitation_forwarding_properties asserts ground_state,
  reference_state and method forward to the parent ElectronicStates.

* fix CI: restore master test-data source and clear flake8 errors

The master merge accidentally reverted the test-data sync to the old
gradients-branch versions:
- update_testdata.sh SOURCE reverted from 0.7.5 back to 0.7.4
- SHA256SUMS *_mpdata.hdf5 hashes reverted to 0.7.4 values

The 0.7.4 server no longer hosts ch2nh2_sto3g_hfdata.hdf5, so pytest
collection failed with 'missing test data file'. Restore both files to
match origin/master.

Also clear pre-existing flake8 errors blocking the Code style job:
unused imports (unittest, itertools) and E302 in
functionality_gradients_test.py, and E501/W293 in dump_fdiff_gradient.py.

* fix CI test failures from master merge

Three merge regressions where gradient-branch code/tests conflicted with
master's state:

- LazyMp_test: master's mp2_diffdm produces the canonical 'oc' block
  (o1o2), and the 0.7.5 reference data stores dm_o1o2 for the adcc
  source (dm_o2o1 for adcman). Restore master's block list and the
  adcman-specific label remapping (fixes KeyError 'dm_o2o1').

- workflow: revert the gradient-branch 'warn instead of fail' changes
  back to master's strict InputError behaviour for too-low conv_tol and
  too-few guesses (workflow.py and workflow_test.py now match master).

- functionality_gradients_test: master's strict guess validation rejects
  requesting more states than available. h2o/sto3g cvs-adc0/adc1 only
  support 2 CVS singlets, so cap n_singlets to the number of reference
  states (len(energy_ref)) instead of the hard-coded 5.
* chore: open PR for issue 220

* implement restricted pair-density fast path

* strengthen RHF fast-path tests from review findings 2 and 3
* chore: open PR for issue 221

* implement geometry optimization scanner core

* refactor scanner API to accept PySCF SCF object

* fix scanner root tracking and add MP2+ADC2 excited example

Copy the PySCF Mole snapshot in the tracking descriptor so previous-step
AO bases are not silently mutated by the SCF scanner; add a regression
test for descriptor mol independence.

Rework the excited-state example to optimise on the MP2 ground-state
surface first and then run the ADC(2) excited-state optimisation from
the relaxed geometry using cc-pVDZ.

* switch excited example to s-trans-butadiene/6-31G*

Use the s-trans-butadiene geometry optimisation showcase from Rehn &
Dreuw (J. Chem. Phys. 150, 174110, 2019) with the 6-31G* basis.

* add scanner unit and geomeTRIC end-to-end gradient scanner tests

Extend the scanner unit tests with unconverged-SCF handling, an
excited-state match against the explicit run_adc + nuclear_gradient
loop, fixed-index and density-overlap root tracking, tracking-loss and
ambiguity guards, and SCF guess continuity.

Add functionality_geomopt_test.py with an end-to-end geomeTRIC ground-
state optimisation reaching a stationary point (gradient ~ 0) and a
calc_new custom-engine contract check; both skip cleanly when PySCF or
geomeTRIC is unavailable.

* harden scanner: reject MP3 gradients, observable root tracking, eager validation

Address review findings on PR 226:
- Reject ground-state MP targets with level != 2 (no MP3 gradient exists)
- Make root-tracking diagnostics observable on last_tracking every call
- Narrow _safe_ao_ndarray to expected unavailable-operator exceptions
- Validate follow eagerly at construction
- Warn on ignored run_adc kwargs for the string-MP target spelling
- Add root-reorder, excited-state e2e, and validation/scoring tests

* fix flake8: silence intended scanner re-export F401, wrap long example line

* declare gradients __all__ instead of noqa for scanner re-exports
@maxscheurer

Copy link
Copy Markdown
Member Author

Closed in favor of #230

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants