open Lacaml.D let full_ldl m_A = let n = Mat.dim1 m_A in assert (Mat.dim2 m_A = n); let v_D = Vec.make0 n in let m_Lt = Mat.identity n in let v_D_inv = Vec.make0 n in let compute_d j = let l_jk = Mat.col m_Lt j in let l_jk__d_k = Vec.mul ~n:(j-1) l_jk v_D in m_A.{j,j} -. dot ~n:(j-1) l_jk l_jk__d_k in let compute_l i = let l_ik__d_k = Mat.col m_Lt i |> Vec.mul ~n:(i-1) v_D in fun j -> assert (i > j); let l_jk = Mat.col m_Lt j in v_D_inv.{j} *. ( m_A.{j,i} -. dot ~n:(j-1) l_ik__d_k l_jk ) in for i=1 to n do for j=1 to (i-1) do m_Lt.{j,i} <- compute_l i j; done; let d_i = compute_d i in v_D.{i} <- d_i; v_D_inv.{i} <- 1. /. d_i; done; m_Lt, v_D let pivoted_ldl threshold m_A = (** {% $P A P^\dagger = L D L^\dagger$. %} Input : Matrix $A$ Output : Matrices $L, D, P$. *) let n = Mat.dim1 m_A in assert (Mat.dim2 m_A = n); let pi = Array.init (n+1) (fun i -> i) in let v_D = Vec.init n (fun i -> abs_float m_A.{i,i}) in let m_Lt = Mat.identity n in let v_D_inv = Vec.make0 n in let compute_d i = let l_ik = Mat.col m_Lt i in let l_ik__d_k = Vec.mul ~n:(i-1) l_ik v_D in m_A.{pi.(i),pi.(i)} -. dot ~n:(i-1) l_ik l_ik__d_k in let compute_l i = let l_ik__d_k = Mat.col m_Lt i |> Vec.mul ~n:(i-1) v_D in fun j -> assert (i > j); let l_jk = Mat.col m_Lt j in v_D_inv.{j} *. ( m_A.{pi.(j),pi.(i)} -. dot ~n:(j-1) l_ik__d_k l_jk ) in let maxloc v i = let rec aux pos value i = if i > n then pos else if v_D.{i} > value then (aux [@tailcall]) i v_D.{i} (i+1) else (aux [@tailcall]) pos value (i+1) in aux i v.{i} (i+1) in let pivot i = let j = maxloc v_D i in let p_i, p_j = pi.(i), pi.(j) in pi.(i) <- p_j; pi.(j) <- p_i; in let () = try for i=1 to n do pivot i; for j=1 to (i-1) do m_Lt.{j,i} <- compute_l i j; done; let d_i = compute_d i in if abs_float d_i < threshold then raise Exit; v_D.{i} <- d_i; v_D_inv.{i} <- 1. /. d_i; done with Exit -> () in m_Lt, v_D, pi let make_ldl ?(threshold=Constants.epsilon) m_A = pivoted_ldl m_A let test_case () = let matrix_diff m_A m_B = Mat.syrk_trace (Mat.sub m_A m_B) in let vector_diff v_A v_B = let v = Vec.sub v_A v_B in dot v v in let m_A = Mat.random 1000 1000 in let m_A = Mat.add m_A (Mat.transpose_copy m_A) in let test_full_small () = let m_A = Mat.of_array [| [| 4. ; 12. ; -16. |] ; [| 12. ; 37. ; -43. |] ; [| -16. ; -43. ; 98. |] |] in let m_L_ref = Mat.of_array [| [| 1. ; 0. ; 0. |] ; [| 3. ; 1. ; 0. |] ; [| -4. ; 5. ; 1. |] |] in let m_Lt_ref = Mat.transpose_copy m_L_ref in let v_D_ref = Vec.of_array [| 4. ; 1. ; 9. |] in let m_Lt, v_D = full_ldl m_A in Alcotest.(check (float 1.e-15)) "full L" 0. (matrix_diff m_Lt m_Lt_ref); Alcotest.(check (float 1.e-15)) "full D" 0. (vector_diff v_D v_D_ref); let m_D = Mat.of_diag v_D in let m_B = gemm ~transa:`T m_Lt @@ gemm m_D m_Lt in Alcotest.(check (float 1.e-15)) "full L" 0. (matrix_diff m_A m_B); () in let test_full () = let m_Lt, v_D = full_ldl m_A in let m_D = Mat.of_diag v_D in let m_B = gemm ~transa:`T m_Lt @@ gemm m_D m_Lt in Alcotest.(check (float 1.e-15)) "full D" 0. (matrix_diff m_A m_B); () in let test_pivoted () = let m_Lt, v_D, pi = pivoted_ldl 0. m_A in let n = Mat.dim1 m_A in let m_P = Mat.make0 n n in for i=1 to n do m_P.{i,pi.(i)} <- 1. done; let m_D = Mat.of_diag v_D in let m_B = gemm ~transa:`T m_P @@ gemm ~transa:`T m_Lt @@ gemm m_D @@ gemm m_Lt m_P in Alcotest.(check (float 1.e-14)) "pivoted D" 0. (matrix_diff m_A m_B); () in let test_truncated () = let m_Lt, v_D, pi = pivoted_ldl 0.001 m_A in let n = Mat.dim1 m_Lt in let m_P = Mat.make0 n n in for i=1 to n do m_P.{i,pi.(i)} <- 1. done; let m_D = Mat.of_diag v_D in let m_B = gemm ~transa:`T m_P @@ gemm ~transa:`T m_Lt @@ gemm m_D @@ gemm m_Lt m_P in Alcotest.(check (float 1.e-3)) "full D" 0. (matrix_diff m_A m_B); () in [ "Small", `Quick, test_full_small; "Full", `Quick, test_full; "Pivoted", `Quick, test_pivoted ; "Truncated", `Quick, test_truncated ; ]