19073: Fix dup tag detection.
[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 {
576                                 fasta = append(fasta, bytes.ToLower(buf)...)
577                         }
578                 }
579                 todo <- jobT{seqlabel, fasta}
580         }()
581         type foundtag struct {
582                 pos   int
583                 tagid tagID
584         }
585         found := make([]foundtag, 2000000)
586         path := make([]tileLibRef, 2000000)
587         totalFoundTags := 0
588         totalPathLen := 0
589         skippedSequences := 0
590         taglen := tilelib.taglib.TagLen()
591         var stats []importStats
592         for job := range todo {
593                 if len(job.fasta) == 0 {
594                         continue
595                 } else if !matchChromosome.MatchString(job.label) {
596                         skippedSequences++
597                         continue
598                 }
599                 log.Debugf("%s %s tiling", filelabel, job.label)
600
601                 found = found[:0]
602                 tilelib.taglib.FindAll(job.fasta, func(tagid tagID, pos, taglen int) {
603                         found = append(found, foundtag{pos: pos, tagid: tagid})
604                 })
605                 totalFoundTags += len(found)
606                 if len(found) == 0 {
607                         log.Warnf("%s %s no tags found", filelabel, job.label)
608                 }
609
610                 droppedDup := 0
611                 if !tilelib.useDups {
612                         // Remove any tags that appeared more than once
613                         dup := map[tagID]bool{}
614                         for _, ft := range found {
615                                 _, dup[ft.tagid] = dup[ft.tagid]
616                         }
617                         dst := 0
618                         for _, ft := range found {
619                                 if !dup[ft.tagid] {
620                                         found[dst] = ft
621                                         dst++
622                                 }
623                         }
624                         droppedDup = len(found) - dst
625                         log.Infof("%s %s dropping %d non-unique tags", filelabel, job.label, droppedDup)
626                         found = found[:dst]
627                 }
628
629                 droppedOOO := 0
630                 if tilelib.skipOOO {
631                         keep := longestIncreasingSubsequence(len(found), func(i int) int { return int(found[i].tagid) })
632                         for i, x := range keep {
633                                 found[i] = found[x]
634                         }
635                         droppedOOO = len(found) - len(keep)
636                         log.Infof("%s %s dropping %d out-of-order tags", filelabel, job.label, droppedOOO)
637                         found = found[:len(keep)]
638                 }
639
640                 log.Infof("%s %s getting %d librefs", filelabel, job.label, len(found))
641                 throttle := &throttle{Max: runtime.NumCPU()}
642                 path = path[:len(found)]
643                 var lowquality int64
644                 for i, f := range found {
645                         i, f := i, f
646                         throttle.Acquire()
647                         go func() {
648                                 defer throttle.Release()
649                                 var startpos, endpos int
650                                 if i == 0 {
651                                         startpos = 0
652                                 } else {
653                                         startpos = f.pos
654                                 }
655                                 if i == len(found)-1 {
656                                         endpos = len(job.fasta)
657                                 } else {
658                                         endpos = found[i+1].pos + taglen
659                                 }
660                                 path[i] = tilelib.getRef(f.tagid, job.fasta[startpos:endpos], isRef)
661                                 if countBases(job.fasta[startpos:endpos]) != endpos-startpos {
662                                         atomic.AddInt64(&lowquality, 1)
663                                 }
664                         }()
665                 }
666                 throttle.Wait()
667
668                 log.Infof("%s %s copying path", filelabel, job.label)
669
670                 pathcopy := make([]tileLibRef, len(path))
671                 copy(pathcopy, path)
672                 ret[job.label] = pathcopy
673
674                 basesIn := countBases(job.fasta)
675                 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)
676                 stats = append(stats, importStats{
677                         InputFile:             filelabel,
678                         InputLabel:            job.label,
679                         InputLength:           len(job.fasta),
680                         InputCoverage:         basesIn,
681                         PathLength:            len(path),
682                         DroppedOutOfOrderTags: droppedOOO,
683                         DroppedRepeatedTags:   droppedDup,
684                 })
685
686                 totalPathLen += len(path)
687         }
688         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)
689         return ret, stats, scanner.Err()
690 }
691
692 func (tilelib *tileLibrary) Len() int64 {
693         return atomic.LoadInt64(&tilelib.variants)
694 }
695
696 // Return a tileLibRef for a tile with the given tag and sequence,
697 // adding the sequence to the library if needed.
698 func (tilelib *tileLibrary) getRef(tag tagID, seq []byte, usedByRef bool) tileLibRef {
699         dropSeq := false
700         if !tilelib.retainNoCalls {
701                 for _, b := range seq {
702                         if b != 'a' && b != 'c' && b != 'g' && b != 't' {
703                                 dropSeq = true
704                                 break
705                         }
706                 }
707         }
708         seqhash := blake2b.Sum256(seq)
709         var vlock sync.Locker
710
711         tilelib.mtx.RLock()
712         if len(tilelib.vlock) > int(tag) {
713                 vlock = tilelib.vlock[tag]
714         }
715         tilelib.mtx.RUnlock()
716
717         if vlock != nil {
718                 vlock.Lock()
719                 for i, varhash := range tilelib.variant[tag] {
720                         if varhash == seqhash {
721                                 vlock.Unlock()
722                                 return tileLibRef{Tag: tag, Variant: tileVariantID(i + 1)}
723                         }
724                 }
725                 vlock.Unlock()
726         } else {
727                 tilelib.mtx.Lock()
728                 if tilelib.variant == nil && tilelib.taglib != nil {
729                         tilelib.variant = make([][][blake2b.Size256]byte, tilelib.taglib.Len())
730                         tilelib.vlock = make([]sync.Locker, tilelib.taglib.Len())
731                         for i := range tilelib.vlock {
732                                 tilelib.vlock[i] = new(sync.Mutex)
733                         }
734                 }
735                 if int(tag) >= len(tilelib.variant) {
736                         oldlen := len(tilelib.vlock)
737                         for i := 0; i < oldlen; i++ {
738                                 tilelib.vlock[i].Lock()
739                         }
740                         // If we haven't seen the tag library yet (as
741                         // in a merge), tilelib.taglib.Len() is
742                         // zero. We can still behave correctly, we
743                         // just need to expand the tilelib.variant and
744                         // tilelib.vlock slices as needed.
745                         if int(tag) >= cap(tilelib.variant) {
746                                 // Allocate 2x capacity.
747                                 newslice := make([][][blake2b.Size256]byte, int(tag)+1, (int(tag)+1)*2)
748                                 copy(newslice, tilelib.variant)
749                                 tilelib.variant = newslice[:int(tag)+1]
750                                 newvlock := make([]sync.Locker, int(tag)+1, (int(tag)+1)*2)
751                                 copy(newvlock, tilelib.vlock)
752                                 tilelib.vlock = newvlock[:int(tag)+1]
753                         } else {
754                                 // Use previously allocated capacity,
755                                 // avoiding copy.
756                                 tilelib.variant = tilelib.variant[:int(tag)+1]
757                                 tilelib.vlock = tilelib.vlock[:int(tag)+1]
758                         }
759                         for i := oldlen; i < len(tilelib.vlock); i++ {
760                                 tilelib.vlock[i] = new(sync.Mutex)
761                         }
762                         for i := 0; i < oldlen; i++ {
763                                 tilelib.vlock[i].Unlock()
764                         }
765                 }
766                 vlock = tilelib.vlock[tag]
767                 tilelib.mtx.Unlock()
768         }
769
770         vlock.Lock()
771         for i, varhash := range tilelib.variant[tag] {
772                 if varhash == seqhash {
773                         vlock.Unlock()
774                         return tileLibRef{Tag: tag, Variant: tileVariantID(i + 1)}
775                 }
776         }
777         atomic.AddInt64(&tilelib.variants, 1)
778         tilelib.variant[tag] = append(tilelib.variant[tag], seqhash)
779         variant := tileVariantID(len(tilelib.variant[tag]))
780         vlock.Unlock()
781
782         if tilelib.retainTileSequences && !dropSeq {
783                 seqCopy := append([]byte(nil), seq...)
784                 if tilelib.seq2 == nil {
785                         tilelib.mtx.Lock()
786                         if tilelib.seq2 == nil {
787                                 tilelib.seq2lock = map[[2]byte]sync.Locker{}
788                                 m := map[[2]byte]map[[blake2b.Size256]byte][]byte{}
789                                 var k [2]byte
790                                 for i := 0; i < 256; i++ {
791                                         k[0] = byte(i)
792                                         for j := 0; j < 256; j++ {
793                                                 k[1] = byte(j)
794                                                 m[k] = map[[blake2b.Size256]byte][]byte{}
795                                                 tilelib.seq2lock[k] = &sync.Mutex{}
796                                         }
797                                 }
798                                 tilelib.seq2 = m
799                         }
800                         tilelib.mtx.Unlock()
801                 }
802                 var k [2]byte
803                 copy(k[:], seqhash[:])
804                 locker := tilelib.seq2lock[k]
805                 locker.Lock()
806                 tilelib.seq2[k][seqhash] = seqCopy
807                 locker.Unlock()
808         }
809
810         saveSeq := seq
811         if dropSeq {
812                 // Save the hash, but not the sequence
813                 saveSeq = nil
814         }
815         if tilelib.encoder != nil {
816                 tilelib.encoder.Encode(LibraryEntry{
817                         TileVariants: []TileVariant{{
818                                 Tag:      tag,
819                                 Ref:      usedByRef,
820                                 Variant:  variant,
821                                 Blake2b:  seqhash,
822                                 Sequence: saveSeq,
823                         }},
824                 })
825         }
826         if tilelib.onAddTileVariant != nil {
827                 tilelib.onAddTileVariant(tileLibRef{tag, variant}, seqhash, saveSeq)
828         }
829         return tileLibRef{Tag: tag, Variant: variant}
830 }
831
832 func (tilelib *tileLibrary) hashSequence(hash [blake2b.Size256]byte) []byte {
833         var partition [2]byte
834         copy(partition[:], hash[:])
835         return tilelib.seq2[partition][hash]
836 }
837
838 func (tilelib *tileLibrary) TileVariantSequence(libref tileLibRef) []byte {
839         if libref.Variant == 0 || len(tilelib.variant) <= int(libref.Tag) || len(tilelib.variant[libref.Tag]) < int(libref.Variant) {
840                 return nil
841         }
842         return tilelib.hashSequence(tilelib.variant[libref.Tag][libref.Variant-1])
843 }
844
845 // Tidy deletes unreferenced tile variants and renumbers variants so
846 // more common variants have smaller IDs.
847 func (tilelib *tileLibrary) Tidy() {
848         log.Print("Tidy: compute inref")
849         inref := map[tileLibRef]bool{}
850         for _, refseq := range tilelib.refseqs {
851                 for _, librefs := range refseq {
852                         for _, libref := range librefs {
853                                 inref[libref] = true
854                         }
855                 }
856         }
857         log.Print("Tidy: compute remap")
858         remap := make([][]tileVariantID, len(tilelib.variant))
859         throttle := throttle{Max: runtime.NumCPU() + 1}
860         for tag, oldvariants := range tilelib.variant {
861                 tag, oldvariants := tagID(tag), oldvariants
862                 if tag%1000000 == 0 {
863                         log.Printf("Tidy: tag %d", tag)
864                 }
865                 throttle.Acquire()
866                 go func() {
867                         defer throttle.Release()
868                         uses := make([]int, len(oldvariants))
869                         for _, cg := range tilelib.compactGenomes {
870                                 for phase := 0; phase < 2; phase++ {
871                                         cgi := int(tag)*2 + phase
872                                         if cgi < len(cg) && cg[cgi] > 0 {
873                                                 uses[cg[cgi]-1]++
874                                         }
875                                 }
876                         }
877
878                         // Compute desired order of variants:
879                         // neworder[x] == index in oldvariants that
880                         // should move to position x.
881                         neworder := make([]int, len(oldvariants))
882                         for i := range neworder {
883                                 neworder[i] = i
884                         }
885                         sort.Slice(neworder, func(i, j int) bool {
886                                 if cmp := uses[neworder[i]] - uses[neworder[j]]; cmp != 0 {
887                                         return cmp > 0
888                                 } else {
889                                         return bytes.Compare(oldvariants[neworder[i]][:], oldvariants[neworder[j]][:]) < 0
890                                 }
891                         })
892
893                         // Replace tilelib.variant[tag] with a new
894                         // re-ordered slice of hashes, and make a
895                         // mapping from old to new variant IDs.
896                         remaptag := make([]tileVariantID, len(oldvariants)+1)
897                         newvariants := make([][blake2b.Size256]byte, 0, len(neworder))
898                         for _, oldi := range neworder {
899                                 if uses[oldi] > 0 || inref[tileLibRef{Tag: tag, Variant: tileVariantID(oldi + 1)}] {
900                                         newvariants = append(newvariants, oldvariants[oldi])
901                                         remaptag[oldi+1] = tileVariantID(len(newvariants))
902                                 }
903                         }
904                         tilelib.variant[tag] = newvariants
905                         remap[tag] = remaptag
906                 }()
907         }
908         throttle.Wait()
909
910         // Apply remap to genomes and reference sequences, so they
911         // refer to the same tile variants using the changed IDs.
912         log.Print("Tidy: apply remap")
913         var wg sync.WaitGroup
914         for _, cg := range tilelib.compactGenomes {
915                 cg := cg
916                 wg.Add(1)
917                 go func() {
918                         defer wg.Done()
919                         for idx, variant := range cg {
920                                 cg[idx] = remap[tagID(idx/2)][variant]
921                         }
922                 }()
923         }
924         for _, refcs := range tilelib.refseqs {
925                 for _, refseq := range refcs {
926                         refseq := refseq
927                         wg.Add(1)
928                         go func() {
929                                 defer wg.Done()
930                                 for i, tv := range refseq {
931                                         refseq[i].Variant = remap[tv.Tag][tv.Variant]
932                                 }
933                         }()
934                 }
935         }
936         wg.Wait()
937         log.Print("Tidy: done")
938 }
939
940 func countBases(seq []byte) int {
941         n := 0
942         for _, c := range seq {
943                 if isbase[c] {
944                         n++
945                 }
946         }
947         return n
948 }