yog_io/pajek
Pajek (.net) format serialization support.
Provides functions to serialize and deserialize graphs in the Pajek .net format, a standard format for social network analysis used by the Pajek software and compatible with many network analysis tools.
Format Overview
Pajek files have a structured text format with distinct sections:
- Vertices:
*Vertices Nfollowed by node definitions - Arcs:
*Arcssection for directed edges - Edges:
*Edgessection for undirected edges
Example
import yog/model.{Directed}
import yog_io/pajek
// 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 Pajek
let pajek_string = pajek.serialize(graph)
// Write to file
let assert Ok(Nil) = pajek.write("graph.net", graph)
Output Format
*Vertices 3
1 "Alice"
2 "Bob"
3 "Carol"
*Arcs
1 2 5
2 3 3
Characteristics
- Social network standard: Widely used in academic research
- Node coordinates: Supports x, y positioning
- Visual attributes: Node shapes, colors, sizes
- Weighted edges: Supports edge weights
- 1-indexed: Node numbering starts at 1
Parsing Behavior
When parsing Pajek .net files, the following behaviors apply:
-
*Node IDs from Vertices section: All nodes must be declared in the *Vertices section at the beginning of the file. Node IDs are preserved from the file.
-
Graph type determination: The graph type (directed/undirected) is determined by the edge section header:
*Arcsfor directed graphs,*Edgesfor undirected graphs. Header matching is case-insensitive. -
Multi-word labels: Node labels can contain spaces when enclosed in double quotes (e.g.,
1 "Alice Smith"). Quotes are required for multi-word labels and are stripped during parsing. -
Optional weights: Edge/arc lines can include optional weights as a third field (e.g.,
1 2 5.0). Weights are parsed as floats and passed to the edge parser. -
Comment handling: Lines starting with
%are treated as comments and ignored during parsing. -
Strict node references: Edges/arcs must reference node IDs that exist in the *Vertices section. Unlike TGF, Pajek does not auto-create missing nodes. Edges with invalid node IDs are skipped and added to warnings.
-
Visual attributes: The parser currently extracts only the node label, ignoring additional fields like coordinates, shapes, and colors. These are preserved during serialization if provided via options.
-
Section order: Files must start with a *Vertices section. The *Arcs or *Edges section follows. Additional sections (if present) are ignored.
-
Whitespace handling: Multiple consecutive spaces are handled correctly in both vertex and edge lines. Leading and trailing whitespace is trimmed.
-
Malformed lines: Lines that cannot be parsed are skipped and collected as warnings in the
PajekResult, rather than causing the parse to fail.
References
Types
Node visual attributes for Pajek format.
pub type NodeAttributes {
NodeAttributes(
x: option.Option(Float),
y: option.Option(Float),
shape: option.Option(NodeShape),
size: option.Option(Float),
color: option.Option(String),
)
}
Constructors
-
NodeAttributes( x: option.Option(Float), y: option.Option(Float), shape: option.Option(NodeShape), size: option.Option(Float), color: option.Option(String), )Arguments
- x
-
X coordinate (0.0 to 1.0)
- y
-
Y coordinate (0.0 to 1.0)
- shape
-
Node shape
- size
-
Node size
- color
-
Node color (hex or name)
Node shape options for Pajek visualization.
pub type NodeShape {
Ellipse
Box
Diamond
Triangle
Cross
Empty
CustomShape(String)
}
Constructors
-
EllipseDefault ellipse shape
-
BoxBox/rectangle shape
-
DiamondDiamond shape
-
TriangleTriangle shape
-
CrossCross shape
-
EmptyEmpty shape (invisible node)
-
CustomShape(String)Custom shape name
Errors that can occur during Pajek operations.
pub type PajekError {
EmptyInput
InvalidVerticesLine(line: Int, content: String)
InvalidVertexLine(line: Int, content: String)
InvalidSectionHeader(line: Int, content: String)
InvalidArcLine(line: Int, content: String)
InvalidNodeId(line: Int, value: String)
InvalidWeight(line: Int, value: String)
NodeIdOutOfRange(line: Int, id: Int, max: Int)
MissingVerticesSection
ReadError(path: String, error: String)
WriteError(path: String, error: String)
}
Constructors
-
EmptyInputEmpty input string
-
InvalidVerticesLine(line: Int, content: String)Invalid *Vertices line
-
InvalidVertexLine(line: Int, content: String)Invalid vertex/node line
-
InvalidSectionHeader(line: Int, content: String)Invalid *Arcs or *Edges section header
-
InvalidArcLine(line: Int, content: String)Invalid arc/edge line
-
InvalidNodeId(line: Int, value: String)Invalid node ID
-
InvalidWeight(line: Int, value: String)Invalid weight value
-
NodeIdOutOfRange(line: Int, id: Int, max: Int)Node ID out of range
-
MissingVerticesSectionMissing *Vertices section
-
ReadError(path: String, error: String)File read error
-
WriteError(path: String, error: String)File write error
Options for Pajek serialization.
pub type PajekOptions(n, e) {
PajekOptions(
node_label: fn(n) -> String,
edge_weight: fn(e) -> option.Option(Float),
node_attributes: fn(n) -> NodeAttributes,
include_coordinates: Bool,
include_visuals: Bool,
)
}
Constructors
-
PajekOptions( node_label: fn(n) -> String, edge_weight: fn(e) -> option.Option(Float), node_attributes: fn(n) -> NodeAttributes, include_coordinates: Bool, include_visuals: Bool, )Arguments
- node_label
-
Function to convert node data to a label string
- edge_weight
-
Function to convert edge data to a weight (optional)
- node_attributes
-
Function to get node attributes (for visualization)
- include_coordinates
-
Include node coordinates in output
- include_visuals
-
Include visual attributes (shape, color, size)
Result type for Pajek parsing.
pub type PajekResult(n, e) {
PajekResult(
graph: model.Graph(n, e),
warnings: List(#(Int, String)),
)
}
Constructors
-
PajekResult( graph: model.Graph(n, e), warnings: List(#(Int, String)), )Arguments
- warnings
-
Lines that couldn’t be parsed (with line numbers)
Values
pub fn default_node_attributes() -> NodeAttributes
Default node attributes (no special visualization).
pub fn default_options() -> PajekOptions(String, String)
Default Pajek options for String node and edge data.
Default configuration:
- Node labels: Uses the node data’s string representation
- Edge weights: None (no weights)
- Node attributes: None
- Include coordinates: False
- Include visuals: False
pub fn options_with(
node_label node_label: fn(n) -> String,
edge_weight edge_weight: fn(e) -> option.Option(Float),
node_attributes node_attributes: fn(n) -> NodeAttributes,
include_coordinates include_coordinates: Bool,
include_visuals include_visuals: Bool,
) -> PajekOptions(n, e)
Creates Pajek options with custom functions.
Example
let options = pajek.options_with(
node_label: fn(p) { p.name },
edge_weight: fn(w) { Some(w) },
node_attributes: fn(p) { pajek.NodeAttributes(
x: Some(p.x),
y: Some(p.y),
shape: Some(pajek.Ellipse),
size: None,
color: None,
)},
include_coordinates: True,
include_visuals: False,
)
pub fn parse(
input: String,
) -> Result(PajekResult(String, String), PajekError)
Parses a Pajek string into a graph with String labels.
Convenience function for the common case where both node and edge data are just Strings.
Example
let pajek_string = "*Vertices 2\n1 \"Alice\"\n2 \"Bob\"\n*Arcs\n1 2"
case pajek.parse(pajek_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(option.Option(Float)) -> e,
) -> Result(PajekResult(n, e), PajekError)
Parses a Pajek string into a graph with custom parsers.
Example
let pajek_string = "*Vertices 2\n1 \"Alice\"\n2 \"Bob\"\n*Arcs\n1 2"
let result = pajek.parse_with(
pajek_string,
node_parser: fn(s) { s },
edge_parser: fn(_) { "" },
)
case result {
Ok(pajek.PajekResult(graph, warnings)) -> {
// Use the graph
process_graph(graph)
}
Error(e) -> handle_error(e)
}
pub fn read(
path: String,
) -> Result(PajekResult(String, String), PajekError)
Reads a graph from a Pajek file.
Convenience function that reads node and edge data as strings.
Example
let result = pajek.read("graph.net")
case result {
Ok(pajek.PajekResult(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(option.Option(Float)) -> e,
) -> Result(PajekResult(n, e), PajekError)
Reads a graph from a Pajek file with custom parsers.
Example
let result = pajek.read_with(
"graph.net",
node_parser: fn(s) { Person(s, 0.0, 0.0) },
edge_parser: fn(w) { case w { Some(val) -> val None -> 0.0 } },
)
pub fn serialize(graph: model.Graph(String, String)) -> String
Serializes a graph to Pajek format.
Convenience function for graphs with String node and edge data.
Example
let pajek_string = pajek.serialize(graph)
pub fn serialize_with(
options: PajekOptions(n, e),
graph: model.Graph(n, e),
) -> String
Serializes a graph to Pajek format with custom options.
Time Complexity: O(V + E)
Example
import yog/model.{Directed}
import yog_io/pajek
type Person {
Person(name: String, x: Float, y: Float)
}
let graph =
model.new(Directed)
|> model.add_node(1, Person("Alice", 0.5, 0.5))
|> model.add_node(2, Person("Bob", 0.7, 0.3))
let options = pajek.options_with(
node_label: fn(p) { p.name },
edge_weight: fn(w) { None },
node_attributes: fn(p) { pajek.NodeAttributes(
x: Some(p.x),
y: Some(p.y),
shape: None,
size: None,
color: None,
)},
include_coordinates: True,
include_visuals: False,
)
let pajek_string = pajek.serialize_with(options, graph)
pub fn to_string(graph: model.Graph(String, String)) -> String
Converts a graph to a Pajek 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 Pajek file.
Example
let assert Ok(Nil) = pajek.write("graph.net", graph)
pub fn write_with(
path: String,
options: PajekOptions(n, e),
graph: model.Graph(n, e),
) -> Result(Nil, simplifile.FileError)
Writes a graph to a Pajek file with custom options.
Example
let options = pajek.options_with(
node_label: fn(p) { p.name },
edge_weight: fn(w) { Some(w) },
node_attributes: fn(_) { pajek.default_node_attributes() },
include_coordinates: False,
include_visuals: False,
)
let assert Ok(Nil) = pajek.write_with("graph.net", options, graph)