API Reference

Core concepts

All bindings expose the same underlying algorithm through a single function call: dress_fit(). Pass in the graph (vertices, edges, optional weights), and get back a result struct containing every output array.

result = dress_fit(n_vertices, sources, targets, [weights], [variant], ...)

The result always contains the same fields across every language: edge dress values, edge weights, node dress norms, iteration count, and final convergence delta.

Variants

Constant Value Neighbourhood \(N[u]\) Combined weight \(\bar{w}(u,v)\)
UNDIRECTED 0 \(\{u\} \cup\) all neighbours (ignoring direction) \(2\,w(u,v)\)
DIRECTED 1 \(\{u\} \cup\) all neighbours (in + out) \(w(u,v) + w(v,u)\)
FORWARD 2 \(\{u\} \cup\) out-neighbours \(w(u,v)\)
BACKWARD 3 \(\{u\} \cup\) in-neighbours \(w(v,u)\)

Result fields

Field Type Description
sources int array [E] Edge source vertices (0-based)
targets int array [E] Edge target vertices (0-based)
edge_dress float array [E] DRESS similarity per edge
edge_weight float array [E] Variant-specific edge weight
node_dress float array [N] Per-node aggregated DRESS norm
iterations int Number of iterations performed
delta float Final maximum per-edge change

Language-specific APIs

C

#include "dress/dress.h"

/* Variant enum */
typedef enum {
    DRESS_VARIANT_UNDIRECTED = 0,
    DRESS_VARIANT_DIRECTED   = 1,
    DRESS_VARIANT_FORWARD    = 2,
    DRESS_VARIANT_BACKWARD   = 3
} dress_variant_t;

/* Construct a dress graph from an edge list.
   Takes ownership of U, V, W (freed by free_dress_graph).
   W may be NULL for unweighted graphs. */
p_dress_graph_t init_dress_graph(int N, int E,
                                 int *U, int *V, double *W,
                                 dress_variant_t variant,
                                 int precompute_intercepts);

/* Run iterative fitting. */
void fit(p_dress_graph_t g, int max_iterations, double epsilon,
         int *iterations, double *delta);

/* Free all memory. */
void free_dress_graph(p_dress_graph_t g);

/* Result fields on the struct: */
g->edge_dress[e]   /* per-edge DRESS value */
g->edge_weight[e]  /* per-edge weight */
g->node_dress[u]   /* per-node norm */
g->U[e], g->V[e]   /* edge endpoints */
g->N, g->E         /* vertex / edge count */

C (igraph wrapper)

#include "dress_igraph.h"

/* Result structure (pointers are zero-copy views into internal data) */
typedef struct {
    int     N;           /* number of vertices                       */
    int     E;           /* number of edges                          */
    const int    *src;   /* [E] edge source endpoints                */
    const int    *dst;   /* [E] edge target endpoints                */
    const double *dress; /* [E] DRESS similarity per edge            */
    const double *weight;/* [E] variant edge weight                  */
    const double *node_dress; /* [N] per-node DRESS norm             */
    int     iterations;  /* iterations performed                     */
    double  delta;       /* final max-delta at convergence           */
} dress_igraph_result_t;

/* Compute DRESS on an igraph graph.
   weight_attr: edge attribute name (e.g. "weight"), or NULL.
   Returns 0 on success. */
int dress_igraph_compute(const igraph_t *graph,
                         const char *weight_attr,
                         dress_variant_t variant,
                         int max_iters, double epsilon,
                         int precompute,
                         dress_igraph_result_t *result);

/* Free internal memory (struct itself is not freed). */
void dress_igraph_free(dress_igraph_result_t *result);

/* Copy per-edge DRESS values into an igraph_vector_t. */
int dress_igraph_to_vector(const dress_igraph_result_t *result,
                           igraph_vector_t *out);

Build with:

gcc -O2 my_app.c dress_igraph.c dress.c \
    $(pkg-config --cflags --libs igraph) -lm -fopenmp

Or via CMake:

cmake -B build -DDRESS_BUILD_IGRAPH=ON
cmake --build build

C++

#include "dress/dress.hpp"

/* RAII wrapper. Copies vectors into malloc'd buffers automatically. */

/* Unweighted */
DRESS g(N, sources_vec, targets_vec,
        DRESS_VARIANT_UNDIRECTED, precompute_intercepts);

/* Weighted */
DRESS g(N, sources_vec, targets_vec, weights_vec,
        DRESS_VARIANT_UNDIRECTED, precompute_intercepts);

/* Fit and read results */
auto [iterations, delta] = g.fit(max_iterations, epsilon);

g.numVertices()        // int
g.numEdges()           // int
g.edgeDress(e)         // double, DRESS value for edge e
g.edgeWeight(e)        // double
g.nodeDress(u)         // double
g.edgeSource(e)        // int
g.edgeTarget(e)        // int

/* Bulk zero-copy access */
g.edgeDressValues()    // const double*
g.edgeWeights()        // const double*
g.nodeDressValues()    // const double*
g.edgeSources()        // const int*
g.edgeTargets()        // const int*

Python

from dress import dress_fit, UNDIRECTED, DIRECTED, FORWARD, BACKWARD

# Unweighted
result = dress_fit(n_vertices, sources, targets)

# Weighted
result = dress_fit(n_vertices, sources, targets,
                   weights=[1.0, 2.0, 3.0])

# With options
result = dress_fit(n_vertices, sources, targets,
                   variant=DIRECTED,
                   max_iterations=200,
                   epsilon=1e-12)

result.sources      # list[int]
result.targets      # list[int]
result.edge_dress   # list[float]
result.edge_weight  # list[float]
result.node_dress   # list[float]
result.iterations   # int
result.delta        # float

The dress_fit() function works identically whether the C extension or the pure-Python backend is active. It returns a single DRESSResult with all arrays and metadata.

For advanced use (e.g. re-fitting with different parameters), the low-level DRESS class is also available:

from dress import DRESS, UNDIRECTED

g = DRESS(n_vertices, sources, targets, variant=UNDIRECTED)
fit_info = g.fit(max_iterations=100, epsilon=1e-6)
fit_info.iterations   # int
fit_info.delta        # float

# Read values from graph object
g.edge_dress(e)       # float, DRESS value for edge e
g.edge_weight(e)      # float
g.node_dress(u)       # float
g.n_vertices          # int
g.n_edges             # int

Python (pure, no C dependencies)

from dress.core import dress_fit, UNDIRECTED

result = dress_fit(n_vertices, sources, targets,
                   variant=UNDIRECTED)

result.sources      # list[int]
result.targets      # list[int]
result.edge_dress   # list[float]
result.edge_weight  # list[float]
result.node_dress   # list[float]
result.iterations   # int
result.delta        # float

The pure-Python backend is used automatically when the C extension is not available (import dress falls back to dress.core).

NetworkX integration

import networkx as nx
from dress.networkx import dress_graph

G = nx.karate_club_graph()
result = dress_graph(G, variant=UNDIRECTED,
                     max_iterations=100, epsilon=1e-6)

# Or write attributes back onto the graph:
result = dress_graph(G, set_attributes=True)
G.edges[0, 1]["dress"]      # per-edge similarity
G.nodes[0]["dress_norm"]     # per-node norm

Rust

use dress_graph::{DRESS, Variant, DressResult, DressError};

let result: Result<DressResult, DressError> =
    DRESS::builder(n, sources, targets)
        .weights(weights)                   // optional
        .variant(Variant::Undirected)       // default
        .max_iterations(100)                // default
        .epsilon(1e-6)                      // default
        .precompute_intercepts(true)        // default
        .build_and_fit();

let r = result.unwrap();
r.sources       // Vec<i32>
r.targets       // Vec<i32>
r.edge_dress    // Vec<f64>
r.edge_weight   // Vec<f64>
r.node_dress    // Vec<f64>
r.iterations    // i32
r.delta         // f64

Go

import dress "github.com/velicast/dress-graph/go"

result, err := dress.Fit(
    n,                      // int, number of vertices
    sources,                // []int32
    targets,                // []int32
    weights,                // []float64 or nil
    dress.Undirected,       // Variant
    100,                    // maxIterations
    1e-6,                   // epsilon
    true,                   // precomputeIntercepts
)

result.Sources     // []int32
result.Targets     // []int32
result.EdgeDress   // []float64
result.EdgeWeight  // []float64
result.NodeDress   // []float64
result.Iterations  // int
result.Delta       // float64

JavaScript (WASM)

import { dressFit, Variant } from './dress.js';

const result = await dressFit({
    numVertices: n,
    sources,                              // Int32Array or number[]
    targets,                              // Int32Array or number[]
    weights,                              // Float64Array, number[], or null
    variant: Variant.UNDIRECTED,          // default
    maxIterations: 100,                   // default
    epsilon: 1e-6,                        // default
    precomputeIntercepts: true,           // default
});

result.sources     // Int32Array
result.targets     // Int32Array
result.edgeDress   // Float64Array
result.edgeWeight  // Float64Array
result.nodeDress   // Float64Array
result.iterations  // number
result.delta       // number

Julia

using DRESS

result = dress_fit(N, sources, targets;
                   weights = nothing,           # optional Vector{Float64}
                   variant = UNDIRECTED,         # UNDIRECTED/DIRECTED/FORWARD/BACKWARD
                   max_iterations = 100,
                   epsilon = 1e-6,
                   precompute_intercepts = true)

result.sources      # Vector{Int32}
result.targets      # Vector{Int32}
result.edge_dress   # Vector{Float64}
result.edge_weight  # Vector{Float64}
result.node_dress   # Vector{Float64}
result.iterations   # Int
result.delta        # Float64

R

library(dress.graph)

result <- dress_fit(
  n_vertices,
  sources,                    # integer vector (0-based)
  targets,                    # integer vector (0-based)
  weights = NULL,             # optional numeric vector
  variant = DRESS_UNDIRECTED, # DRESS_UNDIRECTED / DRESS_DIRECTED /
                              # DRESS_FORWARD / DRESS_BACKWARD
  max_iterations = 100L,
  epsilon = 1e-6,
  precompute_intercepts = FALSE
)

result$sources      # integer [E]
result$targets      # integer [E]
result$edge_dress   # numeric [E]
result$edge_weight  # numeric [E]
result$node_dress   # numeric [N]
result$iterations   # integer
result$delta        # numeric

MATLAB / Octave

result = dress_fit(n_vertices, sources, targets, ...
    'Weights',              [],    ...   % double [E x 1] or []
    'Variant',              0,     ...   % 0=UNDIRECTED, 1=DIRECTED,
                                         % 2=FORWARD, 3=BACKWARD
    'MaxIterations',        100,   ...
    'Epsilon',              1e-6,  ...
    'PrecomputeIntercepts', false);

result.sources      % int32 [E x 1]
result.targets      % int32 [E x 1]
result.edge_dress   % double [E x 1]
result.edge_weight  % double [E x 1]
result.node_dress   % double [N x 1]
result.iterations   % int32
result.delta        % double