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,