Download features.py from infernogod/enzyme-model: direct link, hf CLI and curl.
- Browser
- Download file 2.91 kB
-
https://huggingface.co/infernogod/enzyme-model/resolve/main/features.py
- Command line
-
hf download hf://infernogod/enzyme-model/features.py
-
curl -L -o features.py https://huggingface.co/infernogod/enzyme-model/resolve/main/features.py
2.91 kB
| """Candidate-EC features from DIAMOND hits (shared by training and inference).""" | |
| import math | |
| import numpy as np | |
| MAX_CAND = 10 | |
| FEATURES = [ | |
| 'v1', 'v4', 'v8', 'vrank', 'best_bits_rel', 'best_bits', 'best_pid', 'best_rank', | |
| 'cnt10', 'cnt50', 'frac10', 'qcov', 'scov', 'log_ev', 'best_nec', 'l3', 'l2', 'l1', | |
| 'log_freq', 'top_bits', 'top_pid', 'log_qlen', 'n_hits', 'n_cand', 'top_nec', | |
| 'margin4', 'pid_gap', | |
| ] | |
| def query_candidates(hits, labels, freq): | |
| """hits: list of (sseqid, pid, qlen, slen, evalue, bits, qstart, qend, sstart, send), | |
| sorted by bitscore descending. Returns (candidate ECs, feature matrix).""" | |
| if not hits: | |
| return [], np.zeros((0, len(FEATURES)), dtype=np.float32) | |
| top_bits = hits[0][5] | |
| top_pid = hits[0][1] | |
| qlen = hits[0][2] | |
| stats = {} | |
| tot1 = tot4 = tot8 = 0.0 | |
| lvl = [dict(), dict(), dict()] # L3, L2, L1 prefix votes (b^4) | |
| for r, (s, pid, ql, sl, ev, b, qs, qe, ss, se) in enumerate(hits): | |
| ecs = labels[s] | |
| n = len(ecs) | |
| rb = b / top_bits | |
| w1, w4, w8 = rb / n, rb ** 4 / n, rb ** 8 / n | |
| tot1 += rb | |
| tot4 += rb ** 4 | |
| tot8 += rb ** 8 | |
| prefixes = [set(), set(), set()] | |
| for e in ecs: | |
| st = stats.get(e) | |
| if st is None: | |
| st = stats[e] = [0.0, 0.0, 0.0, b, pid, r, 0, 0, | |
| (qe - qs + 1) / ql, (se - ss + 1) / sl, ev, n] | |
| st[0] += w1 | |
| st[1] += w4 | |
| st[2] += w8 | |
| if r < 10: | |
| st[6] += 1 | |
| st[7] += 1 | |
| p = e.split('.') | |
| for L in range(3): | |
| prefixes[L].add('.'.join(p[:3 - L])) | |
| for L in range(3): | |
| for pre in prefixes[L]: | |
| lvl[L][pre] = lvl[L].get(pre, 0.0) + rb ** 4 | |
| cands = sorted(stats, key=lambda e: (-stats[e][1], -freq.get(e, 0), e))[:MAX_CAND] | |
| v4s = [stats[e][1] for e in cands] | |
| margin = (v4s[0] - v4s[1]) / tot4 if len(v4s) > 1 else v4s[0] / tot4 | |
| top_nec = len(labels[hits[0][0]]) | |
| n10 = min(10, len(hits)) | |
| X = np.zeros((len(cands), len(FEATURES)), dtype=np.float32) | |
| for i, e in enumerate(cands): | |
| st = stats[e] | |
| p = e.split('.') | |
| X[i] = [st[0] / tot1, st[1] / tot4, st[2] / tot8, i, st[3] / top_bits, st[3], st[4], st[5], | |
| st[6], st[7], st[6] / n10, st[8], st[9], math.log10(st[10] + 1e-180), st[11], | |
| lvl[0]['.'.join(p[:3])] / tot4, lvl[1]['.'.join(p[:2])] / tot4, | |
| lvl[2][p[0]] / tot4, math.log1p(freq.get(e, 0)), top_bits, top_pid, | |
| math.log(qlen), len(hits), len(stats), top_nec, margin, top_pid - st[4]] | |
| return cands, X | |
| def parse_line(line): | |
| q, s, pid, length, ql, sl, ev, b, qs, qe, ss, se = line.rstrip('\n').split('\t') | |
| return q, (s, float(pid), int(ql), int(sl), float(ev), float(b), int(qs), int(qe), int(ss), int(se)) | |