From c34b6932cf0a0156fa2d4a934e3798e77415bb44 Mon Sep 17 00:00:00 2001 From: CoderBear801 Date: Sat, 7 Mar 2020 15:50:39 -0800 Subject: [PATCH 1/3] feat: Implement function to calcualte charge cost issue: https://github.com/Telenav/osrm-backend/issues/143 --- .../charge_strategy_creator.go | 16 +++- .../chargingstrategy/chargingstrategy.go | 89 ++++++++++++++++--- .../chargingstrategy/chargingstrategy_test.go | 71 +++++++++++++++ .../chargingstrategy/null_charge_strategy.go | 17 ++++ .../oasis/stationgraph/station_graph.go | 1 - .../oasis/stationgraph/station_graph_test.go | 12 --- 6 files changed, 177 insertions(+), 29 deletions(-) create mode 100644 integration/oasis/chargingstrategy/chargingstrategy_test.go create mode 100644 integration/oasis/chargingstrategy/null_charge_strategy.go diff --git a/integration/oasis/chargingstrategy/charge_strategy_creator.go b/integration/oasis/chargingstrategy/charge_strategy_creator.go index c0c62a9ba90..056f94edb2d 100644 --- a/integration/oasis/chargingstrategy/charge_strategy_creator.go +++ b/integration/oasis/chargingstrategy/charge_strategy_creator.go @@ -1,14 +1,22 @@ package chargingstrategy -// ChargingStrategy contains charging related information -type ChargingStrategy struct { - ChargingTime float64 +// ChargingStatus contains charging related information +type ChargingStatus struct { ChargingEnergy float64 } +// ChargingCost represents the cost needed to reach certain states +type ChargingCost struct { + Duration float64 + // Later could add money usage, etc +} + // ChargingStrategyCreator defines interface related with creation of charging strategy type ChargingStrategyCreator interface { // CreateChargingStrategies creates charge strategies which could be used by other algorithm - CreateChargingStrategies() []ChargingStrategy + CreateChargingStrategies() []ChargingStatus + + // EvaluateCost accepts current status and target status and returns cost needed + EvaluateCost(arrivalEnergy float64, targetState ChargingStatus) ChargingCost } diff --git a/integration/oasis/chargingstrategy/chargingstrategy.go b/integration/oasis/chargingstrategy/chargingstrategy.go index f8e798c903b..93a30b7d22e 100644 --- a/integration/oasis/chargingstrategy/chargingstrategy.go +++ b/integration/oasis/chargingstrategy/chargingstrategy.go @@ -1,5 +1,9 @@ package chargingstrategy +import ( + "github.com/golang/glog" +) + type fakeChargingStrategyCreator struct { maxEnergyLevel float64 } @@ -11,24 +15,85 @@ func NewFakeChargingStrategyCreator(maxEnergyLevel float64) *fakeChargingStrateg } } +// @todo: +// - Influence of returning candidate with no charge time and additional energy // CreateChargingStrategies returns different charging strategy -// Initial implementation: 1 hour charge for 60% of max energy, -// 2 hour charge for 80% -// 4 hour charge for 100% -func (f *fakeChargingStrategyCreator) CreateChargingStrategies() []ChargingStrategy { - - return []ChargingStrategy{ - ChargingStrategy{ - ChargingTime: 3600, +func (f *fakeChargingStrategyCreator) CreateChargingStrategies() []ChargingStatus { + return []ChargingStatus{ + ChargingStatus{ ChargingEnergy: f.maxEnergyLevel * 0.6, }, - ChargingStrategy{ - ChargingTime: 7200, + ChargingStatus{ ChargingEnergy: f.maxEnergyLevel * 0.8, }, - ChargingStrategy{ - ChargingTime: 14400, + ChargingStatus{ ChargingEnergy: f.maxEnergyLevel, }, } } + +// Fake charge strategy +// From empty energy: +// 1 hour charge to 60% of max energy +// 2 hour charge to 80%, means from 60% ~ 80% need 1 hour +// 4 hour charge to 100%, means from 80% ~ 100% need 2 hours +func (f *fakeChargingStrategyCreator) EvaluateCost(arrivalEnergy float64, targetState ChargingStatus) ChargingCost { + sixtyPercentOfMaxEnergy := f.maxEnergyLevel * 0.6 + eightyPercentOfMaxEnergy := f.maxEnergyLevel * 0.8 + noNeedCharge := ChargingCost{ + Duration: 0.0, + } + + if arrivalEnergy > targetState.ChargingEnergy || + floatEquals(targetState.ChargingEnergy, 0.0) { + return noNeedCharge + } + + totalTime := 0.0 + currentEnergy := arrivalEnergy + if arrivalEnergy < sixtyPercentOfMaxEnergy { + energyNeeded4Stage1 := sixtyPercentOfMaxEnergy - arrivalEnergy + totalTime += energyNeeded4Stage1 / sixtyPercentOfMaxEnergy * 3600.0 + currentEnergy = sixtyPercentOfMaxEnergy + } + + if floatEquals(targetState.ChargingEnergy, sixtyPercentOfMaxEnergy) { + return ChargingCost{ + Duration: totalTime, + } + } + + if arrivalEnergy < eightyPercentOfMaxEnergy { + energyNeeded4Stage2 := eightyPercentOfMaxEnergy - currentEnergy + totalTime += energyNeeded4Stage2 / (eightyPercentOfMaxEnergy - sixtyPercentOfMaxEnergy) * 3600.0 + currentEnergy = eightyPercentOfMaxEnergy + } + if floatEquals(targetState.ChargingEnergy, eightyPercentOfMaxEnergy) { + return ChargingCost{ + Duration: totalTime, + } + } + + if arrivalEnergy < f.maxEnergyLevel { + energyNeeded4Stage3 := f.maxEnergyLevel - currentEnergy + totalTime += energyNeeded4Stage3 / (f.maxEnergyLevel - eightyPercentOfMaxEnergy) * 7200.0 + } + + if floatEquals(targetState.ChargingEnergy, f.maxEnergyLevel) { + return ChargingCost{ + Duration: totalTime, + } + } + + glog.Fatalf("Invalid charging state %#v\n", targetState) + return noNeedCharge +} + +var epsilon float64 = 0.00000001 + +func floatEquals(a, b float64) bool { + if (a-b) < epsilon && (b-a) < epsilon { + return true + } + return false +} diff --git a/integration/oasis/chargingstrategy/chargingstrategy_test.go b/integration/oasis/chargingstrategy/chargingstrategy_test.go new file mode 100644 index 00000000000..5ec7e753d6c --- /dev/null +++ b/integration/oasis/chargingstrategy/chargingstrategy_test.go @@ -0,0 +1,71 @@ +package chargingstrategy + +import ( + "reflect" + "testing" +) + +func TestFakeChargingStrategyCreator(t *testing.T) { + cases := []struct { + arrivalEnergyLevel float64 + maxEnergyLevel float64 + expectResult []ChargingCost + }{ + { + 10000, + 50000, + []ChargingCost{ + ChargingCost{ + Duration: 2400.0, + }, + ChargingCost{ + Duration: 6000.0, + }, + ChargingCost{ + Duration: 13200.0, + }, + }, + }, + { + 32000, + 50000, + []ChargingCost{ + ChargingCost{ + Duration: 0.0, + }, + ChargingCost{ + Duration: 2880.0, + }, + ChargingCost{ + Duration: 10080.0, + }, + }, + }, + { + 41000, + 50000, + []ChargingCost{ + ChargingCost{ + Duration: 0.0, + }, + ChargingCost{ + Duration: 0.0, + }, + ChargingCost{ + Duration: 6480.0, + }, + }, + }, + } + + for _, c := range cases { + var actualResult []ChargingCost + strategy := NewFakeChargingStrategyCreator(c.maxEnergyLevel) + for _, state := range strategy.CreateChargingStrategies() { + actualResult = append(actualResult, strategy.EvaluateCost(c.arrivalEnergyLevel, state)) + } + if !reflect.DeepEqual(actualResult, c.expectResult) { + t.Errorf("parse case %#v, expect\n %#v but got\n %#v", c, c.expectResult, actualResult) + } + } +} diff --git a/integration/oasis/chargingstrategy/null_charge_strategy.go b/integration/oasis/chargingstrategy/null_charge_strategy.go new file mode 100644 index 00000000000..316f1e5c102 --- /dev/null +++ b/integration/oasis/chargingstrategy/null_charge_strategy.go @@ -0,0 +1,17 @@ +package chargingstrategy + +type nullChargeStrategy struct { +} + +// NewNullChargeStrategy creates nullChargeStrategy used to bypass unit tests +func NewNullChargeStrategy() *nullChargeStrategy { + return &nullChargeStrategy{} +} + +func (f *nullChargeStrategy) CreateChargingStrategies() []ChargingStatus { + return []ChargingStatus{} +} + +func (f *nullChargeStrategy) EvaluateCost(arrivalEnergy float64, targetState ChargingStatus) ChargingCost { + return ChargingCost{} +} diff --git a/integration/oasis/stationgraph/station_graph.go b/integration/oasis/stationgraph/station_graph.go index 0f2a256892d..8700d1d92b9 100644 --- a/integration/oasis/stationgraph/station_graph.go +++ b/integration/oasis/stationgraph/station_graph.go @@ -67,7 +67,6 @@ func (sg *stationGraph) getChargeStationsNodes(id string, currEnergyLevel, maxEn n := &node{ id: nodeID(sg.stationsCount), chargeInfo: chargeInfo{ - chargeTime: strategy.ChargingTime, chargeEnergy: strategy.ChargingEnergy, }, } diff --git a/integration/oasis/stationgraph/station_graph_test.go b/integration/oasis/stationgraph/station_graph_test.go index 427e671d5b8..53ae2f503a1 100644 --- a/integration/oasis/stationgraph/station_graph_test.go +++ b/integration/oasis/stationgraph/station_graph_test.go @@ -265,18 +265,6 @@ func testConnectivity(t *testing.T, graph *stationGraph, from string, tos []stri t.Errorf("incorrect node generated for %s expect 3 nodes but got %d", from, len(fns)) } - if !floatEquals(fns[0].arrivalEnergy, 0.0) || - !floatEquals(fns[0].chargeEnergy, maxEnergyLevel*0.6) || - !floatEquals(fns[0].chargeTime, 3600) || - !floatEquals(fns[1].arrivalEnergy, 0.0) || - !floatEquals(fns[1].chargeEnergy, maxEnergyLevel*0.8) || - !floatEquals(fns[1].chargeTime, 7200) || - !floatEquals(fns[2].arrivalEnergy, 0.0) || - !floatEquals(fns[2].chargeEnergy, maxEnergyLevel) || - !floatEquals(fns[2].chargeTime, 14400) { - t.Errorf("incorrect charge information generated for node %s", from) - } - index := 0 for _, to := range tos { tns := graph.getChargeStationsNodes(to, 0.0, 0.0) From 4267f678b9ba83c4ae531acc1515598cc678cff5 Mon Sep 17 00:00:00 2001 From: CoderBear801 Date: Mon, 9 Mar 2020 11:30:00 -0700 Subject: [PATCH 2/3] fix: rename `CharingStatus` -> `State` --- .../chargingstrategy/charge_strategy_creator.go | 8 ++++---- .../oasis/chargingstrategy/chargingstrategy.go | 12 ++++++------ .../oasis/chargingstrategy/null_charge_strategy.go | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/integration/oasis/chargingstrategy/charge_strategy_creator.go b/integration/oasis/chargingstrategy/charge_strategy_creator.go index 056f94edb2d..befde4bad48 100644 --- a/integration/oasis/chargingstrategy/charge_strategy_creator.go +++ b/integration/oasis/chargingstrategy/charge_strategy_creator.go @@ -1,7 +1,7 @@ package chargingstrategy -// ChargingStatus contains charging related information -type ChargingStatus struct { +// State contains charging related information +type State struct { ChargingEnergy float64 } @@ -15,8 +15,8 @@ type ChargingCost struct { type ChargingStrategyCreator interface { // CreateChargingStrategies creates charge strategies which could be used by other algorithm - CreateChargingStrategies() []ChargingStatus + CreateChargingStrategies() []State // EvaluateCost accepts current status and target status and returns cost needed - EvaluateCost(arrivalEnergy float64, targetState ChargingStatus) ChargingCost + EvaluateCost(arrivalEnergy float64, targetState State) ChargingCost } diff --git a/integration/oasis/chargingstrategy/chargingstrategy.go b/integration/oasis/chargingstrategy/chargingstrategy.go index 93a30b7d22e..36915441052 100644 --- a/integration/oasis/chargingstrategy/chargingstrategy.go +++ b/integration/oasis/chargingstrategy/chargingstrategy.go @@ -18,15 +18,15 @@ func NewFakeChargingStrategyCreator(maxEnergyLevel float64) *fakeChargingStrateg // @todo: // - Influence of returning candidate with no charge time and additional energy // CreateChargingStrategies returns different charging strategy -func (f *fakeChargingStrategyCreator) CreateChargingStrategies() []ChargingStatus { - return []ChargingStatus{ - ChargingStatus{ +func (f *fakeChargingStrategyCreator) CreateChargingStrategies() []State { + return []State{ + State{ ChargingEnergy: f.maxEnergyLevel * 0.6, }, - ChargingStatus{ + State{ ChargingEnergy: f.maxEnergyLevel * 0.8, }, - ChargingStatus{ + State{ ChargingEnergy: f.maxEnergyLevel, }, } @@ -37,7 +37,7 @@ func (f *fakeChargingStrategyCreator) CreateChargingStrategies() []ChargingStatu // 1 hour charge to 60% of max energy // 2 hour charge to 80%, means from 60% ~ 80% need 1 hour // 4 hour charge to 100%, means from 80% ~ 100% need 2 hours -func (f *fakeChargingStrategyCreator) EvaluateCost(arrivalEnergy float64, targetState ChargingStatus) ChargingCost { +func (f *fakeChargingStrategyCreator) EvaluateCost(arrivalEnergy float64, targetState State) ChargingCost { sixtyPercentOfMaxEnergy := f.maxEnergyLevel * 0.6 eightyPercentOfMaxEnergy := f.maxEnergyLevel * 0.8 noNeedCharge := ChargingCost{ diff --git a/integration/oasis/chargingstrategy/null_charge_strategy.go b/integration/oasis/chargingstrategy/null_charge_strategy.go index 316f1e5c102..8dc2dded527 100644 --- a/integration/oasis/chargingstrategy/null_charge_strategy.go +++ b/integration/oasis/chargingstrategy/null_charge_strategy.go @@ -8,10 +8,10 @@ func NewNullChargeStrategy() *nullChargeStrategy { return &nullChargeStrategy{} } -func (f *nullChargeStrategy) CreateChargingStrategies() []ChargingStatus { - return []ChargingStatus{} +func (f *nullChargeStrategy) CreateChargingStrategies() []State { + return []State{} } -func (f *nullChargeStrategy) EvaluateCost(arrivalEnergy float64, targetState ChargingStatus) ChargingCost { +func (f *nullChargeStrategy) EvaluateCost(arrivalEnergy float64, targetState State) ChargingCost { return ChargingCost{} } From 0593cba185e82ad94f79967dcbf61cb4c7e25984 Mon Sep 17 00:00:00 2001 From: CoderBear801 Date: Tue, 10 Mar 2020 10:23:03 -0700 Subject: [PATCH 3/3] fix: move floateuqals to integration/util. --- .../chargingstrategy/chargingstrategy.go | 18 ++---- integration/util/float_operation.go | 15 +++++ integration/util/float_operation_test.go | 55 +++++++++++++++++++ 3 files changed, 75 insertions(+), 13 deletions(-) create mode 100644 integration/util/float_operation.go create mode 100644 integration/util/float_operation_test.go diff --git a/integration/oasis/chargingstrategy/chargingstrategy.go b/integration/oasis/chargingstrategy/chargingstrategy.go index 36915441052..a7502d2d998 100644 --- a/integration/oasis/chargingstrategy/chargingstrategy.go +++ b/integration/oasis/chargingstrategy/chargingstrategy.go @@ -1,6 +1,7 @@ package chargingstrategy import ( + "github.com/Telenav/osrm-backend/integration/util" "github.com/golang/glog" ) @@ -45,7 +46,7 @@ func (f *fakeChargingStrategyCreator) EvaluateCost(arrivalEnergy float64, target } if arrivalEnergy > targetState.ChargingEnergy || - floatEquals(targetState.ChargingEnergy, 0.0) { + util.FloatEquals(targetState.ChargingEnergy, 0.0) { return noNeedCharge } @@ -57,7 +58,7 @@ func (f *fakeChargingStrategyCreator) EvaluateCost(arrivalEnergy float64, target currentEnergy = sixtyPercentOfMaxEnergy } - if floatEquals(targetState.ChargingEnergy, sixtyPercentOfMaxEnergy) { + if util.FloatEquals(targetState.ChargingEnergy, sixtyPercentOfMaxEnergy) { return ChargingCost{ Duration: totalTime, } @@ -68,7 +69,7 @@ func (f *fakeChargingStrategyCreator) EvaluateCost(arrivalEnergy float64, target totalTime += energyNeeded4Stage2 / (eightyPercentOfMaxEnergy - sixtyPercentOfMaxEnergy) * 3600.0 currentEnergy = eightyPercentOfMaxEnergy } - if floatEquals(targetState.ChargingEnergy, eightyPercentOfMaxEnergy) { + if util.FloatEquals(targetState.ChargingEnergy, eightyPercentOfMaxEnergy) { return ChargingCost{ Duration: totalTime, } @@ -79,7 +80,7 @@ func (f *fakeChargingStrategyCreator) EvaluateCost(arrivalEnergy float64, target totalTime += energyNeeded4Stage3 / (f.maxEnergyLevel - eightyPercentOfMaxEnergy) * 7200.0 } - if floatEquals(targetState.ChargingEnergy, f.maxEnergyLevel) { + if util.FloatEquals(targetState.ChargingEnergy, f.maxEnergyLevel) { return ChargingCost{ Duration: totalTime, } @@ -88,12 +89,3 @@ func (f *fakeChargingStrategyCreator) EvaluateCost(arrivalEnergy float64, target glog.Fatalf("Invalid charging state %#v\n", targetState) return noNeedCharge } - -var epsilon float64 = 0.00000001 - -func floatEquals(a, b float64) bool { - if (a-b) < epsilon && (b-a) < epsilon { - return true - } - return false -} diff --git a/integration/util/float_operation.go b/integration/util/float_operation.go new file mode 100644 index 00000000000..5ee973ba726 --- /dev/null +++ b/integration/util/float_operation.go @@ -0,0 +1,15 @@ +package util + +const epsilon float64 = 0.00000001 + +// FloatEquals compares two float64 numbers and returns true if they equal to each other +// with precision limit of 0.00000001 +// More information about float numbers: +// Floating Point Numbers https://users.cs.fiu.edu/~downeyt/cop2400/float.htm +// What Every Computer Scientist Should Know About Floating-Point Arithmetic https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html +func FloatEquals(a, b float64) bool { + if (a-b) < epsilon && (b-a) < epsilon { + return true + } + return false +} diff --git a/integration/util/float_operation_test.go b/integration/util/float_operation_test.go new file mode 100644 index 00000000000..c8dd88a6939 --- /dev/null +++ b/integration/util/float_operation_test.go @@ -0,0 +1,55 @@ +package util + +import "testing" + +func TestFloatEquals(t *testing.T) { + cases := []struct { + num1 float64 + num2 float64 + expect bool + }{ + { + 32.33333, + 32.33333, + true, + }, + { + 32.33333333, + 32.33333334, + false, + }, + { + 32.333333333, + 32.333333334, + true, + }, + { + 32.33333, + -32.33333, + false, + }, + { + -32.33333, + -32.33333, + true, + }, + { + -32.33333333, + -32.33333334, + false, + }, + { + -32.333333333, + -32.333333334, + true, + }, + } + + for _, c := range cases { + actual := FloatEquals(c.num1, c.num2) + if actual != c.expect { + t.Errorf("TestFloatEquals failed with case \n %#v expect \n %#v but got %#v\n", c, c.expect, actual) + } + } + +}