Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve the usability of callback functions in ground state optimization #208

Open
20akshay00 opened this issue Dec 13, 2024 · 0 comments
Open

Comments

@20akshay00
Copy link

20akshay00 commented Dec 13, 2024

When playing around with different solvers and new algorithms to compute the ground state of a system, we typically want to track certain properties including but not limited to the energy and galerkin error over the iterations of the solver. Currently there is find_groundstate which takes in an algorithm, and the algorithm takes in an argument for a callback function where the state of the solver is exposed and can be altered. As I am working with FiniteMPS, I have only looked into DMRG and GradientGrassmann so far;

  • GradientGrassmann expects finalize!(x, f, g, numiter) -> x, f, g
  • DMRG expects finalize(iter, state, H, envs) -> state, envs

In order to keep track of some quantities during the optimization, something along these lines has to be done:

function custom_find_groundstate(args...)
  history = []
  function custom_finalizer(iter, state, H, envs) # specific to DMRG
      current_error = maximum(pos -> calc_galerkin(state, pos, envs), 1:length(state))
      current_energy = expectation_value(state, H, envs)
      custom_observable = expectation_value(state, ...)
      push!(history, (; current_error, current_energy, custom_observable, t=Base.time()))
      return state, envs
  end
  
  return find_groundstate(args...; finalize=custom_finalizer), history
end

Alternatively, if some kind of local state is to be preserved, something like this can also be done:

mutable struct IterationData
      energy
      error
      observable
      IterationData() = new([], [], [])
end

function (data::IterationData)(iter, state, H, envs) # specific to DMRG
   push!( data.error, maximum(pos -> calc_galerkin(state, pos, envs), 1:length(state)))
    push!(data.energy, expectation_value(state, H, envs))
    push!(data.observable, expectation_value(state, ...))
    return state, envs
end
  
custom_data = IterationData()
psi, envs, delta = find_groundstate(args...; finalize=custom_data), history

While this is functional, it is a bit clunky and also has to be modified per algorithm as they expect functions with different signatures. There may also be some minor inconsistencies as in #207. Following are possible improvements:

  • Ideally there should be a uniform interface for the signature of finalize, although I am unsure if there is a clean way to do so for these two methods (and any other added later on) given that they operate with different quantities. For example, always having access to at least H (or E = <H>, but consistently either one of them) and psi within finalize makes sense to me.
  • Custom data can be returned as the third return value from find_groundstate instead of just delta.
  • A way to quit the optimization loop in find_groundstate using a custom convergence criteria instead of a hard-coded one which is also solver dependant :/.

My use case for this is really just to quantitatively judge the performance of Gradient methods vs. DMRG for my system in order to decide which one to use hereafter. I think this would be a fairly common use case for anyone working with atypical systems when DMRG is no longer sufficient, so having a convenient way to do this would be quite helpful.

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

No branches or pull requests

1 participant