diff --git a/integration/oasis/reachable_by_single_charge.go b/integration/oasis/reachable_by_single_charge.go index 27cccaa272f..d3b58b7f06e 100644 --- a/integration/oasis/reachable_by_single_charge.go +++ b/integration/oasis/reachable_by_single_charge.go @@ -77,6 +77,7 @@ func generateResponse4SingleChargeStation(w http.ResponseWriter, req *oasis.Requ station := new(oasis.ChargeStation) station.WaitTime = 0.0 + // @todo ChargeTime and ChargeRange need to be adjusted according to chargingstrategy station.ChargeTime = 7200.0 station.ChargeRange = req.MaxRange station.DetailURL = "url" diff --git a/integration/oasis/stationgraph/graph.go b/integration/oasis/stationgraph/graph.go index 8de0be3646c..b37e3c3adf8 100644 --- a/integration/oasis/stationgraph/graph.go +++ b/integration/oasis/stationgraph/graph.go @@ -1,6 +1,7 @@ package stationgraph import ( + "github.com/Telenav/osrm-backend/integration/oasis/chargingstrategy" "github.com/golang/glog" ) @@ -8,6 +9,7 @@ type graph struct { nodes []*node startNodeID nodeID endNodeID nodeID + strategy chargingstrategy.ChargingStrategyCreator } func (g *graph) dijkstra() []nodeID { @@ -32,8 +34,7 @@ func (g *graph) dijkstra() []nodeID { node := g.nodes[n] for _, neighbor := range node.neighbors { if g.nodes[n].isLocationReachable(neighbor.distance) { - // chargeTime := g.nodes[neighbor.targetNodeID].calcChargeTime(node, neighbor.distance, g.strategy) - chargeTime := 0.0 + chargeTime := g.nodes[neighbor.targetNodeID].calcChargeTime(node, neighbor.distance, g.strategy) if m.add(neighbor.targetNodeID, n, neighbor.distance, neighbor.duration+chargeTime) { g.nodes[neighbor.targetNodeID].updateArrivalEnergy(node, neighbor.distance) g.nodes[neighbor.targetNodeID].updateChargingTime(chargeTime) diff --git a/integration/oasis/stationgraph/graph_test.go b/integration/oasis/stationgraph/graph_test.go index 317bc33a1b2..b5dc0b3f984 100644 --- a/integration/oasis/stationgraph/graph_test.go +++ b/integration/oasis/stationgraph/graph_test.go @@ -3,6 +3,8 @@ package stationgraph import ( "reflect" "testing" + + "github.com/Telenav/osrm-backend/integration/oasis/chargingstrategy" ) func TestGraphGeneral(t *testing.T) { @@ -19,6 +21,7 @@ func TestGraphGeneral(t *testing.T) { // node_2 -> node_3, duration = 50, distance = 50 // node_3 -> node_4, duration = 10, distance = 10 &graph{ + strategy: chargingstrategy.NewNullChargeStrategy(), nodes: []*node{ &node{ id: 0, @@ -109,6 +112,7 @@ func TestGraphGeneral(t *testing.T) { // node_2 -> node_3, duration = 50, distance = 50 // node_3 -> node_4, duration = 10, distance = 10 &graph{ + strategy: chargingstrategy.NewNullChargeStrategy(), nodes: []*node{ &node{ id: 0, @@ -199,6 +203,7 @@ func TestGraphGeneral(t *testing.T) { // node_2 -> node_3, duration = 50, distance = 50 // node_3 -> node_4, duration = 10, distance = 10 &graph{ + strategy: chargingstrategy.NewNullChargeStrategy(), nodes: []*node{ &node{ id: 0, @@ -298,6 +303,7 @@ func TestGraphGeneral(t *testing.T) { // node_6 -> node_8, duration = 20, distance = 20 // node_7 -> node_8, duration = 30, distance = 30 &graph{ + strategy: chargingstrategy.NewNullChargeStrategy(), nodes: []*node{ &node{ id: 0, @@ -480,6 +486,7 @@ func TestGraphGeneral(t *testing.T) { // node_6 -> node_8, duration = 20, distance = 20 // node_7 -> node_8, duration = 30, distance = 30 &graph{ + strategy: chargingstrategy.NewNullChargeStrategy(), nodes: []*node{ &node{ id: 0, @@ -644,197 +651,198 @@ func TestGraphGeneral(t *testing.T) { }, []nodeID{1, 4, 5}, }, - // { - // // case 5: each station only charges 16 - // // without considering charing, shortest path is 0 -> 2 -> 4 -> 5 -> 8 - // // considering charing, shortest path is 0 -> 1 -> 4 -> 5 -> 8 - // // 0 -> 1 -> 4 -> 5 -> 8, avoid information of charging - // // node_0 -> node_1, duration = 15, distance = 15 - // // node_0 -> node_2, duration = 20, distance = 20 - // // node_1 -> node_3, duration = 20, distance = 20 - // // node_1 -> node_4, duration = 15, distance = 15 - // // node_2 -> node_3, duration = 30, distance = 30 - // // node_2 -> node_4, duration = 5, distance = 5 - // // node_3 -> node_5, duration = 10, distance = 10 - // // node_3 -> node_6, duration = 10, distance = 10 - // // node_3 -> node_7, duration = 10, distance = 10 - // // node_4 -> node_5, duration = 15, distance = 15 - // // node_4 -> node_6, duration = 15, distance = 15 - // // node_4 -> node_7, duration = 15, distance = 15 - // // node_5 -> node_8, duration = 10, distance = 10 - // // node_6 -> node_8, duration = 20, distance = 20 - // // node_7 -> node_8, duration = 30, distance = 30 - // &graph{ - // nodes: []*node{ - // &node{ - // id: 0, - // neighbors: []*neighbor{ - // &neighbor{ - // targetNodeID: 1, - // distance: 15, - // duration: 15, - // }, - // &neighbor{ - // targetNodeID: 2, - // distance: 20, - // duration: 20, - // }, - // }, - // chargeInfo: chargeInfo{ - // arrivalEnergy: 16, - // chargeEnergy: 0, - // }, - // }, - // &node{ - // id: 1, - // neighbors: []*neighbor{ - // &neighbor{ - // targetNodeID: 3, - // distance: 20, - // duration: 20, - // }, - // &neighbor{ - // targetNodeID: 4, - // distance: 15, - // duration: 15, - // }, - // }, - // chargeInfo: chargeInfo{ - // arrivalEnergy: 0, - // chargeEnergy: 16, - // }, - // }, - // &node{ - // id: 2, - // neighbors: []*neighbor{ - // &neighbor{ - // targetNodeID: 4, - // distance: 5, - // duration: 5, - // }, - // &neighbor{ - // targetNodeID: 3, - // distance: 30, - // duration: 30, - // }, - // }, - // chargeInfo: chargeInfo{ - // arrivalEnergy: 0, - // chargeEnergy: 16, - // }, - // }, - // &node{ - // id: 3, - // neighbors: []*neighbor{ - // &neighbor{ - // targetNodeID: 5, - // distance: 10, - // duration: 10, - // }, - // &neighbor{ - // targetNodeID: 6, - // distance: 10, - // duration: 10, - // }, - // &neighbor{ - // targetNodeID: 7, - // distance: 10, - // duration: 10, - // }, - // }, - // chargeInfo: chargeInfo{ - // arrivalEnergy: 0, - // chargeEnergy: 16, - // }, - // }, - // &node{ - // id: 4, - // neighbors: []*neighbor{ - // &neighbor{ - // targetNodeID: 5, - // distance: 15, - // duration: 15, - // }, - // &neighbor{ - // targetNodeID: 6, - // distance: 15, - // duration: 15, - // }, - // &neighbor{ - // targetNodeID: 7, - // distance: 15, - // duration: 15, - // }, - // }, - // chargeInfo: chargeInfo{ - // arrivalEnergy: 0, - // chargeEnergy: 16, - // }, - // }, - // &node{ - // id: 5, - // neighbors: []*neighbor{ - // &neighbor{ - // targetNodeID: 8, - // distance: 10, - // duration: 10, - // }, - // }, - // chargeInfo: chargeInfo{ - // arrivalEnergy: 999, - // chargeEnergy: 999, - // }, - // }, - // &node{ - // id: 6, - // neighbors: []*neighbor{ - // &neighbor{ - // targetNodeID: 8, - // distance: 20, - // duration: 20, - // }, - // }, - // chargeInfo: chargeInfo{ - // arrivalEnergy: 0, - // chargeEnergy: 16, - // }, - // }, - // &node{ - // id: 7, - // neighbors: []*neighbor{ - // &neighbor{ - // targetNodeID: 8, - // distance: 30, - // duration: 30, - // }, - // }, - // chargeInfo: chargeInfo{ - // arrivalEnergy: 0, - // chargeEnergy: 16, - // }, - // }, - // &node{ - // id: 8, - // neighbors: []*neighbor{ - // &neighbor{}, - // }, - // chargeInfo: chargeInfo{ - // arrivalEnergy: 0, - // chargeEnergy: 0, - // }, - // }, - // }, - // startNodeID: 0, - // endNodeID: 8, - // }, - // []nodeID{1, 4, 5}, - // }, + { + // case 5: each station only charges 16 + // without considering charing, shortest path is 0 -> 2 -> 4 -> 5 -> 8 + // considering charing, shortest path is 0 -> 1 -> 4 -> 5 -> 8 + // 0 -> 1 -> 4 -> 5 -> 8, avoid information of charging + // node_0 -> node_1, duration = 15, distance = 15 + // node_0 -> node_2, duration = 20, distance = 20 + // node_1 -> node_3, duration = 20, distance = 20 + // node_1 -> node_4, duration = 15, distance = 15 + // node_2 -> node_3, duration = 30, distance = 30 + // node_2 -> node_4, duration = 5, distance = 5 + // node_3 -> node_5, duration = 10, distance = 10 + // node_3 -> node_6, duration = 10, distance = 10 + // node_3 -> node_7, duration = 10, distance = 10 + // node_4 -> node_5, duration = 15, distance = 15 + // node_4 -> node_6, duration = 15, distance = 15 + // node_4 -> node_7, duration = 15, distance = 15 + // node_5 -> node_8, duration = 10, distance = 10 + // node_6 -> node_8, duration = 20, distance = 20 + // node_7 -> node_8, duration = 30, distance = 30 + &graph{ + strategy: chargingstrategy.NewNullChargeStrategy(), + nodes: []*node{ + &node{ + id: 0, + neighbors: []*neighbor{ + &neighbor{ + targetNodeID: 1, + distance: 15, + duration: 15, + }, + &neighbor{ + targetNodeID: 2, + distance: 20, + duration: 20, + }, + }, + chargeInfo: chargeInfo{ + arrivalEnergy: 16, + chargeEnergy: 16, + }, + }, + &node{ + id: 1, + neighbors: []*neighbor{ + &neighbor{ + targetNodeID: 3, + distance: 20, + duration: 20, + }, + &neighbor{ + targetNodeID: 4, + distance: 15, + duration: 15, + }, + }, + chargeInfo: chargeInfo{ + arrivalEnergy: 0, + chargeEnergy: 16, + }, + }, + &node{ + id: 2, + neighbors: []*neighbor{ + &neighbor{ + targetNodeID: 4, + distance: 5, + duration: 5, + }, + &neighbor{ + targetNodeID: 3, + distance: 30, + duration: 30, + }, + }, + chargeInfo: chargeInfo{ + arrivalEnergy: 0, + chargeEnergy: 16, + }, + }, + &node{ + id: 3, + neighbors: []*neighbor{ + &neighbor{ + targetNodeID: 5, + distance: 10, + duration: 10, + }, + &neighbor{ + targetNodeID: 6, + distance: 10, + duration: 10, + }, + &neighbor{ + targetNodeID: 7, + distance: 10, + duration: 10, + }, + }, + chargeInfo: chargeInfo{ + arrivalEnergy: 0, + chargeEnergy: 16, + }, + }, + &node{ + id: 4, + neighbors: []*neighbor{ + &neighbor{ + targetNodeID: 5, + distance: 15, + duration: 15, + }, + &neighbor{ + targetNodeID: 6, + distance: 15, + duration: 15, + }, + &neighbor{ + targetNodeID: 7, + distance: 15, + duration: 15, + }, + }, + chargeInfo: chargeInfo{ + arrivalEnergy: 0, + chargeEnergy: 16, + }, + }, + &node{ + id: 5, + neighbors: []*neighbor{ + &neighbor{ + targetNodeID: 8, + distance: 10, + duration: 10, + }, + }, + chargeInfo: chargeInfo{ + arrivalEnergy: 999, + chargeEnergy: 999, + }, + }, + &node{ + id: 6, + neighbors: []*neighbor{ + &neighbor{ + targetNodeID: 8, + distance: 20, + duration: 20, + }, + }, + chargeInfo: chargeInfo{ + arrivalEnergy: 0, + chargeEnergy: 16, + }, + }, + &node{ + id: 7, + neighbors: []*neighbor{ + &neighbor{ + targetNodeID: 8, + distance: 30, + duration: 30, + }, + }, + chargeInfo: chargeInfo{ + arrivalEnergy: 0, + chargeEnergy: 16, + }, + }, + &node{ + id: 8, + neighbors: []*neighbor{ + &neighbor{}, + }, + chargeInfo: chargeInfo{ + arrivalEnergy: 0, + chargeEnergy: 0, + }, + }, + }, + startNodeID: 0, + endNodeID: 8, + }, + []nodeID{1, 4, 5}, + }, } - for _, c := range cases { + for i, c := range cases { s := c.g.dijkstra() if !reflect.DeepEqual(c.chargeStations, s) { - t.Errorf("expect %v but got %v", c.chargeStations, s) + t.Errorf("for test case %d expect %v but got %v", i, c.chargeStations, s) } } } diff --git a/integration/oasis/stationgraph/node.go b/integration/oasis/stationgraph/node.go index f50a7e81d2c..b745f8955b4 100644 --- a/integration/oasis/stationgraph/node.go +++ b/integration/oasis/stationgraph/node.go @@ -51,8 +51,8 @@ func (n *node) calcChargeTime(prev *node, distance float64, strategy chargingstr if arrivalEnergy < 0 { glog.Fatalf("Before updateNode should check isLocationReachable() prev.arrivalEnergy=%#v distance=%#v", prev.arrivalEnergy, distance) } - //return strategy.EvaluateCost(arrivalEnergy, chargingstrategy.ChargingStatus{ChargingEnergy: n.chargeEnergy}).Duration - return 0.0 + + return strategy.EvaluateCost(arrivalEnergy, chargingstrategy.State{ChargingEnergy: n.chargeEnergy}).Duration } func (n *node) updateChargingTime(chargingTime float64) { diff --git a/integration/oasis/stationgraph/station_graph.go b/integration/oasis/stationgraph/station_graph.go index 8700d1d92b9..7c9ef16f1c3 100644 --- a/integration/oasis/stationgraph/station_graph.go +++ b/integration/oasis/stationgraph/station_graph.go @@ -2,6 +2,7 @@ package stationgraph import ( "github.com/Telenav/osrm-backend/integration/oasis/chargingstrategy" + "github.com/Telenav/osrm-backend/integration/oasis/solution" "github.com/Telenav/osrm-backend/integration/oasis/stationfinder" "github.com/golang/glog" ) @@ -22,6 +23,7 @@ func NewStationGraph(c chan stationfinder.WeightBetweenNeighbors, currEnergyLeve g: &graph{ startNodeID: invalidNodeID, endNodeID: invalidNodeID, + strategy: strategy, }, stationID2Nodes: make(map[string][]*node), num2StationID: make(map[uint32]string), @@ -43,9 +45,54 @@ func NewStationGraph(c chan stationfinder.WeightBetweenNeighbors, currEnergyLeve return sg.constructGraph() } +func (sg *stationGraph) GenerateChargeSolutions() []*solution.Solution { + nodes := sg.g.dijkstra() + if nil == nodes { + glog.Warning("Failed to generate charge stations for stationGraph.\n") + return nil + } + + var result []*solution.Solution + + sol := &solution.Solution{} + sol.ChargeStations = make([]*solution.ChargeStation, 0) + var totalDistance, totalDuration float64 + + startNodeID := sg.stationID2Nodes[stationfinder.OrigLocationID][0].id + sg.g.accumulateDistanceAndDuration(startNodeID, nodes[0], &totalDistance, &totalDuration) + for i := 0; i < len(nodes); i++ { + if i != len(nodes)-1 { + sg.g.accumulateDistanceAndDuration(nodes[i], nodes[i+1], &totalDistance, &totalDuration) + } else { + endNodeID := sg.stationID2Nodes[stationfinder.DestLocationID][0].id + sg.g.accumulateDistanceAndDuration(nodes[i], endNodeID, &totalDistance, &totalDuration) + } + + station := &solution.ChargeStation{} + station.ArrivalEnergy = sg.g.getChargeInfo(nodes[i]).arrivalEnergy + station.ChargeRange = sg.g.getChargeInfo(nodes[i]).chargeEnergy + station.ChargeTime = sg.g.getChargeInfo(nodes[i]).chargeTime + station.Location = solution.Location{ + Lat: sg.g.getLocationInfo(nodes[i]).lat, + Lon: sg.g.getLocationInfo(nodes[i]).lon, + } + station.StationID = sg.num2StationID[uint32(nodes[i])] + + sol.ChargeStations = append(sol.ChargeStations, station) + + } + + sol.Distance = totalDistance + sol.Duration = totalDuration + sol.RemainingRage = sg.stationID2Nodes[stationfinder.DestLocationID][0].arrivalEnergy + + result = append(result, sol) + return result +} + func (sg *stationGraph) buildNeighborInfoBetweenNodes(neighborInfo stationfinder.NeighborInfo, currEnergyLevel, maxEnergyLevel float64) { - for _, fromNode := range sg.getChargeStationsNodes(neighborInfo.FromID, currEnergyLevel, maxEnergyLevel) { - for _, toNode := range sg.getChargeStationsNodes(neighborInfo.ToID, currEnergyLevel, maxEnergyLevel) { + for _, fromNode := range sg.getChargeStationsNodes(neighborInfo.FromID, neighborInfo.FromLocation, currEnergyLevel, maxEnergyLevel) { + for _, toNode := range sg.getChargeStationsNodes(neighborInfo.ToID, neighborInfo.ToLocation, currEnergyLevel, maxEnergyLevel) { fromNode.neighbors = append(fromNode.neighbors, &neighbor{ targetNodeID: toNode.id, distance: neighborInfo.Distance, @@ -55,12 +102,12 @@ func (sg *stationGraph) buildNeighborInfoBetweenNodes(neighborInfo stationfinder } } -func (sg *stationGraph) getChargeStationsNodes(id string, currEnergyLevel, maxEnergyLevel float64) []*node { +func (sg *stationGraph) getChargeStationsNodes(id string, location stationfinder.StationCoordinate, currEnergyLevel, maxEnergyLevel float64) []*node { if _, ok := sg.stationID2Nodes[id]; !ok { if sg.isStart(id) { - sg.constructStartNode(id, currEnergyLevel) + sg.constructStartNode(id, location, currEnergyLevel) } else if sg.isEnd(id) { - sg.constructEndNode(id) + sg.constructEndNode(id, location) } else { var nodes []*node for _, strategy := range sg.strategy.CreateChargingStrategies() { @@ -69,6 +116,10 @@ func (sg *stationGraph) getChargeStationsNodes(id string, currEnergyLevel, maxEn chargeInfo: chargeInfo{ chargeEnergy: strategy.ChargingEnergy, }, + locationInfo: locationInfo{ + lat: location.Lat, + lon: location.Lon, + }, } nodes = append(nodes, n) sg.num2StationID[sg.stationsCount] = id @@ -93,21 +144,33 @@ func (sg *stationGraph) getStationID(id nodeID) string { return sg.num2StationID[uint32(id)] } -func (sg *stationGraph) constructStartNode(id string, currEnergyLevel float64) { +func (sg *stationGraph) constructStartNode(id string, location stationfinder.StationCoordinate, currEnergyLevel float64) { n := &node{ - id: nodeID(sg.stationsCount), - chargeInfo: chargeInfo{arrivalEnergy: currEnergyLevel}, + id: nodeID(sg.stationsCount), + chargeInfo: chargeInfo{ + arrivalEnergy: currEnergyLevel, + chargeTime: 0.0, + chargeEnergy: currEnergyLevel, + }, + locationInfo: locationInfo{ + lat: location.Lat, + lon: location.Lon, + }, } sg.stationID2Nodes[id] = []*node{n} sg.num2StationID[sg.stationsCount] = id sg.stationsCount += 1 } -func (sg *stationGraph) constructEndNode(id string) { +func (sg *stationGraph) constructEndNode(id string, location stationfinder.StationCoordinate) { n := &node{ id: nodeID(sg.stationsCount), + locationInfo: locationInfo{ + lat: location.Lat, + lon: location.Lon, + }, } sg.stationID2Nodes[id] = []*node{n} sg.num2StationID[sg.stationsCount] = id @@ -115,6 +178,8 @@ func (sg *stationGraph) constructEndNode(id string) { } func (sg *stationGraph) constructGraph() *stationGraph { + sg.g.nodes = make([]*node, int(sg.stationsCount)) + for k, v := range sg.stationID2Nodes { if sg.isStart(k) { sg.g.startNodeID = v[0].id @@ -125,7 +190,7 @@ func (sg *stationGraph) constructGraph() *stationGraph { } for _, n := range v { - sg.g.nodes = append(sg.g.nodes, n) + sg.g.nodes[n.id] = n } } diff --git a/integration/oasis/stationgraph/station_graph_test.go b/integration/oasis/stationgraph/station_graph_test.go index 53ae2f503a1..42f7e633516 100644 --- a/integration/oasis/stationgraph/station_graph_test.go +++ b/integration/oasis/stationgraph/station_graph_test.go @@ -1,10 +1,13 @@ package stationgraph import ( + "fmt" "math" + "reflect" "testing" "github.com/Telenav/osrm-backend/integration/oasis/chargingstrategy" + "github.com/Telenav/osrm-backend/integration/oasis/solution" "github.com/Telenav/osrm-backend/integration/oasis/stationfinder" ) @@ -34,9 +37,9 @@ start ------- station 2 \ / end -- Each charge station will generate 3 virtual node, represent for different charge strategy: - use different time to charge different amount of energy -- In total there should be 17 nodes in graph +- Each charge station will try to generate up to 3 virtual node based on fakechargestrategy, + each node represent for situation of time spend in charging and get to different energy level +- In total there could be 17 nodes in graph + start node + end node + station 1 * 3 (node represent for with 60% of total energy, with 80% of total energy, with 100% of total energy, respectively) @@ -53,109 +56,197 @@ start ------- station 2 \ / end + connection to station 5's node with 100% of total energy Each node with name `station1` will have different id, and each of them will have 6 neighbor nodes */ -func TestConstructStationGraph(t *testing.T) { - neighbors := [][]stationfinder.NeighborInfo{ - []stationfinder.NeighborInfo{ - stationfinder.NeighborInfo{ - FromID: "orig_location", - ToID: "station1", - Cost: stationfinder.Cost{ - Duration: 22.2, - Distance: 22.2, - }, - }, - stationfinder.NeighborInfo{ - FromID: "orig_location", - ToID: "station2", - Cost: stationfinder.Cost{ - Duration: 11.1, - Distance: 11.1, - }, - }, - stationfinder.NeighborInfo{ - FromID: "orig_location", - ToID: "station3", - Cost: stationfinder.Cost{ - Duration: 33.3, - Distance: 33.3, - }, +var fakeNeighborsGraph = [][]stationfinder.NeighborInfo{ + []stationfinder.NeighborInfo{ + stationfinder.NeighborInfo{ + FromID: "orig_location", + FromLocation: stationfinder.StationCoordinate{ + Lat: 0.0, + Lon: 0.0, + }, + ToID: "station1", + ToLocation: stationfinder.StationCoordinate{ + Lat: 1.1, + Lon: 1.1, + }, + Cost: stationfinder.Cost{ + Duration: 22.2, + Distance: 22.2, + }, + }, + stationfinder.NeighborInfo{ + FromID: "orig_location", + FromLocation: stationfinder.StationCoordinate{ + Lat: 0.0, + Lon: 0.0, + }, + ToID: "station2", + ToLocation: stationfinder.StationCoordinate{ + Lat: 2.2, + Lon: 2.2, + }, + Cost: stationfinder.Cost{ + Duration: 11.1, + Distance: 11.1, }, }, - []stationfinder.NeighborInfo{ - stationfinder.NeighborInfo{ - FromID: "station1", - ToID: "station4", - Cost: stationfinder.Cost{ - Duration: 44.4, - Distance: 44.4, - }, - }, - stationfinder.NeighborInfo{ - FromID: "station1", - ToID: "station5", - Cost: stationfinder.Cost{ - Duration: 34.3, - Distance: 34.4, - }, - }, - stationfinder.NeighborInfo{ - FromID: "station2", - ToID: "station4", - Cost: stationfinder.Cost{ - Duration: 11.1, - Distance: 11.1, - }, - }, - stationfinder.NeighborInfo{ - FromID: "station2", - ToID: "station5", - Cost: stationfinder.Cost{ - Duration: 14.4, - Distance: 14.4, - }, - }, - stationfinder.NeighborInfo{ - FromID: "station3", - ToID: "station4", - Cost: stationfinder.Cost{ - Duration: 22.2, - Distance: 22.2, - }, - }, - stationfinder.NeighborInfo{ - FromID: "station3", - ToID: "station5", - Cost: stationfinder.Cost{ - Duration: 15.5, - Distance: 15.5, - }, + stationfinder.NeighborInfo{ + FromID: "orig_location", + FromLocation: stationfinder.StationCoordinate{ + Lat: 0.0, + Lon: 0.0, + }, + ToID: "station3", + Cost: stationfinder.Cost{ + Duration: 33.3, + Distance: 33.3, + }, + ToLocation: stationfinder.StationCoordinate{ + Lat: 3.3, + Lon: 3.3, }, }, - []stationfinder.NeighborInfo{ - stationfinder.NeighborInfo{ - FromID: "station4", - ToID: stationfinder.DestLocationID, - Cost: stationfinder.Cost{ - Duration: 44.4, - Distance: 44.4, - }, - }, - stationfinder.NeighborInfo{ - FromID: "station5", - ToID: stationfinder.DestLocationID, - Cost: stationfinder.Cost{ - Duration: 33.3, - Distance: 33.3, - }, + }, + []stationfinder.NeighborInfo{ + stationfinder.NeighborInfo{ + FromID: "station1", + FromLocation: stationfinder.StationCoordinate{ + Lat: 1.1, + Lon: 1.1, + }, + ToID: "station4", + ToLocation: stationfinder.StationCoordinate{ + Lat: 4.4, + Lon: 4.4, + }, + Cost: stationfinder.Cost{ + Duration: 44.4, + Distance: 44.4, }, }, - } + stationfinder.NeighborInfo{ + FromID: "station1", + FromLocation: stationfinder.StationCoordinate{ + Lat: 1.1, + Lon: 1.1, + }, + ToID: "station5", + ToLocation: stationfinder.StationCoordinate{ + Lat: 5.5, + Lon: 5.5, + }, + Cost: stationfinder.Cost{ + Duration: 34.4, + Distance: 34.4, + }, + }, + stationfinder.NeighborInfo{ + FromID: "station2", + FromLocation: stationfinder.StationCoordinate{ + Lat: 2.2, + Lon: 2.2, + }, + ToID: "station4", + ToLocation: stationfinder.StationCoordinate{ + Lat: 4.4, + Lon: 4.4, + }, + Cost: stationfinder.Cost{ + Duration: 11.1, + Distance: 11.1, + }, + }, + stationfinder.NeighborInfo{ + FromID: "station2", + FromLocation: stationfinder.StationCoordinate{ + Lat: 2.2, + Lon: 2.2, + }, + ToID: "station5", + ToLocation: stationfinder.StationCoordinate{ + Lat: 5.5, + Lon: 5.5, + }, + Cost: stationfinder.Cost{ + Duration: 14.4, + Distance: 14.4, + }, + }, + stationfinder.NeighborInfo{ + FromID: "station3", + FromLocation: stationfinder.StationCoordinate{ + Lat: 3.3, + Lon: 3.3, + }, + ToID: "station4", + ToLocation: stationfinder.StationCoordinate{ + Lat: 4.4, + Lon: 4.4, + }, + Cost: stationfinder.Cost{ + Duration: 22.2, + Distance: 22.2, + }, + }, + stationfinder.NeighborInfo{ + FromID: "station3", + FromLocation: stationfinder.StationCoordinate{ + Lat: 3.3, + Lon: 3.3, + }, + ToID: "station5", + ToLocation: stationfinder.StationCoordinate{ + Lat: 5.5, + Lon: 5.5, + }, + Cost: stationfinder.Cost{ + Duration: 15.5, + Distance: 15.5, + }, + }, + }, + []stationfinder.NeighborInfo{ + stationfinder.NeighborInfo{ + FromID: "station4", + FromLocation: stationfinder.StationCoordinate{ + Lat: 4.4, + Lon: 4.4, + }, + ToID: stationfinder.DestLocationID, + ToLocation: stationfinder.StationCoordinate{ + Lat: 6.6, + Lon: 6.6, + }, + Cost: stationfinder.Cost{ + Duration: 44.4, + Distance: 44.4, + }, + }, + stationfinder.NeighborInfo{ + FromID: "station5", + FromLocation: stationfinder.StationCoordinate{ + Lat: 5.5, + Lon: 5.5, + }, + ToID: stationfinder.DestLocationID, + ToLocation: stationfinder.StationCoordinate{ + Lat: 6.6, + Lon: 6.6, + }, + Cost: stationfinder.Cost{ + Duration: 33.3, + Distance: 33.3, + }, + }, + }, +} +func TestConstructStationGraph(t *testing.T) { // generate channel contains neighbors information // simulate real situation using different go-routine c := make(chan stationfinder.WeightBetweenNeighbors) go func() { - for _, n := range neighbors { + for _, n := range fakeNeighborsGraph { neighborsInfo := stationfinder.WeightBetweenNeighbors{ NeighborsInfo: n, Err: nil, @@ -165,7 +256,7 @@ func TestConstructStationGraph(t *testing.T) { close(c) }() - currEnergyLevel := 20.0 + currEnergyLevel := 0.0 maxEnergyLevel := 50.0 graph := NewStationGraph(c, currEnergyLevel, maxEnergyLevel, chargingstrategy.NewFakeChargingStrategyCreator(maxEnergyLevel)) @@ -176,15 +267,24 @@ func TestConstructStationGraph(t *testing.T) { testStart(t, graph, currEnergyLevel, maxEnergyLevel) testEnd(t, graph, currEnergyLevel, maxEnergyLevel) - testConnectivity(t, graph, "station1", []string{"station4", "station5"}, neighbors, currEnergyLevel, maxEnergyLevel) - testConnectivity(t, graph, "station2", []string{"station4", "station5"}, neighbors, currEnergyLevel, maxEnergyLevel) - testConnectivity(t, graph, "station3", []string{"station4", "station5"}, neighbors, currEnergyLevel, maxEnergyLevel) - testConnectivity(t, graph, "station4", []string{stationfinder.DestLocationID}, neighbors, currEnergyLevel, maxEnergyLevel) - testConnectivity(t, graph, "station5", []string{stationfinder.DestLocationID}, neighbors, currEnergyLevel, maxEnergyLevel) + testConnectivity(t, graph, "station1", locationInfo{lat: 1.1, lon: 1.1}, + []string{"station4", "station5"}, fakeNeighborsGraph, currEnergyLevel, maxEnergyLevel) + + testConnectivity(t, graph, "station2", locationInfo{lat: 2.2, lon: 2.2}, + []string{"station4", "station5"}, fakeNeighborsGraph, currEnergyLevel, maxEnergyLevel) + + testConnectivity(t, graph, "station3", locationInfo{lat: 3.3, lon: 3.3}, + []string{"station4", "station5"}, fakeNeighborsGraph, currEnergyLevel, maxEnergyLevel) + + testConnectivity(t, graph, "station4", locationInfo{lat: 4.4, lon: 4.4}, + []string{stationfinder.DestLocationID}, fakeNeighborsGraph, currEnergyLevel, maxEnergyLevel) + + testConnectivity(t, graph, "station5", locationInfo{lat: 5.5, lon: 5.5}, + []string{stationfinder.DestLocationID}, fakeNeighborsGraph, currEnergyLevel, maxEnergyLevel) } func testStart(t *testing.T, graph *stationGraph, currEnergyLevel, maxEnergyLevel float64) { - sn := graph.getChargeStationsNodes(stationfinder.OrigLocationID, currEnergyLevel, maxEnergyLevel) + sn := graph.getChargeStationsNodes(stationfinder.OrigLocationID, stationfinder.StationCoordinate{}, currEnergyLevel, maxEnergyLevel) if len(sn) != 1 { t.Errorf("incorrect start node generated expect only one node but got %d", len(sn)) } @@ -201,6 +301,15 @@ func testStart(t *testing.T, graph *stationGraph, currEnergyLevel, maxEnergyLeve }, sn[0].chargeInfo) } + startLocation := graph.g.getLocationInfo(sn[0].id) + if !floatEquals(startLocation.lat, 0.0) || + !floatEquals(startLocation.lon, 0.0) { + t.Errorf("incorrect location information for start node expect %v but got %v", locationInfo{ + lat: 0.0, + lon: 0.0, + }, startLocation) + } + if len(sn[0].neighbors) != 9 { t.Errorf("incorrect neighbors count for start node expect %d but got %d", 9, len(sn[0].neighbors)) } @@ -237,7 +346,7 @@ func testStart(t *testing.T, graph *stationGraph, currEnergyLevel, maxEnergyLeve } func testEnd(t *testing.T, graph *stationGraph, currEnergyLevel, maxEnergyLevel float64) { - se := graph.getChargeStationsNodes(stationfinder.DestLocationID, currEnergyLevel, maxEnergyLevel) + se := graph.getChargeStationsNodes(stationfinder.DestLocationID, stationfinder.StationCoordinate{}, currEnergyLevel, maxEnergyLevel) if len(se) != 1 { t.Errorf("incorrect end node generated expect only one node but got %d", len(se)) } @@ -253,21 +362,36 @@ func testEnd(t *testing.T, graph *stationGraph, currEnergyLevel, maxEnergyLevel chargeEnergy: 0.0, }, se[0].chargeInfo) } + + endLocation := graph.g.getLocationInfo(se[0].id) + if !floatEquals(endLocation.lat, 6.6) || + !floatEquals(endLocation.lon, 6.6) { + t.Errorf("incorrect location information for end node expect %v but got %v", locationInfo{ + lat: 6.6, + lon: 6.6, + }, endLocation) + } + if len(se[0].neighbors) != 0 { t.Errorf("incorrect neighbors count for end node expect %d but got %d", 0, len(se[0].neighbors)) } } -func testConnectivity(t *testing.T, graph *stationGraph, from string, tos []string, mockArray [][]stationfinder.NeighborInfo, currEnergyLevel, maxEnergyLevel float64) { - fns := graph.getChargeStationsNodes(from, 0.0, 0.0) +func testConnectivity(t *testing.T, graph *stationGraph, from string, fromLocation locationInfo, + tos []string, mockArray [][]stationfinder.NeighborInfo, currEnergyLevel, maxEnergyLevel float64) { + fns := graph.getChargeStationsNodes(from, stationfinder.StationCoordinate{}, 0.0, 0.0) - if len(fns) != 3 { - t.Errorf("incorrect node generated for %s expect 3 nodes but got %d", from, len(fns)) + for _, fromNode := range fns { + if !floatEquals(fromNode.locationInfo.lat, fromLocation.lat) || + !floatEquals(fromNode.locationInfo.lon, fromLocation.lon) { + t.Errorf("incorrect location information generated for node %s expect %+v got %+v", + from, fromLocation, fromNode.locationInfo) + } } index := 0 for _, to := range tos { - tns := graph.getChargeStationsNodes(to, 0.0, 0.0) + tns := graph.getChargeStationsNodes(to, stationfinder.StationCoordinate{}, 0.0, 0.0) expectDuration := math.MaxFloat64 expectDistance := math.MaxFloat64 @@ -299,6 +423,100 @@ func testConnectivity(t *testing.T, graph *stationGraph, from string, tos []stri } } +// based on original graph, best charge solution is +// start -> station 2 -> station 4 -> end +// when start, initial energy is 20 +// start -> station 2, time/duration = 11.1, this case will choose charging for 60% +// station 2 -> station 5, time/duration = 14.4, this cause will choose charging for 80% +func TestGenerateChargeSolutions1(t *testing.T) { + + fakeGraph1 := make([][]stationfinder.NeighborInfo, len(fakeNeighborsGraph)) + for i := range fakeNeighborsGraph { + fakeGraph1[i] = make([]stationfinder.NeighborInfo, len(fakeNeighborsGraph[i])) + copy(fakeGraph1[i], fakeNeighborsGraph[i]) + } + + // generate channel contains neighbors information + // simulate real situation using different go-routine + c := make(chan stationfinder.WeightBetweenNeighbors) + go func() { + for _, n := range fakeGraph1 { + neighborsInfo := stationfinder.WeightBetweenNeighbors{ + NeighborsInfo: n, + Err: nil, + } + c <- neighborsInfo + } + close(c) + }() + + currEnergyLevel := 20.0 + maxEnergyLevel := 50.0 + graph := NewStationGraph(c, currEnergyLevel, maxEnergyLevel, + chargingstrategy.NewFakeChargingStrategyCreator(maxEnergyLevel)) + if graph == nil { + t.Error("create Station graph failed, expect none-empty graph but result is empty") + } + + solutions := graph.GenerateChargeSolutions() + fmt.Printf("### %#v\n", solutions[0]) + fmt.Printf("### %#v\n", solutions[0].ChargeStations[0]) + fmt.Printf("### %#v\n", solutions[0].ChargeStations[1]) + if len(solutions) != 1 { + t.Errorf("expect to have 1 solution but got %d.\n", len(solutions)) + } + sol := solutions[0] + // 58.8 = 11.1 + 14.4 + 33.3 + if !floatEquals(sol.Distance, 58.8) { + t.Errorf("Incorrect distance calculated for fakeGraph1 expect 58.89 but got %#v.\n", sol.Distance) + } + + // 7918.8 = 11.1 + 2532(60% charge) + 14.4 + 5328(80% charge) + 33.3 + if !floatEquals(sol.Duration, 7918.8) { + t.Errorf("Incorrect duration calculated for fakeGraph1 expect 10858.8 but got %#v.\n", sol.Duration) + } + + // 6.7 = 40 - 33.3 + if !floatEquals(sol.RemainingRage, 6.7) { + t.Errorf("Incorrect duration calculated for fakeGraph1 expect 10858.8 but got %#v.\n", sol.RemainingRage) + } + + if len(sol.ChargeStations) != 2 { + t.Errorf("Expect to have 2 charge stations for fakeGraph1 but got %d.\n", len(sol.ChargeStations)) + } + + expectStation1 := &solution.ChargeStation{ + Location: solution.Location{ + Lat: 2.2, + Lon: 2.2, + }, + StationID: "station2", + ArrivalEnergy: 8.9, + WaitTime: 0, + ChargeTime: 2532, + ChargeRange: 30, + } + if !reflect.DeepEqual(sol.ChargeStations[0], expectStation1) { + t.Errorf("Expect first charge stations info for fakeGraph1 is %#v but got %#v\n", expectStation1, sol.ChargeStations[0]) + } + + expectStation2 := &solution.ChargeStation{ + Location: solution.Location{ + Lat: 5.5, + Lon: 5.5, + }, + StationID: "station5", + ArrivalEnergy: 15.6, + WaitTime: 0, + ChargeTime: 5328, + ChargeRange: 40, + } + if !reflect.DeepEqual(sol.ChargeStations[1], expectStation2) { + t.Errorf("Expect second charge stations info for fakeGraph1 is %#v but got %#v\n", expectStation2, sol.ChargeStations[1]) + } + +} + var epsilon float64 = 0.00000001 func floatEquals(a, b float64) bool {