Permalink
Fetching contributors…
Cannot retrieve contributors at this time
340 lines (287 sloc) 10.8 KB
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))