Add column headings to slice-numpy -pca sample list output.
[lightning.git] / tilelib.go
1 // Copyright (C) The Lightning Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package lightning
6
7 import (
8         "bufio"
9         "bytes"
10         "context"
11         "encoding/gob"
12         "fmt"
13         "io"
14         "os"
15         "regexp"
16         "runtime"
17         "sort"
18         "strings"
19         "sync"
20         "sync/atomic"
21
22         "github.com/klauspost/pgzip"
23         log "github.com/sirupsen/logrus"
24         "golang.org/x/crypto/blake2b"
25 )
26
27 type tileVariantID uint16 // 1-based
28
29 type tileLibRef struct {
30         Tag     tagID
31         Variant tileVariantID
32 }
33
34 type tileSeq map[string][]tileLibRef
35
36 func (tseq tileSeq) Variants() ([]tileVariantID, int, int) {
37         maxtag := 0
38         for _, refs := range tseq {
39                 for _, ref := range refs {
40                         if maxtag < int(ref.Tag) {
41                                 maxtag = int(ref.Tag)
42                         }
43                 }
44         }
45         vars := make([]tileVariantID, maxtag+1)
46         var kept, dropped int
47         for _, refs := range tseq {
48                 for _, ref := range refs {
49                         if vars[int(ref.Tag)] != 0 {
50                                 dropped++
51                         } else {
52                                 kept++
53                         }
54                         vars[int(ref.Tag)] = ref.Variant
55                 }
56         }
57         return vars, kept, dropped
58 }
59
60 type tileLibrary struct {
61         retainNoCalls       bool
62         skipOOO             bool
63         retainTileSequences bool
64         useDups             bool
65
66         taglib         *tagLibrary
67         variant        [][][blake2b.Size256]byte
68         refseqs        map[string]map[string][]tileLibRef
69         compactGenomes map[string][]tileVariantID
70         seq2           map[[2]byte]map[[blake2b.Size256]byte][]byte
71         seq2lock       map[[2]byte]sync.Locker
72         variants       int64
73         // if non-nil, write out any tile variants added while tiling
74         encoder *gob.Encoder
75         // set Ref flag when writing new variants to encoder
76         encodeRef bool
77
78         onAddTileVariant func(libref tileLibRef, hash [blake2b.Size256]byte, seq []byte) error
79         onAddGenome      func(CompactGenome) error
80         onAddRefseq      func(CompactSequence) error
81
82         mtx   sync.RWMutex
83         vlock []sync.Locker
84 }
85
86 func (tilelib *tileLibrary) loadTagSet(newtagset [][]byte) error {
87         // Loading a tagset means either passing it through to the
88         // output (if it's the first one we've seen), or just ensuring
89         // it doesn't disagree with what we already have.
90         if len(newtagset) == 0 {
91                 return nil
92         }
93         tilelib.mtx.Lock()
94         defer tilelib.mtx.Unlock()
95         if tilelib.taglib == nil || tilelib.taglib.Len() == 0 {
96                 tilelib.taglib = &tagLibrary{}
97                 err := tilelib.taglib.setTags(newtagset)
98                 if err != nil {
99                         return err
100                 }
101                 if tilelib.encoder != nil {
102                         err = tilelib.encoder.Encode(LibraryEntry{
103                                 TagSet: newtagset,
104                         })
105                         if err != nil {
106                                 return err
107                         }
108                 }
109         } else if tilelib.taglib.Len() != len(newtagset) {
110                 return fmt.Errorf("cannot merge libraries with differing tagsets")
111         } else {
112                 current := tilelib.taglib.Tags()
113                 for i := range newtagset {
114                         if !bytes.Equal(newtagset[i], current[i]) {
115                                 return fmt.Errorf("cannot merge libraries with differing tagsets")
116                         }
117                 }
118         }
119         return nil
120 }
121
122 func (tilelib *tileLibrary) loadTileVariants(tvs []TileVariant, variantmap map[tileLibRef]tileVariantID) error {
123         for _, tv := range tvs {
124                 // Assign a new variant ID (unique across all inputs)
125                 // for each input variant.
126                 variantmap[tileLibRef{Tag: tv.Tag, Variant: tv.Variant}] = tilelib.getRef(tv.Tag, tv.Sequence, tv.Ref).Variant
127         }
128         return nil
129 }
130
131 func (tilelib *tileLibrary) loadCompactGenomes(cgs []CompactGenome, variantmap map[tileLibRef]tileVariantID) error {
132         log.Debugf("loadCompactGenomes: %d", len(cgs))
133         var wg sync.WaitGroup
134         errs := make(chan error, 1)
135         for _, cg := range cgs {
136                 wg.Add(1)
137                 cg := cg
138                 go func() {
139                         defer wg.Done()
140                         for i, variant := range cg.Variants {
141                                 if len(errs) > 0 {
142                                         return
143                                 }
144                                 if variant == 0 {
145                                         continue
146                                 }
147                                 tag := tagID(i / 2)
148                                 newvariant, ok := variantmap[tileLibRef{Tag: tag, Variant: variant}]
149                                 if !ok {
150                                         err := fmt.Errorf("oops: genome %q has variant %d for tag %d, but that variant was not in its library", cg.Name, variant, tag)
151                                         select {
152                                         case errs <- err:
153                                         default:
154                                         }
155                                         return
156                                 }
157                                 // log.Tracef("loadCompactGenomes: cg %s tag %d variant %d => %d", cg.Name, tag, variant, newvariant)
158                                 cg.Variants[i] = newvariant
159                         }
160                         if tilelib.onAddGenome != nil {
161                                 err := tilelib.onAddGenome(cg)
162                                 if err != nil {
163                                         select {
164                                         case errs <- err:
165                                         default:
166                                         }
167                                         return
168                                 }
169                         }
170                         if tilelib.encoder != nil {
171                                 err := tilelib.encoder.Encode(LibraryEntry{
172                                         CompactGenomes: []CompactGenome{cg},
173                                 })
174                                 if err != nil {
175                                         select {
176                                         case errs <- err:
177                                         default:
178                                         }
179                                         return
180                                 }
181                         }
182                         if tilelib.compactGenomes != nil {
183                                 tilelib.mtx.Lock()
184                                 defer tilelib.mtx.Unlock()
185                                 tilelib.compactGenomes[cg.Name] = cg.Variants
186                         }
187                 }()
188         }
189         wg.Wait()
190         go close(errs)
191         return <-errs
192 }
193
194 func (tilelib *tileLibrary) loadCompactSequences(cseqs []CompactSequence, variantmap map[tileLibRef]tileVariantID) error {
195         log.Infof("loadCompactSequences: %d todo", len(cseqs))
196         for _, cseq := range cseqs {
197                 log.Infof("loadCompactSequences: checking %s", cseq.Name)
198                 for _, tseq := range cseq.TileSequences {
199                         for i, libref := range tseq {
200                                 if libref.Variant == 0 {
201                                         // No variant (e.g., import
202                                         // dropped tiles with
203                                         // no-calls) = no translation.
204                                         continue
205                                 }
206                                 v, ok := variantmap[libref]
207                                 if !ok {
208                                         return fmt.Errorf("oops: CompactSequence %q has variant %d for tag %d, but that variant was not in its library", cseq.Name, libref.Variant, libref.Tag)
209                                 }
210                                 tseq[i].Variant = v
211                         }
212                 }
213                 if tilelib.encoder != nil {
214                         if err := tilelib.encoder.Encode(LibraryEntry{
215                                 CompactSequences: []CompactSequence{cseq},
216                         }); err != nil {
217                                 return err
218                         }
219                 }
220                 if tilelib.onAddRefseq != nil {
221                         err := tilelib.onAddRefseq(cseq)
222                         if err != nil {
223                                 return err
224                         }
225                 }
226                 log.Infof("loadCompactSequences: checking %s done", cseq.Name)
227         }
228         tilelib.mtx.Lock()
229         defer tilelib.mtx.Unlock()
230         if tilelib.refseqs == nil {
231                 tilelib.refseqs = map[string]map[string][]tileLibRef{}
232         }
233         for _, cseq := range cseqs {
234                 tilelib.refseqs[cseq.Name] = cseq.TileSequences
235         }
236         log.Info("loadCompactSequences: done")
237         return nil
238 }
239
240 func allFiles(path string, re *regexp.Regexp) ([]string, error) {
241         var files []string
242         f, err := open(path)
243         if err != nil {
244                 return nil, err
245         }
246         defer f.Close()
247         fis, err := f.Readdir(-1)
248         if err != nil {
249                 return []string{path}, nil
250         }
251         for _, fi := range fis {
252                 if fi.Name() == "." || fi.Name() == ".." {
253                         continue
254                 } else if child := path + "/" + fi.Name(); fi.IsDir() {
255                         add, err := allFiles(child, re)
256                         if err != nil {
257                                 return nil, err
258                         }
259                         files = append(files, add...)
260                 } else if re == nil || re.MatchString(child) {
261                         files = append(files, child)
262                 }
263         }
264         sort.Strings(files)
265         return files, nil
266 }
267
268 var matchGobFile = regexp.MustCompile(`\.gob(\.gz)?$`)
269
270 func (tilelib *tileLibrary) LoadDir(ctx context.Context, path string) error {
271         log.Infof("LoadDir: walk dir %s", path)
272         files, err := allFiles(path, matchGobFile)
273         if err != nil {
274                 return err
275         }
276         ctx, cancel := context.WithCancel(ctx)
277         defer cancel()
278         var mtx sync.Mutex
279         allcgs := make([][]CompactGenome, len(files))
280         allcseqs := make([][]CompactSequence, len(files))
281         allvariantmap := map[tileLibRef]tileVariantID{}
282         errs := make(chan error, len(files))
283         log.Infof("LoadDir: read %d files", len(files))
284         for fileno, path := range files {
285                 fileno, path := fileno, path
286                 go func() {
287                         f, err := open(path)
288                         if err != nil {
289                                 errs <- err
290                                 return
291                         }
292                         defer f.Close()
293                         defer log.Infof("LoadDir: finished reading %s", path)
294
295                         var variantmap = map[tileLibRef]tileVariantID{}
296                         var cgs []CompactGenome
297                         var cseqs []CompactSequence
298                         err = DecodeLibrary(f, strings.HasSuffix(path, ".gz"), func(ent *LibraryEntry) error {
299                                 if ctx.Err() != nil {
300                                         return ctx.Err()
301                                 }
302                                 if len(ent.TagSet) > 0 {
303                                         mtx.Lock()
304                                         if tilelib.taglib == nil || tilelib.taglib.Len() != len(ent.TagSet) {
305                                                 // load first set of tags, or
306                                                 // report mismatch if 2 sets
307                                                 // have different #tags.
308                                                 if err := tilelib.loadTagSet(ent.TagSet); err != nil {
309                                                         mtx.Unlock()
310                                                         return err
311                                                 }
312                                         }
313                                         mtx.Unlock()
314                                 }
315                                 for _, tv := range ent.TileVariants {
316                                         variantmap[tileLibRef{Tag: tv.Tag, Variant: tv.Variant}] = tilelib.getRef(tv.Tag, tv.Sequence, tv.Ref).Variant
317                                 }
318                                 cgs = append(cgs, ent.CompactGenomes...)
319                                 cseqs = append(cseqs, ent.CompactSequences...)
320                                 return nil
321                         })
322                         allcgs[fileno] = cgs
323                         allcseqs[fileno] = cseqs
324                         mtx.Lock()
325                         defer mtx.Unlock()
326                         for k, v := range variantmap {
327                                 allvariantmap[k] = v
328                         }
329                         errs <- err
330                 }()
331         }
332         for range files {
333                 err := <-errs
334                 if err != nil {
335                         return err
336                 }
337         }
338
339         log.Info("LoadDir: loadCompactGenomes")
340         var flatcgs []CompactGenome
341         for _, cgs := range allcgs {
342                 flatcgs = append(flatcgs, cgs...)
343         }
344         err = tilelib.loadCompactGenomes(flatcgs, allvariantmap)
345         if err != nil {
346                 return err
347         }
348
349         log.Info("LoadDir: loadCompactSequences")
350         var flatcseqs []CompactSequence
351         for _, cseqs := range allcseqs {
352                 flatcseqs = append(flatcseqs, cseqs...)
353         }
354         err = tilelib.loadCompactSequences(flatcseqs, allvariantmap)
355         if err != nil {
356                 return err
357         }
358
359         log.Info("LoadDir done")
360         return nil
361 }
362
363 func (tilelib *tileLibrary) WriteDir(dir string) error {
364         ntilefiles := 128
365         nfiles := ntilefiles + len(tilelib.refseqs)
366         files := make([]*os.File, nfiles)
367         for i := range files {
368                 f, err := os.OpenFile(fmt.Sprintf("%s/library.%04d.gob.gz", dir, i), os.O_CREATE|os.O_WRONLY, 0666)
369                 if err != nil {
370                         return err
371                 }
372                 defer f.Close()
373                 files[i] = f
374         }
375         bufws := make([]*bufio.Writer, nfiles)
376         for i := range bufws {
377                 bufws[i] = bufio.NewWriterSize(files[i], 1<<26)
378         }
379         zws := make([]*pgzip.Writer, nfiles)
380         for i := range zws {
381                 zws[i] = pgzip.NewWriter(bufws[i])
382                 defer zws[i].Close()
383         }
384         encoders := make([]*gob.Encoder, nfiles)
385         for i := range encoders {
386                 encoders[i] = gob.NewEncoder(zws[i])
387         }
388
389         cgnames := make([]string, 0, len(tilelib.compactGenomes))
390         for name := range tilelib.compactGenomes {
391                 cgnames = append(cgnames, name)
392         }
393         sort.Strings(cgnames)
394
395         refnames := make([]string, 0, len(tilelib.refseqs))
396         for name := range tilelib.refseqs {
397                 refnames = append(refnames, name)
398         }
399         sort.Strings(refnames)
400
401         log.Infof("WriteDir: writing %d files", nfiles)
402         ctx, cancel := context.WithCancel(context.Background())
403         defer cancel()
404         errs := make(chan error, nfiles)
405         for start := range files {
406                 start := start
407                 go func() {
408                         err := encoders[start].Encode(LibraryEntry{TagSet: tilelib.taglib.Tags()})
409                         if err != nil {
410                                 errs <- err
411                                 return
412                         }
413                         if refidx := start - ntilefiles; refidx >= 0 {
414                                 // write each ref to its own file
415                                 // (they seem to load very slowly)
416                                 name := refnames[refidx]
417                                 errs <- encoders[start].Encode(LibraryEntry{CompactSequences: []CompactSequence{{
418                                         Name:          name,
419                                         TileSequences: tilelib.refseqs[name],
420                                 }}})
421                                 return
422                         }
423                         for i := start; i < len(cgnames); i += ntilefiles {
424                                 err := encoders[start].Encode(LibraryEntry{CompactGenomes: []CompactGenome{{
425                                         Name:     cgnames[i],
426                                         Variants: tilelib.compactGenomes[cgnames[i]],
427                                 }}})
428                                 if err != nil {
429                                         errs <- err
430                                         return
431                                 }
432                         }
433                         tvs := []TileVariant{}
434                         for tag := start; tag < len(tilelib.variant) && ctx.Err() == nil; tag += ntilefiles {
435                                 tvs = tvs[:0]
436                                 for idx, hash := range tilelib.variant[tag] {
437                                         tvs = append(tvs, TileVariant{
438                                                 Tag:      tagID(tag),
439                                                 Variant:  tileVariantID(idx + 1),
440                                                 Blake2b:  hash,
441                                                 Sequence: tilelib.hashSequence(hash),
442                                         })
443                                 }
444                                 err := encoders[start].Encode(LibraryEntry{TileVariants: tvs})
445                                 if err != nil {
446                                         errs <- err
447                                         return
448                                 }
449                         }
450                         errs <- nil
451                 }()
452         }
453         for range files {
454                 err := <-errs
455                 if err != nil {
456                         return err
457                 }
458         }
459         log.Info("WriteDir: flushing")
460         for i := range zws {
461                 err := zws[i].Close()
462                 if err != nil {
463                         return err
464                 }
465                 err = bufws[i].Flush()
466                 if err != nil {
467                         return err
468                 }
469                 err = files[i].Close()
470                 if err != nil {
471                         return err
472                 }
473         }
474         log.Info("WriteDir: done")
475         return nil
476 }
477
478 // Load library data from rdr. Tile variants might be renumbered in
479 // the process; in that case, genomes variants will be renumbered to
480 // match.
481 func (tilelib *tileLibrary) LoadGob(ctx context.Context, rdr io.Reader, gz bool) error {
482         cgs := []CompactGenome{}
483         cseqs := []CompactSequence{}
484         variantmap := map[tileLibRef]tileVariantID{}
485         err := DecodeLibrary(rdr, gz, func(ent *LibraryEntry) error {
486                 if ctx.Err() != nil {
487                         return ctx.Err()
488                 }
489                 if err := tilelib.loadTagSet(ent.TagSet); err != nil {
490                         return err
491                 }
492                 if err := tilelib.loadTileVariants(ent.TileVariants, variantmap); err != nil {
493                         return err
494                 }
495                 cgs = append(cgs, ent.CompactGenomes...)
496                 cseqs = append(cseqs, ent.CompactSequences...)
497                 return nil
498         })
499         if err != nil {
500                 return err
501         }
502         if ctx.Err() != nil {
503                 return ctx.Err()
504         }
505         err = tilelib.loadCompactGenomes(cgs, variantmap)
506         if err != nil {
507                 return err
508         }
509         err = tilelib.loadCompactSequences(cseqs, variantmap)
510         if err != nil {
511                 return err
512         }
513         return nil
514 }
515
516 func (tilelib *tileLibrary) dump(out io.Writer) {
517         printTV := func(tag int, variant tileVariantID) {
518                 if variant < 1 {
519                         fmt.Fprintf(out, " -")
520                 } else if tag >= len(tilelib.variant) {
521                         fmt.Fprintf(out, " (!tag=%d)", tag)
522                 } else if int(variant) > len(tilelib.variant[tag]) {
523                         fmt.Fprintf(out, " (tag=%d,!variant=%d)", tag, variant)
524                 } else {
525                         fmt.Fprintf(out, " %x", tilelib.variant[tag][variant-1][:8])
526                 }
527         }
528         for refname, refseqs := range tilelib.refseqs {
529                 for seqname, seq := range refseqs {
530                         fmt.Fprintf(out, "ref %s %s", refname, seqname)
531                         for _, libref := range seq {
532                                 printTV(int(libref.Tag), libref.Variant)
533                         }
534                         fmt.Fprintf(out, "\n")
535                 }
536         }
537         for name, cg := range tilelib.compactGenomes {
538                 fmt.Fprintf(out, "cg %s", name)
539                 for tag, variant := range cg {
540                         printTV(tag/2, variant)
541                 }
542                 fmt.Fprintf(out, "\n")
543         }
544 }
545
546 type importStats struct {
547         InputFile             string
548         InputLabel            string
549         InputLength           int
550         InputCoverage         int
551         PathLength            int
552         DroppedRepeatedTags   int
553         DroppedOutOfOrderTags int
554 }
555
556 func (tilelib *tileLibrary) TileFasta(filelabel string, rdr io.Reader, matchChromosome *regexp.Regexp, isRef bool) (tileSeq, []importStats, error) {
557         ret := tileSeq{}
558         type jobT struct {
559                 label string
560                 fasta []byte
561         }
562         todo := make(chan jobT, 1)
563         scanner := bufio.NewScanner(rdr)
564         scanner.Buffer(make([]byte, 256), 1<<29) // 512 MiB, in case fasta does not have line breaks
565         go func() {
566                 defer close(todo)
567                 var fasta []byte
568                 var seqlabel string
569                 for scanner.Scan() {
570                         buf := scanner.Bytes()
571                         if len(buf) > 0 && buf[0] == '>' {
572                                 todo <- jobT{seqlabel, append([]byte(nil), fasta...)}
573                                 seqlabel, fasta = strings.SplitN(string(buf[1:]), " ", 2)[0], fasta[:0]
574                                 log.Debugf("%s %s reading fasta", filelabel, seqlabel)
575                         } else if len(buf) > 0 && buf[0] == '#' {
576                                 // ignore testdata comment
577                         } else {
578                                 fasta = append(fasta, bytes.ToLower(buf)...)
579                         }
580                 }
581                 todo <- jobT{seqlabel, fasta}
582         }()
583         type foundtag struct {
584                 pos   int
585                 tagid tagID
586         }
587         found := make([]foundtag, 2000000)
588         path := make([]tileLibRef, 2000000)
589         totalFoundTags := 0
590         totalPathLen := 0
591         skippedSequences := 0
592         taglen := tilelib.taglib.TagLen()
593         var stats []importStats
594         for job := range todo {
595                 if len(job.fasta) == 0 {
596                         continue
597                 } else if !matchChromosome.MatchString(job.label) {
598                         skippedSequences++
599                         continue
600                 }
601                 log.Debugf("%s %s tiling", filelabel, job.label)
602
603                 found = found[:0]
604                 tilelib.taglib.FindAll(job.fasta, func(tagid tagID, pos, taglen int) {
605                         found = append(found, foundtag{pos: pos, tagid: tagid})
606                 })
607                 totalFoundTags += len(found)
608                 if len(found) == 0 {
609                         log.Warnf("%s %s no tags found", filelabel, job.label)
610                 }
611
612                 droppedDup := 0
613                 if !tilelib.useDups {
614                         // Remove any tags that appeared more than once
615                         dup := map[tagID]bool{}
616                         for _, ft := range found {
617                                 _, dup[ft.tagid] = dup[ft.tagid]
618                         }
619                         dst := 0
620                         for _, ft := range found {
621                                 if !dup[ft.tagid] {
622                                         found[dst] = ft
623                                         dst++
624                                 }
625                         }
626                         droppedDup = len(found) - dst
627                         log.Infof("%s %s dropping %d non-unique tags", filelabel, job.label, droppedDup)
628                         found = found[:dst]
629                 }
630
631                 droppedOOO := 0
632                 if tilelib.skipOOO {
633                         keep := longestIncreasingSubsequence(len(found), func(i int) int { return int(found[i].tagid) })
634                         for i, x := range keep {
635                                 found[i] = found[x]
636                         }
637                         droppedOOO = len(found) - len(keep)
638                         log.Infof("%s %s dropping %d out-of-order tags", filelabel, job.label, droppedOOO)
639                         found = found[:len(keep)]
640                 }
641
642                 log.Infof("%s %s getting %d librefs", filelabel, job.label, len(found))
643                 throttle := &throttle{Max: runtime.NumCPU()}
644                 path = path[:len(found)]
645                 var lowquality int64
646                 for i, f := range found {
647                         i, f := i, f
648                         throttle.Acquire()
649                         go func() {
650                                 defer throttle.Release()
651                                 var startpos, endpos int
652                                 if i == 0 {
653                                         startpos = 0
654                                 } else {
655                                         startpos = f.pos
656                                 }
657                                 if i == len(found)-1 {
658                                         endpos = len(job.fasta)
659                                 } else {
660                                         endpos = found[i+1].pos + taglen
661                                 }
662                                 path[i] = tilelib.getRef(f.tagid, job.fasta[startpos:endpos], isRef)
663                                 if countBases(job.fasta[startpos:endpos]) != endpos-startpos {
664                                         atomic.AddInt64(&lowquality, 1)
665                                 }
666                         }()
667                 }
668                 throttle.Wait()
669
670                 log.Infof("%s %s copying path", filelabel, job.label)
671
672                 pathcopy := make([]tileLibRef, len(path))
673                 copy(pathcopy, path)
674                 ret[job.label] = pathcopy
675
676                 basesIn := countBases(job.fasta)
677                 log.Infof("%s %s fasta in %d coverage in %d path len %d low-quality %d", filelabel, job.label, len(job.fasta), basesIn, len(path), lowquality)
678                 stats = append(stats, importStats{
679                         InputFile:             filelabel,
680                         InputLabel:            job.label,
681                         InputLength:           len(job.fasta),
682                         InputCoverage:         basesIn,
683                         PathLength:            len(path),
684                         DroppedOutOfOrderTags: droppedOOO,
685                         DroppedRepeatedTags:   droppedDup,
686                 })
687
688                 totalPathLen += len(path)
689         }
690         log.Printf("%s tiled with total path len %d in %d sequences (skipped %d sequences that did not match chromosome regexp, skipped %d out-of-order tags)", filelabel, totalPathLen, len(ret), skippedSequences, totalFoundTags-totalPathLen)
691         return ret, stats, scanner.Err()
692 }
693
694 func (tilelib *tileLibrary) Len() int64 {
695         return atomic.LoadInt64(&tilelib.variants)
696 }
697
698 // Return a tileLibRef for a tile with the given tag and sequence,
699 // adding the sequence to the library if needed.
700 func (tilelib *tileLibrary) getRef(tag tagID, seq []byte, usedByRef bool) tileLibRef {
701         dropSeq := false
702         if !tilelib.retainNoCalls {
703                 for _, b := range seq {
704                         if b != 'a' && b != 'c' && b != 'g' && b != 't' {
705                                 dropSeq = true
706                                 break
707                         }
708                 }
709         }
710         seqhash := blake2b.Sum256(seq)
711         var vlock sync.Locker
712
713         tilelib.mtx.RLock()
714         if len(tilelib.vlock) > int(tag) {
715                 vlock = tilelib.vlock[tag]
716         }
717         tilelib.mtx.RUnlock()
718
719         if vlock != nil {
720                 vlock.Lock()
721                 for i, varhash := range tilelib.variant[tag] {
722                         if varhash == seqhash {
723                                 vlock.Unlock()
724                                 return tileLibRef{Tag: tag, Variant: tileVariantID(i + 1)}
725                         }
726                 }
727                 vlock.Unlock()
728         } else {
729                 tilelib.mtx.Lock()
730                 if tilelib.variant == nil && tilelib.taglib != nil {
731                         tilelib.variant = make([][][blake2b.Size256]byte, tilelib.taglib.Len())
732                         tilelib.vlock = make([]sync.Locker, tilelib.taglib.Len())
733                         for i := range tilelib.vlock {
734                                 tilelib.vlock[i] = new(sync.Mutex)
735                         }
736                 }
737                 if int(tag) >= len(tilelib.variant) {
738                         oldlen := len(tilelib.vlock)
739                         for i := 0; i < oldlen; i++ {
740                                 tilelib.vlock[i].Lock()
741                         }
742                         // If we haven't seen the tag library yet (as
743                         // in a merge), tilelib.taglib.Len() is
744                         // zero. We can still behave correctly, we
745                         // just need to expand the tilelib.variant and
746                         // tilelib.vlock slices as needed.
747                         if int(tag) >= cap(tilelib.variant) {
748                                 // Allocate 2x capacity.
749                                 newslice := make([][][blake2b.Size256]byte, int(tag)+1, (int(tag)+1)*2)
750                                 copy(newslice, tilelib.variant)
751                                 tilelib.variant = newslice[:int(tag)+1]
752                                 newvlock := make([]sync.Locker, int(tag)+1, (int(tag)+1)*2)
753                                 copy(newvlock, tilelib.vlock)
754                                 tilelib.vlock = newvlock[:int(tag)+1]
755                         } else {
756                                 // Use previously allocated capacity,
757                                 // avoiding copy.
758                                 tilelib.variant = tilelib.variant[:int(tag)+1]
759                                 tilelib.vlock = tilelib.vlock[:int(tag)+1]
760                         }
761                         for i := oldlen; i < len(tilelib.vlock); i++ {
762                                 tilelib.vlock[i] = new(sync.Mutex)
763                         }
764                         for i := 0; i < oldlen; i++ {
765                                 tilelib.vlock[i].Unlock()
766                         }
767                 }
768                 vlock = tilelib.vlock[tag]
769                 tilelib.mtx.Unlock()
770         }
771
772         vlock.Lock()
773         for i, varhash := range tilelib.variant[tag] {
774                 if varhash == seqhash {
775                         vlock.Unlock()
776                         return tileLibRef{Tag: tag, Variant: tileVariantID(i + 1)}
777                 }
778         }
779         atomic.AddInt64(&tilelib.variants, 1)
780         tilelib.variant[tag] = append(tilelib.variant[tag], seqhash)
781         variant := tileVariantID(len(tilelib.variant[tag]))
782         vlock.Unlock()
783
784         if tilelib.retainTileSequences && !dropSeq {
785                 seqCopy := append([]byte(nil), seq...)
786                 if tilelib.seq2 == nil {
787                         tilelib.mtx.Lock()
788                         if tilelib.seq2 == nil {
789                                 tilelib.seq2lock = map[[2]byte]sync.Locker{}
790                                 m := map[[2]byte]map[[blake2b.Size256]byte][]byte{}
791                                 var k [2]byte
792                                 for i := 0; i < 256; i++ {
793                                         k[0] = byte(i)
794                                         for j := 0; j < 256; j++ {
795                                                 k[1] = byte(j)
796                                                 m[k] = map[[blake2b.Size256]byte][]byte{}
797                                                 tilelib.seq2lock[k] = &sync.Mutex{}
798                                         }
799                                 }
800                                 tilelib.seq2 = m
801                         }
802                         tilelib.mtx.Unlock()
803                 }
804                 var k [2]byte
805                 copy(k[:], seqhash[:])
806                 locker := tilelib.seq2lock[k]
807                 locker.Lock()
808                 tilelib.seq2[k][seqhash] = seqCopy
809                 locker.Unlock()
810         }
811
812         saveSeq := seq
813         if dropSeq {
814                 // Save the hash, but not the sequence
815                 saveSeq = nil
816         }
817         if tilelib.encoder != nil {
818                 tilelib.encoder.Encode(LibraryEntry{
819                         TileVariants: []TileVariant{{
820                                 Tag:      tag,
821                                 Ref:      usedByRef,
822                                 Variant:  variant,
823                                 Blake2b:  seqhash,
824                                 Sequence: saveSeq,
825                         }},
826                 })
827         }
828         if tilelib.onAddTileVariant != nil {
829                 tilelib.onAddTileVariant(tileLibRef{tag, variant}, seqhash, saveSeq)
830         }
831         return tileLibRef{Tag: tag, Variant: variant}
832 }
833
834 func (tilelib *tileLibrary) hashSequence(hash [blake2b.Size256]byte) []byte {
835         var partition [2]byte
836         copy(partition[:], hash[:])
837         return tilelib.seq2[partition][hash]
838 }
839
840 func (tilelib *tileLibrary) TileVariantSequence(libref tileLibRef) []byte {
841         if libref.Variant == 0 || len(tilelib.variant) <= int(libref.Tag) || len(tilelib.variant[libref.Tag]) < int(libref.Variant) {
842                 return nil
843         }
844         return tilelib.hashSequence(tilelib.variant[libref.Tag][libref.Variant-1])
845 }
846
847 // Tidy deletes unreferenced tile variants and renumbers variants so
848 // more common variants have smaller IDs.
849 func (tilelib *tileLibrary) Tidy() {
850         log.Print("Tidy: compute inref")
851         inref := map[tileLibRef]bool{}
852         for _, refseq := range tilelib.refseqs {
853                 for _, librefs := range refseq {
854                         for _, libref := range librefs {
855                                 inref[libref] = true
856                         }
857                 }
858         }
859         log.Print("Tidy: compute remap")
860         remap := make([][]tileVariantID, len(tilelib.variant))
861         throttle := throttle{Max: runtime.NumCPU() + 1}
862         for tag, oldvariants := range tilelib.variant {
863                 tag, oldvariants := tagID(tag), oldvariants
864                 if tag%1000000 == 0 {
865                         log.Printf("Tidy: tag %d", tag)
866                 }
867                 throttle.Acquire()
868                 go func() {
869                         defer throttle.Release()
870                         uses := make([]int, len(oldvariants))
871                         for _, cg := range tilelib.compactGenomes {
872                                 for phase := 0; phase < 2; phase++ {
873                                         cgi := int(tag)*2 + phase
874                                         if cgi < len(cg) && cg[cgi] > 0 {
875                                                 uses[cg[cgi]-1]++
876                                         }
877                                 }
878                         }
879
880                         // Compute desired order of variants:
881                         // neworder[x] == index in oldvariants that
882                         // should move to position x.
883                         neworder := make([]int, len(oldvariants))
884                         for i := range neworder {
885                                 neworder[i] = i
886                         }
887                         sort.Slice(neworder, func(i, j int) bool {
888                                 if cmp := uses[neworder[i]] - uses[neworder[j]]; cmp != 0 {
889                                         return cmp > 0
890                                 } else {
891                                         return bytes.Compare(oldvariants[neworder[i]][:], oldvariants[neworder[j]][:]) < 0
892                                 }
893                         })
894
895                         // Replace tilelib.variant[tag] with a new
896                         // re-ordered slice of hashes, and make a
897                         // mapping from old to new variant IDs.
898                         remaptag := make([]tileVariantID, len(oldvariants)+1)
899                         newvariants := make([][blake2b.Size256]byte, 0, len(neworder))
900                         for _, oldi := range neworder {
901                                 if uses[oldi] > 0 || inref[tileLibRef{Tag: tag, Variant: tileVariantID(oldi + 1)}] {
902                                         newvariants = append(newvariants, oldvariants[oldi])
903                                         remaptag[oldi+1] = tileVariantID(len(newvariants))
904                                 }
905                         }
906                         tilelib.variant[tag] = newvariants
907                         remap[tag] = remaptag
908                 }()
909         }
910         throttle.Wait()
911
912         // Apply remap to genomes and reference sequences, so they
913         // refer to the same tile variants using the changed IDs.
914         log.Print("Tidy: apply remap")
915         var wg sync.WaitGroup
916         for _, cg := range tilelib.compactGenomes {
917                 cg := cg
918                 wg.Add(1)
919                 go func() {
920                         defer wg.Done()
921                         for idx, variant := range cg {
922                                 cg[idx] = remap[tagID(idx/2)][variant]
923                         }
924                 }()
925         }
926         for _, refcs := range tilelib.refseqs {
927                 for _, refseq := range refcs {
928                         refseq := refseq
929                         wg.Add(1)
930                         go func() {
931                                 defer wg.Done()
932                                 for i, tv := range refseq {
933                                         refseq[i].Variant = remap[tv.Tag][tv.Variant]
934                                 }
935                         }()
936                 }
937         }
938         wg.Wait()
939         log.Print("Tidy: done")
940 }
941
942 func countBases(seq []byte) int {
943         n := 0
944         for _, c := range seq {
945                 if isbase[c] {
946                         n++
947                 }
948         }
949         return n
950 }