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.HGraphEncoderalso exposes a mutable stream withupdate/remove.FlatRelationEncoder.stream()is append-only.FlatRelationEncoder.mutable_stream()supportsupdate/remove.FlatHorizonEncoder.stream()is mutable.FlatTransitionEncoder.stream()andFlatTransitionEffectsEncoder.stream()are mutable wrapper-level streams built on flat horizon streaming.
LGAN API
HGraphEncoderandFlatRelationEncodershare the same main-lane LGAN knobs:include_lgan_edgeslgan_tn_edge_pos,lgan_nn_edge_pos,lgan_rr_edge_poslgan_anchor_sourcesfor extragoal/subgoal/historyLGAN anchors without prediction targetstarget_sourcesstays separate fromlgan_anchor_sourceson those main lanes:target_sourcescontrols prediction/readout targetslgan_anchor_sourcesonly creates extra LGAN anchor rowsHorizonEncoderandFlatHorizonEncoderdo not uselgan_anchor_sources. Their LGAN anchors are candidate state rows from the DAG.Transition*EncoderandFlatTransition*Encoderfollow the horizon rule: LGAN anchors come from successor-state candidates, not fromlgan_anchor_sources.
Action Contract
HGraphEncoderexpects flat action inputs (single action list or per-state flat lists).FlatRelationEncoderalso 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
HorizonEncoderwithTransitionDAGor arustworkx.PyDiGraph. FlatHorizonEncoderandFlatTransition*Encoderderive candidate/state structure fromdagorsuccessor(s)and currently reject explicit non-emptyactionsorhistory_subgoalspayloads.
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:
successorsaccepts a single shared successor, an aligned iterable,BatchParam.shared(...), orBatchParam.separate([...]) - horizon encoder:
dagsaccepts a shared DAG, an aligned iterable ofTransitionDAG | rustworkx.PyDiGraph | None,BatchParam.shared(...), orBatchParam.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 aPyDiGraphyourself. 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 payloadcandidate_id(mapping key or attribute). If candidate IDs are only partially present, conversion fails by default; passfallback_missing_candidate_id_to_node_index=Trueto fill missing values from rustworkx node indices.PyDiGraphinterop assumes the graph is already a valid horizon DAG/tree. The converter imports it directly intoTransitionDAGand treats malformed graph structure as an input error.- Encoder support:
HGraphEncoder: per-stategoals,actions,subgoal_layers,history_subgoalsFlatRelationEncoder: per-stategoals,actions,subgoal_layers,history_subgoalsColorEncoder: per-stategoals,subgoal_layers(actionsare accepted as inputs but non-empty payloads are rejected)ILGEncoder: per-stategoals,actions,subgoal_layersTransition*Encoder: shared or per-entrysuccessors, per-stategoals,subgoal_layers(actions/history are explicit inputs but currently rejected when non-empty)HorizonEncoder: shared or per-entrydags, per-stategoals,subgoal_layers(actions/history are explicit inputs but currently rejected when non-empty)FlatHorizonEncoder: shared or per-entrydags, per-stategoals,subgoal_layers(actions/history are explicit inputs but currently rejected when non-empty)FlatTransition*Encoder: shared or per-entrysuccessors, per-stategoals,subgoal_layers(actions/history are explicit inputs but currently rejected when non-empty)