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
|
package obidefault
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
var __no_progress_bar__ = false
|
var __no_progress_bar__ = false
|
||||||
|
|
||||||
func ProgressBar() bool {
|
func ProgressBar() bool {
|
||||||
@@ -17,3 +19,22 @@ func SetNoProgressBar(b bool) {
|
|||||||
func NoProgressBarPtr() *bool {
|
func NoProgressBarPtr() *bool {
|
||||||
return &__no_progress_bar__
|
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
|
Workers int
|
||||||
// Seed seeds the random vertex orders of the restarts.
|
// Seed seeds the random vertex orders of the restarts.
|
||||||
Seed int64
|
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.
|
// CliqueResult is the outcome of FindClique.
|
||||||
@@ -68,6 +72,9 @@ func FindClique(ctx context.Context, n int, filter NeighborFilter, target int, o
|
|||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
if len(clique) > len(best) {
|
if len(clique) > len(best) {
|
||||||
best = append(best[:0], clique...)
|
best = append(best[:0], clique...)
|
||||||
|
if opts.Progress != nil {
|
||||||
|
opts.Progress(len(best))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
mutex.Unlock()
|
mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,3 +141,23 @@ func TestFindCliqueTimeout(t *testing.T) {
|
|||||||
t.Fatalf("invalid best clique %v", r.Clique)
|
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)
|
filter := obikmer.NewKmerDissimilarityFilter(kmers, size, metric, dmin)
|
||||||
|
progress := startCliqueProgress(target)
|
||||||
result := obigraph.FindClique(ctx, len(kmers), filter, 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 {
|
switch {
|
||||||
case result.Reached:
|
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