Skip to content

Commit

Permalink
test pass for field summary comparison to new aggregation for #75 #78
Browse files Browse the repository at this point in the history
  • Loading branch information
roznet committed Feb 21, 2021
1 parent 3b37aae commit 0965b96
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 67 deletions.
50 changes: 20 additions & 30 deletions ConnectStats/src/GCHistoryFieldDataHolder.m
Original file line number Diff line number Diff line change
Expand Up @@ -177,43 +177,33 @@ -(void)addNumberWithUnit:(GCNumberWithUnit*)num{
[self addNumberWithUnit:num withTimeWeight:1.0 distWeight:1.0 for:gcHistoryStatsAll];
}
-(void)addNumberWithUnit:(GCNumberWithUnit*)num withTimeWeight:(double)tw distWeight:(double)dw for:(gcHistoryStats)which{
if ([num.unit isEqualToUnit:self.unit]) {
if (!isinf(num.value)) {
self.sum[which] += num.value;
self.max[which] = MAX(self.max[which], num.value);
self.min[which] = MIN(self.min[which], num.value);
self.timewsum[which] += num.value * tw;
self.distwsum[which] += num.value * dw;
}
}else{
[self convertToUnit:num.unit];
double val = [num convertToUnit:self.unit].value;
if (!isinf(val)) {
self.sum[which] += val;
self.max[which] = MAX(self.max[which], val);
self.min[which] = MIN(self.min[which], val);
self.timewsum[which] = val * tw;
self.distwsum[which] = val * dw;
}
if( self.unit == nil ){
self.unit = num.unit.referenceUnit ?: num.unit;
}
GCNumberWithUnit * cnum = [num convertToUnit:self.unit];

if (!isinf(cnum.value)) {
self.sum[which] += cnum.value;
self.max[which] = MAX(self.max[which], cnum.value);
self.min[which] = MIN(self.min[which], cnum.value);
self.timewsum[which] += cnum.value * tw;
self.distwsum[which] += cnum.value * dw;
}

self.count[which] +=1.;
self.timeweight[which]+=tw;
self.distweight[which]+=dw;
}
-(void)addSumWithUnit:(GCNumberWithUnit*)num andCount:(NSUInteger)count for:(gcHistoryStats)which{
if ([num.unit isEqualToUnit:self.unit]) {
self.sum[which] += num.value;
self.max[which] = MAX(self.max[which], num.value);
self.min[which] = MIN(self.min[which], num.value);
}else{
[self convertToUnit:num.unit];
double val = [num convertToUnit:self.unit].value;
self.sum[which] += val;
self.max[which] = MAX(self.max[which], val);
self.min[which] = MIN(self.min[which], val);

if( self.unit == nil ){
self.unit = num.unit.referenceUnit ?: num.unit;
}
self.count[which] +=count;
GCNumberWithUnit * cnum = [num convertToUnit:self.unit];

self.sum[which] += cnum.value;
self.max[which] = MAX(self.max[which], cnum.value);
self.min[which] = MIN(self.min[which], cnum.value);
self.count[which] += count;
}

@end
5 changes: 5 additions & 0 deletions ConnectStats/src/GCHistoryFieldSummaryStats.m
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ +(GCHistoryFieldSummaryStats*)fieldStatsWithActivities:(NSArray<GCActivity*>*)ac
}
GCNumberWithUnit * nu = [act numberWithUnitForField:field];
if (nu) {
if( nu.value == 0.0 && !field.isZeroValid){
continue;
}

// weight is either duration (everything) or dist (for pace = invlinear)
double timeweight = [act summaryFieldValueInStoreUnit:gcFieldFlagSumDuration];
double distweight = [act summaryFieldValueInStoreUnit:gcFieldFlagSumDistance];
Expand All @@ -90,6 +94,7 @@ +(GCHistoryFieldSummaryStats*)fieldStatsWithActivities:(NSArray<GCActivity*>*)ac
[holderAll addNumberWithUnit:nu withTimeWeight:timeweight distWeight:distweight for:gcHistoryStatsAll];

if (weekBucket==nil) {

weekBucket = [GCStatsDateBuckets statsDateBucketFor:NSCalendarUnitWeekOfYear
referenceDate:refOrNil
andCalendar:[GCAppGlobal calculationCalendar]];
Expand Down
34 changes: 24 additions & 10 deletions ConnectStats/src/GCHistoryStatistics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ class SummaryStatistics {
let cnt = Double( self.cnt )
return ((cnt*self.ssq-self.sum*self.sum)/(cnt*(cnt-1))).squareRoot()
}
var avg : Double {
var avg : Double? {
guard self.cnt != 0 else { return nil }
let cnt = Double( self.cnt )
return self.sum / cnt
}
Expand All @@ -99,6 +100,20 @@ class SummaryStatistics {
var timeweightsum : Double
var distweightsum : Double

var wavg : Double? {
switch self.unit.sumWeightBy {
case .distance:
guard self.distweight != 0.0 else { return nil }
return self.distweightsum / self.distweight
case .time:
guard self.timeweight != 0.0 else { return nil }
return self.timeweightsum / self.timeweight
default: // cover case .count
guard self.cnt != 0 else { return nil }
return self.sum / Double( self.cnt)
}
}

var timeweightavg : Double { return self.timeweightsum / self.timeweight }
var distweightavg : Double { return self.distweightsum / self.distweight }

Expand Down Expand Up @@ -141,10 +156,11 @@ class SummaryStatistics {
static private let mps = GCUnit.mps()
static private let dimensionless = GCUnit.dimensionless()

func numberWithUnit(stats : Stat) -> GCNumberWithUnit {
func numberWithUnit(stats : Stat) -> GCNumberWithUnit? {
switch stats {
case .avg:
return GCNumberWithUnit(unit: self.unit, andValue: self.avg)
guard let avg = self.avg else { return nil }
return GCNumberWithUnit(unit: self.unit, andValue: avg)
case .max:
return GCNumberWithUnit(unit: self.unit, andValue: self.max)
case .min:
Expand All @@ -155,17 +171,20 @@ class SummaryStatistics {
return GCNumberWithUnit(unit: self.unit, andValue: self.sum)
case .wavg:
if self.unit.canConvert(to: SummaryStatistics.mps) {
guard self.timeweight != 0.0 else { return nil }
return GCNumberWithUnit(unit: SummaryStatistics.mps, andValue: self.distweight/self.timeweight).convert(to: self.unit)
}else{
return GCNumberWithUnit(unit: self.unit, andValue:self.timeweightsum/self.timeweight)
guard let wavg = self.wavg else { return nil }
return GCNumberWithUnit(unit: self.unit, andValue: wavg)
}
}
}
}

extension SummaryStatistics : CustomStringConvertible {
var description: String {
return "Statistics(\(self.unit.abbr) cnt: \(self.cnt), sum: \(self.sum), avg: \(self.avg), max: \(self.max))"
let avg = self.avg != nil ? "\(self.avg!)" : "nil"
return "Statistics(\(self.unit.abbr) cnt: \(self.cnt), sum: \(self.sum), avg: \(avg), max: \(self.max))"
}
}

Expand Down Expand Up @@ -245,11 +264,6 @@ class IndexData {
}else{
self.data[field] = SummaryStatistics(numberWithUnit: nu, timeweight: duration, distweight: distance)
}
if field.fieldFlag == gcFieldFlag.weightedMeanSpeed,
let val = self.data[field]{

print( "\(activity) \(field) \(nu) \(duration) \(val.timeweight)")
}
}
}
}
Expand Down
78 changes: 51 additions & 27 deletions ConnectStatsTests/GCTestAggregatedStats.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,30 @@ import XCTest
@testable import ConnectStats
import RZUtils


extension GCNumberWithUnit {
func isAlmostEqual(to other : GCNumberWithUnit) -> Bool {
if self.unit == other.unit {
let scale = max(abs(value), abs(other.value), .leastNormalMagnitude)
let tolerance = Double.ulpOfOne.squareRoot()
return abs(value-other.value) < scale*tolerance
}else{
return self.isAlmostEqual(to: other.convert(to: self.unit))
}
}

func assertEqual(to other : GCNumberWithUnit, message : String){
if self.unit == other.unit {
let scale = Swift.max(abs(value), abs(other.value), .leastNormalMagnitude)
let tolerance = Double.ulpOfOne.squareRoot()
XCTAssertEqual(value, other.value, accuracy: tolerance*scale, "unit: \(self.unit) \(message)")

}else{
self.assertEqual(to: other.convert(to: self.unit), message:message)
}
}
}

extension SummaryStatistics {
func compare(field : GCField, dataHolder: GCHistoryAggregatedDataHolder, message : String) {
XCTAssertTrue(dataHolder.hasField(field), message)
Expand All @@ -43,7 +67,7 @@ extension SummaryStatistics {
(SummaryStatistics.Stat.avg, gcAggregatedType.avg),
(SummaryStatistics.Stat.wavg , gcAggregatedType.wvg)
] {
let newnu = self.numberWithUnit(stats: newstat).convert(to: unit)
guard let newnu = self.numberWithUnit(stats: newstat)?.convert(to: unit) else { XCTAssertTrue(false); continue }
guard var oldnu = dataHolder.number(withUnit: field, statType: oldstat)?.convert(to: unit) else { XCTAssertTrue(false); continue }

// Special case weighted value for speed in old comes from preferred number
Expand All @@ -55,12 +79,10 @@ extension SummaryStatistics {
continue
}

let scale = Swift.max(abs(newnu.value), abs(oldnu.value), .leastNormalMagnitude)
let tolerance = Double.ulpOfOne.squareRoot()

XCTAssertEqual(newnu.value, oldnu.value, accuracy: scale*tolerance,"\(field) \(newstat) \(message)")
if( abs( newnu.value - oldnu.value ) > scale*tolerance){
let newnu2 = self.numberWithUnit(stats: newstat).convert(to: unit)
newnu.assertEqual(to: oldnu, message: "\(field) \(newstat) \(message)")

if( !newnu.isAlmostEqual(to: oldnu) ){
guard let newnu2 = self.numberWithUnit(stats: newstat)?.convert(to: unit) else { XCTAssertTrue(false); continue }
guard let oldnu2 = dataHolder.number(withUnit: field, statType: oldstat)?.convert(to: unit) else { XCTAssertTrue(false); continue }
guard let speednu2 = dataHolder.preferredNumber(withUnit: field) else { XCTAssertTrue(false); return }
print( "\(newnu2) \(oldnu2) \(speednu2)")
Expand All @@ -69,9 +91,20 @@ extension SummaryStatistics {
}

func compare(field : GCField, summaryHolder : GCHistoryFieldDataHolder, stat : gcHistoryStats, message : String){
XCTAssertEqual(self.cnt, Int(summaryHolder.count(withUnit: stat).value))
XCTAssertTrue(self.numberWithUnit(stats: .sum).isAlmostEqual(to: summaryHolder.sum(withUnit: stat)))
XCTAssertTrue(self.numberWithUnit(stats: .max).isAlmostEqual(to: summaryHolder.max(withUnit: stat)))
let cnt = Int(summaryHolder.count(withUnit: stat).value)
XCTAssertEqual(self.cnt, cnt, "\(field) \(stat) \(message)")
if field.canSum {
guard let nu = self.numberWithUnit(stats: .sum) else { XCTAssertTrue(false); return }
let oldnu = summaryHolder.sum(withUnit: stat)
nu.assertEqual(to: oldnu, message: "\(field) \(stat) \(message)")
}else{
// build number manually with wavg other wise would speed would not match
guard let wavg = self.wavg else { XCTAssertTrue(false); return }
guard let oldnu = summaryHolder.weightedAverage(withUnit: stat) else { XCTAssertTrue(false); return }
let nu = GCNumberWithUnit(unit: self.unit, andValue: wavg)
nu.assertEqual(to: oldnu, message: "\(field) \(stat) \(message)")

}
}
}

Expand All @@ -89,17 +122,6 @@ extension DateBucket {
}
}
}
extension GCNumberWithUnit {
func isAlmostEqual(to other : GCNumberWithUnit) -> Bool {
if self.unit == other.unit {
let scale = max(abs(value), abs(other.value), .leastNormalMagnitude)
let tolerance = Double.ulpOfOne.squareRoot()
return abs(value-other.value) < scale*tolerance
}else{
return self.isAlmostEqual(to: other.convert(to: self.unit))
}
}
}
class GCTestAggregatedStats: XCTestCase {

override func setUpWithError() throws {
Expand All @@ -116,7 +138,7 @@ class GCTestAggregatedStats: XCTestCase {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.

let calendar = Calendar.current
let calendar = GCAppGlobal.calculationCalendar()

let referenceDateComponents = DateComponents(year: 2021, month: 2, day: 16 )
guard let referenceDate = calendar.date(from: referenceDateComponents) else { XCTAssertTrue(false); return }
Expand Down Expand Up @@ -161,6 +183,7 @@ class GCTestAggregatedStats: XCTestCase {


func activitiesForStatsTest(n : Int = Int.max, activityType : String = GC_TYPE_ALL, focus : Bool = false) -> [GCActivity] {
let calendar = GCAppGlobal.calculationCalendar()
guard let db = GCTestsSamples.sampleActivityDatabase("activities_stats.db") else { XCTAssertTrue(false); return [] }
let organizer = GCActivitiesOrganizer(testModeWithDb: db)

Expand All @@ -171,8 +194,8 @@ class GCTestAggregatedStats: XCTestCase {
var focusbucket : DateBucket? = nil

if focus {
let datecomponents = DateComponents(calendar: Calendar.current, year: 2012, month: 2, day: 2)
guard let date = Calendar.current.date(from: datecomponents) else { XCTAssertTrue(false); return [] }
let datecomponents = DateComponents(calendar: calendar, year: 2012, month: 2, day: 2)
guard let date = calendar.date(from: datecomponents) else { XCTAssertTrue(false); return [] }
focusbucket = DateBucket(date: date, unit: .month, calendar: Calendar.current)
}

Expand Down Expand Up @@ -212,7 +235,7 @@ class GCTestAggregatedStats: XCTestCase {
let fieldsdata = GCHistoryFieldSummaryStats.fieldStats(withActivities: activities, matching: nil, referenceDate: nil, ignoreMode: gcIgnoreMode.activityFocus)
print( "\(fieldsdata) \(performance)")

if let basedate = activities.last?.date {
if let basedate = activities.first?.date {
let indexes : Set<Index> = [
Index(dateBucket: DateBucket(date: basedate, unit: .weekOfYear, calendar: calendar)!),
Index(dateBucket: DateBucket(date: basedate, unit: .month, calendar: calendar)!),
Expand All @@ -234,9 +257,10 @@ class GCTestAggregatedStats: XCTestCase {

for (k,v) in data.data {
let summary = fieldsdata.data(for: k)
//v.compare(field: k, summaryHolder: summary, stat: stat, message: "")
v.compare(field: k, summaryHolder: summary, stat: stat, message: "\(bucket)")
}

}else{
XCTAssertTrue(false)
}
}
print( "\(aggfieldstats) \(performance)")
Expand Down

0 comments on commit 0965b96

Please sign in to comment.