pycopykat 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pycopykat/__init__.py +8 -0
- pycopykat/_rcompat/__init__.py +1 -0
- pycopykat/_rcompat/hclust.py +158 -0
- pycopykat/_rcompat/mixtools.py +95 -0
- pycopykat/_rcompat/rbase.py +112 -0
- pycopykat/_rcompat/rgamma.py +702 -0
- pycopykat/_rcompat/rng.py +153 -0
- pycopykat/cli.py +133 -0
- pycopykat/config.py +46 -0
- pycopykat/core.py +598 -0
- pycopykat/data/hg20_220kb_bins.parquet +0 -0
- pycopykat/data/hg20_cycle_genes.txt +1316 -0
- pycopykat/data/hg20_gene_anno.parquet +0 -0
- pycopykat/data/mm10_gene_anno.parquet +0 -0
- pycopykat/io/__init__.py +0 -0
- pycopykat/io/annotation.py +83 -0
- pycopykat/io/h5ad.py +304 -0
- pycopykat/kernels/__init__.py +4 -0
- pycopykat/kernels/kalman.py +77 -0
- pycopykat/pipeline.py +78 -0
- pycopykat/py.typed +0 -0
- pycopykat/result.py +50 -0
- pycopykat/validation/__init__.py +4 -0
- pycopykat/validation/metrics.py +97 -0
- pycopykat/viz/__init__.py +12 -0
- pycopykat/viz/heatmap.py +234 -0
- pycopykat-0.2.0.dist-info/METADATA +128 -0
- pycopykat-0.2.0.dist-info/RECORD +31 -0
- pycopykat-0.2.0.dist-info/WHEEL +4 -0
- pycopykat-0.2.0.dist-info/entry_points.txt +2 -0
- pycopykat-0.2.0.dist-info/licenses/LICENSE +338 -0
pycopykat/__init__.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""pycopykat: a Python implementation of CopyKAT (Gao et al. 2021) that reproduces R copykat."""
|
|
2
|
+
from pycopykat.config import CopykatConfig
|
|
3
|
+
from pycopykat.io.h5ad import copykat_by_batch
|
|
4
|
+
from pycopykat.pipeline import copykat
|
|
5
|
+
from pycopykat.result import CopykatResult
|
|
6
|
+
|
|
7
|
+
__all__ = ["CopykatConfig", "CopykatResult", "copykat", "copykat_by_batch"]
|
|
8
|
+
__version__ = "0.2.0"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Pieces of R reproduced with R's arithmetic and random stream."""
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""R's ``hclust`` (``ward.D`` and ``ward.D2``) and ``cutree``.
|
|
2
|
+
|
|
3
|
+
``hclust`` follows R's Fortran routine (F. Murtagh's nearest-neighbour list, ``hclust.f``): at
|
|
4
|
+
every step the pair with the smallest nearest-neighbour dissimilarity is merged (the first one on
|
|
5
|
+
ties), the dissimilarities to the new cluster are updated with the Lance-Williams formula in the
|
|
6
|
+
same order of operations, and the nearest neighbours are refreshed as R does, including the fix
|
|
7
|
+
for neighbours to the left of the new cluster. ``ward.D2`` squares the dissimilarities first and
|
|
8
|
+
reports square roots as heights. ``cutree(k)`` undoes the last ``k - 1`` merges and numbers the
|
|
9
|
+
groups in the order in which the observations first appear, as R does.
|
|
10
|
+
|
|
11
|
+
The update of each step is vectorised over the remaining clusters.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
|
|
19
|
+
_INF = 1e300
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class HClust:
|
|
24
|
+
"""An R ``hclust`` result: ``merge`` (n-1 x 2, R's signed convention), ``height`` and
|
|
25
|
+
``order`` (1-based, as in R), plus the raw merge pairs (0-based)."""
|
|
26
|
+
|
|
27
|
+
merge: np.ndarray
|
|
28
|
+
height: np.ndarray
|
|
29
|
+
order: np.ndarray
|
|
30
|
+
pairs: np.ndarray
|
|
31
|
+
labels: np.ndarray | None = None
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def n(self) -> int:
|
|
35
|
+
return self.pairs.shape[0] + 1
|
|
36
|
+
|
|
37
|
+
def cutree(self, k: int) -> np.ndarray:
|
|
38
|
+
return cutree(self.pairs, k)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def hclust(d: np.ndarray, method: str = "ward.D", labels=None) -> HClust:
|
|
42
|
+
"""``hclust(as.dist(d), method)`` for a square symmetric dissimilarity matrix ``d``."""
|
|
43
|
+
if method not in ("ward.D", "ward.D2"):
|
|
44
|
+
raise ValueError("method must be 'ward.D' or 'ward.D2'")
|
|
45
|
+
D = np.array(d, dtype=np.float64, copy=True)
|
|
46
|
+
n = D.shape[0]
|
|
47
|
+
if n < 2:
|
|
48
|
+
raise ValueError("must have n >= 2 objects to cluster")
|
|
49
|
+
if method == "ward.D2":
|
|
50
|
+
D = D * D
|
|
51
|
+
np.fill_diagonal(D, _INF)
|
|
52
|
+
idx = np.arange(n)
|
|
53
|
+
membr = np.ones(n)
|
|
54
|
+
active = np.ones(n, dtype=bool)
|
|
55
|
+
# nearest neighbour to the right of each i (first minimum)
|
|
56
|
+
upper = np.where(idx[None, :] > idx[:, None], D, np.inf)
|
|
57
|
+
nn = np.argmin(upper, axis=1)
|
|
58
|
+
disnn = upper[idx, nn]
|
|
59
|
+
del upper
|
|
60
|
+
nn[n - 1] = -1
|
|
61
|
+
disnn[n - 1] = np.inf
|
|
62
|
+
pairs = np.empty((n - 1, 2), dtype=np.int64)
|
|
63
|
+
crit = np.empty(n - 1)
|
|
64
|
+
for step in range(n - 1):
|
|
65
|
+
cand = np.where(active[: n - 1], disnn[: n - 1], np.inf)
|
|
66
|
+
im = int(np.argmin(cand))
|
|
67
|
+
dmin = cand[im]
|
|
68
|
+
jm = int(nn[im])
|
|
69
|
+
i2, j2 = min(im, jm), max(im, jm)
|
|
70
|
+
pairs[step] = (i2, j2)
|
|
71
|
+
crit[step] = np.sqrt(dmin) if method == "ward.D2" else dmin
|
|
72
|
+
active[j2] = False
|
|
73
|
+
ks = np.flatnonzero(active)
|
|
74
|
+
ks = ks[ks != i2]
|
|
75
|
+
if ks.size:
|
|
76
|
+
d12 = D[i2, j2]
|
|
77
|
+
mk = membr[ks]
|
|
78
|
+
new = ((membr[i2] + mk) * D[i2, ks] + (membr[j2] + mk) * D[j2, ks]) - mk * d12
|
|
79
|
+
new = new / ((membr[i2] + membr[j2]) + mk)
|
|
80
|
+
D[i2, ks] = new
|
|
81
|
+
D[ks, i2] = new
|
|
82
|
+
right = ks > i2
|
|
83
|
+
if right.any():
|
|
84
|
+
r = int(np.argmin(new[right]))
|
|
85
|
+
dnew, jj = new[right][r], int(ks[right][r])
|
|
86
|
+
else:
|
|
87
|
+
dnew, jj = np.inf, -1
|
|
88
|
+
left = ~right
|
|
89
|
+
if left.any():
|
|
90
|
+
kl, vl = ks[left], new[left]
|
|
91
|
+
closer = vl < disnn[kl]
|
|
92
|
+
disnn[kl[closer]] = vl[closer]
|
|
93
|
+
nn[kl[closer]] = i2
|
|
94
|
+
else:
|
|
95
|
+
dnew, jj = np.inf, -1
|
|
96
|
+
membr[i2] += membr[j2]
|
|
97
|
+
disnn[i2] = dnew
|
|
98
|
+
nn[i2] = jj
|
|
99
|
+
rows = np.flatnonzero(active[: n - 1] & ((nn[: n - 1] == i2) | (nn[: n - 1] == j2)))
|
|
100
|
+
if rows.size:
|
|
101
|
+
sub = D[rows]
|
|
102
|
+
ok = active[None, :] & (idx[None, :] > rows[:, None])
|
|
103
|
+
sub = np.where(ok, sub, np.inf)
|
|
104
|
+
jmin = np.argmin(sub, axis=1)
|
|
105
|
+
dm = sub[np.arange(rows.size), jmin]
|
|
106
|
+
none = ~np.isfinite(dm)
|
|
107
|
+
nn[rows] = np.where(none, -1, jmin)
|
|
108
|
+
disnn[rows] = np.where(none, np.inf, dm)
|
|
109
|
+
merge, order = _hcass2(pairs + 1, n)
|
|
110
|
+
return HClust(merge=merge, height=crit, order=order, pairs=pairs,
|
|
111
|
+
labels=None if labels is None else np.asarray(labels))
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _hcass2(pairs1: np.ndarray, n: int) -> tuple[np.ndarray, np.ndarray]:
|
|
115
|
+
"""R's ``hcass2``: the signed merge matrix and the leaf order (both 1-based)."""
|
|
116
|
+
ia = pairs1[:, 0].tolist()
|
|
117
|
+
ib = pairs1[:, 1].tolist()
|
|
118
|
+
iia, iib = list(ia), list(ib)
|
|
119
|
+
last = {} # cluster representative -> step that last merged it
|
|
120
|
+
for i in range(n - 1):
|
|
121
|
+
a, b = ia[i], ib[i]
|
|
122
|
+
iia[i] = last.get(a, -a)
|
|
123
|
+
iib[i] = last.get(b, -b)
|
|
124
|
+
last[min(a, b)] = i + 1
|
|
125
|
+
for i in range(n - 1):
|
|
126
|
+
if iia[i] > 0 and iib[i] < 0:
|
|
127
|
+
iia[i], iib[i] = iib[i], iia[i]
|
|
128
|
+
if iia[i] > 0 and iib[i] > 0:
|
|
129
|
+
iia[i], iib[i] = min(iia[i], iib[i]), max(iia[i], iib[i])
|
|
130
|
+
order = [iia[n - 2], iib[n - 2]]
|
|
131
|
+
for i in range(n - 2, 0, -1):
|
|
132
|
+
j = order.index(i)
|
|
133
|
+
order[j:j + 1] = [iia[i - 1], iib[i - 1]]
|
|
134
|
+
return np.column_stack([iia, iib]).astype(np.int64), -np.asarray(order, dtype=np.int64)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def cutree(pairs: np.ndarray, k: int) -> np.ndarray:
|
|
138
|
+
"""``cutree(tree, k)`` from the 0-based merge pairs: 1-based group numbers in the order of
|
|
139
|
+
first appearance."""
|
|
140
|
+
n = pairs.shape[0] + 1
|
|
141
|
+
if not 1 <= k <= n:
|
|
142
|
+
raise ValueError(f"k must be between 1 and {n}")
|
|
143
|
+
parent = np.arange(n)
|
|
144
|
+
|
|
145
|
+
def find(x):
|
|
146
|
+
while parent[x] != x:
|
|
147
|
+
parent[x] = parent[parent[x]]
|
|
148
|
+
x = parent[x]
|
|
149
|
+
return x
|
|
150
|
+
|
|
151
|
+
for a, b in pairs[: n - k]:
|
|
152
|
+
ra, rb = find(int(a)), find(int(b))
|
|
153
|
+
parent[rb] = ra
|
|
154
|
+
roots = np.array([find(i) for i in range(n)])
|
|
155
|
+
_, first, inv = np.unique(roots, return_index=True, return_inverse=True)
|
|
156
|
+
rank = np.empty(first.size, dtype=np.int64)
|
|
157
|
+
rank[np.argsort(first)] = np.arange(1, first.size + 1)
|
|
158
|
+
return rank[inv]
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""mixtools ``normalmixEM`` as CopyKAT calls it: three components with a common standard deviation
|
|
2
|
+
(``arbvar = FALSE``), starting from given weights, means and sigma, run on several vectors at once.
|
|
3
|
+
|
|
4
|
+
Each EM iteration follows mixtools: ``lambda = colMeans(post)``; each mean is
|
|
5
|
+
``sum(post[, j] * x) / (n * lambda[j])``; the common sigma is
|
|
6
|
+
``sqrt(sum(post * res) / (n * sum(lambda)))``; the posteriors, squared residuals and
|
|
7
|
+
log-likelihood come from mixtools' ``normpost`` C routine (scaled by the component with the
|
|
8
|
+
smallest standardised residual, the log-likelihood added in order). The loop stops when the
|
|
9
|
+
log-likelihood gain is at most ``epsilon`` or after ``maxit`` iterations.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
|
|
15
|
+
import numpy as np
|
|
16
|
+
|
|
17
|
+
_LD = np.longdouble
|
|
18
|
+
_HALF_LOG_2PI = 0.91893853320467274178
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class MixFit:
|
|
23
|
+
lam: np.ndarray # (B, 3)
|
|
24
|
+
mu: np.ndarray # (B, 3)
|
|
25
|
+
sigma: np.ndarray # (B,)
|
|
26
|
+
loglik: np.ndarray # (B,)
|
|
27
|
+
iterations: np.ndarray
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _normpost(x, mu, sigma, lam):
|
|
31
|
+
"""mixtools ``normpost`` for B vectors at once: posteriors and squared residuals (B, 3, n) and
|
|
32
|
+
the log-likelihood (B,)."""
|
|
33
|
+
B, n = x.shape
|
|
34
|
+
lsr = lam / sigma[:, None] # LamSigRatio (B, 3)
|
|
35
|
+
llsr = np.log(lsr)
|
|
36
|
+
res2 = x[:, None, :] - mu[:, :, None]
|
|
37
|
+
res2 *= res2
|
|
38
|
+
den = (2.0 * sigma) * sigma
|
|
39
|
+
work = res2 / den[:, None, None]
|
|
40
|
+
w0, w1, w2 = work[:, 0], work[:, 1], work[:, 2]
|
|
41
|
+
# first component with the smallest standardised residual (j == 0 || r < min)
|
|
42
|
+
lt1 = w1 < w0
|
|
43
|
+
mn = np.where(lt1, w1, w0)
|
|
44
|
+
lt2 = w2 < mn
|
|
45
|
+
mn = np.where(lt2, w2, mn)
|
|
46
|
+
minj = np.where(lt2, 2, np.where(lt1, 1, 0))
|
|
47
|
+
rb = np.arange(B)[:, None]
|
|
48
|
+
lsr_min = lsr[rb, minj]
|
|
49
|
+
post = np.empty_like(work)
|
|
50
|
+
rowsum = np.ones((B, n))
|
|
51
|
+
for j in range(3):
|
|
52
|
+
wj = (lsr[:, j][:, None] / lsr_min) * np.exp(mn - work[:, j])
|
|
53
|
+
is_min = minj == j
|
|
54
|
+
wj[is_min] = 1.0
|
|
55
|
+
post[:, j] = wj
|
|
56
|
+
rowsum += np.where(is_min, 0.0, wj)
|
|
57
|
+
post /= rowsum[:, None, :]
|
|
58
|
+
terms = (np.log(rowsum) - mn) + llsr[rb, minj]
|
|
59
|
+
terms[:, 0] += -float(n) * _HALF_LOG_2PI
|
|
60
|
+
ll = np.cumsum(terms, axis=1)[:, -1]
|
|
61
|
+
return post, res2, ll
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def normalmix3(x: np.ndarray, sigma0: np.ndarray, mu0=(-0.2, 0.0, 0.2), lam0=None,
|
|
65
|
+
epsilon: float = 1e-8, maxit: int = 1000) -> MixFit:
|
|
66
|
+
"""``normalmixEM(x[b], lambda = rep(1, 3)/3, mu = mu0, sigma = sigma0[b], arbvar = FALSE,
|
|
67
|
+
ECM = FALSE, maxit = maxit)`` for each row ``x[b]``."""
|
|
68
|
+
x = np.atleast_2d(np.asarray(x, dtype=np.float64))
|
|
69
|
+
B, n = x.shape
|
|
70
|
+
lam_init = np.full(3, 1.0) / 3.0 if lam0 is None else np.asarray(lam0, dtype=np.float64)
|
|
71
|
+
lam_init = lam_init / lam_init.sum()
|
|
72
|
+
lam = np.tile(lam_init, (B, 1))
|
|
73
|
+
mu = np.tile(np.asarray(mu0, dtype=np.float64), (B, 1))
|
|
74
|
+
sigma = np.asarray(sigma0, dtype=np.float64).reshape(B).copy()
|
|
75
|
+
post, res, ll = _normpost(x, mu, sigma, lam)
|
|
76
|
+
iters = np.zeros(B, dtype=np.int64)
|
|
77
|
+
act = np.ones(B, dtype=bool)
|
|
78
|
+
while act.any():
|
|
79
|
+
a = np.flatnonzero(act)
|
|
80
|
+
xa, pa, ra = x[a], post[a], res[a]
|
|
81
|
+
lam_a = (pa.sum(axis=2, dtype=_LD) / n).astype(np.float64)
|
|
82
|
+
mu_a = (pa * xa[:, None, :]).sum(axis=2, dtype=_LD).astype(np.float64) / (float(n) * lam_a)
|
|
83
|
+
num = (pa * ra).reshape(a.size, 3 * n).sum(axis=1, dtype=_LD)
|
|
84
|
+
sl = lam_a.astype(_LD)
|
|
85
|
+
sl = (sl[:, 0] + sl[:, 1]) + sl[:, 2]
|
|
86
|
+
sig_a = np.sqrt(num.astype(np.float64) / (float(n) * sl.astype(np.float64)))
|
|
87
|
+
if np.any(sig_a < 1e-8):
|
|
88
|
+
raise FloatingPointError("normalmixEM: a variance went to zero (R restarts at random)")
|
|
89
|
+
p2, r2, ll2 = _normpost(xa, mu_a, sig_a, lam_a)
|
|
90
|
+
diff = ll2 - ll[a]
|
|
91
|
+
lam[a], mu[a], sigma[a] = lam_a, mu_a, sig_a
|
|
92
|
+
post[a], res[a], ll[a] = p2, r2, ll2
|
|
93
|
+
iters[a] += 1
|
|
94
|
+
act[a] = (diff > epsilon) & (iters[a] < maxit)
|
|
95
|
+
return MixFit(lam=lam, mu=mu, sigma=sigma, loglik=ll, iterations=iters)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Pieces of base R with R's arithmetic.
|
|
2
|
+
|
|
3
|
+
``mean`` adds in long double and corrects with a second pass (summary.c); ``var``/``sd`` use that
|
|
4
|
+
mean and a long-double sum of squares (cov.c); ``median`` averages the two middle values for even
|
|
5
|
+
lengths; ``qnorm`` is R's ``qnorm5`` (Wichura's AS 241).
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
_LD = np.longdouble
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _seq_sum(m: np.ndarray, axis: int) -> np.ndarray:
|
|
15
|
+
"""Long-double sum along ``axis``, adding the slices in order (R's LDOUBLE loops)."""
|
|
16
|
+
m = np.moveaxis(np.asarray(m), axis, 0)
|
|
17
|
+
s = np.zeros(m.shape[1:], dtype=_LD)
|
|
18
|
+
for i in range(m.shape[0]):
|
|
19
|
+
s += m[i]
|
|
20
|
+
return s
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def r_means(m: np.ndarray, axis: int = 0) -> np.ndarray:
|
|
24
|
+
"""``apply(m, axis + 1 == 2 ? 2 : 1, mean)``: R ``mean`` of each slice along ``axis``."""
|
|
25
|
+
m = np.asarray(m, dtype=np.float64)
|
|
26
|
+
n = m.shape[axis]
|
|
27
|
+
s = _seq_sum(m, axis) / n
|
|
28
|
+
ok = np.isfinite(s.astype(np.float64))
|
|
29
|
+
if ok.any():
|
|
30
|
+
d = np.moveaxis(m, axis, 0).astype(_LD) - s
|
|
31
|
+
t = np.zeros_like(s)
|
|
32
|
+
for i in range(d.shape[0]):
|
|
33
|
+
t += d[i]
|
|
34
|
+
s = np.where(ok, s + t / n, s)
|
|
35
|
+
return s.astype(np.float64)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def r_mean(x) -> float:
|
|
39
|
+
"""``mean(x)`` for a double vector."""
|
|
40
|
+
return float(r_means(np.asarray(x, dtype=np.float64).reshape(-1, 1), 0)[0])
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def r_sds(m: np.ndarray, axis: int = 0) -> np.ndarray:
|
|
44
|
+
"""``sd`` of each slice along ``axis`` (cov.c: long-double mean, then the squares added in
|
|
45
|
+
long double and divided by n - 1)."""
|
|
46
|
+
m = np.asarray(m, dtype=np.float64)
|
|
47
|
+
n = m.shape[axis]
|
|
48
|
+
mu = r_means(m, axis)
|
|
49
|
+
d = np.moveaxis(m, axis, 0) - mu
|
|
50
|
+
ss = _seq_sum(d * d, 0)
|
|
51
|
+
return np.sqrt((ss / (n - 1)).astype(np.float64))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def r_sd(x) -> float:
|
|
55
|
+
"""``sd(x)``."""
|
|
56
|
+
return float(r_sds(np.asarray(x, dtype=np.float64).reshape(-1, 1), 0)[0])
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def r_medians(m: np.ndarray, axis: int = 0) -> np.ndarray:
|
|
60
|
+
"""``median`` of each slice along ``axis`` (the mean of the two middle values for even n)."""
|
|
61
|
+
return np.median(np.asarray(m, dtype=np.float64), axis=axis)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def qnorm(p, log=np.log) -> np.ndarray:
|
|
65
|
+
"""``qnorm(p)`` (lower tail): R's ``qnorm5``. ``p`` must lie in (0, 1).
|
|
66
|
+
|
|
67
|
+
``log`` evaluates the tails (|p - 0.5| > 0.425); NumPy's ``log`` can differ from the C library's
|
|
68
|
+
in the last bit."""
|
|
69
|
+
p = np.asarray(p, dtype=np.float64)
|
|
70
|
+
q = p - 0.5
|
|
71
|
+
val = np.empty_like(p)
|
|
72
|
+
c = np.abs(q) <= 0.425
|
|
73
|
+
if c.any():
|
|
74
|
+
qq = q[c]
|
|
75
|
+
r = 0.180625 - qq * qq
|
|
76
|
+
val[c] = (qq * (((((((r * 2509.0809287301226727 + 33430.575583588128105) * r
|
|
77
|
+
+ 67265.770927008700853) * r + 45921.953931549871457) * r
|
|
78
|
+
+ 13731.693765509461125) * r + 1971.5909503065514427) * r
|
|
79
|
+
+ 133.14166789178437745) * r + 3.387132872796366608)
|
|
80
|
+
/ (((((((r * 5226.495278852854561 + 28729.085735721942674) * r
|
|
81
|
+
+ 39307.89580009271061) * r + 21213.794301586595867) * r
|
|
82
|
+
+ 5394.1960214247511077) * r + 687.1870074920579083) * r
|
|
83
|
+
+ 42.313330701600911252) * r + 1.0))
|
|
84
|
+
t = ~c
|
|
85
|
+
if t.any():
|
|
86
|
+
qt, pt = q[t], p[t]
|
|
87
|
+
r = np.sqrt(-log(np.where(qt > 0, 1.0 - pt, pt)))
|
|
88
|
+
out = np.empty_like(r)
|
|
89
|
+
a = r <= 5.0
|
|
90
|
+
if a.any():
|
|
91
|
+
ra = r[a] - 1.6
|
|
92
|
+
out[a] = ((((((((ra * 7.7454501427834140764e-4 + 0.0227238449892691845833) * ra
|
|
93
|
+
+ 0.24178072517745061177) * ra + 1.27045825245236838258) * ra
|
|
94
|
+
+ 3.64784832476320460504) * ra + 5.7694972214606914055) * ra
|
|
95
|
+
+ 4.6303378461565452959) * ra + 1.42343711074968357734)
|
|
96
|
+
/ (((((((ra * 1.05075007164441684324e-9 + 5.475938084995344946e-4) * ra
|
|
97
|
+
+ 0.0151986665636164571966) * ra + 0.14810397642748007459) * ra
|
|
98
|
+
+ 0.68976733498510000455) * ra + 1.6763848301838038494) * ra
|
|
99
|
+
+ 2.05319162663775882187) * ra + 1.0))
|
|
100
|
+
b = ~a
|
|
101
|
+
if b.any():
|
|
102
|
+
rb = r[b] - 5.0
|
|
103
|
+
out[b] = ((((((((rb * 2.01033439929228813265e-7 + 2.71155556874348757815e-5) * rb
|
|
104
|
+
+ 0.0012426609473880784386) * rb + 0.026532189526576123093) * rb
|
|
105
|
+
+ 0.29656057182850489123) * rb + 1.7848265399172913358) * rb
|
|
106
|
+
+ 5.4637849111641143699) * rb + 6.6579046435011037772)
|
|
107
|
+
/ (((((((rb * 2.04426310338993978564e-15 + 1.4215117583164458887e-7) * rb
|
|
108
|
+
+ 1.8463183175100546818e-5) * rb + 7.868691311456132591e-4) * rb
|
|
109
|
+
+ 0.0148753612908506148525) * rb + 0.13692988092273580531) * rb
|
|
110
|
+
+ 0.59983220655588793769) * rb + 1.0))
|
|
111
|
+
val[t] = np.where(qt < 0.0, -out, out)
|
|
112
|
+
return val
|