NVIDIA / cuda-quantum

C++ and Python support for the CUDA Quantum programming model for heterogeneous quantum-classical workflows
https://nvidia.github.io/cuda-quantum/
Other
515 stars 184 forks source link

[RFC] CUDA Quantum Applications: Chemistry #100

Closed amccaskey closed 1 year ago

amccaskey commented 1 year ago

Background

The framework is at a stable point where we can begin to think about applications that build on top of the CUDA Quantum primitives / programming model. A good first use case for this is in quantum chemistry. We have use cases where we need to compute ground states for prototypical molecules via something like the variational quantum eigensolver. But this could evolve into future algorithms that are not variational.

Problem

We lack support for common data structures that would make it easier for programmers to write quantum chemistry application code.

Proposal

We should create a set of data structures that allow our users to describe a molecular system at a high level and generate the spin_op data type necessary for integration with cudaq::observe(). I propose the following structures:


namespace cudaq {

struct atom {
  const std::string name;
  const std::vector<double> coordinates;
};

class molecular_geometry {
private:
  std::vector<atom> atoms;

public:
  molecular_geometry(std::initializer_list<atom> &&args);
  std::size_t size() const ;
  auto begin();
  auto end();
  auto begin() const ;
  auto end() const;
};

class one_body_integrals {
private:
  std::unique_ptr<double> data;

public:
  std::vector<std::size_t> shape;
  one_body_integrals(const std::vector<std::size_t> &shape);
  double &operator()(std::size_t i, std::size_t j);
  void dump();
};

class two_body_integals {
private:
  std::unique_ptr<double> data;

public:
  std::vector<std::size_t> shape;
  two_body_integals(const std::vector<std::size_t> &shape);
  double &operator()(std::size_t p, std::size_t q, std::size_t r,
                     std::size_t s);
  void dump();
};

struct molecular_hamiltonian {
  spin_op hamiltonian;
  one_body_integrals one_body;
  two_body_integals two_body;
  std::size_t n_electrons;
  std::size_t n_orbitals;
  double nuclear_repulsion;
  double hf_energy;
};

molecular_hamiltonian create_molecule(const molecular_geometry &geometry,
                                      const std::string &basis,
                                      int multiplicity, int charge);

} 

With users creating molecules in the following manner and having access to all the usual data

cudaq::molecular_geometry geometry{{"H", {0., 0., 0.}},
                                     {"H", {0., 0., .7474}}};
auto molecule = cudaq::create_molecule(geometry, "sto-3g", 1, 0);

// Print out the cudaq::spin_op and the integrals
molecule.hamiltonian.dump();
molecule.one_body.dump();
molecule.two_body.dump();

// Can get other useful information
auto nElectrons = molecule.n_electrons;
auto nOrbitals = molecule.n_orbitals;
auto nuclearRepulsion = molecule.nuclear_repulsion;
auto hartreeFockEnergy = molecule.hf_energy;

Dependencies

Thus far I've described the public API for creating molecular hamiltonians in CUDA Quantum C++. An initial implementation should implement create_molecule in a manner that is extensible for the 3rd party library that can generate the hamiltonian. A first implementation there could use the pybind embedded interpreter to invoke pyscf.

Another thing we will need is a library that allows us to provide a getter / setter for general tensor data with runtime-known shape. For this I propose that we pull in as a tpl the https://github.com/xtensor-stack/xtensor header-only library. It is BSD-3 clause.

DmitryLyakh commented 1 year ago
  1. struct atom: coordinates can always be represented by a 3-dimensional static array (do we really need an std::vector?).
  2. The One- and Two-body integrals should probably be stored via std::vector\<std::complex\<double>>, just for generality (no every Hamiltonian is real, for example relativistic).
  3. Since the rank of the 1- and 2-body integrals is known at compile time, their shape should be stored as a static array with 2 and 4 dimensions, respectively.
  4. It might make sense to template: template\<int BodyRank> struct hamiltonian_integrals, and then use instantiations hamiltonian_integrals\<1>, hamiltonian_integrals\<2>, etc. In general, we also have effective molecular hamiltonians which have higher body ranks (1-, 2-, 3-, 4-, etc.). These structures will also be applicable in nuclear physics, for example. Alternatively, we could have a single class hamiltonian_integrals and make the body rank a runtime ctor value.
amccaskey commented 1 year ago

Thanks for that feedback @DmitryLyakh . I'll make some updates.