Mercurial > gemma
changeset 3367:ecb4baa2be1a
Simplified waterlevel classification.
author | Sascha L. Teichmann <sascha.teichmann@intevation.de> |
---|---|
date | Wed, 22 May 2019 10:47:04 +0200 |
parents | 8974efd71917 |
children | 70605404f37d |
files | pkg/common/time.go pkg/common/time_test.go pkg/controllers/bottlenecks.go |
diffstat | 3 files changed, 64 insertions(+), 211 deletions(-) [+] |
line wrap: on
line diff
--- a/pkg/common/time.go Wed May 22 09:33:40 2019 +0200 +++ b/pkg/common/time.go Wed May 22 10:47:04 2019 +0200 @@ -27,60 +27,9 @@ DateFormat = "2006-01-02" ) -type ValueRangeKind int - -const ( - ValueBelow ValueRangeKind = -1 - ValueInside ValueRangeKind = 0 - ValueAbove ValueRangeKind = +1 -) - var utc0 = time.Unix(0, 0) -func InterpolateValueByTime(t1 time.Time, m1 float64, t2 time.Time, m2 float64) func(time.Time) (float64, ValueRangeKind) { - - // f(t1) = m1 - // f(t2) = m2 - // m1 = t1*a + b <=> b = m1 - t1*a - // m2 = t2*a + b - // m1 - m2 = a*(t1 - t2) - // a = (m1 - m2)/(t1 - t2) for t1 != t2 - - if t1.Equal(t2) { - return func(t time.Time) (float64, ValueRangeKind) { - switch { - case t.Before(t1): - return 0, ValueBelow - case t.After(t1): - return 0, ValueAbove - default: - return m1 + (m2-m1)/2, ValueInside - } - } - } - var min, max time.Time - if t1.Before(t2) { - min, max = t1, t2 - } else { - min, max = t2, t1 - } - - a := (m1 - m2) / t1.Sub(t2).Seconds() - b := m1 - a*t1.Sub(utc0).Seconds() - - return func(t time.Time) (float64, ValueRangeKind) { - switch { - case t.Before(min): - return 0, ValueBelow - case t.After(max): - return 0, ValueAbove - default: - return a*t.Sub(utc0).Seconds() + b, ValueInside - } - } -} - -func InterpolateTimeByValue(t1 time.Time, m1 float64, t2 time.Time, m2 float64) func(float64) (time.Time, ValueRangeKind) { +func InterpolateTime(t1 time.Time, m1 float64, t2 time.Time, m2 float64) func(float64) time.Time { // f(m1) = t1 // f(m2) = t2 @@ -91,34 +40,17 @@ // a = (t1-t2)/(m1 - m2) for m1 != m2 if m1 == m2 { - return func(m float64) (time.Time, ValueRangeKind) { - switch { - case m < m1: - return time.Time{}, ValueBelow - case m > m1: - return time.Time{}, ValueAbove - default: - return t1.Add(t2.Sub(t1) / 2), ValueInside - } - } + t := t1.Add(t2.Sub(t1) / 2) + return func(float64) time.Time { return t } } - min, max := math.Min(m1, m2), math.Max(m1, m2) - a := t1.Sub(t2).Seconds() / (m1 - m2) b := t1.Sub(utc0).Seconds() - m1*a - return func(m float64) (time.Time, ValueRangeKind) { - switch { - case m < min: - return time.Time{}, ValueBelow - case m > max: - return time.Time{}, ValueAbove - default: - x := m*a + b - secs := math.Ceil(x) - nsecs := math.Ceil((x - secs) * (999999999 + 1)) - return time.Unix(int64(secs), int64(nsecs)), ValueInside - } + return func(m float64) time.Time { + x := m*a + b + secs := math.Ceil(x) + nsecs := math.Ceil((x - secs) * (999999999 + 1)) + return time.Unix(int64(secs), int64(nsecs)) } }
--- a/pkg/common/time_test.go Wed May 22 09:33:40 2019 +0200 +++ b/pkg/common/time_test.go Wed May 22 10:47:04 2019 +0200 @@ -23,11 +23,11 @@ t1 := time.Now().UTC() t2 := t1.Add(time.Hour).UTC() - f := InterpolateTimeByValue(t1, 10, t2, 20) + f := InterpolateTime(t1, 10, t2, 20) - v1, _ := f(10) - v2, _ := f(20) - v3, _ := f(15) + v1 := f(10) + v2 := f(20) + v3 := f(15) t3 := t1.Add(time.Hour / 2)
--- a/pkg/controllers/bottlenecks.go Wed May 22 09:33:40 2019 +0200 +++ b/pkg/controllers/bottlenecks.go Wed May 22 10:47:04 2019 +0200 @@ -124,130 +124,70 @@ return float64(measurement.value) } -func (measurements availMeasurements) classifyAvailMeasurements( +func (measurements availMeasurements) classify( from, to time.Time, - classes []referenceValue, + breaks []referenceValue, access func(*availMeasurement) float64, ) []time.Duration { - type classValues struct { - when time.Time - kind common.ValueRangeKind - } - - //var invalid time.Duration - result := make([]time.Duration, len(classes)+1) - - if len(measurements) == 0 || - to.Before(measurements[0].when) || - from.After(measurements[len(measurements)-1].when) { - return result + if len(breaks) == 0 { + return []time.Duration{} } - cvs := make([]classValues, len(classes)) + result := make([]time.Duration, len(breaks)+1) + classes := make([]float64, len(breaks)+2) + values := make([]time.Time, len(classes)) - classify := func(v func(float64) (time.Time, common.ValueRangeKind)) { - for i := range classes { - cvs[i].when, cvs[i].kind = v(classes[i].value) - } + // Add sentinels + classes[0] = breaks[0].value - 9999 + classes[len(classes)-1] = breaks[len(breaks)-1].value + 9999 + for i := range breaks { + classes[i+1] = breaks[i].value } - vbt := func(p1, p2 *availMeasurement) func(time.Time) (float64, common.ValueRangeKind) { - return common.InterpolateValueByTime(p1.when, access(p1), p2.when, access(p2)) - } - - // var total time.Duration - -pairs: for i := 0; i < len(measurements)-1; i++ { p1 := &measurements[i] p2 := &measurements[i+1] - var start, end time.Time - - switch { - case !p2.when.After(p1.when): - // Segment invalid - // log.Println("invalid") - continue pairs + if p1.when.After(to) { + return result + } - case p1.when.After(to) || p2.when.Before(from): - // Segment complete outside. - // log.Println("complete outside") - continue pairs - - case (p1.when.Before(from) || p1.when.Equal(from)) && (to.Before(p2.when) || to.Equal(p2.when)): - // log.Println("(from, to) complete inside current interval") - v := vbt(p1, p2) - f, _ := v(from) - t, _ := v(to) - classify(common.InterpolateTimeByValue(from, f, to, t)) - start, end = from, to + if p2.when.Before(from) { + continue + } - case (from.Before(p1.when) || from.Equal(p1.when)) && (p2.when.Before(to) || p2.when.Equal(to)): - //log.Println("current interval complete inside (from, to)") - classify(common.InterpolateTimeByValue( - p1.when, access(p1), - p2.when, access(p2), - )) - start, end = p1.when, p2.when + f := common.InterpolateTime( + p1.when, access(p1), + p2.when, access(p2), + ) - case p1.when.After(from): - // log.Println("p1 > from") - pt := minTime(to, p2.when) - t, _ := vbt(p1, p2)(pt) - classify(common.InterpolateTimeByValue( - p1.when, access(p1), - pt, t, - )) - start, end = p1.when, pt - - case p2.when.Before(to): - // log.Println("p2 < to") - pf := maxTime(from, p1.when) - f, _ := vbt(p1, p2)(pf) - classify(common.InterpolateTimeByValue( - pf, f, - p2.when, access(p2), - )) - start, end = pf, p2.when - - default: - log.Printf("warn: unexpected case. That should not happen. %v - %v, %v - %v\n", - p1.when, p2.when, from, to) - continue pairs + for j, c := range classes { + values[j] = f(c) } - // total += end.Sub(start) + for j := 0; j < len(values)-1; j++ { + start, end := orderTime(values[j], values[j+1]) - for i := len(cvs) - 1; i >= 0; i-- { - switch cvs[i].kind { - case common.ValueAbove: - result[i+1] += end.Sub(start) - continue pairs + lo, hi := maxTime(p1.when, from), minTime(p2.when, to) - case common.ValueInside: - // -> split - if access(p1) < classes[i].value { - // started below -> second part above - diff := absDuration(end.Sub(cvs[i].when)) - result[i+1] += diff - end = cvs[i].when - } else { - // started above -> first part above - diff := absDuration(cvs[i].when.Sub(start)) - result[i+1] += diff - start = cvs[i].when - } + if start.After(hi) || end.Before(lo) { + continue } + + start, end = maxTime(start, lo), minTime(end, hi) + result[j] += end.Sub(start) } - result[0] += end.Sub(start) } - // log.Printf("total all: %f\n", to.Sub(from).Hours()) - // log.Printf("total: %f\n", total.Hours()) + return result +} - return result +func orderTime(a, b time.Time) (time.Time, time.Time) { + if a.Before(b) { + return a, b + } + return b, a } func minTime(a, b time.Time) time.Time { @@ -264,14 +204,6 @@ return b } -func absDuration(x time.Duration) time.Duration { - if x < 0 { - log.Printf("warn: negative duration %v\n", x) - return -x - } - return x -} - func durationsToPercentage(from, to time.Time, classes []time.Duration) []float64 { percents := make([]float64, len(classes)) total := 100 / to.Sub(from).Seconds() @@ -353,6 +285,7 @@ case err != nil: return nil, err } + log.Printf("info: LDC = %.2f\n", value) return []referenceValue{{0, value}}, nil } @@ -486,13 +419,13 @@ return } - lnwl := ms.classifyAvailMeasurements( + lnwl := ms.classify( from, to, lnwlRefs, (*availMeasurement).getValue, ) - afd := ms.classifyAvailMeasurements( + afd := ms.classify( from, to, afdRefs, (*availMeasurement).getDepth, @@ -667,41 +600,29 @@ return } - // log.Println(len(ms)) + //log.Println(len(ms)) //for i := range ms { // log.Println(ms[i].when, ms[i].depth) //} - interval := intervals[mode](from, to) + log.Printf("info: measurements: %d\n", len(ms)) + if len(ms) > 1 { + log.Printf("info: first: %v\n", ms[0].when) + log.Printf("info: last: %v\n", ms[len(ms)-1].when) + log.Printf("info: interval: %.2f [h]\n", ms[len(ms)-1].when.Sub(ms[0].when).Hours()) + } - //log.Printf("first: %v\n", ms[0].when) - //log.Printf("last: %v\n", ms[len(ms)-1].when) + interval := intervals[mode](from, to) for pfrom, pto, label := interval(); label != ""; pfrom, pto, label = interval() { - samples := ms - - /* - //log.Printf("pfrom: %v\n", pfrom) - - // Find good starting point - idx := sort.Search(len(ms), func(i int) bool { - return !ms[i].when.Before(pfrom) - }) - //log.Printf("%d\n", idx) - if idx > 0 { - idx-- - } - samples := ms[idx:] - */ - - ranges := samples.classifyAvailMeasurements( + ranges := ms.classify( pfrom, pto, afdRefs, (*availMeasurement).getDepth, ) - ldc := samples.classifyAvailMeasurements( + ldc := ms.classify( pfrom, pto, ldcRefs, (*availMeasurement).getDepth,