Skip to content

How to Choose an Encoder

Scenario Guide

Scenario Recommended Encoder Why
Encode current planning state with goals/actions/history HGraphEncoder Flexible heterogeneous state graph with optional goal/action/history features
Encode current planning state as packed flat relations FlatRelationEncoder Compact packed relation tensors with target-entity metadata and flat inspectability
Encode root state plus lookahead DAG structure HorizonEncoder Adds candidate-target DAG semantics and horizon-specific attributes
Encode root state plus lookahead DAG structure as flat packed relations FlatHorizonEncoder Compact state-target carrier rows over a TransitionDAG without relation nodes
Learn full transition structure (state -> successor) TransitionHGraphEncoder Represents both source and successor graph structure
Learn full transition structure (state -> successor) in the flat lane FlatTransitionEncoder Reuses the flat horizon/state-target substrate for compact successor encoding
Learn transition effects/deltas only TransitionEffectsHGraphEncoder Focuses on change signals instead of full duplicated structure
Learn transition effects/deltas only in the flat lane FlatTransitionEffectsEncoder Emits only changed successor structure on the compact flat carrier
Build compact homogeneous baselines ColorEncoder Lower-dimensional homogeneous encoding for simple baselines
Use ILG topology/features from Python ILGEncoder Pure-Python implementation with ILG atom/action/object construction

Minimal Runnable Examples

This page fragment is generated from docs/snippets/hgraph_basic.py. It is intended to show the exact code and typical output for this version.

Commit: 390f8090430944521df7c322996af8aa89f78469

Program

from __future__ import annotations

import mifrost

from ._helpers import load_problem, print_encoding_summary


def main() -> None:
    _space, domain, _problem, state = load_problem("blocks", "smedium")
    encoder = mifrost.HGraphEncoder(domain)
    encoding = encoder.encode(state)
    print_encoding_summary(encoding, label="HGraphEncoder.encode(initial_state)")


if __name__ == "__main__":
    main()

Output

== HGraphEncoder.encode(initial_state) ==
python: 3.12.13
platform: Linux-6.17.0-1018-azure-x86_64-with-glibc2.39
graph_kind: hetero
num_graphs: 1
num_nodes: 12
num_edges: 26
schema_fingerprint: 2869292577121628696
node_types_count: 28
edge_types_count: 54
node_types_head: ['[+]clear[g]', '[+]clear[g][sat]', '[+]handempty[g]', '[+]handempty[g][sat]', '[+]holding[g]', '[+]holding[g][sat]', '[+]on[g]', '[+]on[g][sat]', '[+]ontable[g]', '[+]ontable[g][sat]']
edge_types_head: ['[+]clear[g][sat]|0|_symbol_', '[+]clear[g]|0|_symbol_', '[+]holding[g][sat]|0|_symbol_', '[+]holding[g]|0|_symbol_', '[+]on[g][sat]|0|_symbol_', '[+]on[g][sat]|1|_symbol_', '[+]on[g]|0|_symbol_', '[+]on[g]|1|_symbol_', '[+]ontable[g][sat]|0|_symbol_', '[+]ontable[g]|0|_symbol_']
SNAPSHOT_JSON_BEGIN
{
  "edge_types_count": 54,
  "edge_types_head": [
    "[+]clear[g][sat]|0|_symbol_",
    "[+]clear[g]|0|_symbol_",
    "[+]holding[g][sat]|0|_symbol_",
    "[+]holding[g]|0|_symbol_",
    "[+]on[g][sat]|0|_symbol_",
    "[+]on[g][sat]|1|_symbol_",
    "[+]on[g]|0|_symbol_",
    "[+]on[g]|1|_symbol_",
    "[+]ontable[g][sat]|0|_symbol_",
    "[+]ontable[g]|0|_symbol_"
  ],
  "graph_kind": "hetero",
  "label": "HGraphEncoder.encode(initial_state)",
  "node_types_count": 28,
  "node_types_head": [
    "[+]clear[g]",
    "[+]clear[g][sat]",
    "[+]handempty[g]",
    "[+]handempty[g][sat]",
    "[+]holding[g]",
    "[+]holding[g][sat]",
    "[+]on[g]",
    "[+]on[g][sat]",
    "[+]ontable[g]",
    "[+]ontable[g][sat]"
  ],
  "num_edges": 26,
  "num_graphs": 1,
  "num_nodes": 12,
  "platform": "Linux-6.17.0-1018-azure-x86_64-with-glibc2.39",
  "python": "3.12.13",
  "schema_fingerprint": 2869292577121628696
}
SNAPSHOT_JSON_END

