(** %{ $ \langle ij | H F | kl \rangle $ %} integrals. *)

open Lacaml.D

module Fis = FourIdxStorage


type t = {
  simulation : Simulation.t ;
  aux_basis  : MOBasis.t ;
  f_0 : Determinant.t -> float ;
  f_1 : Determinant.t -> Determinant.t -> float ;
  f_2 : Determinant.t -> Determinant.t -> float ;
  f_3 : Determinant.t -> Determinant.t -> float ;
}


let sum l f = Array.fold_left (fun accu i -> accu +. f i) 0. l

let array_3_init d1 d2 d3 fx =
  let f k = 
    let x = 
      Bigarray.(Array2.create Float64 fortran_layout d1 d2)
    in
    for j=1 to d2 do
      for i=1 to d1 do
        x.{i,j} <-  fx i j k
      done
    done;
    (k,x)
  in
  let result =
      SharedMemory.create Bigarray.Float64 [| d1 ; d2 ; d3 |]
      |> Bigarray.array3_of_genarray
  in
  Util.list_range 1 d3
  |> Stream.of_list 
  |> Farm.run ~f ~ordered:false
  |> Stream.iter (fun (k,x) ->
        for j=1 to d2 do
            for i=1 to d1 do
              result.{i,j,k} <-  x.{i,j}
            done
        done)
  ;
  if Parallel.master then Printf.printf "Broadcast d3\n%!" ;
  try Parallel.InterNode.broadcast (lazy result) 
  with Invalid_argument _ ->
   begin
      Printf.eprintf "Array too large.\n%!";
      failwith "Array too large"
   end

let array_4_init d1 d2 d3 d4 fx =
  let f (k,l) = 
    let x = 
      Bigarray.(Array2.create Float64 fortran_layout d1 d2)
    in
    for j=1 to d2 do
      for i=1 to d1 do
        x.{i,j} <-  fx i j k l
      done
    done;
    (k,l,x)
  in
  let result =
      SharedMemory.create Bigarray.Float64 [| d1;d2;d3;d4 |] 
  in
  Util.list_range 1 d4
  |> List.rev_map (fun l -> 
    Util.list_range 1 d3
    |> List.rev_map (fun k -> (k,l)) )
  |> List.concat
  |> List.rev
  |> Stream.of_list 
  |> Farm.run ~f ~ordered:false
  |> Stream.iter (fun (k,l,x) ->
        for j=1 to d2 do
          for i=1 to d1 do
            result.{i,j,k,l} <-  x.{i,j}
          done
        done)
  ;
  if Parallel.master then Printf.printf "Broadcast d4\n%!" ;
  try Parallel.InterNode.broadcast (lazy result)
  with Invalid_argument _ ->
    begin
      Printf.eprintf "Array too large... splitting.\n%!";
      let x = 
        Bigarray.(Array3.create Float64 fortran_layout d1 d2 d3)
      in
      for l=1 to d4 do
        if Parallel.master then
          begin
            for k=1 to d3 do
              for j=1 to d2 do
                for i=1 to d1 do
                  x.{i,j,k} <- result.{i,j,k,l} 
                done
              done
            done;
            ignore @@ Parallel.InterNode.broadcast (lazy x)
          end
        else 
          begin
            ignore @@ Parallel.InterNode.broadcast (lazy x);
            for k=1 to d3 do
              for j=1 to d2 do
                for i=1 to d1 do
                  result.{i,j,k,l} <- x.{i,j,k} 
                done
              done
            done
          end
      done;
      result
    end

let array_5_init d1 d2 d3 d4 d5 fx =
  let f (l,m) =
    let x = 
      Bigarray.(Array3.create Float64 fortran_layout d1 d2 d3)
    in
    for k=1 to d3 do
      for j=1 to d2 do
        for i=1 to d1 do
          x.{i,j,k} <- fx i j k l m
        done
      done
    done;
    (l,m,x)
  in
  let result =
      SharedMemory.create Bigarray.Float64 [| d1;d2;d3;d4;d5 |]
  in
  Util.list_range 1 d5
  |> List.rev_map (fun m -> 
    Util.list_range 1 d4
    |> List.rev_map (fun l -> (l,m)) )
  |> List.concat
  |> List.rev
  |> Stream.of_list 
  |> Farm.run ~f ~ordered:false
  |> Stream.iter (fun (l,m,x) ->
      for k=1 to d3 do
        for j=1 to d2 do
          for i=1 to d1 do
            result.{i,j,k,l,m} <-  x.{i,j,k}
          done
        done
      done)
  ;
  if Parallel.master then Printf.printf "Broadcast d5\n%!" ;
  try
    Parallel.InterNode.broadcast (lazy result)
  with Invalid_argument _ ->
    begin
      Printf.eprintf "Array too large... splitting.\n%!";
      let x = 
        Bigarray.(Genarray.create Float64 fortran_layout [| d1; d2; d3; d4 |])
      in
      for m=1 to d5 do
        if Parallel.master then
          begin
            for l=1 to d4 do
              for k=1 to d3 do
                for j=1 to d2 do
                  for i=1 to d1 do
                    x.{i,j,k,l} <- result.{i,j,k,l,m} 
                  done
                done
              done
            done;
            ignore @@ Parallel.InterNode.broadcast (lazy x)
          end
        else 
          begin
            ignore @@ Parallel.InterNode.broadcast (lazy x);
            for l=1 to d4 do
              for k=1 to d3 do
                for j=1 to d2 do
                  for i=1 to d1 do
                    result.{i,j,k,l,m} <- x.{i,j,k,l} 
                  done
                done
              done
            done
          end
      done;
      result
    end


