diff --git a/integration/oasis/chargingstrategy/charge_strategy_creator.go b/integration/oasis/chargingstrategy/charge_strategy_creator.go index c0c62a9ba90..befde4bad48 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 +// State contains charging related information +type State 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() []State + + // EvaluateCost accepts current status and target status and returns cost needed + EvaluateCost(arrivalEnergy float64, targetState State) ChargingCost } diff --git a/integration/oasis/chargingstrategy/chargingstrategy.go b/integration/oasis/chargingstrategy/chargingstrategy.go index f8e798c903b..a7502d2d998 100644 --- a/integration/oasis/chargingstrategy/chargingstrategy.go +++ b/integration/oasis/chargingstrategy/chargingstrategy.go @@ -1,5 +1,10 @@ package chargingstrategy +import ( + "github.com/Telenav/osrm-backend/integration/util" + "github.com/golang/glog" +) + type fakeChargingStrategyCreator struct { maxEnergyLevel float64 } @@ -11,24 +16,76 @@ 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() []State { + return []State{ + State{ ChargingEnergy: f.maxEnergyLevel * 0.6, }, - ChargingStrategy{ - ChargingTime: 7200, + State{ ChargingEnergy: f.maxEnergyLevel * 0.8, }, - ChargingStrategy{ - ChargingTime: 14400, + State{ 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 State) ChargingCost { + sixtyPercentOfMaxEnergy := f.maxEnergyLevel * 0.6 + eightyPercentOfMaxEnergy := f.maxEnergyLevel * 0.8 + noNeedCharge := ChargingCost{ + Duration: 0.0, + } + + if arrivalEnergy > targetState.ChargingEnergy || + util.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 util.FloatEquals(targetState.ChargingEnergy, sixtyPercentOfMaxEnergy) { + return ChargingCost{ + Duration: totalTime, + } + } + + if arrivalEnergy < eightyPercentOfMaxEnergy { + energyNeeded4Stage2 := eightyPercentOfMaxEnergy - currentEnergy + totalTime += energyNeeded4Stage2 / (eightyPercentOfMaxEnergy - sixtyPercentOfMaxEnergy) * 3600.0 + currentEnergy = eightyPercentOfMaxEnergy + } + if util.FloatEquals(targetState.ChargingEnergy, eightyPercentOfMaxEnergy) { + return ChargingCost{ + Duration: totalTime, + } + } + + if arrivalEnergy < f.maxEnergyLevel { + energyNeeded4Stage3 := f.maxEnergyLevel - currentEnergy + totalTime += energyNeeded4Stage3 / (f.maxEnergyLevel - eightyPercentOfMaxEnergy) * 7200.0 + } + + if util.FloatEquals(targetState.ChargingEnergy, f.maxEnergyLevel) { + return ChargingCost{ + Duration: totalTime, + } + } + + glog.Fatalf("Invalid charging state %#v\n", targetState) + return noNeedCharge +} 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..8dc2dded527 --- /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() []State { + return []State{} +} + +func (f *nullChargeStrategy) EvaluateCost(arrivalEnergy float64, targetState State) 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) 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) + } + } + +}