Part I: Overview
Version: 1.0 Draft Last Updated: 2025-12-20
1. Introduction
1.1 What is HGraph?
HGraph is a functional reactive programming (FRP) framework implementing event-driven computation through a forward propagation graph (FPG) paradigm. It provides:
A Python DSL for defining reactive computations
A type-safe time-series abstraction layer
An efficient runtime for event propagation
Support for both simulation and real-time execution
1.2 Design Goals
Declarative: Programs describe what to compute, not when
Type-Safe: Full type checking at graph construction time
Reactive: Computation is triggered by data changes
Composable: Complex graphs built from simple, reusable components
Deterministic: Same inputs produce same outputs (for simulation)
Efficient: Minimal unnecessary computation through change tracking
1.3 Key Innovation
HGraph unifies several programming paradigms:
graph TD
subgraph "HGraph Unifies"
FRP[Functional Reactive Programming]
DF[Dataflow Programming]
EP[Event Processing]
ST[Stream Processing]
end
FRP --> HG[HGraph]
DF --> HG
EP --> HG
ST --> HG
2. Core Concepts
2.1 Forward Propagation Graph (FPG)
An HGraph program is a directed acyclic graph (DAG) where:
Nodes are computation units (functions decorated with
@compute_node,@sink_node, etc.)Edges are connections that carry time-series values
Evaluation proceeds forward from sources to sinks
graph LR
subgraph "Source Layer"
S1[Push Source]
S2[Pull Source]
S3[Const]
end
subgraph "Compute Layer"
C1[Compute Node]
C2[Compute Node]
C3[Compute Node]
end
subgraph "Sink Layer"
K1[Sink Node]
K2[Sink Node]
end
S1 --> C1
S2 --> C1
S3 --> C2
C1 --> C3
C2 --> C3
C3 --> K1
C3 --> K2
2.2 Time-Series
A time-series is a sequence of values that change over discrete time points (ticks):
Time: t1 t2 t3 t4 t5
│ │ │ │ │
TS[int]: 10 → 10 → 15 → 15 → 20
(no change) (modified) (modified)
Properties of time-series:
Property |
Description |
|---|---|
|
Current value at this tick |
|
Change since last tick (type-specific) |
|
True if value changed this tick |
|
True if value exists and can be read |
|
Time of most recent modification |
2.3 Nodes
Nodes are the computational units of HGraph:
graph TB
subgraph "Node Types"
direction LR
SRC[Source Nodes]
CMP[Compute Nodes]
SNK[Sink Nodes]
GRP[Graph Nodes]
end
SRC --> |"Inject data"| E[Edges]
E --> |"Transform"| CMP
CMP --> E2[Edges]
E2 --> |"Consume"| SNK
GRP --> |"Compose"| SRC
GRP --> |"Compose"| CMP
GRP --> |"Compose"| SNK
Node Type |
Purpose |
Has Output? |
|---|---|---|
|
Inject async events |
Yes |
|
Generate scheduled values |
Yes |
|
Transform time-series |
Yes |
|
Perform side effects |
No |
|
Compose other nodes |
Depends |
2.4 Evaluation Model
HGraph uses a push-based reactive evaluation model:
sequenceDiagram
participant Source
participant Engine
participant Node1
participant Node2
participant Sink
Source->>Engine: Value modified
Engine->>Engine: Schedule dependent nodes
Engine->>Node1: Evaluate
Node1->>Node1: Read inputs, compute
Node1->>Engine: Output modified
Engine->>Node2: Evaluate
Node2->>Node2: Read inputs, compute
Node2->>Engine: Output modified
Engine->>Sink: Evaluate
Sink->>Sink: Perform side effect
Key evaluation rules:
Nodes only evaluate when their active inputs are modified
Nodes only evaluate when their valid inputs have values
Evaluation proceeds in topological order (sources before dependents)
All nodes scheduled for a tick evaluate before time advances
3. Two-Phase Architecture
HGraph separates graph construction (wiring) from execution (runtime):
graph TB
subgraph "Phase 1: Wiring (Compile-Time)"
D[Decorated Functions]
W[Wiring Engine]
G[Graph Builder]
D --> W
W --> |"Type Check"| W
W --> |"Resolve Types"| W
W --> G
end
subgraph "Phase 2: Runtime (Execution)"
G --> R[Runtime Graph]
R --> E[Evaluation Engine]
E --> |"Tick"| E
end
style D fill:#e1f5fe
style G fill:#fff3e0
style R fill:#e8f5e9
3.1 Wiring Phase
During wiring:
Parse decorators and extract signatures
Resolve type variables from concrete inputs
Validate type compatibility between connections
Build graph structure (nodes and edges)
Create builders for runtime instantiation
3.2 Runtime Phase
During runtime:
Initialize all nodes in topological order
Start nodes (create threads, open resources)
Evaluate in a loop until end time or stop requested
Stop nodes (cleanup threads)
Dispose all nodes in reverse topological order
4. Execution Modes
HGraph supports two execution modes:
4.1 Simulation Mode
evaluate_graph(my_graph,
run_mode=EvaluationMode.SIMULATION,
start_time=datetime(2024, 1, 1),
end_time=datetime(2024, 1, 2))
Characteristics:
Time advances as fast as possible
Push sources not allowed
Deterministic and reproducible
now=evaluation_time+ computation time
4.2 Real-Time Mode
evaluate_graph(my_graph,
run_mode=EvaluationMode.REAL_TIME)
Characteristics:
Time advances with wall clock
Push sources allowed
Engine sleeps between scheduled events
now= actual wall clock time
5. Type System Overview
HGraph has a rich type system with two major branches:
graph TD
HT[HgTypeMetaData]
HT --> SC[Scalar Types]
HT --> TS[Time-Series Types]
SC --> AT[Atomic Types<br/>int, str, float, bool, etc.]
SC --> CT[Collection Types<br/>tuple, set, dict, list]
SC --> CS[Compound Scalar<br/>dataclass-based]
SC --> IT[Injectable Types<br/>STATE, SCHEDULER, etc.]
TS --> TSV[TS - Scalar Value]
TS --> TSB[TSB - Bundle]
TS --> TSL[TSL - List]
TS --> TSD[TSD - Dict]
TS --> TSS[TSS - Set]
TS --> TSW[TSW - Window]
TS --> REF[REF - Reference]
5.1 Scalar Types
Values that exist at a single point in time:
Atomic:
int,float,str,bool,datetime,timedelta, etc.Collections:
tuple[T, ...],set[T],dict[K, V]Compound: Dataclass-based composite types
5.2 Time-Series Types
Values that change over time:
Type |
Description |
|---|---|
|
Single scalar value changing over time |
|
Bundle of named time-series fields |
|
Fixed-size list of time-series |
|
Dynamic dictionary of time-series |
|
Set with add/remove tracking |
|
Sliding window buffer |
|
Indirect reference to another time-series |
6. Hello World Example
from hgraph import graph, TS, const, debug_print, run_graph
@graph
def hello_world():
message = const("Hello, HGraph!")
debug_print("Message", message)
run_graph(hello_world)
Output:
[1970-01-01 00:00:00.000001][1] Message: Hello, HGraph!
Explanation:
@graphmarkshello_worldas a wiring functionconst("Hello, HGraph!")creates aTS[str]sourcedebug_printis a sink node that outputs to consolerun_graphconstructs and executes the graph
7. Component Architecture
graph TB
subgraph "User Code"
UC[Python Functions<br/>with Decorators]
end
subgraph "Wiring Layer"
WC[Wiring Context]
WS[Wiring Signatures]
WN[Wiring Nodes]
WP[Wiring Ports]
GB[Graph Builder]
end
subgraph "Builder Layer"
NB[Node Builders]
TB[Type Builders]
IB[Input/Output Builders]
end
subgraph "Runtime Layer"
G[Graph]
N[Nodes]
IO[Inputs/Outputs]
SCH[Scheduler]
CLK[Clock]
ENG[Engine]
end
UC --> WC
WC --> WS
WS --> WN
WN --> WP
WP --> GB
GB --> NB
NB --> TB
TB --> IB
IB --> G
G --> N
N --> IO
G --> SCH
G --> CLK
SCH --> ENG
8. Lifecycle Overview
All runtime components follow a consistent lifecycle:
stateDiagram-v2
[*] --> Constructed
Constructed --> Initialized: initialise()
Initialized --> Started: start()
Started --> Evaluating: eval()
Evaluating --> Started: tick complete
Started --> Stopped: stop()
Stopped --> Started: start()
Stopped --> Disposed: dispose()
Disposed --> [*]
Phase |
Description |
|---|---|
Constructed |
Object created, properties set |
Initialized |
One-time setup, cache preparation |
Started |
Active operation, resources acquired |
Evaluating |
Processing data in evaluation loop |
Stopped |
Paused, resources may be released |
Disposed |
Final cleanup, no recovery possible |
9. Key Invariants
9.1 Modification Tracking
modified = (evaluation_time == last_modified_time)
A time-series is modified only if it was changed in the current tick.
9.2 Notification Idempotency
Notifications are deduplicated per tick:
if notify_time != current_time:
notify_time = current_time
propagate_to_parent()
9.3 Topological Ordering
Nodes are always evaluated in topological order:
Sources before compute nodes
Compute nodes before their dependents
All predecessors evaluated before any successor
9.4 Validity Propagation
input.valid = input.bound and input.output.valid
An input is valid only if bound to a valid output.
10. Reference Locations
Component |
Python Location |
C++ Location |
|---|---|---|
Types |
|
|
Wiring |
|
N/A (Python only) |
Runtime |
|
|
Builders |
|
|
Operators |
|
N/A (Python only) |
11. Next Steps
Continue to:
02_TYPE_SYSTEM.md - Complete type system specification
03_WIRING_SYSTEM.md - Graph construction details
04_RUNTIME_SYSTEM.md - Execution semantics