GNN Integration Layer: Python Framework Base:
discopy(Categorical Quantum Diagrams) Simulation Architecture: Structural / Categorical Semantics (No POMDP Simulation) Documentation Version: 2.0.0
The Generalized Notation Notation (GNN) pipeline translates theoretical model specifications into executable Python code utilizing the discopy framework for categorical diagram generation. Unlike the other numerical backends (PyMDP, JAX, RxInfer, ActiveInference.jl, PyTorch, NumPyro), DisCoPy does not run a numerical simulation of the Active Inference perception-action loop. Instead, it constructs a compositional representation of the model architecture as string diagrams in a monoidal category, providing structural analysis and formal verification of the model's type-theoretic properties.
Within the GNN cross-framework comparison, DisCoPy serves as the categorical semantics reference — validating that the model's compositional structure (morphisms, types, and their compositions) is well-formed.
The DisCoPy implementation consists of three interconnected layers:
- Parameter Parsing:
pomdp_processor.py→discopy_renderer.py(Extracting GNN variable dimensions and connection topology) - Diagram Generation:
DisCoPyRenderer._generate_discopy_diagram_code()(Building the Python script defining categorical types, morphisms, and their compositions) - Execution Context:
discopy_executor.py(Executing the generated Python script with syntax validation and log persistence)
DisCoPy extracts dimensions through GNN's model parameters and variable definitions:
model_params = gnn_spec.get('model_parameters', {})
num_states = model_params.get('num_hidden_states', 3)
num_observations = model_params.get('num_obs', 3)
num_actions = model_params.get('num_actions', 3)
# Override from variable definitions if available
for var in gnn_spec.get('variables', []):
if var.get('name') == 'A' and 'dimensions' in var:
num_observations = var['dimensions'][0]
num_states = var['dimensions'][1]
elif var.get('name') == 'B' and 'dimensions' in var:
num_actions = var['dimensions'][2]Unlike the numerical frameworks, DisCoPy does not inject actual matrix values. It only uses dimensional information to parameterize the type system.
DisCoPy defines four fundamental types representing the Active Inference model's abstract spaces:
S = Ty('S') # Hidden states
O = Ty('O') # Observations
A = Ty('A') # Actions
P = Ty('P') # ProbabilitiesThese types form the objects of the monoidal category in which the Active Inference model is expressed.
Each GNN model component is represented as a morphism (Box) in the category, with explicit domain and codomain types:
| Component | Box Name | Domain | Codomain | Interpretation |
|---|---|---|---|---|
| A matrix | 'A' |
S |
O ⊗ P |
Observation likelihood `P(o\ |
| B matrix | 'B' |
S ⊗ A |
S ⊗ P |
State transition `P(s'\ |
| C vector | 'C' |
I (unit) |
O ⊗ P |
Preferred observations |
| D vector | 'D' |
I (unit) |
S ⊗ P |
Prior state beliefs |
| E vector | 'E' |
I (unit) |
A ⊗ P |
Policy priors |
| State Inference | 'StateInf' |
O |
S ⊗ P |
Posterior inference |
| Policy Inference | 'PolicyInf' |
S ⊗ P |
A ⊗ P |
Policy evaluation |
| Action Selection | 'ActionSel' |
A ⊗ P |
A |
Action sampling |
The @ operator in DisCoPy represents the tensor product of types. For example, S @ A denotes the product type "state combined with action", which is the natural domain of the transition morphism B.
The core Active Inference loop is expressed as a sequential composition of morphisms:
perception_action_loop = (
state_inf # O → S ⊗ P
>> policy_inf # S ⊗ P → A ⊗ P
>> action_sel # A ⊗ P → A
)This composition >> represents the functorial mapping:
O → S ⊗ P → A ⊗ P → A
The loop takes an observation and produces an action through sequential inference stages.
The full generative model composes the prior vectors (D, C) as parallel tensor products alongside the perception-action loop. If the type composition succeeds, the result is a properly-typed categorical circuit (D ⊗ C) >> StateInf ⊗ Id(P) >> PolicyInf >> ActionSel. If type-dimensional constraints prevent composition, the system falls back gracefully to the perception-action loop alone.
The analyze_circuit_structure() function performs type-theoretic validation:
analysis_results = {
'num_components': len(components), # 8 components total
'loop_domain': str(loop.dom), # 'O'
'loop_codomain': str(loop.cod), # 'A'
'model_domain': str(model.dom), # 'O'
'model_codomain': str(model.cod) # 'A'
}Key structural properties verified:
- Type consistency: All morphism compositions are well-typed (domain of next = codomain of previous)
- Composition depth: Number of sequential morphism stages
- Component count: Total number of defined morphisms
DisCoPy exports two JSON files to discopy_diagrams/:
Contains the structural analysis results:
{
"num_components": 8,
"loop_domain": "O",
"loop_codomain": "A",
"model_domain": "O",
"model_codomain": "A"
}Contains the complete circuit metadata:
{
"model_name": "Active Inference POMDP Agent",
"timestamp": "2026-02-22T...",
"parameters": {
"num_states": 3,
"num_observations": 3,
"num_actions": 3
},
"components": [
"A_matrix", "B_matrix", "C_vector", "D_vector",
"E_vector", "state_inference", "policy_inference",
"action_selection"
],
"analysis": { ... }
}DisCoPy does not produce beliefs, actions, observations, or efe_history arrays. It appears in the cross-framework comparison report with ❌ for all numerical data coverage columns. This is by design — its role is structural, not computational.
| Aspect | DisCoPy | Numerical Frameworks |
|---|---|---|
| Purpose | Structural validation | Computational simulation |
| Output | Category diagrams, type analysis | Belief/action/EFE trajectories |
| GNN Data Used | Dimensions, variable names, connections | Full matrix values |
| Numerical Results | None | beliefs, actions, observations, EFE |
| Validation | Type-consistency of compositions | Beliefs sum to 1, actions in range |
| Package | Purpose |
|---|---|
discopy |
Categorical diagram construction and composition |
discopy.monoidal |
Ty, Box, Id primitives |
discopy.drawing |
Equation visualization (structural) |
numpy |
Numerical utilities |
json |
Telemetry serialization |
| Pipeline Stage | Module | Key Function | Lines |
|---|---|---|---|
| Rendering | discopy_renderer.py | _generate_discopy_diagram_code() |
— |
| Entry Point | discopy_renderer.py | render_gnn_to_discopy() |
— |
| GNN Parsing | discopy_renderer.py | _parse_gnn_content() |
— |
| Execution | discopy_executor.py | execute_discopy_script() |
L37-137 |
| Validation | discopy_executor.py | DisCoPyExecutor.validate_diagram() |
L156-182 |
| Analysis | analyzer.py | generate_analysis_from_logs() |
— |
| Visualization | analyzer.py | create_discopy_visualizations() |
— |
| Data Extraction | analyzer.py | extract_circuit_data() |
— |
| Structure Analysis | analyzer.py | analyze_diagram_structure() |
— |
| ID | Area | Description | Impact |
|---|---|---|---|
| D-1 | Execution | execute_discopy_script() in discopy_executor.py with syntax validation and log persistence |
✅ FIXED |
| D-2 | Rendering | No actual matrix values are injected into the generated script — only dimensions | Low |
| D-3 | Rendering | generative_model was just assigned to perception_action_loopD_vector @ C_vector priors with graceful handling |
✅ FIXED |
| D-4 | Analysis | create_discopy_visualizations() at 250 lines generates visualizations from circuit_info.json but could also render actual string diagrams using discopy.drawing if matplotlib is available |
Medium |
| D-5 | Telemetry | No numerical simulation data exported — could add an optional mode to evaluate the circuit with actual tensor values using discopy.tensor |
Low |
- Cross-Framework Methodology: Details on the correlation methodology and benchmarking metrics.
- Architecture Reference: Deep dive into the pipeline orchestrator and module integration.
- GNN Implementations Index: Return to the master framework implementer manifest.
- Back to GNN START_HERE