Mercurial > gemma
changeset 3027:84e6577a474b
Fairway availability: More robust time and value interpolations including corner cases. Still TODO: Distribute to classes.
author | Sascha L. Teichmann <sascha.teichmann@intevation.de> |
---|---|
date | Fri, 12 Apr 2019 12:11:40 +0200 |
parents | 619f03a0062a |
children | 188fb0133e50 |
files | pkg/common/time.go pkg/controllers/bottlenecks.go |
diffstat | 2 files changed, 155 insertions(+), 54 deletions(-) [+] |
line wrap: on
line diff
--- a/pkg/common/time.go Fri Apr 12 11:49:08 2019 +0200 +++ b/pkg/common/time.go Fri Apr 12 12:11:40 2019 +0200 @@ -14,7 +14,10 @@ package common -import "time" +import ( + "math" + "time" +) const ( // time.RFC3339 equals "simplified ISO format as defined by ECMA-262" @@ -23,3 +26,94 @@ TimeFormat = time.RFC3339 DateFormat = "2006-01-02" ) + +type ValueRangeKind int + +const ( + ValueBelow ValueRangeKind = -1 + ValueInside ValueRangeKind = 0 + ValueAbove ValueRangeKind = +1 +) + +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 == 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*float64(t1.Unix()) + + 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*float64(t.Unix()) + b, ValueInside + } + } +} + +func InterpolateTimeByValue(t1 time.Time, m1 float64, t2 time.Time, m2 float64) func(float64) (time.Time, ValueRangeKind) { + + // f(m1) = t1 + // f(m2) = t2 + // t1 = m1*a + b <=> b = t1 - m1*a + // t2 = m2*a + b + + // t1 - t2 = a*(m1 - m2) + // 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 + } + } + } + + min, max := math.Min(m1, m2), math.Max(m1, m2) + + a := t1.Sub(t2).Seconds() / (m1 - m2) + b := float64(t1.Unix()) - 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: + return time.Unix(int64(math.Ceil(m*a+b)), 0), ValueInside + } + } +}
--- a/pkg/controllers/bottlenecks.go Fri Apr 12 11:49:08 2019 +0200 +++ b/pkg/controllers/bottlenecks.go Fri Apr 12 12:11:40 2019 +0200 @@ -107,79 +107,86 @@ return b } -func interpolate( - m1, m2 *availMeasurement, - diff time.Duration, -) int { - tdiff := m2.when.Sub(m1.when) - - // f(0) = m1.value - // f(tdiff) = m2.value - // f(x) = m*x + b - // m1.value = m*0 + b <=> b = m1.value - // m2.value = m*tdiff + b <=> m = (m2.value - b)/tdiff - // f(diff) = diff*(m2.value - m1.value)/tdiff + m1.value - - return int(diff*time.Duration(m2.value-m1.value)/tdiff) + m1.value -} - func classifyAvailMeasurements( from, to time.Time, measurements []availMeasurement, classes []availReferenceValue, ) []time.Duration { - results := make([]time.Duration, len(classes)+2) - - if from.Before(measurements[0].when) { - results[len(results)-1] = measurements[0].when.Sub(from) - from = measurements[0].when + type classValues struct { + when time.Time + kind common.ValueRangeKind } - if to.After(measurements[len(measurements)-1].when) { - results[len(results)-1] += to.Sub(measurements[len(measurements)-1].when) - to = measurements[len(measurements)-1].when + var invalid time.Duration + + cvs := make([]classValues, len(classes)) + + classify := func(v func(float64) (time.Time, common.ValueRangeKind)) { + for j := range classes { + cvs[j].when, cvs[j].kind = v(float64(classes[j].value)) + } } + valInt := func(p1, p2 *availMeasurement) func(time.Time) (float64, common.ValueRangeKind) { + return common.InterpolateValueByTime( + p1.when, float64(p1.value), + p2.when, float64(p2.value), + ) + } + +pairs: for i := 0; i < len(measurements)-1; i++ { p1 := &measurements[i] p2 := &measurements[i+1] - tdiff := p2.when.Sub(p1.when) - if tdiff <= 0 { - continue - } + + switch { + case !p2.when.After(p2.when): + // Segment invalid + continue pairs - if from.After(p2.when) || to.Before(p1.when) { - continue - } + case p1.when.After(to) || p2.when.Before(from): + // Segment complete outside. + continue pairs - if from.After(p1.when) { - tdiff2 := from.Sub(p1.when) - vf := interpolate(p1, p2, tdiff2) - p1 = &availMeasurement{when: from, value: vf} - tdiff = p2.when.Sub(from) - } + case p1.when.After(from) && p2.when.Before(to): + // (from-to) is complete inside segment. + invalid += p1.when.Sub(from) + invalid += to.Sub(p2.when) + v := valInt(p1, p2) + f, _ := v(from) + t, _ := v(to) + classify(common.InterpolateTimeByValue(from, f, to, t)) - if to.Before(p2.when) { - tdiff2 := p2.when.Sub(to) - vt := interpolate(p1, p2, tdiff2) - p2 = &availMeasurement{when: to, value: vt} - tdiff = p2.when.Sub(p1.when) - } + case p1.when.After(from): + // from is inside segment + invalid += p1.when.Sub(from) + f, _ := valInt(p1, p2)(from) + classify(common.InterpolateTimeByValue( + from, f, + p2.when, float64(p2.value), + )) - if max(p1.value, p2.value) <= classes[0].value { - results[0] += tdiff - continue + case p2.when.Before(to): + // to is inside segment + invalid += to.Sub(p2.when) + t, _ := valInt(p1, p2)(to) + classify(common.InterpolateTimeByValue( + p1.when, float64(p1.value), + to, t, + )) + + case !p1.when.Before(from) && !to.After(p2.when): + // Segment complete inside. + classify(common.InterpolateTimeByValue( + p1.when, float64(p1.value), + p2.when, float64(p2.value), + )) } - if min(p1.value, p2.value) > classes[len(classes)-1].value { - results[len(results)-2] += tdiff - continue - } - - // TODO: Do the real classes. + // TODO: Distribute to classes. } - return results + return nil } func bottleneckAvailabilty(