This page fragment is generated from docs/snippets/horizon_basic.py. It is intended to show the exact code and typical output for this version.

Commit: 390f8090430944521df7c322996af8aa89f78469

Program

from __future__ import annotations

import mifrost

from ._helpers import (
    _as_advanced_action,
    _as_advanced_state,
    load_problem,
    print_encoding_summary,
)


def main() -> None:
    space, domain, problem, root = load_problem("blocks", "small")
    goals = list(problem.get_goal_condition().get_literals())

    transitions = list(space.get_forward_transitions(root))[:3]
    if not transitions:
        raise RuntimeError("No transitions available for horizon example")

    adv_root = _as_advanced_state(root)
    dag = mifrost.TransitionDAG(adv_root)
    for action, target in transitions:
        if target is None:
            continue
        dag.register_transition(
            adv_root, _as_advanced_state(target), _as_advanced_action(action)
        )

    encoder = mifrost.HorizonEncoder(domain)
    encoding = encoder.encode(root, dag=dag, goals=goals)
    print_encoding_summary(encoding, label="HorizonEncoder.encode(root, dag, goals)")


if __name__ == "__main__":
    main()

Output

== HorizonEncoder.encode(root, dag, goals) ==
python: 3.12.13
platform: Linux-6.17.0-1018-azure-x86_64-with-glibc2.39
graph_kind: hetero
num_graphs: 1
num_nodes: 18
num_edges: 40
schema_fingerprint: 5944796290213023967
node_types_count: 45
edge_types_count: 122
node_types_head: ['[+]clear[g]', '[+]clear[g][sat]', '[+]clear[g][sat][state]', '[+]handempty[g]', '[+]handempty[g][sat]', '[+]handempty[g][sat][state]', '[+]holding[g]', '[+]holding[g][sat]', '[+]holding[g][sat][state]', '[+]on[g]']
edge_types_head: ['[+]clear[g][sat][state]|0|_symbol_', '[+]clear[g][sat][state]|1|_symbol_', '[+]clear[g][sat]|0|_symbol_', '[+]clear[g]|0|_symbol_', '[+]handempty[g][sat][state]|0|_symbol_', '[+]holding[g][sat][state]|0|_symbol_', '[+]holding[g][sat][state]|1|_symbol_', '[+]holding[g][sat]|0|_symbol_', '[+]holding[g]|0|_symbol_', '[+]on[g][sat][state]|0|_symbol_']
SNAPSHOT_JSON_BEGIN
{
  "edge_types_count": 122,
  "edge_types_head": [
    "[+]clear[g][sat][state]|0|_symbol_",
    "[+]clear[g][sat][state]|1|_symbol_",
    "[+]clear[g][sat]|0|_symbol_",
    "[+]clear[g]|0|_symbol_",
    "[+]handempty[g][sat][state]|0|_symbol_",
    "[+]holding[g][sat][state]|0|_symbol_",
    "[+]holding[g][sat][state]|1|_symbol_",
    "[+]holding[g][sat]|0|_symbol_",
    "[+]holding[g]|0|_symbol_",
    "[+]on[g][sat][state]|0|_symbol_"
  ],
  "graph_kind": "hetero",
  "label": "HorizonEncoder.encode(root, dag, goals)",
  "node_types_count": 45,
  "node_types_head": [
    "[+]clear[g]",
    "[+]clear[g][sat]",
    "[+]clear[g][sat][state]",
    "[+]handempty[g]",
    "[+]handempty[g][sat]",
    "[+]handempty[g][sat][state]",
    "[+]holding[g]",
    "[+]holding[g][sat]",
    "[+]holding[g][sat][state]",
    "[+]on[g]"
  ],
  "num_edges": 40,
  "num_graphs": 1,
  "num_nodes": 18,
  "platform": "Linux-6.17.0-1018-azure-x86_64-with-glibc2.39",
  "python": "3.12.13",
  "schema_fingerprint": 5944796290213023967
}
SNAPSHOT_JSON_END

