Design of the Hamiltonian Class

These are musings on the design of the Hamiltonian class and should be turned into full documentation at some point.

  • The Hamiltonian will always be an indefinite operator.

    • This is necessary because, in general, it will contain a mix of one- and two-particle operators. Bra-kets of such an operator only makes sense with many-particle wavefunctions.

  • We will want subsets of the terms.

  • What should the template parameters, if any, be?

    • On one hand, it would be nice to know ahead of time what terms the Hamiltonian has. On the other this will complicate interfaces.

    • Order of parameters can be managed by convention.

    • Is it templated on operators present or particles present?

    • If on particles, how do we know that it say contains not just single Electron terms, but also terms that depend on pairs of Electron objects?

// These are the proposed templating schemes

// Templated on operators ("easily" deduced from ctor)
Hamiltonian<T_e_type, V_en_type, V_ee_type, V_nn_type> H0;

// Templated on particle types (no distinction of what definite terms appear)
Hamiltonian<ManyElectrons, Nuclei> H1

// Templated on definite sizes
Hamiltonian<ManyElectrons, std::pair<ManyElectrons, ManyElectrons>, ...> H2;

// Would also be forms for definite particle types

Creation, usage APIs:

auto [T_e, V_en, V_ee, V_en, V_nn] = get_operators();

// Works with C++17, could deduce any of the above types
Hamiltonian H(T_e + V_en + V_ee + V_en + V_nn);

// If templated, SFINAE could be used to enable/disable these functions
T_e = H.T_e(); // Takes an optional offset in case there's more than one

// Can get commonly needed subsets of terms
auto H_e = H.H_e(); // Gets the electronic Hamiltonian
auto H_core = H.H_core(); // Gets core Hamiltonian

Fock F(H); // Builds the Fock operator that is consistent with H

Defining property types:

// Add the terms you want to be able to handle.
using H_type = Hamiltonian<sort_operators_t<V_en_type, T_e_type, ...>>;

using pt = EvaluateBraKet<BraKet<Determinant, H_type, Determinant>>;

Problems is what happens when Hamiltonians contain a subset of terms. For example:

using H_type0 = Hamiltonian<sort_operators_t<V_en_type, T_e_type, ...>>;
using H_type1 = Hamiltonian<sort_operator_t<V_en_type>>;

using pt0 = EvaluateBraKet<BraKet<Determinant, H_type0, Determinant>>;
using pt1 = EvaluateBraKet<BraKet<Determinant, H_type1, Determinant>>;

Here Hamiltonians of H_type0 could only be passed to pt0 and Hamiltonians of H_type1 could only be passed to pt1. More than likely pt0 could handle Hamiltonians of H_type1 (though it is unlikely that pt1 could handle Hamiltonians of H_type0).

Verdict: Go with type-erased to avoid combinatorial interaction of terms.