let make ~frozen_core ~simulation ~mo_basis ~aux_basis_filename () =
  
  let f12 = Util.of_some @@ Simulation.f12 simulation in                                      
  let mo_num = MOBasis.size mo_basis in

  (* Add auxiliary basis set *)                                                               
  let simulation =
    let charge        = Charge.to_int @@ Simulation.charge simulation 
    and multiplicity  = Electrons.multiplicity @@ Simulation.electrons simulation
    and nuclei        = Simulation.nuclei simulation
    in
    let general_basis = 
      Basis.general_basis @@ Simulation.basis simulation
    in
    GeneralBasis.combine [
      general_basis ; GeneralBasis.read aux_basis_filename
    ]
    |> Basis.of_nuclei_and_general_basis ~f12 nuclei
    |> Simulation.make ~charge ~multiplicity ~nuclei 
  in

  let aux_basis = 
    MOBasis.of_mo_basis simulation mo_basis
  in

  let aux_num = MOBasis.size aux_basis in

  (* While in a sequential region, initiate the parallel
     4-idx transformation to avoid nested parallel jobs
  *)
  ignore @@ MOBasis.two_e_ints aux_basis;
  ignore @@ MOBasis.f12_ints aux_basis;

  (* Compute the <ij|QHF|kl> integrals *)
  if Parallel.master then Printf.eprintf "Computing HF12 integrals\n%!";

  let mos_cabs =
    Util.list_range (mo_num+1) aux_num
    |> Array.of_list 
  in

  let n_core =
    if frozen_core then
      (Nuclei.small_core @@ Simulation.nuclei @@ MOBasis.simulation mo_basis) / 2
    else 0
  in

  let mos_in   =
    Util.list_range (n_core+1) mo_num
    |> Array.of_list
  in

  let mos_a k =
    Determinant.alfa k
    |> Spindeterminant.to_list
    |> List.filter (fun i -> i > n_core)
    |> Array.of_list 
  in

  let mos_b k =
    Determinant.beta k
    |> Spindeterminant.to_list
    |> List.filter (fun i -> i > n_core)
    |> Array.of_list 
  in

  let h_one =
    let h =
      MOBasis.one_e_ints aux_basis
    in fun i j _ -> h.{i,j}
  in

  let h_two =
    let two_e_ints = MOBasis.two_e_ints aux_basis in
    let h2 i j k l (s:Spin.t) (s':Spin.t) =
      if s' <> s then
          ERI.get_phys two_e_ints i j k l
      else
          (ERI.get_phys two_e_ints i j k l) -.
          (ERI.get_phys two_e_ints i j l k)
    in
    h2
  in

  let f_two =
    let f12_ints = MOBasis.f12_ints aux_basis in
    let f2 i j k l (s:Spin.t) (s':Spin.t) =
      if s' <> s then
          0.375 *. F12.get_phys f12_ints i j k l +.
          0.125 *. F12.get_phys f12_ints i j l k
      else
          0.25 *. (
          (F12.get_phys f12_ints i j k l) -.
          (F12.get_phys f12_ints i j l k) )
    in
    f2
  in

  let f_one = fun _ _ _ -> 0. in


(*
  let s, s' = Spin.(Alfa, Beta) in
  let m_3_Hschwarz_aa = 
    array_3_init mo_num mo_num mo_num (fun i j k ->
      sum mos_cabs (fun a -> let x = h_two a i j k s s in x *. x)
    )
  in
  let m_3_Hschwarz_ab = 
    array_3_init mo_num mo_num mo_num (fun i j k ->
      sum mos_cabs (fun a -> let x = h_two a i j k s s' in x *. x)
    )
  in
  let m_3_Fschwarz_aa = 
    array_3_init mo_num mo_num mo_num (fun i j k ->
      sum mos_cabs (fun a -> let x = f_two a i j k s s in x *. x)
    )
  in
  let m_3_Fschwarz_ab = 
    array_3_init mo_num mo_num mo_num (fun i j k ->
      sum mos_cabs (fun a -> let x = f_two a i j k s s' in x *. x)
    )
  in

  let eps = 1.e-15 in

  let sum_schwarz i j k l m n s1 s2 s3 s4 =
    if (s1  = s2 && m_3_Hschwarz_aa.{i,j,k} < eps ) ||
       (s1 <> s2 && m_3_Hschwarz_ab.{i,j,k} < eps ) ||
       (s3  = s4 && m_3_Fschwarz_aa.{l,m,n} < eps ) ||
       (s3 <> s4 && m_3_Fschwarz_ab.{l,m,n} < eps ) 
    then
      0.
    else
      Array.fold_left (fun accu a ->
          accu +. h_two a i j k s1 s2 *. f_two a l m n s3 s4
          ) 0. mos_cabs
  in
*)

  (* Pre-compute dressed integrals *)
  if Parallel.master then Printf.printf "Computing m_0111_1H_1F\n%!" ;
  let m_0111_1H_1F =
    Vec.init mo_num (fun i ->
          sum mos_cabs (fun a ->
              h_one a i Spin.Alfa *. f_one a i Spin.Alfa ))
  in

  if Parallel.master then Printf.printf "Computing m_0111_1H_2Fa\n%!" ;
  let m_0111_1H_2Fa, m_0111_2Ha_2Fa =

      let m_0122_Haa =
        array_3_init mo_num mo_num mo_num (fun i j k ->
            sum mos_cabs (fun a ->
              h_two a k i j Spin.Alfa Spin.Alfa *. f_two a k i j Spin.Alfa Spin.Alfa
              ) )
      in

      let m_0111_1H_2Fa =
        Mat.init_cols mo_num mo_num (fun i j ->
            sum mos_cabs (fun a ->
              h_one a i Spin.Alfa *. f_two a j i j Spin.Alfa Spin.Alfa +.
              h_two a j i j Spin.Alfa Spin.Alfa *. f_one a i Spin.Alfa
            ) +.
            if i < j then 0. else
                begin
                    sum mos_cabs (fun a ->
                        sum mos_cabs (fun b -> if b >= a then 0. else
                          h_two i j a b Spin.Alfa Spin.Alfa *. f_two a b i j Spin.Alfa Spin.Alfa
                        )
                    ) +.
                    sum mos_in (fun k -> m_0122_Haa.{i,j,k})
                end
        )
      in

      let m_0111_2Ha_2Fa =
        array_3_init mo_num mo_num mo_num (fun i j k ->
          sum mos_cabs (fun a ->
            h_two a j i j Spin.Alfa Spin.Alfa *.
            f_two a k i k Spin.Alfa Spin.Alfa
          ) -. if i < j then 0. else m_0122_Haa.{i,j,k}
        )
      in m_0111_1H_2Fa, m_0111_2Ha_2Fa
  in

  if Parallel.master then Printf.printf "Computing m_0111_1H_2Fb\n%!" ;
  let m_0111_1H_2Fb, m_0111_2Hb_2Fb =
      let m_0122_Hab =
        array_3_init mo_num mo_num mo_num (fun i j k ->
            sum mos_cabs (fun a ->
              h_two a k i j Spin.Alfa Spin.Beta *. f_two a k i j Spin.Alfa Spin.Beta
            ) )
    in

    let m_0111_1H_2Fb =
      Mat.init_cols mo_num mo_num (fun i j ->
          sum mos_cabs (fun a ->
            h_one a i Spin.Alfa *. f_two a j i j Spin.Alfa Spin.Beta +.
            h_two a j i j Spin.Alfa Spin.Beta *. f_one a i Spin.Alfa +.
            h_one a j Spin.Alfa *. f_two a i j i Spin.Alfa Spin.Beta +.
            h_two a i j i Spin.Alfa Spin.Beta *. f_one a j Spin.Alfa
          ) +.
        sum mos_in (fun k -> m_0122_Hab.{i,j,k} +. m_0122_Hab.{j,i,k} ) +.
        sum mos_cabs (fun b ->
          sum mos_cabs (fun a ->
            h_two a b i j Spin.Alfa Spin.Beta *. f_two a b i j Spin.Alfa Spin.Beta
            )
          )
        )
    in

    let m_0111_2Hb_2Fb =
      array_3_init mo_num mo_num mo_num (fun i j k ->
        sum mos_cabs (fun a ->
          h_two a i k i Spin.Alfa Spin.Beta *.
          f_two a j k j Spin.Alfa Spin.Beta +.
          h_two a k i k Spin.Alfa Spin.Beta *.
          f_two a j i j Spin.Alfa Spin.Alfa
        ) -. m_0122_Hab.{k,i,j}
      )
    in
    m_0111_1H_2Fb, m_0111_2Hb_2Fb
  in

  if Parallel.master then Printf.printf "Computing m_0111_2Ha_2Fb\n%!" ;
  let m_0111_2Ha_2Fb =
    array_3_init mo_num mo_num mo_num (fun i j k ->
      sum mos_cabs (fun a ->
        h_two a j i j Spin.Alfa Spin.Alfa *.
        f_two a k i k Spin.Alfa Spin.Beta
      )
    )
  in

  let f_0 ki =

    let mos_i, mos_i' = mos_a ki, mos_b ki in
    let same = (mos_i = mos_i') in

    (* Alpha *)
    let a =
        sum mos_i  (fun i -> m_0111_1H_1F.{i})
    in

    let b =
        if same then a else
        sum mos_i' (fun i -> m_0111_1H_1F.{i})
    in

    let aa =
        sum mos_i (fun j ->
          sum mos_i (fun i -> m_0111_1H_2Fa.{i,j} ))
    in

    let bb =
      if same then aa else
      sum mos_i' (fun j ->
        sum mos_i' (fun i -> m_0111_1H_2Fa.{i,j} ))
    in

    let ab =
      sum mos_i' (fun j ->
        sum mos_i (fun i -> m_0111_1H_2Fb.{i,j}  ))
    in

    let aaa =
        sum mos_i (fun k ->
          sum mos_i (fun j ->
            sum mos_i (fun i -> m_0111_2Ha_2Fa.{i,j,k} )))
    in

    let bbb =
      if same then aaa else
        sum mos_i' (fun k ->
          sum mos_i' (fun j ->
            sum mos_i' (fun i -> m_0111_2Ha_2Fa.{i,j,k} )))
    in

    let baa =
        sum mos_i' (fun k ->
          sum mos_i (fun j ->
            sum mos_i (fun i ->
              m_0111_2Ha_2Fb.{i,j,k} +. m_0111_2Hb_2Fb.{i,j,k}
        )))
    in

    let bba =
        sum mos_i  (fun k ->
          sum mos_i' (fun j ->
            sum mos_i' (fun i ->
              m_0111_2Ha_2Fb.{i,j,k} +. m_0111_2Hb_2Fb.{j,i,k}
        )))
    in

    a +. b +. aa +. bb +. ab +. aaa +. baa +. bba +. bbb
  in

  if Parallel.master then Printf.printf "Computing m_1111_1H_1F\n%!" ;
  let m_1111_1H_1F =
    Mat.init_cols mo_num mo_num (fun i k ->
          sum mos_cabs (fun a -> h_one a i Spin.Alfa *. f_one a k Spin.Alfa ))
  in

  if Parallel.master then Printf.printf "Computing m_1111_2Ha_2Fa\n%!" ;
  let m_1111_2Ha_2Fa =
    array_4_init mo_num mo_num mo_num mo_num (fun l j i k ->
      if l=i then
          sum mos_cabs (fun a ->
            h_two a l j l Spin.Alfa Spin.Alfa *.
            f_two a i j k Spin.Alfa Spin.Alfa )
      else
          sum mos_cabs (fun a ->
            h_two a j i j Spin.Alfa Spin.Alfa *.
            f_two a l k l Spin.Alfa Spin.Alfa +.
            h_two a l j l Spin.Alfa Spin.Alfa *.
            f_two a i j k Spin.Alfa Spin.Alfa )
    )
  in

  if Parallel.master then Printf.printf "Computing m_1111_2Hb_2Fa\n%!" ;
  let m_1111_2Hb_2Fa =
    array_4_init mo_num mo_num mo_num mo_num (fun l j i k ->
      if l=i then
          sum mos_cabs (fun a ->
            h_two a l j l Spin.Alfa Spin.Beta *.
            f_two a i j k Spin.Alfa Spin.Beta )
      else
          sum mos_cabs (fun a ->
            h_two a j i j Spin.Alfa Spin.Beta *.
            f_two a l k l Spin.Alfa Spin.Alfa +.
            h_two a l j l Spin.Alfa Spin.Beta *.
            f_two a i j k Spin.Alfa Spin.Beta )
    )
  in

  if Parallel.master then Printf.printf "Computing m_1111_2Ha_2Fb\n%!" ;
  let m_1111_2Ha_2Fb =
    array_4_init mo_num mo_num mo_num mo_num (fun l j i k ->
      sum mos_cabs (fun a ->
        h_two a j i j Spin.Alfa Spin.Alfa *.
        f_two a l k l Spin.Alfa Spin.Beta +.
        h_two a l j l Spin.Alfa Spin.Beta *.
        f_two a i j k Spin.Alfa Spin.Alfa 
      )
    )
  in

  if Parallel.master then Printf.printf "Computing m_1111_2Hb_2Fb\n%!" ;
  let m_1111_2Hb_2Fb =
    array_4_init mo_num mo_num mo_num mo_num (fun l j i k ->
      sum mos_cabs (fun a ->
        h_two a j i j Spin.Alfa Spin.Beta *.
        f_two a l k l Spin.Alfa Spin.Beta +.
        h_two a l j l Spin.Alfa Spin.Alfa *.
        f_two a i j k Spin.Alfa Spin.Beta 
      )
    )
  in

  if Parallel.master then Printf.printf "Computing m_1121_2Ha_2Fa\n%!" ;
  let m_1121_2Ha_2Fa =
    array_4_init mo_num mo_num mo_num mo_num (fun l j i k ->
      sum mos_cabs (fun a ->
        h_two a k j i Spin.Alfa Spin.Alfa *.
        f_two a l j l Spin.Alfa Spin.Alfa 
      )
    )
  in

  if Parallel.master then Printf.printf "Computing m_1121_2Hb_2Fa\n%!" ;
  let m_1121_2Hb_2Fa =
    array_4_init mo_num mo_num mo_num mo_num (fun l j i k ->
      sum mos_cabs (fun a ->
        h_two a k j i Spin.Alfa Spin.Beta *.
        f_two a l j l Spin.Alfa Spin.Alfa 
      )
    )
  in

  if Parallel.master then Printf.printf "Computing m_1121_2Ha_2Fb\n%!" ;
  let m_1121_2Ha_2Fb =
    array_4_init mo_num mo_num mo_num mo_num (fun l j i k ->
      sum mos_cabs (fun a ->
        h_two a k j i Spin.Alfa Spin.Alfa *.
        f_two a l j l Spin.Alfa Spin.Beta 
      )
    )
  in

  if Parallel.master then Printf.printf "Computing m_1121_2Hb_2Fb\n%!" ;
  let m_1121_2Hb_2Fb =
    array_4_init mo_num mo_num mo_num mo_num (fun l j i k ->
      sum mos_cabs (fun a ->
        h_two a k j i Spin.Alfa Spin.Beta *.
        f_two a l j l Spin.Alfa Spin.Beta 
      )
    )
  in

  if Parallel.master then Printf.printf "Computing m_1122_va\n%!" ;
  let m_1122_va =
    array_4_init mo_num mo_num mo_num mo_num (fun l j i k ->
      sum mos_cabs (fun a ->
        h_two a l j i Spin.Alfa Spin.Alfa *.
        f_two a l j k Spin.Alfa Spin.Alfa 
      )
    )
  in

  if Parallel.master then Printf.printf "Computing m_1111_1H_2Fa\n%!" ;
  let m_1111_1H_2Fa =
    array_3_init mo_num mo_num mo_num (fun j i k ->
        sum mos_in (fun l -> m_1122_va.{l,j,i,k}) +.
        sum mos_cabs (fun a ->
          h_one a i Spin.Alfa *. f_two a j k j Spin.Alfa Spin.Alfa +.
          h_two a j i j Spin.Alfa Spin.Alfa *. f_one a k Spin.Alfa +.
          h_one a j Spin.Alfa *. f_two a i j k Spin.Alfa Spin.Alfa +.
          h_two a k j i Spin.Alfa Spin.Alfa *. f_one a j Spin.Alfa +.
          sum mos_cabs (fun b -> if b > a then 0. else
            h_two b a j i Spin.Alfa Spin.Alfa *.
            f_two b a j k Spin.Alfa Spin.Alfa )
        )
      )
  in

  if Parallel.master then Printf.printf "Computing m_1122_v2\n%!" ;
  let m_1122_v2 =
    array_4_init mo_num mo_num mo_num mo_num (fun l j i k ->
      sum mos_cabs (fun a ->
        h_two a l j i Spin.Alfa Spin.Beta *.
        f_two a l j k Spin.Alfa Spin.Beta 
      )
    )
  in

  if Parallel.master then Printf.printf "Computing m_1122_v3\n%!" ;
  let m_1122_v3 =
    array_4_init mo_num mo_num mo_num mo_num (fun l j i k ->
      sum mos_cabs (fun a ->
        h_two a l i j Spin.Alfa Spin.Beta *.
        f_two a l k j Spin.Alfa Spin.Beta
      )
    )
  in

  if Parallel.master then Printf.printf "Computing m_1111_1H_2Fb\n%!" ;
  let m_1111_1H_2Fb =
    array_3_init mo_num mo_num mo_num (fun j i k ->
      sum mos_in (fun l -> m_1122_v2.{l,j,i,k} +. m_1122_v3.{l,j,i,k}) +.
      sum mos_cabs (fun a ->
        h_one a i Spin.Alfa *. f_two a j k j Spin.Alfa Spin.Beta +.
        h_two a j i j Spin.Alfa Spin.Beta *. f_one a k Spin.Alfa +.
        h_one a j Spin.Beta *. f_two a i j k Spin.Alfa Spin.Beta +.
        h_two a k j i Spin.Alfa Spin.Beta *. f_one a j Spin.Beta +.
        sum mos_cabs (fun b ->
          h_two b a j i Spin.Alfa Spin.Beta *.
          f_two b a j k Spin.Alfa Spin.Beta 
        )
      )
      )
  in

  if Parallel.master then Printf.printf "Computing m_1122_oa\n%!" ;
  let m_1122_oa =
    array_4_init mo_num mo_num mo_num mo_num (fun l j i k ->
    if l > j then
      sum mos_cabs (fun a ->
        h_two a k j l Spin.Alfa Spin.Alfa *.
        f_two a i j l Spin.Alfa Spin.Alfa 
      )
    else 0.
    )
  in

  if Parallel.master then Printf.printf "Computing m_1122_o\n%!" ;
  let m_1122_o =
    array_4_init mo_num mo_num mo_num mo_num (fun l j i k ->
      sum mos_cabs (fun a ->
        h_two a k j l Spin.Alfa Spin.Beta *.
        f_two a i j l Spin.Alfa Spin.Beta 
      )
    )
  in

  let f_1 ki kj =
      let i, k, s, phase =
            match Excitation.of_det ki kj with
            | Excitation.(Single (phase, { hole ; particle ; spin })) ->
              hole, particle, spin, phase
            | _ -> assert false
      in

      let mos_novirt, mos_novirt' =
        let alfa =
          let i = Spindeterminant.bitstring @@ Determinant.alfa ki in
          let j = Spindeterminant.bitstring @@ Determinant.alfa kj in
          Bitstring.to_list (Bitstring.logor i j)
          |> Array.of_list 
        in
        let beta  =
          let i = Spindeterminant.bitstring @@ Determinant.beta ki in
          let j = Spindeterminant.bitstring @@ Determinant.beta kj in
          Bitstring.to_list (Bitstring.logor i j)
          |> Array.of_list 
        in
        match s with
        | Spin.Alfa -> alfa, beta
        | Spin.Beta -> beta, alfa
      in

      let mos_ij, mos_ij' =
        let alfa =
          let i = Spindeterminant.bitstring @@ Determinant.alfa ki in
          let j = Spindeterminant.bitstring @@ Determinant.alfa kj in
          Bitstring.to_list (Bitstring.logand i j)
          |> Array.of_list 
        in
        let beta  =
          let i = Spindeterminant.bitstring @@ Determinant.beta ki in
          let j = Spindeterminant.bitstring @@ Determinant.beta kj in
          Bitstring.to_list (Bitstring.logand i j)
          |> Array.of_list 
        in
        match s with
        | Spin.Alfa -> alfa, beta
        | Spin.Beta -> beta, alfa
      in

      let mos_i, mos_i' =
        match s with
        | Spin.Alfa -> mos_a ki, mos_b ki
        | Spin.Beta -> mos_b ki, mos_a ki
      in

      let mos_j, mos_j' =
        match s with
        | Spin.Alfa -> mos_a kj, mos_b kj
        | Spin.Beta -> mos_b kj, mos_a kj
      in

      let result =
      m_1111_1H_1F.{i,k} +.
      sum mos_ij (fun j ->
            m_1111_1H_2Fa.{j,i,k}
        +. sum mos_i  (fun l -> m_1111_2Ha_2Fa.{l,j,i,k})
        +. sum mos_i' (fun l -> m_1111_2Ha_2Fb.{l,j,i,k})
        +. sum mos_j  (fun l -> m_1121_2Ha_2Fa.{l,j,i,k})
        +. sum mos_j' (fun l -> m_1121_2Ha_2Fb.{l,j,i,k})
        -. sum mos_novirt (fun l -> m_1122_va.{l,j,i,k})
        -. sum mos_ij (fun l -> m_1122_oa.{l,j,i,k} )
        ) +.
      sum mos_ij' (fun j ->
            m_1111_1H_2Fb.{j,i,k}
        +. sum mos_i  (fun l -> m_1111_2Hb_2Fa.{l,j,i,k})
        +. sum mos_i' (fun l -> m_1111_2Hb_2Fb.{l,j,i,k})
        +. sum mos_j  (fun l -> m_1121_2Hb_2Fb.{l,j,i,k})
        +. sum mos_j' (fun l -> m_1121_2Hb_2Fa.{l,j,i,k})
        -. sum mos_novirt  (fun l ->  m_1122_v2.{l,j,i,k})
        -. sum mos_novirt' (fun l ->  m_1122_v3.{l,j,i,k})
        -. sum mos_ij    (fun l ->  m_1122_o.{l,j,i,k})
        )
      in
      match phase with
      | Phase.Pos -> result
      | Phase.Neg -> -. result
  in

  if Parallel.master then Printf.printf "Computing m_2112_1H_2Fa\n%!" ;
  let m_2112_1H_2Fa =
    array_4_init mo_num mo_num mo_num mo_num (fun i j k l ->
      sum mos_cabs (fun a ->
          h_one a i Spin.Alfa *. f_two a j k l Spin.Alfa Spin.Alfa +.
          h_one a j Spin.Alfa *. f_two i a k l Spin.Alfa Spin.Alfa +. 
          h_two a l i j Spin.Alfa Spin.Alfa *. f_one a k Spin.Alfa +.
          h_two a k j i Spin.Alfa Spin.Alfa *. f_one a l Spin.Alfa ) +.
      sum mos_cabs (fun a ->
          sum mos_in  (fun m -> -. h_two m a j i Spin.Alfa Spin.Alfa *.
                                  f_two m a k l Spin.Alfa Spin.Alfa) ) +.
      sum mos_cabs (fun a ->
          sum mos_cabs (fun b -> if b >= a then 0. else
              h_two b a j i Spin.Alfa Spin.Alfa *. f_two b a l k Spin.Alfa Spin.Alfa 
          )
        )
      )
  in

  if Parallel.master then Printf.printf "Computing m_2112_1H_2Fb\n%!" ;
  let m_2112_1H_2Fb =
    array_4_init mo_num mo_num mo_num mo_num (fun i j k l ->
      sum mos_cabs (fun a ->
          h_one a i Spin.Alfa *. f_two a j k l Spin.Alfa Spin.Beta +.
          h_one a j Spin.Alfa *. f_two a i l k Spin.Alfa Spin.Beta +.
          h_two a l i j Spin.Alfa Spin.Beta *. f_one a k Spin.Alfa +.
          h_two a k j i Spin.Alfa Spin.Beta *. f_one a l Spin.Alfa ) +.
      sum mos_cabs (fun a ->
          sum mos_in (fun m ->
            h_two m a j i Spin.Alfa Spin.Beta *.  f_two m a l k Spin.Alfa Spin.Beta +.
            h_two m a i j Spin.Alfa Spin.Beta *.  f_two m a k l Spin.Alfa Spin.Beta ) ) +.
      sum mos_cabs (fun a ->
          sum mos_cabs (fun b -> 
              h_two b a j i Spin.Alfa Spin.Beta *. f_two b a l k Spin.Alfa Spin.Beta 
          )
        )
      )
  in

  if Parallel.master then Printf.printf "Computing m_2112_2Ha_2Fa\n%!" ;
  let m_2112_2Ha_2Fa =
    array_5_init mo_num mo_num mo_num mo_num mo_num (fun n i j k l ->
      sum mos_cabs (fun a ->
          h_two a n i n Spin.Alfa Spin.Alfa *. f_two a j k l Spin.Alfa Spin.Alfa +. 
          h_two a n j n Spin.Alfa Spin.Alfa *. f_two a i l k Spin.Alfa Spin.Alfa 
        )
      )
  in

  if Parallel.master then Printf.printf "Computing m_2112_2Hb_2Fa\n%!" ;
  let m_2112_2Hb_2Fa =
    array_5_init mo_num mo_num mo_num mo_num mo_num (fun n i j k l ->
    sum mos_cabs (fun a ->
        h_two a n i n Spin.Alfa Spin.Beta *. f_two a j k l Spin.Alfa Spin.Alfa +.
        h_two a n j n Spin.Alfa Spin.Beta *. f_two a i l k Spin.Alfa Spin.Alfa
        )
      )
  in

  if Parallel.master then Printf.printf "Computing m_2112_2Ha_2Fb\n%!" ;
  let m_2112_2Ha_2Fb =
    array_5_init mo_num mo_num mo_num mo_num mo_num (fun n i j k l ->
    sum mos_cabs (fun a ->
        h_two a n i n Spin.Alfa Spin.Alfa *. f_two a j k l Spin.Alfa Spin.Beta +.
        h_two a n j n Spin.Alfa Spin.Beta *. f_two a i l k Spin.Alfa Spin.Beta
      )
    )
  in

  if Parallel.master then Printf.printf "Computing m_2121_2Ha_2Fa\n%!" ;
  let m_2121_2Ha_2Fa =
    array_5_init mo_num mo_num mo_num mo_num mo_num (fun n i j k l ->
      sum mos_cabs (fun a ->
          h_two a l i j Spin.Alfa Spin.Alfa *. f_two a n k n Spin.Alfa Spin.Alfa +.
          h_two a k j i Spin.Alfa Spin.Alfa *. f_two a n l n Spin.Alfa Spin.Alfa 
        )
      )
  in

  if Parallel.master then Printf.printf "Computing m_2121_2Hb_2Fa\n%!" ;
  let m_2121_2Hb_2Fa =
    array_5_init mo_num mo_num mo_num mo_num mo_num (fun n i j k l ->
      sum mos_cabs (fun a ->
          h_two a l i j Spin.Alfa Spin.Beta *. f_two a n k n Spin.Alfa Spin.Alfa +.
          h_two a k j i Spin.Alfa Spin.Beta *. f_two a n l n Spin.Alfa Spin.Beta 
        )
      )
  in

  if Parallel.master then Printf.printf "Computing m_2121_2Ha_2Fb\n%!" ;
  let m_2121_2Ha_2Fb =
    array_5_init mo_num mo_num mo_num mo_num mo_num (fun n i j k l ->
      sum mos_cabs (fun a ->
          h_two a l i j Spin.Alfa Spin.Alfa *. f_two a n k n Spin.Alfa Spin.Beta +.
          h_two a k j i Spin.Alfa Spin.Alfa *. f_two a n l n Spin.Alfa Spin.Beta 
        )
      )
  in

  if Parallel.master then Printf.printf "Computing m_2122_2Ha_2Fa_ij\n%!" ;
  let m_2122_2Ha_2Fa_ij =
    let s = Spin.Alfa in
    array_5_init mo_num mo_num mo_num mo_num mo_num (fun n i j k l ->
      sum mos_cabs (fun a ->
             h_two a k i n s s  *.  f_two a j l n s s 
          +. h_two a l i n s s  *.  f_two a j n k s s 
          -. h_two a k j n s s  *.  f_two a i l n s s   
          -. h_two a l j n s s  *.  f_two a i n k s s
        )
      )
  in

  if Parallel.master then Printf.printf "Computing m_2122_2Hb_2Fb_ij\n%!" ;
  let m_2122_2Hb_2Fb_ij =
    let s, s' = Spin.(Alfa, Beta) in
    array_5_init mo_num mo_num mo_num mo_num mo_num (fun n i j k l ->
      sum mos_cabs (fun a ->
            h_two a k n i s  s  *.  f_two a j n l s  s'
        +. h_two a l n j s  s' *.  f_two a i n k s  s 
        -. h_two a k j n s  s' *.  f_two a i l n s  s'
      )
    )
  in

  if Parallel.master then Printf.printf "Computing m_2122_2Hb_2Fb_ij2\n%!" ;
  let m_2122_2Hb_2Fb_ij2 =
    let s, s' = Spin.(Alfa, Beta) in
    array_5_init mo_num mo_num mo_num mo_num mo_num (fun n i j k l ->
    sum mos_cabs (fun a ->
      -. h_two a l i n s  s' *.  f_two a j k n s  s' +.
      (if n < j then
                  h_two a k n i s  s' *.  f_two a j n l s' s'
               +. h_two a l n j s' s' *.  f_two a i n k s  s'
       else 
               -. h_two a k n i s  s' *.  f_two j a n l s' s' 
               -. h_two a l j n s' s' *.  f_two i a k n s  s'
      )
      )
    )
  in

  if Parallel.master then Printf.printf "Computing m_2122_2Ha_2Fa_ij2\n%!" ;
  let m_2122_2Ha_2Fa_ij2 =
    let s, s' = Spin.(Alfa, Beta) in
    array_5_init mo_num mo_num mo_num mo_num mo_num (fun n i j k l ->
      sum mos_cabs (fun a -> h_two a k n i s s' *.  f_two a j n l s s') +.
      sum mos_cabs (fun a -> h_two a l n j s s' *.  f_two a i n k s s') -.
      sum mos_cabs (fun a -> h_two a l n i s s' *.  f_two a j n k s s') -.
      sum mos_cabs (fun a -> h_two a k n j s s' *.  f_two a i n l s s')
      )
  in

  if Parallel.master then Printf.printf "Computing m_2122_2Ha_2Fa_nv\n%!" ;
  let m_2122_2Ha_2Fa_nv =
    let s = Spin.Alfa in
    array_5_init mo_num mo_num mo_num mo_num mo_num (fun n i j k l ->
      sum mos_cabs (fun a -> h_two a n i j s  s  *.  f_two a n l k s  s ) ) 
  in

  if Parallel.master then Printf.printf "Computing m_2122_2Hb_2Fb_nv\n%!" ;
  let m_2122_2Hb_2Fb_nv =
    let s, s' = Spin.(Alfa, Beta) in
    array_5_init mo_num mo_num mo_num mo_num mo_num (fun n i j k l ->
      sum mos_cabs (fun a -> -. h_two a n i j s s'  *.  f_two a n k l s s' ) ) 
  in

  if Parallel.master then Printf.printf "Computing m_2122_2Hb_2Fb_nv2\n%!" ;
  let m_2122_2Hb_2Fb_nv2 =
    let s, s' = Spin.(Alfa, Beta) in
    array_5_init mo_num mo_num mo_num mo_num mo_num (fun n i j k l ->
      sum mos_cabs (fun a -> -. h_two a n j i s s'  *.  f_two a n l k s s' ) ) 
  in

  let f_2 ki kj =
      let i, j, k, l, s, s', phase =
            match Excitation.of_det ki kj with
            | Excitation.(Double (phase, { hole ; particle ; spin }, {hole=hole' ; particle=particle' ; spin=spin' })) ->
              hole, hole', particle, particle', spin, spin', phase
            | _ -> assert false
      in
      let mos_i, mos_i' =
        match s with
        | Spin.Alfa -> mos_a ki, mos_b ki
        | Spin.Beta -> mos_b ki, mos_a ki
      in

      let mos_j, mos_j' =
        match s with
        | Spin.Alfa -> mos_a kj, mos_b kj
        | Spin.Beta -> mos_b kj, mos_a kj
      in

      let mos_ij, mos_ij' =
        let alfa =
          let i = Spindeterminant.bitstring @@ Determinant.alfa ki in
          let j = Spindeterminant.bitstring @@ Determinant.alfa kj in
          Bitstring.to_list (Bitstring.logand i j)
          |> Array.of_list 
        in
        let beta  =
          let i = Spindeterminant.bitstring @@ Determinant.beta ki in
          let j = Spindeterminant.bitstring @@ Determinant.beta kj in
          Bitstring.to_list (Bitstring.logand i j)
          |> Array.of_list 
        in
        match s with
        | Spin.Alfa -> alfa, beta
        | Spin.Beta -> beta, alfa
      in

      let mos_novirt, mos_novirt' =
        let alfa =
          let i = Spindeterminant.bitstring @@ Determinant.alfa ki in
          let j = Spindeterminant.bitstring @@ Determinant.alfa kj in
          Bitstring.to_list (Bitstring.logor i j)
          |> Array.of_list 
        in
        let beta  =
          let i = Spindeterminant.bitstring @@ Determinant.beta ki in
          let j = Spindeterminant.bitstring @@ Determinant.beta kj in
          Bitstring.to_list (Bitstring.logor i j)
          |> Array.of_list 
        in
        match s with
        | Spin.Alfa -> alfa, beta
        | Spin.Beta -> beta, alfa
      in

      let result =
        if s = s' then
          m_2112_1H_2Fa.{i,j,k,l} +.
          sum mos_i      (fun n -> m_2112_2Ha_2Fa.{n,i,j,k,l} ) +.
          sum mos_i'     (fun n -> m_2112_2Hb_2Fa.{n,i,j,k,l} ) +.
          sum mos_j      (fun n -> m_2121_2Ha_2Fa.{n,i,j,k,l} ) +.
          sum mos_j'     (fun n -> m_2121_2Ha_2Fb.{n,i,j,k,l} ) +.
          sum mos_ij     (fun n -> m_2122_2Ha_2Fa_ij.{n,i,j,k,l} ) +.
          sum mos_ij'    (fun n -> m_2122_2Ha_2Fa_ij2.{n,i,j,k,l} ) +.
          sum mos_novirt (fun n -> m_2122_2Ha_2Fa_nv.{n,i,j,k,l} )
        else
          m_2112_1H_2Fb.{i,j,k,l} +.
          sum mos_i      (fun n -> m_2112_2Ha_2Fb.{n,i,j,k,l} ) +.
          sum mos_i'     (fun n -> m_2112_2Ha_2Fb.{n,j,i,l,k} ) +.
          sum mos_j      (fun n -> m_2121_2Hb_2Fa.{n,i,j,k,l} ) +.
          sum mos_j'     (fun n -> m_2121_2Hb_2Fa.{n,j,i,l,k} ) +.
          sum mos_novirt'(fun n -> m_2122_2Hb_2Fb_nv.{n,i,j,k,l} ) +.
          sum mos_novirt (fun n -> m_2122_2Hb_2Fb_nv2.{n,i,j,k,l} )+.
          sum mos_ij     (fun n -> m_2122_2Hb_2Fb_ij.{n,i,j,k,l} ) +.
          sum mos_ij'    (fun n -> m_2122_2Hb_2Fb_ij2.{n,i,j,k,l} )
      in

      match phase with
      | Phase.Pos -> result
      | Phase.Neg -> -. result

  in

  let ncabs = Array.length mos_cabs in
  let tmp_h = Mat.make0 ncabs 9 in
  let tmp_f = Mat.make0 ncabs 9 in

  let f_3 ki kj =
      let i, j, m, k, l, n, s1, s2, s3, phase =
            match Excitation.of_det ki kj with
            | Excitation.(Triple (phase,
                { hole=h1 ; particle=p1 ; spin=s1 },
                { hole=h2 ; particle=p2 ; spin=s2 },
                { hole=h3 ; particle=p3 ; spin=s3 }) ) ->
                      h1, h2, h3, p1, p2, p3, s1, s2, s3, phase
            | _ -> assert false
      in

      let result =
        let open Spin in
        match s1, s2, s3 with
        | Alfa, Alfa, Alfa
        | Beta, Beta, Beta ->
        (*
            sum mos_cabs (fun a -> h_two a k i j s1 s2 *. f_two a m n l s3 s3)
         +. sum mos_cabs (fun a -> h_two a n i j s1 s2 *. f_two a m l k s2 s3)
         -. sum mos_cabs (fun a -> h_two a l i j s1 s2 *. f_two a m n k s3 s3)
         +. sum mos_cabs (fun a -> h_two a l i m s1 s3 *. f_two a j n k s3 s2)
         -. sum mos_cabs (fun a -> h_two a n i m s1 s3 *. f_two a j l k s2 s2)
         -. sum mos_cabs (fun a -> h_two a k i m s1 s3 *. f_two a j n l s3 s2)
         +. sum mos_cabs (fun a -> h_two a n j m s2 s3 *. f_two a i l k s2 s1)
         +. sum mos_cabs (fun a -> h_two a k j m s2 s3 *. f_two a i n l s3 s1)
         -. sum mos_cabs (fun a -> h_two a l j m s2 s3 *. f_two a i n k s3 s1)

            sum_schwarz k i j m n l s1 s2 s3 s3
         +. sum_schwarz n i j m l k s1 s2 s2 s3
         -. sum_schwarz l i j m n k s1 s2 s3 s3
         +. sum_schwarz l i m j n k s1 s3 s3 s2
         -. sum_schwarz n i m j l k s1 s3 s2 s2
         -. sum_schwarz k i m j n l s1 s3 s3 s2
         +. sum_schwarz n j m i l k s2 s3 s2 s1
         +. sum_schwarz k j m i n l s2 s3 s3 s1
         -. sum_schwarz l j m i n k s2 s3 s3 s1
        *)
          Array.iteri (fun idx a -> tmp_f.{idx+1,7} <- f_two a i l k s2 s1) mos_cabs;
          Array.iteri (fun idx a -> tmp_f.{idx+1,5} <- f_two a j l k s2 s2) mos_cabs;
          Array.iteri (fun idx a -> tmp_f.{idx+1,2} <- f_two a m l k s2 s3) mos_cabs;
          Array.iteri (fun idx a -> tmp_f.{idx+1,9} <- f_two a i n k s3 s1) mos_cabs;
          Array.iteri (fun idx a -> tmp_f.{idx+1,4} <- f_two a j n k s3 s2) mos_cabs;
          Array.iteri (fun idx a -> tmp_f.{idx+1,3} <- f_two a m n k s3 s3) mos_cabs;
          Array.iteri (fun idx a -> tmp_f.{idx+1,8} <- f_two a i n l s3 s1) mos_cabs;
          Array.iteri (fun idx a -> tmp_f.{idx+1,6} <- f_two a j n l s3 s2) mos_cabs;
          Array.iteri (fun idx a -> tmp_f.{idx+1,1} <- f_two a m n l s3 s3) mos_cabs;
          Array.iteri (fun idx a -> tmp_h.{idx+1,3} <- if tmp_f.{idx+1,3} = 0. then 0. else  -. h_two a l i j s1 s2) mos_cabs;
          Array.iteri (fun idx a -> tmp_h.{idx+1,1} <- if tmp_f.{idx+1,1} = 0. then 0. else     h_two a k i j s1 s2) mos_cabs;
          Array.iteri (fun idx a -> tmp_h.{idx+1,2} <- if tmp_f.{idx+1,2} = 0. then 0. else     h_two a n i j s1 s2) mos_cabs;
          Array.iteri (fun idx a -> tmp_h.{idx+1,5} <- if tmp_f.{idx+1,5} = 0. then 0. else  -. h_two a n i m s1 s3) mos_cabs;
          Array.iteri (fun idx a -> tmp_h.{idx+1,6} <- if tmp_f.{idx+1,6} = 0. then 0. else  -. h_two a k i m s1 s3) mos_cabs;
          Array.iteri (fun idx a -> tmp_h.{idx+1,4} <- if tmp_f.{idx+1,4} = 0. then 0. else     h_two a l i m s1 s3) mos_cabs;
          Array.iteri (fun idx a -> tmp_h.{idx+1,9} <- if tmp_f.{idx+1,9} = 0. then 0. else  -. h_two a l j m s2 s3) mos_cabs;
          Array.iteri (fun idx a -> tmp_h.{idx+1,7} <- if tmp_f.{idx+1,7} = 0. then 0. else     h_two a n j m s2 s3) mos_cabs;
          Array.iteri (fun idx a -> tmp_h.{idx+1,8} <- if tmp_f.{idx+1,8} = 0. then 0. else     h_two a k j m s2 s3) mos_cabs;
          dot (Mat.as_vec tmp_h) (Mat.as_vec tmp_f)
        | Alfa, Alfa, Beta
        | Beta, Beta, Alfa ->
        (*
            sum mos_cabs (fun a -> h_two a l i j s1 s2 *. f_two a m k n s1 s3)
         -. sum mos_cabs (fun a -> h_two a k i j s1 s2 *. f_two a m l n s1 s3)
         +. sum mos_cabs (fun a -> h_two a l m j s3 s2 *. f_two a i n k s3 s1)
         -. sum mos_cabs (fun a -> h_two a k m j s3 s2 *. f_two a i n l s3 s1)
         +. sum mos_cabs (fun a -> h_two a k m i s3 s1 *. f_two a j n l s3 s2)
         -. sum mos_cabs (fun a -> h_two a l m i s3 s1 *. f_two a j n k s3 s2)
         +. sum mos_cabs (fun a -> h_two a n j m s2 s3 *. f_two a i l k s2 s1)
         -. sum mos_cabs (fun a -> h_two a n i m s1 s3 *. f_two a j l k s2 s2)

            sum_schwarz l i j m k n s1 s2 s1 s3
         -. sum_schwarz k i j m l n s1 s2 s1 s3
         +. sum_schwarz l m j i n k s3 s2 s3 s1
         -. sum_schwarz k m j i n l s3 s2 s3 s1
         +. sum_schwarz k m i j n l s3 s1 s3 s2
         -. sum_schwarz l m i j n k s3 s1 s3 s2
         +. sum_schwarz n j m i l k s2 s3 s2 s1
         -. sum_schwarz n i m j l k s1 s3 s2 s2
        *)
          Array.iteri (fun idx a -> tmp_f.{idx+1,1} <- f_two a m k n s1 s3) mos_cabs;
          Array.iteri (fun idx a -> tmp_f.{idx+1,2} <- f_two a m l n s1 s3) mos_cabs;
          Array.iteri (fun idx a -> tmp_f.{idx+1,4} <- f_two a i n l s3 s1) mos_cabs;
          Array.iteri (fun idx a -> tmp_f.{idx+1,5} <- f_two a j n l s3 s2) mos_cabs;
          Array.iteri (fun idx a -> tmp_f.{idx+1,3} <- f_two a i n k s3 s1) mos_cabs;
          Array.iteri (fun idx a -> tmp_f.{idx+1,6} <- f_two a j n k s3 s2) mos_cabs;
          Array.iteri (fun idx a -> tmp_f.{idx+1,7} <- f_two a i l k s2 s1) mos_cabs;
          Array.iteri (fun idx a -> tmp_f.{idx+1,8} <- f_two a j l k s2 s2) mos_cabs;
          Array.iteri (fun idx a -> tmp_h.{idx+1,1} <- if tmp_f.{idx+1,1} = 0. then 0. else    h_two a l i j s1 s2) mos_cabs;
          Array.iteri (fun idx a -> tmp_h.{idx+1,2} <- if tmp_f.{idx+1,2} = 0. then 0. else -. h_two a k i j s1 s2) mos_cabs;
          Array.iteri (fun idx a -> tmp_h.{idx+1,3} <- if tmp_f.{idx+1,3} = 0. then 0. else +. h_two a l m j s3 s2) mos_cabs;
          Array.iteri (fun idx a -> tmp_h.{idx+1,4} <- if tmp_f.{idx+1,4} = 0. then 0. else -. h_two a k m j s3 s2) mos_cabs;
          Array.iteri (fun idx a -> tmp_h.{idx+1,5} <- if tmp_f.{idx+1,5} = 0. then 0. else +. h_two a k m i s3 s1) mos_cabs;
          Array.iteri (fun idx a -> tmp_h.{idx+1,6} <- if tmp_f.{idx+1,6} = 0. then 0. else -. h_two a l m i s3 s1) mos_cabs;
          Array.iteri (fun idx a -> tmp_h.{idx+1,7} <- if tmp_f.{idx+1,7} = 0. then 0. else +. h_two a n j m s2 s3) mos_cabs;
          Array.iteri (fun idx a -> tmp_h.{idx+1,8} <- if tmp_f.{idx+1,8} = 0. then 0. else -. h_two a n i m s1 s3) mos_cabs;
          dot ~n:(8*ncabs) (Mat.as_vec tmp_h) (Mat.as_vec tmp_f)
        | Alfa, Beta, Beta
        | Beta, Alfa, Alfa ->
        (*
            sum mos_cabs (fun a -> h_two a l i j s1 s2 *. f_two a m k n s1 s3)
         -. sum mos_cabs (fun a -> h_two a n i j s1 s2 *. f_two a m k l s1 s2)
         +. sum mos_cabs (fun a -> h_two a n i m s1 s3 *. f_two a j k l s1 s2)
         +. sum mos_cabs (fun a -> h_two a n j m s2 s3 *. f_two a i l k s2 s1)
         -. sum mos_cabs (fun a -> h_two a l i m s1 s3 *. f_two a j k n s1 s3)
         -. sum mos_cabs (fun a -> h_two a l j m s2 s3 *. f_two a i n k s3 s1)
         +. sum mos_cabs (fun a -> h_two a k m i s3 s1 *. f_two a j n l s3 s2)
         -. sum mos_cabs (fun a -> h_two a k j i s2 s1 *. f_two a m n l s3 s2)

            sum_schwarz l i j m k n s1 s2 s1 s3
         -. sum_schwarz n i j m k l s1 s2 s1 s2
         +. sum_schwarz n i m j k l s1 s3 s1 s2
         +. sum_schwarz n j m i l k s2 s3 s2 s1
         -. sum_schwarz l i m j k n s1 s3 s1 s3
         -. sum_schwarz l j m i n k s2 s3 s3 s1
         +. sum_schwarz k m i j n l s3 s1 s3 s2
         -. sum_schwarz k j i m n l s2 s1 s3 s2
         *)
          Array.iteri (fun idx a -> tmp_f.{idx+1,6} <-    f_two a i n k s3 s1) mos_cabs;
          Array.iteri (fun idx a -> tmp_f.{idx+1,4} <-    f_two a i l k s2 s1) mos_cabs;
          Array.iteri (fun idx a -> tmp_f.{idx+1,5} <-    f_two a j k n s1 s3) mos_cabs;
          Array.iteri (fun idx a -> tmp_f.{idx+1,1} <-    f_two a m k n s1 s3) mos_cabs;
          Array.iteri (fun idx a -> tmp_f.{idx+1,2} <-    f_two a m k l s1 s2) mos_cabs;
          Array.iteri (fun idx a -> tmp_f.{idx+1,3} <-    f_two a j k l s1 s2) mos_cabs;
          Array.iteri (fun idx a -> tmp_f.{idx+1,7} <-    f_two a j n l s3 s2) mos_cabs;
          Array.iteri (fun idx a -> tmp_f.{idx+1,8} <-    f_two a m n l s3 s2) mos_cabs;
          Array.iteri (fun idx a -> tmp_h.{idx+1,1} <- if tmp_f.{idx+1,1} = 0. then 0. else    h_two a l i j s1 s2) mos_cabs;
          Array.iteri (fun idx a -> tmp_h.{idx+1,2} <- if tmp_f.{idx+1,2} = 0. then 0. else -. h_two a n i j s1 s2) mos_cabs;
          Array.iteri (fun idx a -> tmp_h.{idx+1,3} <- if tmp_f.{idx+1,3} = 0. then 0. else +. h_two a n i m s1 s3) mos_cabs;
          Array.iteri (fun idx a -> tmp_h.{idx+1,4} <- if tmp_f.{idx+1,4} = 0. then 0. else +. h_two a n j m s2 s3) mos_cabs;
          Array.iteri (fun idx a -> tmp_h.{idx+1,5} <- if tmp_f.{idx+1,5} = 0. then 0. else -. h_two a l i m s1 s3) mos_cabs;
          Array.iteri (fun idx a -> tmp_h.{idx+1,6} <- if tmp_f.{idx+1,6} = 0. then 0. else -. h_two a l j m s2 s3) mos_cabs;
          Array.iteri (fun idx a -> tmp_h.{idx+1,7} <- if tmp_f.{idx+1,7} = 0. then 0. else +. h_two a k m i s3 s1) mos_cabs;
          Array.iteri (fun idx a -> tmp_h.{idx+1,8} <- if tmp_f.{idx+1,8} = 0. then 0. else -. h_two a k j i s2 s1) mos_cabs;
          dot ~n:(8*ncabs) (Mat.as_vec tmp_h) (Mat.as_vec tmp_f)
        | Beta, Alfa, Beta
        | Alfa, Beta, Alfa ->  assert false
      in
      match phase with
      | Phase.Pos -> result
      | Phase.Neg -> -. result
  in

  let result = 
    { simulation ; aux_basis ;
      f_0 ; f_1 ; f_2 ; f_3 
    } 
  in
  result