This page fragment is generated from docs/snippets/transition_full_vs_delta.py. It is intended to show the exact code and typical output for this version.

Commit: 390f8090430944521df7c322996af8aa89f78469

Program

from __future__ import annotations

import mifrost

from ._helpers import first_successor, load_problem, print_encoding_summary


def main() -> None:
    space, domain, problem, state = load_problem("blocks", "small")
    goals = list(problem.get_goal_condition().get_literals())
    _action, succ = first_successor(space, state)

    full = mifrost.TransitionHGraphEncoder(domain)
    delta = mifrost.TransitionEffectsHGraphEncoder(domain)

    enc_full = full.encode(state, successor=succ, goals=goals)
    enc_delta = delta.encode(state, successor=succ, goals=goals)

    print_encoding_summary(
        enc_full, label="TransitionHGraphEncoder (full) encode(state, successor)"
    )
    print_encoding_summary(
        enc_delta,
        label="TransitionEffectsHGraphEncoder (delta) encode(state, successor)",
    )


if __name__ == "__main__":
    main()

Output

== TransitionHGraphEncoder (full) encode(state, successor) ==
python: 3.12.13
platform: Linux-6.17.0-1018-azure-x86_64-with-glibc2.39
graph_kind: hetero
num_graphs: 1
num_nodes: 12
num_edges: 22
schema_fingerprint: 15812242964936039643
node_types_count: 31
edge_types_count: 60
node_types_head: ['[+]clear[g]', '[+]clear[g][sat]', '[+]handempty[g]', '[+]handempty[g][sat]', '[+]holding[g]', '[+]holding[g][sat]', '[+]on[g]', '[+]on[g][sat]', '[+]ontable[g]', '[+]ontable[g][sat]']
edge_types_head: ['[+]clear[g][sat]|0|_symbol_', '[+]clear[g]|0|_symbol_', '[+]holding[g][sat]|0|_symbol_', '[+]holding[g]|0|_symbol_', '[+]on[g][sat]|0|_symbol_', '[+]on[g][sat]|1|_symbol_', '[+]on[g]|0|_symbol_', '[+]on[g]|1|_symbol_', '[+]ontable[g][sat]|0|_symbol_', '[+]ontable[g]|0|_symbol_']
SNAPSHOT_JSON_BEGIN
{
  "edge_types_count": 60,
  "edge_types_head": [
    "[+]clear[g][sat]|0|_symbol_",
    "[+]clear[g]|0|_symbol_",
    "[+]holding[g][sat]|0|_symbol_",
    "[+]holding[g]|0|_symbol_",
    "[+]on[g][sat]|0|_symbol_",
    "[+]on[g][sat]|1|_symbol_",
    "[+]on[g]|0|_symbol_",
    "[+]on[g]|1|_symbol_",
    "[+]ontable[g][sat]|0|_symbol_",
    "[+]ontable[g]|0|_symbol_"
  ],
  "graph_kind": "hetero",
  "label": "TransitionHGraphEncoder (full) encode(state, successor)",
  "node_types_count": 31,
  "node_types_head": [
    "[+]clear[g]",
    "[+]clear[g][sat]",
    "[+]handempty[g]",
    "[+]handempty[g][sat]",
    "[+]holding[g]",
    "[+]holding[g][sat]",
    "[+]on[g]",
    "[+]on[g][sat]",
    "[+]ontable[g]",
    "[+]ontable[g][sat]"
  ],
  "num_edges": 22,
  "num_graphs": 1,
  "num_nodes": 12,
  "platform": "Linux-6.17.0-1018-azure-x86_64-with-glibc2.39",
  "python": "3.12.13",
  "schema_fingerprint": 15812242964936039643
}
SNAPSHOT_JSON_END
== TransitionEffectsHGraphEncoder (delta) encode(state, successor) ==
python: 3.12.13
platform: Linux-6.17.0-1018-azure-x86_64-with-glibc2.39
graph_kind: hetero
num_graphs: 1
num_nodes: 12
num_edges: 22
schema_fingerprint: 3017462943347528540
node_types_count: 48
edge_types_count: 94
node_types_head: ['[+]clear', '[+]clear[g]', '[+]clear[g][sat]', '[+]clear[sat]', '[+]handempty', '[+]handempty[g]', '[+]handempty[g][sat]', '[+]handempty[sat]', '[+]holding', '[+]holding[g]']
edge_types_head: ['[+]clear[g][sat]|0|_symbol_', '[+]clear[g]|0|_symbol_', '[+]clear[sat]|0|_symbol_', '[+]clear|0|_symbol_', '[+]holding[g][sat]|0|_symbol_', '[+]holding[g]|0|_symbol_', '[+]holding[sat]|0|_symbol_', '[+]holding|0|_symbol_', '[+]on[g][sat]|0|_symbol_', '[+]on[g][sat]|1|_symbol_']
SNAPSHOT_JSON_BEGIN
{
  "edge_types_count": 94,
  "edge_types_head": [
    "[+]clear[g][sat]|0|_symbol_",
    "[+]clear[g]|0|_symbol_",
    "[+]clear[sat]|0|_symbol_",
    "[+]clear|0|_symbol_",
    "[+]holding[g][sat]|0|_symbol_",
    "[+]holding[g]|0|_symbol_",
    "[+]holding[sat]|0|_symbol_",
    "[+]holding|0|_symbol_",
    "[+]on[g][sat]|0|_symbol_",
    "[+]on[g][sat]|1|_symbol_"
  ],
  "graph_kind": "hetero",
  "label": "TransitionEffectsHGraphEncoder (delta) encode(state, successor)",
  "node_types_count": 48,
  "node_types_head": [
    "[+]clear",
    "[+]clear[g]",
    "[+]clear[g][sat]",
    "[+]clear[sat]",
    "[+]handempty",
    "[+]handempty[g]",
    "[+]handempty[g][sat]",
    "[+]handempty[sat]",
    "[+]holding",
    "[+]holding[g]"
  ],
  "num_edges": 22,
  "num_graphs": 1,
  "num_nodes": 12,
  "platform": "Linux-6.17.0-1018-azure-x86_64-with-glibc2.39",
  "python": "3.12.13",
  "schema_fingerprint": 3017462943347528540
}
SNAPSHOT_JSON_END

