diff --git a/ROADMAP.md b/ROADMAP.md index 45c78c9..55a4f6f 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -15,6 +15,9 @@ New ideas, thought about needed features will be store in this file. * Separate benchmarks to BENCHMARK.md **Done** * Better CSV format or another format (JSON / binary). **W.I.P. Splitting single file to multiple** +### WIP +* Separate export functions + ### Planned * Better heuristics for calculationg importance of each vertex. * Max-cost path finder. diff --git a/contraction.go b/contraction.go index edffa5c..e6f4644 100644 --- a/contraction.go +++ b/contraction.go @@ -59,7 +59,7 @@ func (graph *Graph) markNeighbors(inEdges, outEdges []incidentEdge) { // ContractionPath // -// ViaVertex - ID of vertex through which the contraction exists +// ViaVertex - ID of vertex through which the shortcut exists // Cost - summary cost of path between two vertices // type ContractionPath struct { diff --git a/export.go b/export.go index c71dc25..6edf950 100644 --- a/export.go +++ b/export.go @@ -6,88 +6,70 @@ import ( "os" "strconv" "strings" + + "github.com/pkg/errors" ) // ExportToFile Exports graph to file of CSV-format // Header of edges CSV-file: // from_vertex_id - int64, ID of source vertex // to_vertex_id - int64, ID of target vertex -// f_internal - int64, Internal ID of source vertex -// t_internal - int64, Internal ID of target vertex // weight - float64, Weight of an edge // Header of vertices CSV-file: // vertex_id - int64, ID of vertex -// internal_id - int64, Internal ID of vertex // order_pos - int, Position of vertex in hierarchies (evaluted by library) // importance - int, Importance of vertex in graph (evaluted by library) -// Header of contractios CSV-file: +// Header of shortcuts CSV-file: // from_vertex_id - int64, ID of source vertex -// to_vertex_id - int64, ID of arget vertex -// f_internal - int64, Internal ID of source vertex -// t_internal - int64, Internal ID of target vertex -// weight - float64, Weight of an edge -// via_vertex_id - int64, ID of vertex through which the contraction exists -// v_internal - int64, Internal ID of vertex through which the contraction exists +// to_vertex_id - int64, ID of target vertex +// weight - float64, Weight of an shortcut +// via_vertex_id - int64, ID of vertex through which the shortcut exists func (graph *Graph) ExportToFile(fname string) error { fnamePart := strings.Split(fname, ".csv") // to guarantee proper filename and its extension - file, err := os.Create(fnamePart[0] + ".csv") - if err != nil { - return err - } - defer file.Close() - writer := csv.NewWriter(file) - defer writer.Flush() - writer.Comma = ';' - err = writer.Write([]string{"from_vertex_id", "to_vertex_id", "f_internal", "t_internal", "weight"}) + err := graph.ExportEdgesToFile(fnamePart[0] + ".csv") if err != nil { - return err + return errors.Wrap(err, "Can't export edges") } - fileVertices, err := os.Create(fnamePart[0] + "_vertices.csv") + // Write reference information about vertices + err = graph.ExportVerticesToFile(fnamePart[0] + "_vertices.csv") if err != nil { - return err + return errors.Wrap(err, "Can't export shortcuts") } - defer fileVertices.Close() - writerVertices := csv.NewWriter(fileVertices) - defer writerVertices.Flush() - writerVertices.Comma = ';' - err = writerVertices.Write([]string{"vertex_id", "internal_id", "order_pos", "importance"}) + // Write reference information about contractions + err = graph.ExportShortcutsToFile(fnamePart[0] + "_shortcuts.csv") if err != nil { - return err + return errors.Wrap(err, "Can't export shortcuts") } - fileContractions, err := os.Create(fnamePart[0] + "_shortcuts.csv") + return nil +} + +// ExportVerticesToFile Exports edges information to CSV-file with header: +// from_vertex_id - int64, ID of source vertex +// to_vertex_id - int64, ID of target vertex +// weight - float64, Weight of an edge +func (graph *Graph) ExportEdgesToFile(fname string) error { + file, err := os.Create(fname) if err != nil { - return err + return errors.Wrap(err, "Can't create edges file") } - defer fileContractions.Close() + defer file.Close() - writerContractions := csv.NewWriter(fileContractions) - defer writerContractions.Flush() - writerContractions.Comma = ';' - err = writerContractions.Write([]string{"from_vertex_id", "to_vertex_id", "f_internal", "t_internal", "weight", "via_vertex_id", "v_internal"}) + writer := csv.NewWriter(file) + defer writer.Flush() + writer.Comma = ';' + err = writer.Write([]string{"from_vertex_id", "to_vertex_id", "weight"}) if err != nil { - return err + return errors.Wrap(err, "Can't write header to edges file") } for i := 0; i < len(graph.Vertices); i++ { currentVertexExternal := graph.Vertices[i].Label currentVertexInternal := graph.Vertices[i].vertexNum - - // Write reference information about vertex - err = writerVertices.Write([]string{ - fmt.Sprintf("%d", currentVertexExternal), - fmt.Sprintf("%d", currentVertexInternal), - fmt.Sprintf("%d", graph.Vertices[i].orderPos), - fmt.Sprintf("%d", graph.Vertices[i].importance), - }) - if err != nil { - return err - } - // Write reference information about "outcoming" adjacent vertices // Why don't write info about "incoming" adjacent vertices also? Because all edges will be covered due the loop iteration mechanism outcomingNeighbors := graph.Vertices[i].outIncidentEdges @@ -99,18 +81,67 @@ func (graph *Graph) ExportToFile(fname string) error { err = writer.Write([]string{ fmt.Sprintf("%d", currentVertexExternal), fmt.Sprintf("%d", toVertexExternal), - fmt.Sprintf("%d", currentVertexInternal), - fmt.Sprintf("%d", toVertexInternal), strconv.FormatFloat(cost, 'f', -1, 64), }) if err != nil { - return err + return errors.Wrap(err, "Can't write edge information") } } } } - // Write reference information about contractions + return nil +} + +// ExportVerticesToFile Exports vertices information to CSV-file with header: +// vertex_id - int64, ID of vertex +// order_pos - int, Position of vertex in hierarchies (evaluted by library) +// importance - int, Importance of vertex in graph (evaluted by library) +func (graph *Graph) ExportVerticesToFile(fname string) error { + fileVertices, err := os.Create(fname) + if err != nil { + return errors.Wrap(err, "Can't create vertices file") + } + defer fileVertices.Close() + writerVertices := csv.NewWriter(fileVertices) + defer writerVertices.Flush() + writerVertices.Comma = ';' + err = writerVertices.Write([]string{"vertex_id", "order_pos", "importance"}) + if err != nil { + return errors.Wrap(err, "Can't write header to vertices file") + } + for i := 0; i < len(graph.Vertices); i++ { + currentVertexExternal := graph.Vertices[i].Label + err = writerVertices.Write([]string{ + fmt.Sprintf("%d", currentVertexExternal), + fmt.Sprintf("%d", graph.Vertices[i].orderPos), + fmt.Sprintf("%d", graph.Vertices[i].importance), + }) + if err != nil { + return errors.Wrap(err, "Can't write vertex information") + } + } + return nil +} + +// ExportShortcutsToFile Exports shortcuts information to CSV-file with header: +// from_vertex_id - int64, ID of source vertex +// to_vertex_id - int64, ID of target vertex +// weight - float64, Weight of an shortcut +// via_vertex_id - int64, ID of vertex through which the shortcut exists +func (graph *Graph) ExportShortcutsToFile(fname string) error { + fileContractions, err := os.Create(fname) + if err != nil { + return errors.Wrap(err, "Can't create shortcuts file") + } + defer fileContractions.Close() + writerContractions := csv.NewWriter(fileContractions) + defer writerContractions.Flush() + writerContractions.Comma = ';' + err = writerContractions.Write([]string{"from_vertex_id", "to_vertex_id", "weight", "via_vertex_id"}) + if err != nil { + return errors.Wrap(err, "Can't write header to shortucts file") + } for sourceVertexInternal, to := range graph.shortcuts { sourceVertexExternal := graph.Vertices[sourceVertexInternal].Label for targetVertexInternal, viaNodeInternal := range to { @@ -119,16 +150,13 @@ func (graph *Graph) ExportToFile(fname string) error { err = writerContractions.Write([]string{ fmt.Sprintf("%d", sourceVertexExternal), fmt.Sprintf("%d", targetVertexExternal), - fmt.Sprintf("%d", sourceVertexInternal), - fmt.Sprintf("%d", targetVertexInternal), strconv.FormatFloat(viaNodeInternal.Cost, 'f', -1, 64), fmt.Sprintf("%d", viaNodeExternal), - fmt.Sprintf("%d", viaNodeInternal.ViaVertex), }) if err != nil { - return err + return errors.Wrap(err, "Can't write shortcut information") } } } - return err + return nil } diff --git a/graph.go b/graph.go index be83b6b..539174b 100644 --- a/graph.go +++ b/graph.go @@ -114,6 +114,33 @@ func (graph *Graph) AddEdge(from, to int64, weight float64) error { return nil } +// AddShortcut Adds new shortcut between two vertices +// +// from User's definied ID of first vertex of shortcut +// to User's definied ID of last vertex of shortcut +// weight User's definied weight of shortcut +// +func (graph *Graph) AddShortcut(from, to, via int64, weight float64) error { + if graph.frozen { + return ErrGraphIsFrozen + } + fromInternal := graph.mapping[from] + toInternal := graph.mapping[to] + viaInternal := graph.mapping[via] + if _, ok := graph.shortcuts[fromInternal]; !ok { + graph.shortcuts[fromInternal] = make(map[int64]*ContractionPath) + graph.shortcuts[fromInternal][toInternal] = &ContractionPath{ + ViaVertex: viaInternal, + Cost: weight, + } + } + graph.shortcuts[fromInternal][toInternal] = &ContractionPath{ + ViaVertex: viaInternal, + Cost: weight, + } + return nil +} + // AddTurnRestriction Adds new turn restriction between two vertices via some other vertex // // from User's definied ID of source vertex diff --git a/import.go b/import.go index 533aaa1..2e1c561 100644 --- a/import.go +++ b/import.go @@ -14,22 +14,16 @@ import ( // Header of CSV-file containing information about edges: // from_vertex_id - int64, ID of source vertex // to_vertex_id - int64, ID of arget vertex -// f_internal - int64, Internal ID of source vertex -// t_internal - int64, Internal ID of target vertex // weight - float64, Weight of an edge // Header of CSV-file containing information about vertices: // vertex_id - int64, ID of vertex -// internal_id - int64, internal ID of target vertex // order_pos - int, Position of vertex in hierarchies (evaluted by library) // importance - int, Importance of vertex in graph (evaluted by library) -// Header of CSV-file containing information about contractios between vertices: +// Header of CSV-file containing information about shortcuts between vertices: // from_vertex_id - int64, ID of source vertex -// to_vertex_id - int64, ID of arget vertex -// f_internal - int64, Internal ID of source vertex -// t_internal - int64, Internal ID of target vertex -// weight - float64, Weight of an edge -// via_vertex_id - int64, ID of vertex through which the contraction exists -// v_internal - int64, Internal ID of vertex through which the contraction exists +// to_vertex_id - int64, ID of target vertex +// weight - float64, Weight of an shortcut +// via_vertex_id - int64, ID of vertex through which the shortcut exists func ImportFromFile(edgesFname, verticesFname, contractionsFname string) (*Graph, error) { // Read edges first file, err := os.Open(edgesFname) @@ -62,27 +56,18 @@ func ImportFromFile(edgesFname, verticesFname, contractionsFname string) (*Graph return nil, err } - sourceInternal, err := strconv.ParseInt(record[2], 10, 64) - if err != nil { - return nil, err - } - targetInternal, err := strconv.ParseInt(record[3], 10, 64) + weight, err := strconv.ParseFloat(record[2], 64) if err != nil { return nil, err } - weight, err := strconv.ParseFloat(record[4], 64) + err = graph.CreateVertex(sourceExternal) if err != nil { - return nil, err + return nil, errors.Wrap(err, fmt.Sprintf("Can't add source vertex with external_ID = '%d'", sourceExternal)) } - - err = graph.AddVertex(sourceExternal, sourceInternal) - if err != nil { - return nil, errors.Wrap(err, fmt.Sprintf("Can't add source vertex with external_ID = '%d' and internal_ID = '%d'", sourceExternal, sourceInternal)) - } - err = graph.AddVertex(targetExternal, targetInternal) + err = graph.CreateVertex(targetExternal) if err != nil { - return nil, errors.Wrap(err, fmt.Sprintf("Can't add target vertex with external_ID = '%d' and internal_ID = '%d'", targetExternal, targetInternal)) + return nil, errors.Wrap(err, fmt.Sprintf("Can't add target vertex with external_ID = '%d'", targetExternal)) } err = graph.AddEdge(sourceExternal, targetExternal, weight) @@ -115,21 +100,18 @@ func ImportFromFile(edgesFname, verticesFname, contractionsFname string) (*Graph if err != nil { return nil, err } - vertexInternal, err := strconv.ParseInt(record[1], 10, 64) - if err != nil { - return nil, err - } - vertexOrderPos, err := strconv.Atoi(record[2]) + vertexOrderPos, err := strconv.Atoi(record[1]) if err != nil { return nil, err } - vertexImportance, err := strconv.Atoi(record[3]) + vertexImportance, err := strconv.Atoi(record[2]) if err != nil { return nil, err } - if graph.Vertices[vertexInternal].Label != vertexExternal { - return nil, fmt.Errorf("Vertex with Label = %d has wrong reference information. Incoming label info is '%d'", graph.Vertices[vertexInternal].Label, vertexExternal) + vertexInternal, vertexFound := graph.FindVertex(vertexExternal) + if !vertexFound { + return nil, fmt.Errorf("Vertex with Label = %d is not found in graph", vertexExternal) } graph.Vertices[vertexInternal].SetOrderPos(vertexOrderPos) graph.Vertices[vertexInternal].SetImportance(vertexImportance) @@ -162,39 +144,23 @@ func ImportFromFile(edgesFname, verticesFname, contractionsFname string) (*Graph return nil, err } - sourceInternal, err := strconv.ParseInt(record[2], 10, 64) + weight, err := strconv.ParseFloat(record[2], 64) if err != nil { return nil, err } - targetInternal, err := strconv.ParseInt(record[3], 10, 64) + contractionExternal, err := strconv.ParseInt(record[3], 10, 64) if err != nil { return nil, err } - weight, err := strconv.ParseFloat(record[4], 64) + err = graph.AddEdge(sourceExternal, targetExternal, weight) if err != nil { - return nil, err + return nil, errors.Wrap(err, fmt.Sprintf("Can't add shortcut with source_internal_ID = '%d' and target_internal_ID = '%d'", sourceExternal, targetExternal)) } - contractionInternal, err := strconv.ParseInt(record[6], 10, 64) + err = graph.AddShortcut(sourceExternal, targetExternal, contractionExternal, weight) if err != nil { - return nil, err - } - - err = graph.AddEdge(sourceExternal, targetExternal, weight) - if err != nil { - return nil, errors.Wrap(err, fmt.Sprintf("Can't add edge with source_internal_ID = '%d' and target_internal_ID = '%d'", sourceExternal, targetExternal)) - } - if _, ok := graph.shortcuts[sourceInternal]; !ok { - graph.shortcuts[sourceInternal] = make(map[int64]*ContractionPath) - graph.shortcuts[sourceInternal][targetInternal] = &ContractionPath{ - ViaVertex: contractionInternal, - Cost: weight, - } - } - graph.shortcuts[sourceInternal][targetInternal] = &ContractionPath{ - ViaVertex: contractionInternal, - Cost: weight, + return nil, errors.Wrap(err, fmt.Sprintf("Can't add shortcut with source_internal_ID = '%d' and target_internal_ID = '%d' to internal map", sourceExternal, targetExternal)) } } return &graph, nil