yog_io/leda
LEDA (Library of Efficient Data types and Algorithms) graph format support.
Provides functions to serialize and deserialize graphs in the LEDA format, a text-based format used by the LEDA library and compatible with NetworkX.
Format Overview
LEDA files have a structured text format with distinct sections:
- Header:
LEDA.GRAPH - Type declarations: Node type and edge type
- Direction:
-1for directed,-2for undirected - Nodes: Count followed by node data lines
- Edges: Count followed by edge data lines
Example
import yog/model.{Directed}
import yog_io/leda
// Create a simple graph
let graph =
model.new(Directed)
|> model.add_node(1, "Alice")
|> model.add_node(2, "Bob")
let assert Ok(graph) = model.add_edge(graph, from: 1, to: 2, with: "5")
// Serialize to LEDA
let leda_string = leda.serialize(graph)
// Write to file
let assert Ok(Nil) = leda.write("graph.gw", graph)
Output Format
LEDA.GRAPH
string
string
-1
2
|{Alice}|
|{Bob}|
1
1 2 0 |{5}|
Characteristics
- Type-aware: Supports typed node and edge attributes
- 1-indexed: Node numbering starts at 1 (not 0)
- Reversal edges: Undirected graphs use reversal edge indices
- Research compatible: Used in academic graph algorithms
Parsing Behavior
When parsing LEDA files, the following behaviors apply:
-
1-indexed nodes: LEDA format uses 1-based indexing. Node ID 1 refers to the first node in the node section, ID 2 to the second node, etc. The parser preserves these IDs when creating the graph.
-
Sequential order: Nodes must appear in sequential order in the file. The nth node in the node section receives LEDA ID n.
-
Strict node references: Edges must reference node IDs that exist in the node section. Unlike TGF, LEDA does not auto-create missing nodes. Edges with invalid node IDs are skipped and added to warnings.
-
Reversal edges: The third field in edge lines (rev_edge) indicates the index of the reverse edge for undirected graphs. Currently, the parser accepts but does not actively use this field.
-
Type declarations: The parser accepts any string in the node/edge type declarations (lines 2-3) but currently treats all data as strings. Type validation is not enforced.
-
Whitespace handling: Multiple consecutive spaces in data values are collapsed to single spaces. Leading and trailing whitespace is trimmed.
-
Malformed lines: Lines that cannot be parsed are skipped and collected as warnings in the
LedaResult, rather than causing the parse to fail.
References
Types
Errors that can occur during LEDA operations.
pub type LedaError {
EmptyInput
InvalidHeader
InvalidNodeType(line: Int, content: String)
InvalidEdgeType(line: Int, content: String)
InvalidDirection(line: Int, content: String)
InvalidNodeCount(line: Int, content: String)
InvalidEdgeCount(line: Int, content: String)
InvalidNodeData(line: Int, content: String)
InvalidEdgeData(line: Int, content: String)
InvalidNodeId(line: Int, value: String)
NodeIdOutOfRange(line: Int, id: Int, count: Int)
ReadError(path: String, error: String)
WriteError(path: String, error: String)
}
Constructors
-
EmptyInputEmpty input string
-
InvalidHeaderInvalid header (expected “LEDA.GRAPH”)
-
InvalidNodeType(line: Int, content: String)Invalid node type declaration
-
InvalidEdgeType(line: Int, content: String)Invalid edge type declaration
-
InvalidDirection(line: Int, content: String)Invalid direction (expected “-1” or “-2”)
-
InvalidNodeCount(line: Int, content: String)Invalid node count
-
InvalidEdgeCount(line: Int, content: String)Invalid edge count
-
InvalidNodeData(line: Int, content: String)Invalid node data format
-
InvalidEdgeData(line: Int, content: String)Invalid edge data format
-
InvalidNodeId(line: Int, value: String)Invalid node ID (not an integer)
-
NodeIdOutOfRange(line: Int, id: Int, count: Int)Node ID out of range (LEDA uses 1-indexing)
-
ReadError(path: String, error: String)File read error
-
WriteError(path: String, error: String)File write error
Options for LEDA serialization.
pub type LedaOptions(n, e) {
LedaOptions(
node_serializer: fn(n) -> String,
edge_serializer: fn(e) -> String,
node_deserializer: fn(String) -> n,
edge_deserializer: fn(String) -> e,
)
}
Constructors
-
LedaOptions( node_serializer: fn(n) -> String, edge_serializer: fn(e) -> String, node_deserializer: fn(String) -> n, edge_deserializer: fn(String) -> e, )Arguments
- node_serializer
-
Function to convert node data to a string representation
- edge_serializer
-
Function to convert edge data to a string representation
- node_deserializer
-
Function to convert string back to node data
- edge_deserializer
-
Function to convert string back to edge data
Result type for LEDA parsing.
pub type LedaResult(n, e) {
LedaResult(
graph: model.Graph(n, e),
warnings: List(#(Int, String)),
)
}
Constructors
-
LedaResult( graph: model.Graph(n, e), warnings: List(#(Int, String)), )Arguments
- warnings
-
Lines that couldn’t be parsed (with line numbers)
LEDA data types for type declarations in the header.
Note: Currently, serialization always uses "string" for both node and
edge types for maximum compatibility. This type is reserved for future
enhancements to support typed LEDA files with proper int, double, and custom
types. The LEDA format specification supports these types in the header
section (lines 2-3), but the current implementation prioritizes simplicity
and broad compatibility.
pub type LedaType {
VoidType
StringType
IntType
DoubleType
CustomType(String)
}
Constructors
-
VoidTypeVoid type - no data
-
StringTypeString type
-
IntTypeInteger type
-
DoubleTypeDouble/float type
-
CustomType(String)Custom type with name
Values
pub fn default_options() -> LedaOptions(String, String)
Default LEDA options for String node and edge data.
Uses identity functions for serialization/deserialization.
pub fn options_with(
node_serializer node_serializer: fn(n) -> String,
edge_serializer edge_serializer: fn(e) -> String,
node_deserializer node_deserializer: fn(String) -> n,
edge_deserializer edge_deserializer: fn(String) -> e,
) -> LedaOptions(n, e)
Creates LEDA options with custom serializers.
Example
let options = leda.options_with(
node_serializer: fn(p) { p.name },
edge_serializer: fn(w) { int.to_string(w) },
node_deserializer: fn(s) { Person(s, 0) },
edge_deserializer: fn(s) {
case int.parse(s) { Ok(n) -> n Error(_) -> 0 }
},
)
pub fn parse(
input: String,
) -> Result(LedaResult(String, String), LedaError)
Parses a LEDA string into a graph with String labels.
Convenience function for the common case where both node and edge data are just Strings.
Example
let leda_string = "LEDA.GRAPH\nstring\nstring\n-1\n2\n|{Alice}|\n|{Bob}|\n1\n1 2 0 |{follows}|"
case leda.parse(leda_string) {
Ok(result) -> {
// result.graph is Graph(String, String)
process_graph(result.graph)
}
Error(e) -> handle_error(e)
}
pub fn parse_with(
input: String,
node_parser node_parser: fn(String) -> n,
edge_parser edge_parser: fn(String) -> e,
) -> Result(LedaResult(n, e), LedaError)
Parses a LEDA string into a graph with custom options.
Example
let leda_string = "LEDA.GRAPH\nstring\nstring\n-1\n2\n|{Alice}|\n|{Bob}|\n1\n1 2 0 |{follows}|"
let result = leda.parse_with(
leda_string,
node_parser: fn(s) { s },
edge_parser: fn(s) { s },
)
case result {
Ok(leda.LedaResult(graph, warnings)) -> {
// Use the graph
process_graph(graph)
}
Error(e) -> handle_error(e)
}
pub fn read(
path: String,
) -> Result(LedaResult(String, String), LedaError)
Reads a graph from a LEDA file.
Convenience function that reads node and edge data as strings.
Example
let result = leda.read("graph.gw")
case result {
Ok(leda.LedaResult(graph, warnings)) -> {
// Use the graph
process_graph(graph)
}
Error(e) -> handle_error(e)
}
pub fn read_with(
path: String,
node_parser node_parser: fn(String) -> n,
edge_parser edge_parser: fn(String) -> e,
) -> Result(LedaResult(n, e), LedaError)
Reads a graph from a LEDA file with custom parsers.
Example
let result = leda.read_with(
"graph.gw",
node_parser: fn(s) { Person(s, 0) },
edge_parser: fn(s) { case int.parse(s) { Ok(n) -> n Error(_) -> 0 } },
)
pub fn serialize(graph: model.Graph(String, String)) -> String
Serializes a graph to LEDA format.
Convenience function for graphs with String node and edge data.
Example
let leda_string = leda.serialize(graph)
pub fn serialize_with(
options: LedaOptions(n, e),
graph: model.Graph(n, e),
) -> String
Serializes a graph to LEDA format with custom options.
Time Complexity: O(V + E)
Example
import yog/model.{Directed}
import yog_io/leda
type Person {
Person(name: String, age: Int)
}
let graph =
model.new(Directed)
|> model.add_node(1, Person("Alice", 30))
|> model.add_node(2, Person("Bob", 25))
let options = leda.options_with(
node_serializer: fn(p) { p.name },
edge_serializer: fn(w) { w },
node_deserializer: fn(s) { Person(s, 0) },
edge_deserializer: fn(s) { s },
)
let leda_string = leda.serialize_with(options, graph)
pub fn to_string(graph: model.Graph(String, String)) -> String
Converts a graph to a LEDA string.
Alias for serialize for consistency with other modules.
pub fn write(
path: String,
graph: model.Graph(String, String),
) -> Result(Nil, simplifile.FileError)
Writes a graph to a LEDA file.
Example
let assert Ok(Nil) = leda.write("graph.gw", graph)
pub fn write_with(
path: String,
options: LedaOptions(n, e),
graph: model.Graph(n, e),
) -> Result(Nil, simplifile.FileError)
Writes a graph to a LEDA file with custom options.
Example
let options = leda.options_with(
node_serializer: fn(p) { p.name },
edge_serializer: fn(w) { int.to_string(w) },
node_deserializer: fn(s) { Person(s, 0) },
edge_deserializer: fn(s) { case int.parse(s) { Ok(n) -> n Error(_) -> 0 } },
)
let assert Ok(Nil) = leda.write_with("graph.gw", options, graph)