This page fragment is generated from docs/snippets/color_basic.py. It is intended to show the exact code and typical output for this version.

Commit: 390f8090430944521df7c322996af8aa89f78469

Program

from __future__ import annotations

import mifrost

from ._helpers import load_problem, print_encoding_summary


def main() -> None:
    _space, domain, _problem, state = load_problem("blocks", "smedium")
    encoder = mifrost.ColorEncoder(
        domain, edge_features=False, enable_global_predicate_nodes=False
    )
    encoding = encoder.encode(state)
    print_encoding_summary(encoding, label="ColorEncoder.encode(initial_state)")


if __name__ == "__main__":
    main()

Output

== ColorEncoder.encode(initial_state) ==
python: 3.12.13
platform: Linux-6.17.0-1018-azure-x86_64-with-glibc2.39
graph_kind: homo
num_graphs: 1
num_nodes: 17
num_edges: 17
schema_fingerprint: 4576704403193191383
node_types_count: 1
edge_types_count: 1
node_types_head: ['node']
edge_types_head: ['node|edge|node']
SNAPSHOT_JSON_BEGIN
{
  "edge_types_count": 1,
  "edge_types_head": [
    "node|edge|node"
  ],
  "graph_kind": "homo",
  "label": "ColorEncoder.encode(initial_state)",
  "node_types_count": 1,
  "node_types_head": [
    "node"
  ],
  "num_edges": 17,
  "num_graphs": 1,
  "num_nodes": 17,
  "platform": "Linux-6.17.0-1018-azure-x86_64-with-glibc2.39",
  "python": "3.12.13",
  "schema_fingerprint": 4576704403193191383
}
SNAPSHOT_JSON_END

