#+begin_src elisp tangle: no :results none :exports none
(setq pwd (file-name-directory buffer-file-name))
(setq name (file-name-nondirectory (substring buffer-file-name 0 -4)))
(setq lib (concat pwd "lib/"))
(setq testdir (concat pwd "test/"))
(setq mli (concat lib name ".mli"))
(setq ml  (concat lib name ".ml"))
(setq test-ml  (concat testdir name ".ml"))
(org-babel-tangle)
#+end_src 

* MP2
  :PROPERTIES:
  :header-args: :noweb yes :comments both
  :END:

** Type

   #+begin_src ocaml :tangle (eval mli)
type t
   #+end_src

   #+begin_src ocaml :tangle (eval ml) :exports none
type t = {
  energy      : float ;
  mo_basis    : Mo.Basis.t ;
  frozen_core : Mo.Frozen_core.t ;
}
   #+end_src

** Creation

   #+begin_src ocaml :tangle (eval mli)
val make : frozen_core:Mo.Frozen_core.t -> Mo.Basis.t -> t
   #+end_src

   | ~make~ | Creates an MP2 data structure |

   #+begin_example
let mp2 =
  Perturbation.Mp2.make ~frozen_core:(Mo.Frozen_core.(make Small nuclei)) mo_basis
  ;;
val mp2 : Perturbation.Mp2.t = E(MP2)=-0.185523
   #+end_example

   #+begin_src ocaml :tangle (eval ml) :exports none
open Linear_algebra

let make_rmp2 mo_basis mo_class =

  let epsilon = Mo.Basis.mo_energies mo_basis in
  let eri     = Mo.Basis.ee_ints     mo_basis in

  let inactives =
    List.filter (fun i ->
      match i with Mo.Class.Inactive _ -> true | _ -> false) mo_class
  and virtuals =
    List.filter (fun i ->
      match i with Mo.Class.Virtual _ -> true | _ -> false) mo_class
  in

  List.fold_left (fun accu b ->
    match b with Mo.Class.Virtual b ->
      let eps = -. (epsilon%.(b)) in
      accu +. 
      List.fold_left (fun accu a ->
        match a with Mo.Class.Virtual a ->
          let eps = eps -. (epsilon%.(a)) in
          accu +. 
          List.fold_left (fun accu j ->
            match j with Mo.Class.Inactive j ->
              let eps = eps +. epsilon%.(j) in
              accu +. 
              List.fold_left (fun accu i ->
                match i with Mo.Class.Inactive i ->
                  let eps = eps +. epsilon%.(i) in
                  let ijab = Four_idx_storage.get_phys eri i j a b
                  and abji = Four_idx_storage.get_phys eri a b j i in
                  let abij =  ijab in
                  accu +. ijab *. ( abij +. abij -. abji) /. eps 
                           | _ -> accu
              ) 0. inactives
                       | _ -> accu
          ) 0. inactives
                   | _ -> accu
      ) 0. virtuals
               | _ -> accu
  ) 0. virtuals


let make ~frozen_core mo_basis =

  let mo_class =
    Mo.Class.cas_sd mo_basis ~frozen_core 0 0
    |> Mo.Class.to_list
  in

  let energy = 
    match Mo.Basis.mo_type mo_basis with
    | RHF  -> make_rmp2 mo_basis mo_class
    | ROHF -> Common.Util.not_implemented "ROHF MP2"
    | UHF  -> Common.Util.not_implemented "UHF MP2"
    | _    -> invalid_arg "MP2 needs RHF or ROHF MOs"
  in
  { energy ; mo_basis ; frozen_core }
   #+end_src

** Access

   #+begin_src ocaml :tangle (eval mli)
val energy      : t -> float
val mo_basis    : t -> Mo.Basis.t
val frozen_core : t -> Mo.Frozen_core.t
   #+end_src

   | ~energy~      | Returns the MP2 energy                                        |
   | ~mo_basis~    | Returns the MO basis on which the MP2 energy was computed     |
   | ~frozen_core~ | Returns the frozen_core scheme used to compute the MP2 energy |

   #+begin_example

   #+end_example

   #+begin_src ocaml :tangle (eval ml) :exports none
let energy      t = t.energy
let mo_basis    t = t.mo_basis
let frozen_core t = t.frozen_core
   #+end_src

** Printers

   #+begin_src ocaml :tangle (eval mli)
val pp : Format.formatter -> t -> unit
   #+end_src

   #+begin_src ocaml :tangle (eval ml) :exports none
let pp ppf t =
  Format.fprintf ppf "@[E(MP2)=%f@]" t.energy
   #+end_src

** Tests

   #+begin_src ocaml :tangle (eval test-ml) :exports none
open Alcotest
open Particles
    
let wd = Common.Qcaml.root ^ Filename.dir_sep ^ "test"

let tests = 
  [ "HF Water", `Quick, fun () -> 
      let nuclei =
        wd ^ Filename.dir_sep ^ "water.xyz"
        |> Nuclei.of_xyz_file 
      in
      let basis_filename =
        wd ^ Filename.dir_sep ^ "cc-pvdz"
      in
      let ao_basis = 
        Ao.Basis.of_nuclei_and_basis_filename ~kind:`Gaussian
          ~cartesian:false ~nuclei basis_filename
      in

      let simulation = Simulation.make ~nuclei ao_basis in

      let hf = Mo.Hartree_fock.make ~guess:`Huckel simulation in
      Format.printf "%a" (Mo.Hartree_fock.pp) hf;
      check (float 2.e-10) "Energy" (-76.0267987005) (Mo.Hartree_fock.energy hf);

      let mo_basis = Mo.Basis.of_hartree_fock hf in

      let frozen_core = Mo.Frozen_core.(make Small nuclei) in

      let mp2 = Perturbation.Mp2.make ~frozen_core mo_basis in

      let e_mp2 = Perturbation.Mp2.energy mp2 in

      check (float 1.e-9) "MP2" (-0.2016211415) (e_mp2)
  ]
   #+end_src
   
