Skip to content

Conversation

@Awallace3
Copy link
Contributor

@Awallace3 Awallace3 commented Oct 11, 2024

Description

I have added an option to compute the necessary GRAC shifts for SAPT(DFT) automatically. I added a little extra logic to "try harder" at converging cations to hopefully fail less often if this option is specified. These changes will enable more users to call SAPT(DFT) more routinely in their workflows without having to consider acquiring GRAC shifts externally through their own logic or tabulated sources.

User API & Changelog headlines

  • User can now specify -99 for GRAC shifts (A and/or B) to automatically compute the shift required for SAPT(DFT) to make the code more accessible to users. Here is the example usage:
    psi4.set_options(
        {        
            "sapt_dft_grac_shift_a": -99,
            "sapt_dft_grac_shift_b": -99,
        }
    )
    psi4.energy("SAPT(DFT)", molecule=mol_dimer)

Dev notes & details

  • Automatically computes SAPT(DFT) GRAC shifts for monomer A and/or B
  • Logic for trying extra options with level shifts to attempt to converge more cations in approximating the ionization potential

Questions

Checklist

Status

  • Ready for review
  • Ready for merge

@Awallace3 Awallace3 mentioned this pull request Dec 18, 2024
9 tasks
@JonathonMisiewicz
Copy link
Contributor

Rebase when #3256 is merged in (there will be a merge conflict), and then ping me for review. Apologies for not reviewing this earlier.

@Awallace3
Copy link
Contributor Author

Rebase when #3256 is merged in (there will be a merge conflict), and then ping me for review. Apologies for not reviewing this earlier.

Hello @JonathonMisiewicz, since #3256 is now merged into master, I thought that I would notify you. This PR should likely be merged before my SAPT(DFT) External Potential #3257. Please let me know if there are any necessary changes or concerns. Thanks for your help!

Copy link
Contributor

@JonathonMisiewicz JonathonMisiewicz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was looking at your PRs out of order. My mistake.