This page fragment is generated from docs/snippets/ilg_basic.py. It is intended to show the exact code and typical output for this version.

Commit: 390f8090430944521df7c322996af8aa89f78469

Program

from __future__ import annotations

import mifrost

from ._helpers import load_problem, print_encoding_summary


def main() -> None:
    _space, domain, problem, state = load_problem("blocks", "small")
    goals = list(problem.get_goal_condition().get_literals())
    encoder = mifrost.ILGEncoder(domain)
    encoding = encoder.encode(state, goals=goals)
    print_encoding_summary(encoding, label="ILGEncoder.encode(state, goals)")


if __name__ == "__main__":
    main()

Output

== ILGEncoder.encode(state, goals) ==
python: 3.12.13
platform: Linux-6.17.0-1018-azure-x86_64-with-glibc2.39
graph_kind: hetero
num_graphs: 1
num_nodes: 10
num_edges: 16
schema_fingerprint: 16751243357862855874
node_types_count: 6
edge_types_count: 10
node_types_head: ['_symbol_', 'clear', 'handempty', 'object', 'on', 'ontable']
edge_types_head: ['_symbol_|0|clear', '_symbol_|0|object', '_symbol_|0|on', '_symbol_|0|ontable', '_symbol_|1|on', 'clear|0|_symbol_', 'object|0|_symbol_', 'ontable|0|_symbol_', 'on|0|_symbol_', 'on|1|_symbol_']
SNAPSHOT_JSON_BEGIN
{
  "edge_types_count": 10,
  "edge_types_head": [
    "_symbol_|0|clear",
    "_symbol_|0|object",
    "_symbol_|0|on",
    "_symbol_|0|ontable",
    "_symbol_|1|on",
    "clear|0|_symbol_",
    "object|0|_symbol_",
    "ontable|0|_symbol_",
    "on|0|_symbol_",
    "on|1|_symbol_"
  ],
  "graph_kind": "hetero",
  "label": "ILGEncoder.encode(state, goals)",
  "node_types_count": 6,
  "node_types_head": [
    "_symbol_",
    "clear",
    "handempty",
    "object",
    "on",
    "ontable"
  ],
  "num_edges": 16,
  "num_graphs": 1,
  "num_nodes": 10,
  "platform": "Linux-6.17.0-1018-azure-x86_64-with-glibc2.39",
  "python": "3.12.13",
  "schema_fingerprint": 16751243357862855874
}
SNAPSHOT_JSON_END

Stream Support

  • HGraphEncoder, HorizonEncoder, transition encoders, ColorEncoder, and the flat encoder family provide stream wrappers.
  • HGraphEncoder also exposes a mutable stream with update/remove.
  • FlatRelationEncoder.stream() is append-only.
  • FlatRelationEncoder.mutable_stream() supports update/remove.
  • FlatHorizonEncoder.stream() is mutable.
  • FlatTransitionEncoder.stream() and FlatTransitionEffectsEncoder.stream() are mutable wrapper-level streams built on flat horizon streaming.

LGAN API

  • HGraphEncoder and FlatRelationEncoder share the same main-lane LGAN knobs:
  • include_lgan_edges
  • lgan_tn_edge_pos, lgan_nn_edge_pos, lgan_rr_edge_pos
  • lgan_anchor_sources for extra goal / subgoal / history LGAN anchors without prediction targets
  • target_sources stays separate from lgan_anchor_sources on those main lanes:
  • target_sources controls prediction/readout targets
  • lgan_anchor_sources only creates extra LGAN anchor rows
  • HorizonEncoder and FlatHorizonEncoder do not use lgan_anchor_sources. Their LGAN anchors are candidate state rows from the DAG.
  • Transition*Encoder and FlatTransition*Encoder follow the horizon rule: LGAN anchors come from successor-state candidates, not from lgan_anchor_sources.

Action Contract

  • HGraphEncoder expects flat action inputs (single action list or per-state flat lists).
  • FlatRelationEncoder also accepts flat action inputs and materializes grounded-action target entities on the packed carrier.
  • Nested/tuple action payloads are rejected by design.
  • For IW lookahead/macro-transition outputs, use HorizonEncoder with TransitionDAG or a rustworkx.PyDiGraph.
  • FlatHorizonEncoder and FlatTransition*Encoder derive candidate/state structure from dag or successor(s) and currently reject explicit non-empty actions or history_subgoals payloads.

Batch Contract

  • encode_batch(states, *, ...) is kwargs-based (no tuple/sample batch payloads).
  • Batch-capable kwargs accept either shared payloads or per-state sequences.
  • For specialized encoder kwargs, the same shared/per-entry rule applies:
  • transition encoders: successors accepts a single shared successor, an aligned iterable, BatchParam.shared(...), or BatchParam.separate([...])
  • horizon encoder: dags accepts a shared DAG, an aligned iterable of TransitionDAG | rustworkx.PyDiGraph | None, BatchParam.shared(...), or BatchParam.separate([...])
  • High-level encoder batch paths preprocess wrapper/adapter inputs in Python, then dispatch to strict advanced-only C++ batch parsing.
  • Direct low-level _core._parse_* batch helpers stay advanced-only and reject adapter-backed objects.
  • mifrost.transition_dag_from_rustworkx(...) is the explicit conversion helper when you want to pre-normalize a PyDiGraph yourself. The integration is Python-level; there is no raw/native graph handoff into the C++ core.
  • transition_dag_from_rustworkx(...) reads per-node candidate identity from payload candidate_id (mapping key or attribute). If candidate IDs are only partially present, conversion fails by default; pass fallback_missing_candidate_id_to_node_index=True to fill missing values from rustworkx node indices.
  • PyDiGraph interop assumes the graph is already a valid horizon DAG/tree. The converter imports it directly into TransitionDAG and treats malformed graph structure as an input error.
  • Encoder support:
  • HGraphEncoder: per-state goals, actions, subgoal_layers, history_subgoals
  • FlatRelationEncoder: per-state goals, actions, subgoal_layers, history_subgoals
  • ColorEncoder: per-state goals, subgoal_layers (actions are accepted as inputs but non-empty payloads are rejected)
  • ILGEncoder: per-state goals, actions, subgoal_layers
  • Transition*Encoder: shared or per-entry successors, per-state goals, subgoal_layers (actions/history are explicit inputs but currently rejected when non-empty)
  • HorizonEncoder: shared or per-entry dags, per-state goals, subgoal_layers (actions/history are explicit inputs but currently rejected when non-empty)
  • FlatHorizonEncoder: shared or per-entry dags, per-state goals, subgoal_layers (actions/history are explicit inputs but currently rejected when non-empty)
  • FlatTransition*Encoder: shared or per-entry successors, per-state goals, subgoal_layers (actions/history are explicit inputs but currently rejected when non-empty)