Computation Model¶
Graphcal programs describe a directed acyclic graph (DAG) of computations. This page covers the core model: declaration kinds, the @ sigil, evaluation semantics, and naming conventions.
Declaration Kinds¶
Every top-level declaration belongs to one of four kinds:
| Kind | Keyword | Semantics | In DAG? |
|---|---|---|---|
| Parameter | param |
Named DAG input port, optionally with a default value | Yes |
| Node | node |
Computed value derived from other values | Yes |
| Constant | const node |
Compile-time immutable value | No |
| Assertion | assert |
Post-evaluation boolean check | No |
Parameters¶
param dry_mass: Mass = 1200.0 kg; // optional param (has default)
param fuel_mass: Mass; // required param (no default)
A param declares a named input port in the computation graph; it is not an
ordinary private declaration that becomes implicitly public. The declaration
kind itself supplies the external-input role, so params never take pub or
pub(bind):
- A param in the entry DAG can be supplied with
--param,--params-json, or--params-json-file. - A param in a callable DAG can be supplied by name in an
includebinding or inline DAG call. - Its effective value is also externally readable: callers may select it from
an
includeor project it from an inline call. The result is the supplied binding, or the default when no binding was supplied. - A param with a default (
= expr) evaluates that expression when the caller does not supply the port. A default is an ordinary runtime graph expression: it may reference params, nodes, and const nodes with@. - A param without a default is required. Leaving a required port unsatisfied is a compile error.
Defaults participate in the same dependency graph as nodes. They are evaluated in topological dependency order rather than source order, and a cycle through a param default is rejected like any other graph cycle. Overriding a dependency causes every unsupplied dependent default to evaluate from the overridden value. Supplying the defaulted param itself replaces its default expression for that DAG instance.
param base_thrust: Dimensionless = 10.0;
param margin: Dimensionless = @base_thrust * 2.0;
node total: Dimensionless = @margin + 1.0;
Here, overriding base_thrust with 100.0 makes the unsupplied margin equal
200.0; overriding margin itself bypasses @base_thrust * 2.0.
Use a private node for an internal computed value or a private const node
for an internal fixed value. If a sub-computation needs internal
parameterization, put it behind a private DAG or module boundary rather than
using an "internal param" naming convention.
Parameters (and nodes) can carry domain constraints that declare valid value ranges, checked at runtime:
See Type System — Domain Constraints for details.
Nodes¶
Nodes are computed values. Their expressions can reference parameters, other nodes, and constants. Graphcal evaluates nodes in topological order determined by the dependency graph.
Unfinished nodes¶
To design a dependency graph before writing its formulas, give an entire node a todo definition:
param isp: Time = 320.0 s;
const node g0: Acceleration = 9.80665 m/s^2;
node v_exhaust: Velocity = todo { @isp, @g0 };
node mass_ratio: Dimensionless = 3.0;
node delta_v: Velocity = @v_exhaust * ln(@mass_ratio);
The type is mandatory. The braces list all possible direct dependencies while the node is unfinished; transitive dependencies need not be listed. An explicitly empty interface is todo {}. References have the usual name, visibility, and cycle checks. Constant references are permitted without making constants runtime DAG nodes.
Here, mass_ratio evaluates, v_exhaust is TODO, and delta_v is BLOCKED. Blocking follows the static dependency graph, including references in unselected branches. These outcomes are not values, null, or evaluation errors. todo is a whole-node definition, not a function or an expression hole.
Replace todo { … } with a formula to finish the node. The formula then determines its dependencies; no historical contract remains. check accepts otherwise-valid unfinished models and summarizes them (--deny-todo rejects them). eval prints partial results but exits unsuccessfully unless --allow-incomplete is given. That flag never suppresses genuine evaluation or assertion failures.
Constants¶
Constants are evaluated at compile time before the DAG is built. They cannot reference parameters or nodes. DAG calls are anonymous runtime includes, so they are also prohibited in const node expressions and compile-time domain bounds.
Assertions¶
Assertions are post-evaluation checks. They can reference parameters and nodes but are not part of the DAG -- no other declaration can reference an assert. Assertions are always evaluated last, after the entire graph. See Assertions and Attributes for full details.
The @ Sigil¶
The @ prefix is the central scoping mechanism:
| Reference | Meaning | Allowed in |
|---|---|---|
@name |
Parameter, node, or const node in the graph | node expressions, param defaults, dag block bodies |
@dag(args)::out |
Runtime DAG instantiation projecting one output | Runtime expressions, including param defaults |
NAME |
Built-in constant (PI, E, TAU, etc.) |
Everywhere |
name |
Local variable (loop variable, match binding) | Expression bodies |
All user graph declarations require @, including const node values. A bare
identifier never refers to a param, node, or const node; omitting the
sigil is a compile error. This keeps every runtime and compile-time dependency
edge explicit, so scheduling and cycle detection see the same graph expressed
by the source.
Time-scale spellings such as UTC and TT live in a separate static
namespace. A graph declaration may reuse one: @UTC selects the graph value,
while Datetime<UTC> and epoch<UTC>(...) select the time scale. Bare UTC
in an ordinary value expression remains a type error. Built-in numeric
constants such as E and PI are different: their spellings stay reserved in
the graph namespace because a missing @ could otherwise select a valid
numeric value silently.
Where @ Is Allowed¶
| Context | @ Allowed? |
|---|---|
node expression |
Yes |
param default value |
Yes |
const node expression |
No |
dag block body (inside node expressions) |
Yes |
Evaluation Order¶
- Parse -- Source files are parsed into an AST
- Resolve -- Imports are loaded and references are resolved
- Dimension check -- All expressions are checked for dimensional consistency
- Const evaluation -- Constants are evaluated in dependency order
- DAG construction -- A dependency graph is built from
paramdefaults andnodedeclarations - Topological evaluation -- Unsupplied param defaults and nodes are evaluated in dependency order
- Assertion checking -- Assert declarations are evaluated and reported
Cycle Detection¶
Circular dependencies between nodes and param defaults are detected at compile time:
Call-Site Identity¶
When a dag is instantiated — whether via top-level include or via the
inline @dag(args)::out expression form — each syntactic call site is a
fresh instantiation. Two textually distinct occurrences with identical
arguments denote two distinct sub-graphs in the underlying DAG, not a shared
sub-graph. Programs must not rely on sharing across call sites.
Fault Isolation¶
If a node's evaluation fails (e.g., division by zero), only that node and its dependents are affected. Independent nodes still evaluate successfully.
Naming Conventions¶
Graphcal recommends the following naming conventions:
| Declaration | Recommended Convention | Example |
|---|---|---|
param |
lower_snake_case |
dry_mass |
node |
lower_snake_case |
total_dv |
const node |
lower_snake_case |
g0, margin_factor |
assert |
lower_snake_case |
fuel_positive |
dag |
lower_snake_case |
orbital_velocity |
type |
PascalCase |
TransferResult |
dim |
PascalCase |
Velocity |
index |
PascalCase |
Maneuver |
unit |
(various) | km, kN, MPa |
These conventions are not enforced by the compiler, but following them is strongly recommended for consistency and readability.
Comments¶
Graphcal supports line comments:
A /// doc comment documents the declaration that follows it. A doc block is
a contiguous run of /// lines placed directly above the declaration (no
blank line or // comment in between); it attaches through any leading
attributes. Doc comments never change program semantics — they surface in
editor hover (LSP) and as captions in generated output:
Note that //// (four or more slashes) is an ordinary line comment, and a
/// comment placed at the end of a code line documents nothing.