compare_values(
# STO-3G target
0.19807358,
# aug-cc-pvdz target, 0.1307 (using experimental IP from CCCBDB)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this comment. Are the aug-dz and quasi-experimential numbers both provided here? Please elaborate so it's comprehensible to non-specialists.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good point. I have included docstrings for both grac tests to clarify these values.

@pytest.mark.saptdft
def test_saptdft_auto_grac_iterative():
    """
    For SAPT(DFT), one must compute a GRAC shift for each monomer. Ideally,
    this GRAC shift should be close to the experimental Ionization Potential
    (IP) of the monomer. While the present test targets STO-3G, the IP for
    water is 0.1307. Note that using aug-cc-pVDZ, the computed GRAC shift is
    0.13063798506967816, which is close to the experimental value.
"""

options.add_double("SAPT_DFT_GRAC_SHIFT_B", 0.0);
/*- SAPT_DFT_GRAC_CONVERGENCE_TIER will specify how hard psi4 should
try to converge the cation for a GRAC shift before failing the
calculation completely -*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Describe single and iterative... Also, it may be worth updating the SAPT(DFT) section of the documentation to describe this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have updated the description to be:

        /*- SAPT_DFT_GRAC_CONVERGENCE_TIER will specify how Psi4 should
          try to converge the cation for a GRAC shift before failing the
          calculation completely. "SINGLE" will try only once to converge 
          the cation for computing a GRAC shift. "ITERATIVE" will set adjust
          local Psi4 options ("LEVEL_SHIFT", "LEVEL_SHIFT_CUTOFF") to attempt
          to converge the neutral/cation calculations. "ITERATIVE" will
          try 3 times to converge the cation before failing the SAPT(DFT) 
          computation. -*/

sapt_dimer = ref_wfn.molecule()

if do_mon_grac_shift_A or do_mon_grac_shift_B:
monA = sapt_dimer.extract_subsets(1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do both monA and monomerA exist? I suspect that one of them needs to be renamed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I split these into two different molecules because monomerA sets monomerB as ghosts; however, I want monA to not have ghost atoms to correctly compute the GRAC shift independent of the dimer. I can see how this is confusing, so perhaps I name it monA_grac?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That tells us what function uses it but not what it is. monomerA_all_bf, perhaps?


def compute_GRAC_shift(
molecule, sapt_dft_grac_convergence_tier="SINGLE", label="Monomer A"
):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure you formatted this properly? Splitting parentheses across different lines so often is jarring.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am using ruff's formatting defaults, but I will put all arguments on a single line if this is more preferable.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have an (inactive?) PR to add Ruff formatting, #3234. The default Ruff formatting here is against the usual Psi4 style. I'm opposed to this, but a second opinion from another developer is good on style questions.

Including the Ruff formatting also made your PR harder to read, because now I need to separate out your code changes from your formatting changes.

mon_b_shift=None,
do_delta_hf="N/A",
jk_alg="N/A"):
def sapt_dft_grac_convergence_tier_options():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why have a function for this? This should be a module-level variable, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that making it a module-level variable makes more sense.

print(f"{grac_options = }")
for options in grac_options:
for key, val in options.items():
core.set_local_option("SCF", key, val)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How are you restoring these options to default later? Looking at the code, I can't see how you do it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was under the impression that optstash.restore() performs the resetting of options to the defaults; however, I did not all options that should be stored at the top of the function. Thank you for pointing this out.



@pytest.mark.saptdft
def test_saptdft_auto_grac():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two tests are duplicates. Change them into one parameterized test.

/*- SUBSECTION SAPT(DFT) -*/

/*- Monomer A GRAC shift in Hartree -*/
/*- Monomer A GRAC shift in Hartree. Set to -99 to automatically
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like having -99 as a magic number. I'm open to other opinions, but perhaps "DFT_GRAC_CONVERGENCE_TIER" should be renamed to "DFT_GRAC_COMPUTE" and take a "USER" value to specify that Psi should read these options.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I also disliked a magical number; however, it seemed like a compromise for functionality of computing a grac shift for monomer A but not requiring it for monomer B and vice versa. I wouldn't want to force a user to compute a GRAC shift if they already have a shift for one of the monomers.

Perhaps this could be fixed by making SAPT_DFT_GRAC_SHIFT_A's default value of 0.0 enable "SAPT_DFT_GRAC_CONVERGENCE_TIER" (or the renamed form of "DFT_GRAC_COMPUTE") to "SINGLE" and enabling auto grac for A? Same logic would apply for monomer B.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good idea, though I'd adjust the details slightly. If SAPT_DFT_GRAC_SHIFT_A is set (there's a special options method for that), we trust the user-provided value. Otherwise, we use the algorithm in SAPT_DFT_GRAC_CONVERGENCE_TEST. As you said, same logic applies for monomer B.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I was not aware of this special option... I just made a commit that checks to make sure the user has set the now "SAPT_DFT_GRAC_COMPUTE" variable to "SINGLE" or "ITERATIVE" from the default "NONE" in combination with SAPT_DFT_GRAC_SHIFT_A to 0.0. This might be similar to what you just recommended in functionality by ensuring that the user MUST request automatic GRAC shifts. The older error message of grac shifts being 0.0 and performing DFT has been updated to tell the user how to call the automatic GRAC shift computation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's best to query core.has_option_changed in a case like this... That way, we don't have to worry about how close is close enough, or what if the user set it to 0 manually.

Copy link
Contributor Author

@Awallace3 Awallace3 Mar 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I agree that the "how close is close enough" is unsettling to use in practice; however, does this core.has_option_changed approach block users from running multiple SAPT(DFT) calculations in a row from one python script? For instance, compute_GRAC_shift() has to set "SAPT_DFT_GRAC_SHIFT_A" for later usage in the code. If I understand correctly, core.has_option_changed would now be triggered, effectively blocking the next SAPT(DFT) calculation from also automatically computing GRAC shifts. Is there a clean way to allow multiple auto GRAC SAPT(DFT) calls in a single python script to work correctly?

I largely ask because my high-throughput workflow would encounter this issue.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optstash.restore will restore the has_changed state from stash time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh excellent, I have made this change to agree with the has_changed format. Thank you for your patience and quick responses on this issue!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what happens on a day when I'm waiting on cluster jobs to finish. By all means, test that your workflow still works after this change.


def compute_GRAC_shift(
molecule, sapt_dft_grac_convergence_tier="SINGLE", label="Monomer A"
):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have an (inactive?) PR to add Ruff formatting, #3234. The default Ruff formatting here is against the usual Psi4 style. I'm opposed to this, but a second opinion from another developer is good on style questions.

Including the Ruff formatting also made your PR harder to read, because now I need to separate out your code changes from your formatting changes.

sapt_dimer = ref_wfn.molecule()

if do_mon_grac_shift_A or do_mon_grac_shift_B:
monA = sapt_dimer.extract_subsets(1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That tells us what function uses it but not what it is. monomerA_all_bf, perhaps?

Copy link
Member

@loriab loriab left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good

Copy link
Member

@loriab loriab left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hopefully the last pass. :-) Maybe add that violin plot of the neutral vs. given charge schemes to the PR discussion so it gets preserved.

},
{
"LEVEL_SHIFT": 0.01,
"LEVEL_SHIFT_CUTOFF": 0.01,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having level_shift and its cutoff at the same value is unusual (grep of tests below). Should this be revised?

tests/scf-level-shift-cuhf/input.dat:set level_shift 0.5
tests/scf-level-shift-cuhf/input.dat:set level_shift_cutoff 1e-3
tests/scf-level-shift-rks/input.dat:set level_shift 5.0
tests/scf-level-shift-rohf/input.dat:set level_shift 0.7
tests/scf-level-shift-rohf/input.dat:set level_shift_cutoff 1e-4
tests/scf-level-shift-uhf/input.dat:set level_shift 0.5
tests/scf-level-shift-uhf/input.dat:set level_shift_cutoff 1e-5

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a fair point. I will adjust these variables and run through some harder systems to converge to see how they perform. I ran about 4500 dimers with computing GRAC shifts with various level_shift and level_shift_cutoff values that will inform these updates. I am going to test using new values on a dataset before finalizing this response.

)

core.set_variable("SAPT DFT GRAC SHIFT A", mon_a_shift) # P::e SAPT
core.set_variable("SAPT DFT GRAC SHIFT B", mon_b_shift) # P::e SAPT
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't forget the glossary.rst entries

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this be |sapt__sapt_dft_grac_shift_a| to |sapt__sapt dft grac shift a|?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, those two vars, but they'll look like the below. And put them somewhere in the SAPT area since the file doesn't alphabetize.

.. psivar:: SAPT DFT GRAC SHIFT A
   SAPT DFT GRAC SHIFT B

   The gradient-corrected ... [E_h] ...

@loriab loriab moved this from In Progress to LAB Done in LAB's v1.10 Release May 21, 2025
@loriab
Copy link
Member

loriab commented Jun 1, 2025

Next commit make this change to fix the Eco (W) lane, https://github.com/psi4/psi4/pull/3302/files#diff-9886b1fe077112c3a9952964ed992dc3cd38b48d0a506a6b7a00241e4669d2b1R192 . Will watch the dftgrac error on Azure to see if it repeats.

Copy link
Member

@loriab loriab left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few little tweaks that can be incorporated after other review, but lgtm. Thanks!

Copy link
Contributor

@JonathonMisiewicz JonathonMisiewicz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At risk of curmudgeonliness, please do format passes as a separate PR, for the sake of the reviewers.

I have a minor suggestion to clarify the docs but LGTM otherwise.

Co-authored-by: Jonathon Misiewicz <[email protected]>
@loriab loriab enabled auto-merge July 18, 2025 13:53
@loriab loriab added this pull request to the merge queue Jul 18, 2025
Merged via the queue into psi4:master with commit ff5de7c Jul 18, 2025
5 of 6 checks passed
@github-project-automation github-project-automation bot moved this from LAB Done to Done in LAB's v1.10 Release Jul 18, 2025
@loriab loriab mentioned this pull request Sep 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants