diff pkg/controllers/bottlenecks.go @ 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 7c301ff449bc
children 7f12a87c56ff
line wrap: on
line diff
--- 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(