Projections

Projections transform RDF graph structures into simplified forms useful for visualization, LPG-style analysis, or downstream consumption. They bridge the gap between the semantic richness of RDF named graphs and the operational needs of visualization tools, NetworkX pipelines, and JSON APIs.

Two Modes

Graph-to-Graph (CONSTRUCT)

Stays in RDF. Expressed as SPARQL CONSTRUCT queries. Results can be stored as named graphs in the holarchy, traversed by portals, and queried via SPARQL. Use this mode when the output will be consumed by another RDF-aware system or when you want to store a simplified view inside the holarchy.

from holonic.projections import build_construct, CONSTRUCT_STRIP_TYPES

# Build and execute a CONSTRUCT
query = build_construct(CONSTRUCT_STRIP_TYPES, graph_iri="urn:holon:x/interior")
result_graph = ds.construct(query)

Graph-to-Structure (Pythonic)

Exits RDF into Python dicts, ProjectedGraph instances, or other structures. Use this mode for the “last mile” to visualization, JSON export, or LPG tools like NetworkX.

from holonic.projections import project_to_lpg

lpg = project_to_lpg(graph,
    collapse_types=True,
    collapse_literals=True,
    resolve_blanks=True,
    resolve_lists=True,
)
# lpg.nodes: dict[str, ProjectedNode]
# lpg.edges: list[ProjectedEdge]
# lpg.to_dict(): JSON-serializable

What Gets Collapsed

Type Collapse

rdf:type triples become a types: list[str] attribute on the node rather than edges to type nodes. This is what every graph visualization tool wants — the type is metadata about the node, not a relationship to display.

# RDF:
ex:alice rdf:type ex:Person .

# LPG projection:
Node(iri="urn:ex:alice", types=["urn:ex:Person"], ...)

Literal Collapse

Literal-valued triples become entries in the node’s attributes dict. Multi-valued properties accumulate into lists.

# RDF:
ex:alice ex:age 30 .
ex:alice ex:name "Alice" .

# LPG projection:
Node(iri="urn:ex:alice", attributes={"urn:ex:age": 30, "urn:ex:name": "Alice"})

Blank Node Resolution

Blank nodes that serve as structured values (addresses, sensor suites, configurations) are inlined as nested dicts on their parent node.

# RDF:
ex:alice ex:address _:addr .
_:addr ex:city "Vancouver" .
_:addr ex:street "123 Main St" .

# LPG projection:
Node(iri="urn:ex:alice", attributes={
    "urn:ex:address": {"urn:ex:city": "Vancouver", "urn:ex:street": "123 Main St"}
})

RDF List Resolution

rdf:first/rdf:rest chains are resolved into Python lists.

# RDF:
ex:alice ex:skills _:list .
_:list rdf:first "Python" ; rdf:rest _:l2 .
_:l2 rdf:first "SPARQL" ; rdf:rest rdf:nil .

# LPG projection:
Node(iri="urn:ex:alice", attributes={"urn:ex:skills": ["Python", "SPARQL"]})

Reification Collapse

rdf:Statement instances are unfolded into direct edges. Non-structural properties on the statement (confidence, source, timestamp) become edge attributes — the LPG representation of properties on edges.

from holonic.projections import collapse_reification

lpg = collapse_reification(graph, preserve_metadata=True)
# Edge(source=alice, predicate=knows, target=bob,
#      attributes={"confidence": 0.92, "source": "survey-2026"})

Built-in CONSTRUCT Templates

Template

Description

CONSTRUCT_STRIP_TYPES

Remove all rdf:type triples

CONSTRUCT_OBJECT_PROPERTIES_ONLY

Keep only IRI objects (topology)

CONSTRUCT_DATA_PROPERTIES_ONLY

Keep only literal objects

CONSTRUCT_COLLAPSE_REIFICATION

Unfold rdf:Statement to direct triples

CONSTRUCT_LABELS_ONLY

Extract rdfs:label, skos:prefLabel, skos:altLabel

CONSTRUCT_SUBCLASS_TREE

Extract rdfs:subClassOf hierarchy with labels

All templates accept an optional graph_iri parameter for GRAPH scoping:

q = build_construct(CONSTRUCT_STRIP_TYPES, graph_iri="urn:holon:x/interior")

Projection Pipelines

Chain CONSTRUCT and Python transform steps into composable pipelines. Fluent API.

from holonic.projections import (
    ProjectionPipeline,
    CONSTRUCT_STRIP_TYPES,
    strip_blank_nodes,
    localize_predicates,
)

lpg = (
    ProjectionPipeline("viz-prep")
    .add_construct("strip_types", CONSTRUCT_STRIP_TYPES)
    .add_transform("strip_blanks", strip_blank_nodes)
    .add_transform("localize", localize_predicates)
    .apply_to_lpg(source_graph)
)

Steps execute sequentially. Each step receives the output of the previous step. .apply() returns the final Graph; .apply_to_lpg() converts the final graph to a ProjectedGraph.

Client Integration

HolonicDataset provides three projection methods:

project_holon(holon_iri, store_as=..., **kwargs)

Merges all of a holon’s interior graphs and projects to LPG. Optionally stores the result as a projection named graph (cga:hasProjection).

lpg = ds.project_holon("urn:holon:air-picture",
    store_as="urn:holon:air-picture/projection/viz",
    collapse_types=True,
    resolve_blanks=True,
)

project_holarchy(**kwargs)

Projects the holarchy structure itself — holons as nodes, portals and cga:memberOf as edges. Useful for visualizing the topology of the holarchy.

topo = ds.project_holarchy()
# topo.nodes: holons and portals
# topo.edges: membership and portal connections

