mirror of
https://github.com/metabarcoding/obitools4.git
synced 2026-11-09 09:24:41 +00:00
Implement progress tracking for clique finding
Introduces a mechanism for tracking progress during clique finding operations. This includes implementing a progress bar state management system, handling visibility checks, and integrating progress reporting into the clique finding logic and result processing.
This commit is contained in:
1 parent
db2b398dd0
commit
1d692d7e0b
5 files changed
+141
-1
No files matched your search
@@ -1,5 +1,7 @@
|
||||
package obidefault
|
||||
|
||||
import "os"
|
||||
|
||||
var __no_progress_bar__ = false
|
||||
|
||||
func ProgressBar() bool {
|
||||
@@ -17,3 +19,22 @@ func SetNoProgressBar(b bool) {
|
||||
func NoProgressBarPtr() *bool {
|
||||
return &__no_progress_bar__
|
||||
}
|
||||
|
||||
// ProgressBarVisible tells whether a progress bar can be displayed: it is not
|
||||
// disabled by --no-progressbar, the standard error output is a terminal and
|
||||
// the standard output is not piped to another command.
|
||||
func ProgressBarVisible() bool {
|
||||
if !ProgressBar() {
|
||||
return false
|
||||
}
|
||||
|
||||
if e, err := os.Stderr.Stat(); err != nil || (e.Mode()&os.ModeCharDevice) != os.ModeCharDevice {
|
||||
return false
|
||||
}
|
||||
|
||||
if o, err := os.Stdout.Stat(); err == nil && (o.Mode()&os.ModeNamedPipe) == os.ModeNamedPipe {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -21,6 +21,10 @@ type CliqueOptions struct {
|
||||
Workers int
|
||||
// Seed seeds the random vertex orders of the restarts.
|
||||
Seed int64
|
||||
// Progress, if set, is called with the size of the best clique each time a
|
||||
// larger one is found. Calls are serialized and made while a lock is held:
|
||||
// it must return quickly and must not call FindClique.
|
||||
Progress func(best int)
|
||||
}
|
||||
|
||||
// CliqueResult is the outcome of FindClique.
|
||||
@@ -68,6 +72,9 @@ func FindClique(ctx context.Context, n int, filter NeighborFilter, target int, o
|
||||
mutex.Lock()
|
||||
if len(clique) > len(best) {
|
||||
best = append(best[:0], clique...)
|
||||
if opts.Progress != nil {
|
||||
opts.Progress(len(best))
|
||||
}
|
||||
}
|
||||
mutex.Unlock()
|
||||
}
|
||||
|
||||
@@ -141,3 +141,23 @@ func TestFindCliqueTimeout(t *testing.T) {
|
||||
t.Fatalf("invalid best clique %v", r.Clique)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindCliqueProgress(t *testing.T) {
|
||||
adj := randomGraph(rand.New(rand.NewSource(4)), 40, 0.7)
|
||||
|
||||
var seen []int
|
||||
r := FindClique(context.Background(), 40, matrixFilter(adj), 5, CliqueOptions{
|
||||
Progress: func(best int) { seen = append(seen, best) },
|
||||
})
|
||||
if !r.Reached {
|
||||
t.Fatalf("size 5 should be reached: %+v", r)
|
||||
}
|
||||
if len(seen) == 0 || seen[len(seen)-1] != len(r.Clique) {
|
||||
t.Fatalf("progress %v does not end at the clique size %d", seen, len(r.Clique))
|
||||
}
|
||||
for i := 1; i < len(seen); i++ {
|
||||
if seen[i] <= seen[i-1] {
|
||||
t.Fatalf("progress must be strictly increasing: %v", seen)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,8 +163,14 @@ func CLIOligoFamily() ([]string, error) {
|
||||
}
|
||||
|
||||
filter := obikmer.NewKmerDissimilarityFilter(kmers, size, metric, dmin)
|
||||
progress := startCliqueProgress(target)
|
||||
result := obigraph.FindClique(ctx, len(kmers), filter, target,
|
||||
obigraph.CliqueOptions{Workers: obidefault.ParallelWorkers(), Seed: 1})
|
||||
obigraph.CliqueOptions{
|
||||
Workers: obidefault.ParallelWorkers(),
|
||||
Seed: 1,
|
||||
Progress: progress.Update,
|
||||
})
|
||||
progress.Stop(result.Reached)
|
||||
|
||||
switch {
|
||||
case result.Reached:
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package obioligotag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/schollz/progressbar/v3"
|
||||
|
||||
"git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obidefault"
|
||||
)
|
||||
|
||||
// cliqueProgress displays the progress of the search of a family: the bar goes
|
||||
// from 0 to the required family size and follows the size of the best family
|
||||
// found so far. A nil *cliqueProgress does nothing, which is what is used when
|
||||
// no progress bar has to be displayed.
|
||||
type cliqueProgress struct {
|
||||
bar *progressbar.ProgressBar
|
||||
max int
|
||||
stop chan struct{}
|
||||
done sync.WaitGroup
|
||||
}
|
||||
|
||||
// startCliqueProgress starts the progress bar of a search for a family of
|
||||
// target oligonucleotides, or returns nil if no bar can be displayed.
|
||||
func startCliqueProgress(target int) *cliqueProgress {
|
||||
if !obidefault.ProgressBarVisible() {
|
||||
return nil
|
||||
}
|
||||
|
||||
p := &cliqueProgress{
|
||||
max: target,
|
||||
stop: make(chan struct{}),
|
||||
bar: progressbar.NewOptions(target,
|
||||
progressbar.OptionSetWriter(os.Stderr),
|
||||
progressbar.OptionSetWidth(15),
|
||||
progressbar.OptionShowCount(),
|
||||
progressbar.OptionSetElapsedTime(true),
|
||||
progressbar.OptionSetPredictTime(false),
|
||||
progressbar.OptionThrottle(100*time.Millisecond),
|
||||
progressbar.OptionSetDescription("[Searching a family of oligonucleotides]"),
|
||||
),
|
||||
}
|
||||
|
||||
// The search may stay a long time on the same family: keep the elapsed
|
||||
// time alive.
|
||||
p.done.Add(1)
|
||||
go func() {
|
||||
defer p.done.Done()
|
||||
ticker := time.NewTicker(500 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
p.bar.Add(0)
|
||||
case <-p.stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// Update reports the size of the best family found so far.
|
||||
func (p *cliqueProgress) Update(best int) {
|
||||
if p != nil {
|
||||
p.bar.Set(min(best, p.max))
|
||||
}
|
||||
}
|
||||
|
||||
// Stop ends the display. The bar is filled only if the family was found.
|
||||
func (p *cliqueProgress) Stop(reached bool) {
|
||||
if p == nil {
|
||||
return
|
||||
}
|
||||
close(p.stop)
|
||||
p.done.Wait()
|
||||
if reached {
|
||||
p.bar.Finish()
|
||||
} else {
|
||||
p.bar.Exit()
|
||||
}
|
||||
fmt.Fprintln(os.Stderr)
|
||||
}
|
||||
Reference in new issue
Block a user