Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| open Printf | |
| type matrix = float array array | |
| let validate ?(tol=1e-6) q = | |
| let n = Array.length q | |
| if n < 2 then invalid_arg "CamlPaml.Q.validate: trivial matrix" | |
| Array.iteri | |
| fun i qi -> | |
| if Array.length qi <> n then invalid_arg "CamlPaml.Q.validate: non-square matrix" | |
| let rowsum = ref 0. | |
| for j = 0 to n-1 do | |
| if i <> j && qi.(j) < 0. then invalid_arg (sprintf "CamlPaml.Q.validate: Q[%d,%d] = %.2e < 0" i j qi.(j)) | |
| rowsum := !rowsum +. qi.(j) | |
| if abs_float !rowsum > tol then invalid_arg (sprintf "CamlPaml.Q.validate: sum(Q[%d,]) = %.2e != 0" i !rowsum) | |
| q | |
| let check_real ?(tol=1e-6) ({ Complex.re = re; Complex.im = im } as z) = im = 0. || (abs_float im) *. 1000. < (Complex.norm z) || (abs_float re < tol && abs_float im < tol) | |
| let real_of_complex ?(tol=1e-6) z = | |
| if not (check_real ~tol:tol z) then | |
| failwith (sprintf "CamlPaml.Q.real_of_complex %g+%gi" z.Complex.re z.Complex.im) | |
| z.Complex.re | |
| let m_of_cm cm = Gsl.Matrix.of_arrays (Array.map (Array.map real_of_complex) (Gsl.Matrix_complex.to_arrays cm)) | |
| let cm_of_m m = Gsl.Matrix_complex.of_arrays (Array.map (Array.map (fun re -> { Complex.re = re; im = 0. })) (Gsl.Matrix.to_arrays m)) | |
| let v_of_cv cv = Gsl.Vector.of_array (Array.map real_of_complex (Gsl.Vector_complex.to_array cv)) | |
| let gemm ?c a b = | |
| let m,n = Gsl.Matrix.dims a | |
| let n',p = Gsl.Matrix.dims b | |
| if n <> n' then invalid_arg "CamlPaml.Q.gemm: incompatible dimensions" | |
| let c = match c with | |
| | Some c -> c | |
| | None -> Gsl.Matrix.create m p | |
| Gsl.Blas.gemm ~ta:Gsl.Blas.NoTrans ~tb:Gsl.Blas.NoTrans ~alpha:1. ~a:a ~b:b ~beta:0. ~c:c | |
| c | |
| let zgemm ?c a b = | |
| let m,n = Gsl.Matrix_complex.dims a | |
| let n',p = Gsl.Matrix_complex.dims b | |
| if n <> n' then invalid_arg "CamlPaml.Q.zgemm: incompatible dimensions" | |
| let c = match c with | |
| | Some c -> c | |
| | None -> Gsl.Matrix_complex.create m p | |
| Gsl.Blas.Complex.gemm ~ta:Gsl.Blas.NoTrans ~tb:Gsl.Blas.NoTrans ~alpha:Complex.one ~a:a ~b:b ~beta:Complex.zero ~c:c | |
| c | |
| let diagm ?c d a = | |
| let m = Gsl.Vector.length d | |
| let (n,p) = Gsl.Matrix.dims a | |
| if m <> n then invalid_arg "CamlPaml.Q.diagm: incompatible dimensions" | |
| let c = match c with | |
| | Some c -> c | |
| | None -> Gsl.Matrix.create m p | |
| for i = 0 to m-1 do | |
| let d_i = d.{i} | |
| for j = 0 to p-1 do | |
| Bigarray.Array2.unsafe_set c i j (Bigarray.Array2.unsafe_get a i j *. d_i) | |
| c | |
| let zdiagm ?c d a = | |
| let m = Gsl.Vector_complex.length d | |
| let (n,p) = Gsl.Matrix_complex.dims a | |
| if m <> n then invalid_arg "CamlPaml.Q.diagm: incompatible dimensions" | |
| let c = match c with | |
| | Some c -> c | |
| | None -> Gsl.Matrix_complex.create m p | |
| for i = 0 to m-1 do | |
| let d_i = d.{i} | |
| for j = 0 to p-1 do | |
| Bigarray.Array2.unsafe_set c i j (Complex.mul (Bigarray.Array2.unsafe_get a i j) d_i) | |
| c | |
| let zinvm m = | |
| let n,n' = Gsl.Matrix_complex.dims m | |
| if n <> n' then invalid_arg "CamlPaml.Q.zinvm: non-square matrix" | |
| let p = Gsl.Permut.make n | |
| let lu = Gsl.Vectmat.cmat_convert (`CM (Gsl.Matrix_complex.copy m)) | |
| ignore (Gsl.Linalg.complex_LU_decomp lu p) | |
| let m' = Gsl.Vectmat.cmat_convert (`CM (Gsl.Matrix_complex.create n n)) | |
| Gsl.Linalg.complex_LU_invert lu p m' | |
| match m' with | |
| | `CM x -> x | |
| | _ -> assert false | |
| module Diag = struct | |
| (* diagonalized Q = S*L*S' | |
| These are real for reversible models, complex for non-reversible models. *) | |
| type eig_r = { | |
| r_s : Gsl.Matrix.matrix; (* S = right eigenvectors (in the columns) *) | |
| r_s' : Gsl.Matrix.matrix; (* S' = left eigenvectors (in the rows) *) | |
| r_l : Gsl.Vector.vector (* diag(L) = eigenvalues *) | |
| } | |
| type eig_nr = { | |
| nr_s : Gsl.Matrix_complex.matrix; (* S = right eigenvectors (in the columns) *) | |
| nr_s' : Gsl.Matrix_complex.matrix; (* S' = left eigenvectors (in the rows) *) | |
| nr_l : Gsl.Vector_complex.vector; (* diag(L) = eigenvalues *) | |
| } | |
| type t = { | |
| q : Gsl.Matrix.matrix; (* Q *) | |
| eig : [`r of eig_r | `nr of eig_nr]; | |
| pi : Gsl.Vector.vector; | |
| mutable have_pi : bool; | |
| mutable memoized_to_Pt : (float -> Gsl.Matrix.matrix) option; | |
| mutable tol : float; | |
| } | |
| let dim q = | |
| let (n,n') = Gsl.Matrix.dims q.q | |
| assert (n = n') | |
| n | |
| let of_Q ?(tol=1e-6) ?(force_complex=false) qm = | |
| let qm = Gsl.Matrix.of_arrays qm | |
| let l, s = Gsl.Eigen.nonsymmv ~protect:true (`M qm) | |
| let s' = zinvm s | |
| let rev = ref true | |
| let n = Gsl.Vector_complex.length l | |
| for i = 0 to n-1 do if not (check_real ~tol l.{i}) then rev := false | |
| let eig = | |
| if !rev && not force_complex then | |
| `r { r_s = m_of_cm s; r_s' = m_of_cm s'; r_l = v_of_cv l } | |
| else | |
| `nr { nr_s = s; nr_s' = s'; nr_l = l } | |
| { q = qm; eig; | |
| pi = Gsl.Vector.create (fst (Gsl.Matrix.dims qm)); have_pi = false; | |
| memoized_to_Pt = None; tol = tol } | |
| let to_Q q = Gsl.Matrix.to_arrays q.q | |
| let reversible = function | |
| | { eig = `r _ } -> true | |
| | { eig = `nr { nr_l }; tol } -> | |
| let rev = ref true | |
| let n = Gsl.Vector_complex.length nr_l | |
| for i = 0 to n-1 do if not (check_real ~tol:tol nr_l.{i}) then rev := false | |
| !rev | |
| let equilibrium q = | |
| if not q.have_pi then | |
| let eig = match q.eig with | |
| | `r eig -> eig | |
| | `nr { nr_s; nr_s'; nr_l } when reversible q -> { r_s = m_of_cm nr_s; r_s' = m_of_cm nr_s'; r_l = v_of_cv nr_l } | |
| | _ -> failwith "CamlPaml.Q.equilibrium: non-reversible model" | |
| let n = Gsl.Vector.length eig.r_l | |
| let min_L = ref infinity | |
| let min_Lp = ref (-1) | |
| for i = 0 to n-1 do | |
| let mag_i = abs_float eig.r_l.{i} | |
| if mag_i < !min_L then | |
| min_L := mag_i | |
| min_Lp := i | |
| assert (!min_Lp >= 0) | |
| if (abs_float !min_L) > q.tol then | |
| failwith (sprintf "CamlPaml.Q.equilibrium: smallest-magnitude eigenvalue %e is unacceptably large; check rate matrix validity or increase tol" !min_L) | |
| let lev = Gsl.Matrix.row eig.r_s' !min_Lp | |
| let mass = ref 0. | |
| for i = 0 to n-1 do | |
| mass := !mass +. lev.{i} | |
| for i = 0 to n-1 do | |
| q.pi.{i} <- lev.{i} /. !mass | |
| q.have_pi <- true | |
| Gsl.Vector.to_array q.pi | |
| (** normalize to unity mean rate of replacement | |
| let normalize ?tol q = | |
| let n = Gsl.Vector_complex.length q.l | |
| let pi = equilibrium ?tol q | |
| let tot = ref 0. | |
| for i = 0 to n-1 do | |
| tot := !tot +. (-1.) *. pi.(i) *. q.q.{i,i} | |
| let nq = Gsl.Matrix.copy q.q | |
| Gsl.Matrix.scale q.q (1. /. !tot) | |
| let nl = Gsl.Vector_complex.copy q.l | |
| for i = 0 to n-1 do | |
| nl.{i} <- Complex.div nl.{i} { Complex.re = !tot; im = 0. } | |
| { q with q = nq; l = nl } *) | |
| let scale q x = | |
| if x <= 0. then invalid_arg "CamlPaml.Q.scale: nonpositive scale factor" | |
| let xq = Gsl.Matrix.copy q.q | |
| Gsl.Matrix.scale xq x | |
| let xeig = match q.eig with | |
| | `r { r_s; r_s'; r_l } -> | |
| let xl = Gsl.Vector.copy r_l | |
| for i = 0 to (Gsl.Vector.length xl) - 1 do | |
| xl.{i} <- xl.{i} *. x | |
| `r { r_s; r_s'; r_l = xl } | |
| | `nr { nr_s; nr_s'; nr_l } -> | |
| let xl = Gsl.Vector_complex.copy nr_l | |
| let cx = { Complex.re = x; im = 0. } | |
| for i = 0 to (Gsl.Vector_complex.length xl) - 1 do | |
| xl.{i} <- Complex.mul xl.{i} cx | |
| `nr { nr_s; nr_s'; nr_l = xl } | |
| { q with q = xq; eig = xeig; memoized_to_Pt = None } | |
| let real_to_Pt q t = | |
| if t < 0. then invalid_arg "CamlPaml.Q.to_Pt" | |
| let sm = match q.eig with | |
| | `r { r_s; r_s'; r_l } -> | |
| let expLt = Gsl.Vector.copy r_l | |
| for i = 0 to Gsl.Vector.length expLt - 1 do | |
| expLt.{i} <- exp (t *. expLt.{i}) | |
| gemm r_s (diagm expLt r_s') | |
| | `nr { nr_s; nr_s'; nr_l } -> | |
| let ct = { Complex.re = t; im = 0. } | |
| let expLt = Gsl.Vector_complex.copy nr_l | |
| for i = 0 to Gsl.Vector_complex.length expLt - 1 do | |
| expLt.{i} <- Complex.exp (Complex.mul ct expLt.{i}) | |
| m_of_cm (zgemm nr_s (zdiagm expLt nr_s')) | |
| let n,_ = Gsl.Matrix.dims sm | |
| for i = 0 to n-1 do | |
| let tot = ref 0. | |
| let smii = ref 1. | |
| for j = 0 to n-1 do | |
| tot := !tot +. sm.{i,j} | |
| if sm.{i,j} < 0. then | |
| if abs_float sm.{i,j} > q.tol then | |
| failwith (sprintf "CamlPaml.Q.substition_matrix: expm(%.2e*Q)[%d,%d] = %e < 0" t i j sm.{i,j}) | |
| else | |
| sm.{i,j} <- 0. | |
| if i <> j then | |
| smii := !smii -. sm.{i,j} | |
| if abs_float (!tot -. 1.) > q.tol then | |
| failwith (sprintf "CamlPaml.Q.substitution matrix: sum(expm(%.2e*Q)[%d,] = %e > 1" t i !tot) | |
| assert (!smii <= 1. && !smii > 0.) | |
| sm.{i,i} <- !smii | |
| sm | |
| let rec to_Pt q t = | |
| match q.memoized_to_Pt with | |
| | Some f -> f t | |
| | None -> | |
| q.memoized_to_Pt <- Some (Tools.weakly_memoize (real_to_Pt q)) | |
| to_Pt q t | |
| let to_Pt_gc q = q.memoized_to_Pt <- None | |
| let dPt_dt ~q ~t = match q.eig with | |
| | `r _ -> failwith "not implemented" | |
| | `nr { nr_s; nr_s'; nr_l } -> | |
| let n,_ = Gsl.Matrix.dims q.q | |
| let (( * ),(+),(/)) = Complex.mul, Complex.add, Complex.div | |
| let exp= Complex.exp | |
| let s, l, s' = nr_s, nr_l, nr_s' | |
| let ct = { Complex.re = t; Complex.im = 0. } | |
| Array.init n | |
| fun a -> | |
| Array.init n | |
| fun b -> | |
| let x = ref Complex.zero | |
| for c = 0 to n-1 do | |
| x := !x + (s.{a,c} * l.{c} * (exp (l.{c} * ct)) * s'.{c,b}) | |
| real_of_complex !x | |
| (* TODO in richly parameterized models, dQ_dx is likely to be sparse. *) | |
| let dPt_dQ_dx ~q ~t ~dQ_dx = match q.eig with | |
| | `r _ -> failwith "not implemented" | |
| | `nr { nr_s; nr_s'; nr_l } -> | |
| let n,_ = Gsl.Matrix.dims q.q | |
| let dQt_dx = cm_of_m (Gsl.Matrix.of_arrays dQ_dx) | |
| if Gsl.Matrix_complex.dims dQt_dx <> Gsl.Matrix.dims q.q then | |
| invalid_arg "CamlPaml.Q.dP_dx: wrong dimension of dQ_dx" | |
| let ct = { Complex.re = t; Complex.im = 0. } | |
| let exp = Complex.exp | |
| let (( * ),(-),(/)) = Complex.mul, Complex.sub, Complex.div | |
| (* combos 1,3 or 2 (alone) -- 1,3 matches P&S? *) | |
| Gsl.Matrix_complex.scale dQt_dx ct (* 1 *) | |
| let f = Gsl.Matrix_complex.create n n | |
| for i = 0 to pred n do | |
| for j = 0 to pred n do | |
| if nr_l.{i} = nr_l.{j} then | |
| f.{i,j} <- exp (nr_l.{i} * ct) (* * ct *) (* 2 *) | |
| else | |
| f.{i,j} <- (exp (nr_l.{i} * ct) - exp (nr_l.{j} * ct)) / ((nr_l.{i} - nr_l.{j}) * ct (* 3 *)) | |
| (* ehh not being too gentle with the allocator/GC here *) | |
| Gsl.Matrix_complex.mul_elements f (zgemm nr_s' (zgemm dQt_dx nr_s)) | |
| Gsl.Matrix.to_arrays (m_of_cm (zgemm nr_s (zgemm f nr_s'))) | |
| let logm (m:Gsl.Matrix.matrix) = | |
| let n,n' = Gsl.Matrix.dims m | |
| if n <> n' then invalid_arg "CamlPaml.Q.logm: non-square matrix" | |
| let l, s = Gsl.Eigen.nonsymmv ~protect:true (`M m) | |
| let s' = zinvm s | |
| for i = 0 to n-1 do | |
| l.{i} <- Complex.log l.{i} | |
| m_of_cm (zgemm s (zdiagm l s')) | |
| (* Correction of a square matrix with zero row-sums but potentially negative off-diagonal entries into a valid rate matrix. As suggested by Israel, Rosenthal & Wei (2001) *) | |
| let irw2001 rawq = | |
| let n,n' = Gsl.Matrix.dims rawq | |
| assert (n = n') | |
| for i = 0 to n-1 do | |
| let g_i = ref (abs_float rawq.{i,i}) | |
| let b_i = ref 0. | |
| for j = 0 to n-1 do | |
| if i <> j then | |
| if rawq.{i,j} >= 0. then | |
| g_i := !g_i +. rawq.{i,j} | |
| else | |
| b_i := !b_i -. rawq.{i,j} | |
| for j = 0 to n-1 do | |
| let rawqij = rawq.{i,j} | |
| if i <> j && rawqij < 0. then | |
| rawq.{i,j} <- 0. | |
| else if !g_i > 0. then | |
| rawq.{i,j} <- rawqij -. !b_i *. (abs_float rawqij) /. !g_i | |
| rawq | |
| let of_P ?tol m = | |
| P.validate ?tol m | |
| Gsl.Matrix.to_arrays (irw2001 (logm m)) |