apply_pipeline(holon_iri, pipeline, store_as=...)

Applies a ProjectionPipeline to a holon’s merged interiors.

result_graph = ds.apply_pipeline(
    "urn:holon:sensor-fused",
    my_pipeline,
    store_as="urn:holon:sensor-fused/projection/filtered",
)

Utility Functions

Function

Signature

Description

extract_types()

Graph dict[str, list[str]]

IRI → type list mapping

filter_by_class()

Graph, class_iri Graph

Keep only instances of a class

strip_blank_nodes()

Graph Graph

Remove all blank-node triples

localize_predicates()

Graph Graph

Replace full IRIs with local names

API Reference

holonic.projections.project_to_lpg(graph, *, collapse_types=True, collapse_literals=True, resolve_blanks=True, resolve_lists=True, include_predicates=None, exclude_predicates=None)[source]

Project an RDF graph into an LPG-style structure.

This is the primary “exit ramp” from RDF into a property-graph representation suitable for visualization, NetworkX, or LPG tools.

Parameters:
  • graph (Graph) – Source rdflib.Graph.

  • collapse_types (bool) – If True, rdf:type becomes a node attribute, not an edge.

  • collapse_literals (bool) – If True, literal-valued triples become node attributes.

  • resolve_blanks (bool) – If True, blank nodes are inlined as nested dicts on their parent node’s attributes.

  • resolve_lists (bool) – If True, RDF collections (rdf:first/rdf:rest chains) are resolved into Python lists.

  • include_predicates (set[str] | None) – If set, only these predicates appear as edges (whitelist).

  • exclude_predicates (set[str] | None) – If set, these predicates are excluded from edges (blacklist).

Return type:

ProjectedGraph

holonic.projections.collapse_reification(graph, *, preserve_metadata=True)[source]

Collapse RDF reification into direct edges with metadata.

rdf:Statement instances become edges. Non-structural properties on the statement (e.g., prov:wasAttributedTo, dct:created) become edge attributes.

Parameters:
  • graph (Graph) – Source graph containing reified statements.

  • preserve_metadata (bool) – If True, non-structural properties on the rdf:Statement become attributes on the projected edge.

Return type:

ProjectedGraph

holonic.projections.build_construct(template, graph_iri=None)[source]

Instantiate a CONSTRUCT template with optional GRAPH scoping.

If template contains the {graph_clause} placeholder, it is treated as a format string (escaped braces {{ / }} get un-escaped) and the placeholder is substituted with either a GRAPH <iri> clause or an empty string.

If the placeholder is absent, the template is returned as-is — letting callers pass raw CONSTRUCT queries through add_construct without having to escape every brace for str.format.

Return type:

str

Parameters:
  • template (str)

  • graph_iri (str | None)

class holonic.projections.ProjectionPipeline(name='projection')[source]

Bases: object

A composable pipeline of projection steps.

Steps are applied sequentially. Each step takes the output of the previous step as input. Steps can be SPARQL CONSTRUCTs (staying in RDF) or Python functions (Graph→Graph).

The final output can optionally be converted to a ProjectedGraph via project_to_lpg().

> TODO: write metadata about the Pipeline (self-describing) w/n holon boundary (new opaque portal type?)

Example:

```python pipeline = ProjectionPipeline(“visualization”) pipeline.add_construct(“strip_types”, CONSTRUCT_STRIP_TYPES, graph_iri=…) pipeline.add_construct(“labels”, CONSTRUCT_LABELS_ONLY, graph_iri=…) pipeline.add_transform(“custom”, my_transform_fn)

result_graph = pipeline.apply(source_graph) lpg = pipeline.apply_to_lpg(source_graph) ```

steps: list[ProjectionStep]
add_construct(name, template, graph_iri=None)[source]

Add a CONSTRUCT-based step.

Return type:

ProjectionPipeline

Parameters:
  • name (str)

  • template (str)

  • graph_iri (str | None)

add_transform(name, fn)[source]

Add a Python transform step (Graph→Graph).

Return type:

ProjectionPipeline

Parameters:
apply(source, backend=None)[source]

Apply all steps sequentially, returning the final Graph.

Return type:

Graph

Parameters:

source (Graph)

apply_to_graph(source, backend=None)[source]

Apply all steps sequentially, returning the final Graph.

Alias for apply() that matches the terminal-method naming used in SPEC R7.3 alongside apply_to_lpg().

Return type:

Graph

Parameters:

source (Graph)

apply_to_lpg(source, backend=None, **lpg_kwargs)[source]

Apply all steps, then convert the result to an LPG projection.

Return type:

ProjectedGraph

Parameters:

source (Graph)

Parameters:

name (str)

class holonic.projections.ProjectedGraph(nodes=<factory>, edges=<factory>)[source]

Bases: object

An LPG-style projection of an RDF graph.

Nodes have types and literal attributes collapsed onto them. Edges carry only object-property relationships. Blank nodes are resolved into inline structures where possible.

Parameters:
nodes: dict[str, ProjectedNode]
edges: list[ProjectedEdge]
to_dict()[source]

Serialize to a plain dict (JSON-serializable).

Return type:

dict

class holonic.projections.ProjectedNode(iri, types=<factory>, attributes=<factory>, label=None)[source]

Bases: object

A node in a projected graph with collapsed attributes.

Parameters:
iri: str
types: list[str]
attributes: dict[str, Any]
label: str | None = None
class holonic.projections.ProjectedEdge(source, predicate, target, attributes=<factory>)[source]

Bases: object

An edge in a projected graph with collapsed attributes.

Parameters:
source: str
predicate: str
target: str
attributes: dict[str, Any]