Browse the docs · Apsi 1.10.0
The Apsi Language Reference
Version 1.10 · source files use the extension .apsi and the UTF-8 encoding.
This document is the complete reference for Apsi: how programs are written, what every construct means, every
built-in function and module, the interactive REPL and the apsi command line. Read it from the top to learn the
language, or jump to a chapter.
Contents
- A first look
- Running programs
- Lexical structure
- Values and types
- Variables and scope
- Expressions and operators
- Statements and control flow
- Functions
- Structs and records
- Physical units
- Measurement uncertainty
- Tensors and shapes
- Precision and number formats
evolve— differential equations- Automatic differentiation
- Modules and imports
- Tests
- The checker and errors
- Permissions and security
- Execution engines
- Built-in functions
- Standard modules
- The REPL
- The command line
- Projects and
apsi.toml - Style guide
- Grammar
1. A first look
# orbit.apsi — a few lines of everything
use physics.constants # c, G, M_earth, R_earth, …
struct Body:
mass: Mass
radius: Length = 1m
fn surface_gravity(self) -> Acceleration = G * self.mass / self.radius ** 2
let earth = Body(M_earth, radius=R_earth)
print(f"g = {earth.surface_gravity():.3f}") # g = 9.820 m/s^2
fn kinetic(m: Mass, v: Velocity) -> Energy = 0.5 * m * v ** 2
print(kinetic(2kg, 3[m/s])) # 9.0 J
print(1[kWh] as [MJ]) # 3.6 MJ
let squares = [i ** 2 for i in 0..10 if i % 2 == 0]
print(squares |> sum) # 120
test "kinetic energy":
assert kinetic(1kg, 2[m/s]) ~= 2J
What to notice:
- blocks are made by indentation, as in Python;
- numbers can carry units (
2kg,3[m/s]), and units are checked before the program runs; letdeclares a constant,vara variable;fndefines functions,structdefines record types with fields and methods;testblocks live next to the code and run withapsi test.
2. Running programs
apsi hello.apsi # run a file (same as: apsi run hello.apsi)
apsi run hello.apsi a b # program arguments, read with args()
apsi check hello.apsi # check only, do not run
apsi test # run test blocks (default: tests/ of the project)
apsi # interactive REPL
apsi -e "sqrt(2) * 3m" # evaluate one expression
apsi new myproject # create a project folder
Every run first parses the whole file, then the checker reports every error it can prove (unit mismatches, undefined names, wrong arguments, …) without running a single line, then the program runs. Chapter 24 lists every command and flag.
On Linux a file starting with #!/usr/bin/env apsi can be made executable with chmod +x.
3. Lexical structure
3.1 Encoding and lines
-
Source files are UTF-8. A byte-order mark (as written by Windows Notepad) and CRLF line endings are accepted.
-
A comment starts with
#and runs to the end of the line. -
Statements end at the end of a line.
;separates several statements on one line:let a = 1; let b = 2. -
Inside
(),[]and{}lines may be broken freely. -
Outside brackets, a line ending in
\continues on the next line. -
A line that starts with
|>always continues the previous line, so pipelines can be written vertically:let ds = from_tensors(X, y, batch=256) |> map(augment, workers=8) |> prefetch(4)
3.2 Indentation
A block follows a line ending in : and is indented further than that line. Use 4 spaces (a tab counts as 4).
The block ends where the indentation returns to an outer level. A block of a single statement may stay on the
same line: if x < 0: return 0.
3.3 Identifiers
Names start with a letter or _ and continue with letters, digits or _. Any Unicode letter is allowed:
let Δt = 1[ms], let ω = 2 * pi * 50[Hz], let größe = 3m. Names starting with _ are private to their file (not exported by
import). Names are case-sensitive.
3.4 Keywords
Reserved everywhere:
let var fn return if elif else while for in break continue struct
true false none and or not use import assert evolve as pass
Contextual (special only in one place, ordinary names elsewhere): test (starts a test block),
over step using tol record every into (inside evolve).
3.5 Numbers
| Form | Examples | Type |
|---|---|---|
| integer | 42, 1_000_000 |
Int |
| decimal | 3.14, 0.5, 6.02e23, 1e-9, 2.5E+3 |
Float |
| with a unit (letters right after the digits, no space) | 5m, 2kg, 300K, 10ms, 3eV, 5µs |
Quantity |
| with a bracketed unit | 9.81[m/s^2], 8.314[J/(mol K)], 1[N*m], 5[m²] |
Quantity |
_ may separate digit groups. A number written directly before a name is always read as a unit: write
2 * x, not 2x.
3.6 Strings
| Form | Example |
|---|---|
| plain | "text" |
| multi-line | """line 1 ⏎ line 2""" |
| formatted (f-string) | f"x = {x:.3f}, list has {len(xs)} items" |
| formatted, multi-line | f"""…{expr}…""" |
Escapes: \n (newline), \t (tab), \r, \\, \", \0. Any other backslash is kept as it is, so Windows paths
such as "C:\data\x.csv" mostly work, but "C:\\data\\x.csv" or "C:/data/x.csv" is clearer.
In f-strings, {expression} inserts a value and {expression:spec} formats it with a Python-style format spec
(.3f, >8, ,, e, %, …). For quantities the spec formats the number and the unit is appended:
f"{9.81[m/s^2]:.1f}" → 9.8 m/s^2. {{ and }} produce literal braces.
3.7 Operator characters
+ - * / // % ** ^ @ == != < <= > >= ~= = += -= *= /= //= %= **= @= -> => |> .. ..= . , : ' ( ) [ ] { }
Unicode alternatives: × and · for *, − for -, ≤ ≥ ≠ ≈ for <= >= != ~=, → for ->, ± (or +/-)
for uncertainty.
4. Values and types
| Type | Examples | Notes |
|---|---|---|
Int |
42, -7, 1_000 |
arbitrary precision |
Float |
3.14, 6.02e23, inf, nan |
IEEE 754 double |
Quantity (shown as its dimension, e.g. Length) |
2kg, 9.81[m/s^2], 1.5[kWh] |
number + SI dimension, see chapter 10 |
Exact |
exact("1/3"), values inside @precision(exact) |
exact fractions |
Uncertain |
9.81[m/s^2] ± 0.02[m/s^2], 3.0 +/- 0.1 |
see chapter 11 |
Bool |
true, false |
|
Str |
"text", f"x = {x}" |
immutable Unicode text |
None |
none |
"no value" |
List |
[1, 2, 3], [] |
mutable, any element types |
Tuple |
(1, 2), (x,), a, b |
immutable |
Dict |
{"k": 1}, {} |
keys are usually strings or numbers |
Range |
0..10 (10 excluded), 0..=10 (10 included) |
lazy integer ranges, also with units: 0s..10s |
Tensor |
tensor([[1, 2], [3, 4]]), zeros(3, 3), randn(64, 128) |
n-dimensional arrays, may carry a unit |
Record |
record(W=w, b=b) |
ad-hoc container with named fields |
| struct instance | Particle(...) |
see chapter 9 |
Fn |
fn f(x) = …, fn(x) => x * 2, built-ins |
functions are values |
type(x) returns the type name as a string ("Int", "Length", "Tensor", …).
Truthiness. In if, while, and, or and not: false, none, 0, 0.0, empty strings, lists, tuples
and dicts are false; everything else is true.
Equality. == compares values (lists element by element, quantities with their units). a ~= b compares
approximately (relative tolerance 1e-6) and also checks units.
4.1 Strings
Strings support + (concatenation), * with an Int (repetition), indexing s[0], slicing s[1:4], len(s),
in (substring test) and the usual methods:
let s = "Hello, World"
s.upper() s.lower() s.strip() s.split(", ") s.replace("World", "Apsi")
s.startswith("He") s.endswith("!") s.find("o") s.count("l") s.title()
", ".join(["a", "b"]) # or join(["a", "b"], ", ")
chars(s) # list of characters
str(42) + " items" # str() converts anything, int("42"), float("1.5") parse
4.2 Lists, tuples and dicts
var xs = [3, 1, 2]
xs.append(4) # also extend, insert, pop, remove, index, count, sort, reverse, clear, copy
xs[0] xs[-1] xs[1:3] xs[::2]
2 in xs # membership
[1, 2] + [3] # concatenation
len(xs) sorted(xs) reversed(xs) sum(xs) min(xs) max(xs)
let t = (1, "a")
let a, b = t # unpacking
var d = {"a": 1}
d["b"] = 2
d.get("c", 0) d.keys() d.values() d.items() "a" in d keys(d)
4.3 Comprehensions
[x * x for x in 0..10]
[x for x in data if x > 0]
[(i, j) for i in 0..3 for j in 0..3 if i != j]
{k: v * 2 for k, v in d.items()}
5. Variables and scope
let x = 10 # constant: cannot be assigned again
var y = 0 # variable: can change
let a, b = 1, 2 # several names at once (unpacks any sequence; one level, no nesting)
let v: Velocity = 3[m/s] # optional type annotation, checked
y += 1 # += -= *= /= //= %= **= @=
xs[0] = 5 # element assignment (lists, dicts, tensors)
p.mass = 2kg # field assignment (structs, records)
- A name must be declared with
letorvarbefore it is used — a typo is an error, not a new variable. - A
varthat holds a physical quantity keeps its dimension: assigning aTimeto aLengthvariable is an error. - Blocks (
if,for,while, function bodies) open a new scope. Names declared inside are not visible outside. - Loop variables and function parameters are constants.
- Functions can read and modify (
var) variables of the scopes around them (closures). - Declaring the same name again in an inner scope shadows the outer one.
6. Expressions and operators
6.1 Precedence (lowest first)
| Level | Operators | Notes |
|---|---|---|
| lambda | fn(x) => expr |
|
| pipe | x |> f |
x |> f is f(x); x |> f(a) is f(x, a) |
| conditional | a if cond else b |
|
| logical | or, and, not |
short-circuit |
| comparison | == != < <= > >= ~= in not in |
chain: 0 < x < 1 |
| range / conversion | a..b, a..=b, x as [unit] |
|
| additive | + - |
|
| multiplicative | * / // % @ |
@ is matrix multiplication |
| unary | -x, +x |
|
| uncertainty | x ± σ, x +/- σ |
binds tighter than * |
| power | ** or ^ |
right-associative: 2**3**2 == 512, -2**2 == -4 |
| postfix | f(a, k=v), x[i, j], x[1:5:2], x.field |
6.2 Arithmetic
/ always divides exactly (7 / 2 == 3.5); // is floor division (-7 // 2 == -4); % is the remainder with
the sign of the divisor. Arithmetic on quantities follows the unit rules in chapter 10. Arithmetic on tensors is
element-wise with NumPy broadcasting.
6.3 Pipes
data |> normalize |> train(model) # train(normalize(data), model)
[1, 2, 3] |> sum # 6
6.4 Indexing and slicing
x[i], x[-1] (from the end), x[a:b], x[a:b:step], x[:, 0] (tensor column), x[i, j] (tensor element),
x[mask] (tensor boolean mask), x[idx_tensor] (gather).
6.5 Conditional expression
let label = "even" if n % 2 == 0 else "odd"
6.6 Approximate comparison
a ~= b is true when |a - b| <= 1e-6 · max(|a|, |b|) (and units match). For other tolerances use
approx(a, b, rel=1e-9, atol=1e-12).
7. Statements and control flow
if x > 0:
print("positive")
elif x == 0:
print("zero")
else:
print("negative")
while n > 1:
n = n // 2
for i in 0..10: # 0, 1, …, 9
if i == 3:
continue
if i == 7:
break
print(i)
for i, item in enumerate(items): # with an index
pass # does nothing
for k, v in d.items():
print(k, v)
assert total > 0, "total must be positive"
foriterates over ranges, lists, tuples, strings, dict keys, tensors (first axis), data pipelines and channels.breakleaves the innermost loop;continuestarts its next iteration.passis an empty statement for blocks that intentionally do nothing.assert condstops with anAssertionErrorwhencondis false. Without a message, the values of both sides of a comparison are shown:assert x == 3→AssertionError: assertion failed: 4 == 3.returnleaves a function (chapter 8).returnoutside a function is an error.error("message")raises aRuntimeErrorwith your message;exit(code)stops the program.
8. Functions
8.1 Definitions
fn area(r: Length) -> Area = pi * r ** 2 # expression body
fn solve(a, b = 1.0, tol = 1e-9): # block body
var x = a
while abs(x - b) > tol:
x = (x + b) / 2
return x
fn energy(m: Mass, v: Velocity) -> Energy = # expression body on the next line
0.5 * m * v ** 2
let double = fn(x) => x * 2 # anonymous function (lambda)
- A block body without
returnreturnsnone.return a, breturns a tuple. - Parameters may have defaults (
b = 1.0). Arguments may be passed by position or by name (solve(1, tol=1e-6)). Unknown names and wrong counts are reported by the checker. - Functions are values: pass them as arguments, return them, store them in lists and records.
- Functions are closures: they capture the variables around them, and nested functions may modify outer
vars. - Recursion is allowed (the limit is deep — 20,000 frames).
8.2 Type annotations
Annotations on parameters, results and variables are checked by the checker and again at run time.
| Annotation | Means |
|---|---|
Float, Int, Bool, Str, List, Tuple, Dict, Record, Fn, Any |
basic types (Float accepts Int) |
Length, Mass, Time, Velocity, Force, Energy, … |
physical quantity of that dimension (list in 10.6) |
[m/s], [J/K] |
a dimension written as a unit |
Tensor[B, T, D] |
tensor whose shape matches the symbols |
Tensor[N, 3, Length] |
tensor with a unit for its elements |
Tensor[_, 10, f32] |
_ = any size; a dtype converts the tensor (see 13) |
Vec3[Velocity] |
same as Tensor[3, Velocity] (Vec, Vec2, Vec4, Mat, Matrix exist too) |
| a struct name | an instance of that struct |
8.3 Shape generics
fn attention[B, T, D](q: Tensor[B, T, D], k: Tensor[B, T, D], v: Tensor[B, T, D]) -> Tensor[B, T, D]:
let scores = q @ transpose(k, (0, 2, 1)) / sqrt(D)
return softmax(scores) @ v
Shape symbols (B, T, D) are bound to the real sizes at the call. Every argument must agree; otherwise a
ShapeError names the dimension that differs. Inside the function the symbols are ordinary numbers (sqrt(D)).
Writing [B, T, D] after the name is optional — symbols used in Tensor[...] are declared automatically.
8.4 Decorators
A decorator is any function that takes a function and returns a function; @name above fn applies it.
| Decorator | Effect |
|---|---|
@precision(exact) |
every number in the function is an exact fraction — no rounding at all |
@precision(f32) / f16 / f64 |
tensors entering and leaving the function are converted to that dtype |
@precision(bf16) / fp8_e4m3 / fp8_e5m2 / fp6_e3m2 / fp4_e2m1 / int8 / int4 |
tensors are rounded to what the format can represent |
@memo |
remember results by argument values |
@timed |
print how long each call takes |
@app.get("/path") etc. |
register a web route (chapter 22, web) |
@disk_cache("dir") |
remember results on disk across runs (use data) |
@memo
fn fib(n: Int) -> Int = n if n < 2 else fib(n - 1) + fib(n - 2)
print(fib(90)) # instant
9. Structs and records
9.1 Structs
struct Particle:
pos: Vec3[Length]
vel: Vec3[Velocity]
mass: Mass = 1kg # default value
fn kinetic(self) -> Energy = 0.5 * self.mass * dot(self.vel, self.vel)
fn moved(self, dt: Time):
return Particle(self.pos + self.vel * dt, self.vel, mass=self.mass)
let p = Particle(tensor([0.0, 0, 0]) * u.m, tensor([1.0, 0, 0]) * u.m / u.s)
p.kinetic() # 0.5 J
p.mass = 2kg # fields can be changed
print(p) # Particle(pos=…, vel=…, mass=2.0 kg)
- Fields are declared as
name: Typewith an optional= default; fields with defaults come last. - Construct with positional or named arguments:
Particle(pos, vel, mass=3kg). Field types are checked. - Methods are
fndefinitions inside the struct; the first parameter isself. - Struct names can be used as types in annotations.
9.2 Records
record(a=1, b=2) creates an untyped container with named fields — ideal for model parameters:
let layer = record(W=randn(4, 8), b=zeros(8))
layer.W # field access
9.3 Parameter trees (pytrees)
Structs, records, lists, tuples and dicts nest into trees. grad, value_and_grad, tree_map, optimizers and
param_count walk into every leaf, so a whole model is just a tree:
let model = [record(W=randn(2, 16), b=zeros(16)), record(W=randn(16, 1), b=zeros(1))]
let doubled = tree_map(fn(x) => 2 * x, model)
print(param_count(model)) # 65
10. Physical units
10.1 Writing quantities
- letters directly after a number:
5m,2kg,300K,10ms,3eV,5µs - brackets for compound units:
9.81[m/s^2],8.314[J/(mol K)],1[N*m],5[m²],3[kg m/s] - inside brackets:
*,·or a space multiply,/divides,^or**raise to a power (negative and fractional powers allowed:^-1,^(1/2)), and superscripts² ³ ⁻¹work u.<unit>is one of that unit:tensor([1, 2]) * u.km / u.h,x * u.eV- SI prefixes work on base and derived units: Y Z E P T G M k h da d c m u/µ n p f a —
km,µs,MeV,kWh,GPa,mmol
10.2 Available units
m g s A K mol cd Hz N Pa J W Wh C V F ohm/Ω S Wb T H eV L bar rad sr deg min h day yr au ly pc Å atm mmHg psi cal kcal Da ft inch mi lb mph kn rpm
10.3 Rules
+,-, comparisons and%need equal dimensions — except the number 0, which matches any unit (sovar total = 0can accumulate quantities).*,/and@multiply and divide dimensions;**needs a dimensionless exponent.- A dimensionless result becomes a plain
Float:6[km] / 2[km]is3.0. exp log sin cos tanh sigmoid softmax …require dimensionless arguments;sqrthalves the dimension,cbrtthirds it.- Values are stored in SI units; printing picks a readable unit (N, J, W, Pa, …).
- Unit errors are found by the checker before running whenever they can be proved; the rest are found at run time.
10.4 Conversion and access
let d = 12.5[km]
d as [m] # 12500.0 m (display unit; must be the same dimension)
(90deg) as [rad] # 1.5707963… — converting to a dimensionless unit gives a number
d.magnitude # 12.5 (number in the displayed unit)
d.value # 12500.0 (number in SI units)
d.unit # "km"
unit(d) dim(d) magnitude(d)
10.5 Unit-aware functions
Most built-ins accept quantities and give results with the right unit: abs, sqrt, min, max, sum,
mean, std, norm, dot, cross, linspace, arange, linalg.eigh (eigenvalues of a Hamiltonian in J),
integrate (unit [f]·[x]), fft frequencies (Hz when the step has a time unit), grad (∂Energy/∂Length is a Force).
10.6 Quantity types for annotations
Dimensionless, Angle, Length, Mass, Time, Current, Temperature, Amount, LuminousIntensity, Area, Volume, Velocity / Speed, Acceleration, Jerk, Force, Energy / Work / Torque, Power, Pressure / Stress, Momentum / Impulse, AngularMomentum / Action, Frequency / AngularVelocity, Density, Charge, Voltage, Resistance, Capacitance, Inductance, MagneticField, MagneticFlux, ElectricField, Entropy / HeatCapacity, Viscosity, Diffusivity / KinematicViscosity, WaveNumber, Concentration, MolarMass, ThermalConductivity, SpringConstant.
10.7 Physical constants (use physics.constants)
c G h hbar k_B N_A R_gas q_e m_e m_p m_n epsilon_0 mu_0 g0 sigma_SB a_0 alpha_fs M_sun M_earth R_earth AU
— CODATA values with units.
11. Measurement uncertainty
let g = 9.81[m/s^2] ± 0.02[m/s^2]
let L = 1.20m ± 0.01m
let T = 2 * pi * sqrt(L / g)
print(T) # (2.1975 ± 0.0094) s
print(L - L) # (0 ± 0) m — correlations are tracked exactly
value ± σ(orvalue +/- σ) creates a measured value that is an independent error source; σ is the standard uncertainty and must have the value's unit.±binds tighter than*and/:2 * 3.0 ± 0.1is2 * (3.0 ± 0.1).- Every operation (
+ - * / **,sqrt exp log sin cos tan tanh …) propagates errors to first order tracking each source separately, so correlations are right:x - xis0 ± 0,x * xhas twice the relative error ofx. nominal(x),sigma(x)(keeps the unit),relative_uncertainty(x),measure(v, s)(same asv ± s).- Comparisons use the nominal value.
sci.curve_fitreturns fitted parameters as correlated±values.
12. Tensors and shapes
let A = tensor([[1.0, 2], [3, 4]]) # from nested lists (a list of quantities gives a tensor with a unit)
zeros(3, 3) ones(2, 4) full((2, 2), 7.0) eye(3) zeros_like(A)
arange(0, 10, 2) linspace(0, 1, 5) linspace(0m, 1m, 11)
randn(64, 128, seed=1) rand(3) randint(0, 10, 5)
A @ A # matrix product A * A # element-wise
A.T # transpose shape(A), ndim(A), len(A)
A[0, 1] A[:, 0] A[A > 2]
reshape(A, 4) flatten(A) stack([A, A]) concat([A, A], axis=1)
sum(A, axis=0) mean(A) max(A, axis=1, keepdims=true) argmax(A)
- Element types:
f64(default),f32,f16,i32,i64,bool; passdtype=f32to creation functions. - Arithmetic broadcasts like NumPy.
- A tensor can carry one unit for all its elements:
tensor([1.0, 2.0]) * u.m. - Functions and methods of the underlying NumPy array that do not start with
_are available (A.sum(),A.copy()). - Shape annotations (
Tensor[B, T, D]) are checked on every call (chapter 8.3). use gpumoves tensors to an NVIDIA GPU (to_gpu(x)); every operator and autodiff work there too.
13. Precision and number formats
| Format | Bits | Used for |
|---|---|---|
f64 |
64 | default; physics |
f32 |
32 | AI, large simulations |
f16, bf16 |
16 | inference / training |
fp8_e4m3 (fp8), fp8_e5m2 (bf8) |
8 | weights & activations / gradients |
fp6_e3m2, fp6_e2m3 |
6 | OCP MX formats |
fp4_e2m1 (fp4) |
4 | highly compressed weights |
int8, int4, uint8 |
8 / 4 / 8 | quantization |
exact |
– | exact fractions |
@precision(fmt)on a function (chapter 8.4);Tensor[N, bf16]in annotations.quantize(x, fmt)stores real-size codes,dequantize(q)decodes,round_to(x, fmt)rounds with a straight-through gradient (quantization-aware training),qmatmul(a, b)multiplies quantized operands accumulating in FP32.exact("1/3")creates an exact fraction;to_float(x)converts back.- Details and measurements:
PERFORMANCE.md.
14. evolve — differential equations
var x = 1m
var v = 0[m/s]
let k = 4[N/m]
let m = 1kg
evolve x, v over t in 0[s]..10[s] step 1[ms] using rk4 record every 10 into traj:
x' = v
v' = -k / m * x
print(x, v) # the final state
print(traj.t[-1], traj.x[-1]) # recorded trajectory (tensors with units)
Form: evolve STATE, … over TIME in START..END step DT [using METHOD [tol TOL]] [record [every N] into NAME]:
- State variables must be declared with
var; afterevolvethey hold the final state. - Every state needs a derivative line
x' = expression. Its unit must be the unit ofxdivided by the unit of time — checked by the checker and at run time. - The body may contain
let,if,forand function calls; state variables are read-only inside it. The time variable (t) can be used in the expressions. - Methods:
euler,midpoint,rk4(default),rk45(Dormand–Prince with adaptive steps:stepis the first step,tol 1e-10the accuracy, default1e-8),symplectic(semi-implicit Euler, updating from the last state to the first, so order position, velocity),verlet(velocity Verlet; exactly two states: position and velocity). record [every N] into namegivesname.t,name.x,name.v, … as tensors with units.- Speed: the first step runs fully unit-checked; then a unit-erased version runs on plain SI numbers. Both are compared before the switch, and on any difference the checked version is used.
var y = 1.0
evolve y over t in 0[s]..5[s] step 0.5[s] using rk45 tol 1e-10 record into tr:
y' = -y / 1[s]
15. Automatic differentiation
fn potential(x: Length) -> Energy = 0.5 * 10[N/m] * x ** 2
let force = grad(potential) # a new function: dU/dx
print(force(0.1m)) # 1.0 N — the unit is derived
fn loss(w, b) = mean((X @ w + b - y) ** 2)
let l, gs = value_and_grad(loss, wrt=[0, 1])(w, b) # gs[0] = ∂l/∂w, gs[1] = ∂l/∂b
grad(f, wrt=0)returns a function computing ∂f/∂(argumentwrt);wrtmay be a list.value_and_grad(f, wrt=0)returns(value, gradient).- Arguments may be numbers, tensors, quantities or parameter trees (struct/record/list/dict); the gradient has the same shape.
- Gradients have correct units: if
freturnsEnergyandxis aLength, the gradient is aForce. if,for, function calls and indexing work insidef(define-by-run).fmust return a scalar.- Any order:
grad(grad(f)),hessian(f),jacobian(f),laplace(f)(Σ ∂²f/∂xᵢ²);gradmay be used inside a function that is itself being differentiated — enough for physics-informed neural networks (examples/18_pinn.apsi). stop_gradient(x)blocks gradients;derivative(f, x)is the derivative of a scalar function atx;nn.numeric_grad(f, x)checks gradients with finite differences.conv2dandmax_pool2dare differentiable to first order only.
16. Modules and imports
use nn # bring every name of a standard module into scope
use linalg as la # or keep them under a short name: la.eigh(H)
use physics.constants # dotted module names
import "mylib.apsi" # the public names (not starting with _) of another file
import "lib/geometry.apsi" as geo
useis for the standard modules listed in chapter 22.importruns another.apsifile once (cached) and brings in its public top-level names. The path is relative to the importing file. Itstestblocks do not run on import.- Circular imports are reported as errors.
Standard modules: math, physics, physics.units, physics.constants, physics.fields, nn, layers, linalg,
random, sci, precision, data, parallel, web, models, gpu, sys, crypto, io, ffi, python, net.
17. Tests
fn mean_of(xs) = sum(xs) / len(xs)
test "mean of three numbers":
assert mean_of([1, 2, 3]) == 2
test "works with units":
assert mean_of([1m, 3m]) ~= 2m
apsi runskipstestblocks;apsi testruns the file's top-level code first, then each test in that file's scope.- Put tests next to the code, or in
tests/*.apsifiles thatimportthe code. testing()istruewhile tests run — use it to skip side effects such as starting a server.apsi testprints ✓/✗ per test with its time and exits with status 1 when something fails.
18. The checker and errors
Before running, Apsi checks the whole file and reports every problem it can prove, without running any code:
- undeclared names, including typos
- assignments to
letconstants, parameters or functions - unit mismatches in
+ - < ==, in arguments, in return values, invars whose dimension changes, and inexp/log/sinof quantities with units - wrong argument counts and unknown keyword names
evolverules (varstates, a derivative for every state, derivative units, step unit)returnoutside a function, unknown types
The checker never warns falsely: what it cannot prove is left to the run-time checks.
Every error shows the file, line, column, the source line and a caret, followed by the call stack:
orbit.apsi:12:20: UnitError: argument 'v' of kinetic() must be Velocity [m/s], got Length [m] (2.0 m)
let k = kinetic(3kg, 2m)
^
in simulate()
Error kinds: SyntaxError, NameError, TypeError, UnitError, ShapeError, RuntimeError, AssertionError,
PermissionError.
19. Permissions and security
Programs run with a permission profile. By default (standard) files can be read and written; Python, C, the
network and environment variables are off.
| Permission | Allows | Flag |
|---|---|---|
read / write |
files (optionally only some folders) | --allow-read[=DIRS], --allow-write[=DIRS] |
net |
http_get, http_post_json, use web servers |
--allow-net[=HOSTS] |
env |
getenv |
--allow-env[=NAMES] |
python |
pyimport, to_torch, from_torch, ONNX/HF/PyTorch models |
--allow-python (=all for system modules) |
ffi |
cload (C/C++/Rust libraries) |
--allow-ffi[=LIB@SHA256] |
Profiles: sandbox (nothing, not even files), strict, standard (default), developer (everything). Set the
default with apsi config --profile NAME, per project in apsi.toml, or per run with --profile, --sandbox,
--allow-all. apsi audit FILE lists what a program needs and the minimal flags.
Code without a permission gets a PermissionError immediately — imported libraries cannot get around it. Attributes
starting with _ on outside objects are blocked. Apsi Guard scans every program before it runs (skip with
--trust); apsi scan PATH scans on demand. Limits: --max-memory 2GB, --timeout 30s.
Full details: SECURITY.md.
20. Execution engines
| Engine | How it works | Use it for |
|---|---|---|
compile (default) |
the checked program is compiled to Python bytecode: scopes resolved at compile time, specialized type checks, and an erased twin of every function used in hot loops | everything — 5–20× faster than the tree-walker |
interp |
the tree-walking reference interpreter that defines the language's meaning | cross-checking the engines; the REPL |
Select with --engine interp. Both engines pass the same test suite. Closures created in loops capture each
iteration's value. Run-time errors always point back to the line and column in the .apsi file.
Parsed programs are kept in a signed compile cache, so an unchanged file is not parsed again on the next run;
apsi build turns a program into an executable (.exe). Both are described under Executables and the compile
cache in chapter 24. Neural networks built with use layers can also run on PyTorch (CPU or GPU) — see
BACKENDS.md.
21. Built-in functions
Available everywhere without use. Unless noted, functions accept numbers, tensors, quantities and values that
are being differentiated alike. apsi doc NAME (or :doc NAME in the REPL) prints a name's signature.
21.1 Input, output and conversion
| Function | Description |
|---|---|
print(*values, sep=" ", end="\n") |
write values to standard output |
input(prompt="") |
read one line from standard input (without the newline); none at end of input |
args() |
program arguments: apsi run app.apsi a b → ["a", "b"] |
exit(code=0) |
stop the program with an exit status |
str(x) · int(x) · float(x) · bool(x) |
convert (int("42"), float("1.5")) |
list(x=()) · tuple(x) · dict(...) · set(x) |
containers from iterables / keyword arguments |
any(xs) · all(xs) · iter(xs) · next(it[, default]) |
truth tests and iterators |
ord(c) · chr(n) · hex(n) · bin(n) · oct(n) · format(x, spec) · repr(x) |
characters, number bases, formatting |
getattr(obj, name[, default]) · hasattr(obj, name) · callable(x) |
reflection (names starting with _ stay private) |
type(v) |
type name as a string |
error(msg) |
raise a RuntimeError with msg |
testing() |
true while apsi test runs |
platform() |
"windows", "linux" or "macos" |
clock() |
a monotonic time in seconds (subtract two calls to measure) |
permissions() · security_policy() |
the permissions of the running program |
21.2 Sequences and collections
| Function | Description |
|---|---|
len(x) |
number of elements (first axis for tensors) |
range(a, b=none, step=1) |
integers as a list-like range (0..n is usually nicer) |
enumerate(xs, start=0) |
(index, item) pairs |
zip(*xs) |
tuples of corresponding elements |
map(f, xs) · filter(f, xs) |
apply / keep where f is true (also pipeline steps in use data) |
sorted(xs, key=none, reverse=false) · reversed(xs) |
ordering |
unique(xs) |
distinct values in first-seen order |
keys(d) |
keys of a dict, record or struct |
chars(s) · join(xs, sep="") |
split into characters / join strings |
record(**fields) |
a record |
tree_map(f, *trees) · param_count(tree) |
map over / count the leaves of a parameter tree |
pmap(f, xs, workers=none) |
parallel map on a thread pool |
21.3 Math
| Function | Description |
|---|---|
abs sqrt cbrt exp log log10 log2 |
elementary functions (sqrt halves units) |
sin cos tan asin acos atan atan2(y, x) sinh cosh tanh |
trigonometry (radians; deg converts) |
floor ceil round(x, digits=0) |
rounding |
min max sum mean std var prod (axis=, keepdims=) |
reductions; min/max also take several arguments |
cumsum(x, axis) · argmax(x, axis) · argmin(x, axis) |
cumulative sum, positions of extremes |
fsum(xs) |
correctly rounded sum of a list or tensor (no accumulated rounding error; keeps units) |
divmod(a, b) · pow(a, b[, m]) |
quotient and remainder · power (modular with 3 arguments) |
approx(a, b, rel=1e-6, atol=1e-12) |
approximate equality |
exact(x) · to_float(x) |
exact fraction / back to float |
pi tau e inf nan |
constants |
21.4 Units and uncertainty
| Function | Description |
|---|---|
u.<unit> |
one of a unit: u.km, u.eV, u.m / u.s |
unit(x) · dim(x) · magnitude(x) |
unit name, dimension name, number in the displayed unit |
measure(value, sigma) |
same as value ± sigma |
nominal(x) · sigma(x) · relative_uncertainty(x) |
parts of an uncertain value |
21.5 Tensors
| Function | Description |
|---|---|
tensor(x, dtype=none) |
tensor from (nested) lists; a list of quantities gives a tensor with a unit |
zeros(*shape) · ones(*shape) · full(shape, value) · eye(n) · zeros_like(x) · ones_like(x) |
constant tensors |
arange(start, stop, step=1) · linspace(start, stop, n=50) · meshgrid(x, y) |
ranges (with units too) |
randn(*shape, seed) · rand(*shape, seed) · randint(lo, hi, *shape, seed) · permutation(n, seed) · seed(n) |
random numbers (default seed 0, so runs are reproducible) |
shape(x) · ndim(x) · reshape(x, *shape) · flatten(x) · transpose(x, axes) |
shape handling |
stack(items, axis=0) · concat(items, axis=0) · roll(x, shift, axis) · where(cond, a, b) · clip(x, lo, hi) · sort(x) |
data manipulation |
dot(a, b) · matmul(a, b) · outer(a, b) · cross(a, b) · norm(x, axis) · einsum(spec, *xs) |
linear algebra (einsum multiplies units and is differentiable) |
f16 f32 f64 i32 i64 (+ bf16 fp8 … int4) |
dtypes for dtype= and @precision |
21.6 AI and autodiff
| Function | Description |
|---|---|
grad(f, wrt=0) · value_and_grad(f, wrt=0) |
reverse-mode gradients (chapter 15) |
derivative(f, x) · jacobian(f) · hessian(f) · laplace(f) |
higher-order derivatives |
stop_gradient(x) |
block gradient flow |
relu sigmoid tanh softmax(x, axis) log_softmax(x, axis) gelu softplus |
activations |
21.7 Precision
quantize(x, fmt, scale="auto", axis) · dequantize(q) · round_to(x, fmt) · qmatmul(a, b) · nbytes(x) ·
quant_error(x, fmt) · format_info(fmt) · decorators precision(fmt), memo, timed.
21.8 Files and data (permission fs)
read_text(path) · write_text(path, text) · exists(path) · load_json(path) · save_json(path, value) ·
load_csv(path, skip_header=0) · save_csv(path, data, header="") · json_parse(text) · json_str(value).
Relative paths are resolved against the running .apsi file.
21.9 Other languages and the network
| Function | Permission | Description |
|---|---|---|
pyimport(name) |
python | import a Python module |
to_py(x) · from_py(x) |
python | convert values (units become {value, unit}) |
to_torch(x, device) · from_torch(t) |
python | exchange tensors with PyTorch |
cload(path) · library_path(path) |
ffi | load a C library; lib.fn(name, [arg types], result type) |
http_get(url, timeout=30) · http_post_json(url, data, timeout=30) |
net | HTTP requests |
getenv(name, default=none) |
env | environment variables (secrets, API keys) |
22. Standard modules
Use with use NAME (all names) or use NAME as short (short.name).
math
abs sqrt cbrt exp log log10 log2 sin cos tan asin acos atan atan2 sinh cosh tanh floor ceil round pi tau e inf nan
physics, physics.constants, physics.fields, physics.units
- constants (CODATA, with units):
c G h hbar k_B N_A R_gas q_e m_e m_p m_n epsilon_0 mu_0 g0 sigma_SB a_0 alpha_fs M_sun M_earth R_earth AU - fields:
laplacian(u, dx, boundary="periodic"|"fixed")(∇² in any number of dimensions) ·gradient(u, dx, axis=0)·meshgrid(x, y) physicscontains both;physics.unitsexists for clarity (units are always available)
nn — neural networks
| Function | Description |
|---|---|
dense(n_in, n_out, seed, scale) |
parameters record(W, b) (He initialization) |
mlp_init(sizes, seed=0) |
a list of dense layers, e.g. mlp_init([2, 64, 3]) |
conv_init(c_in, c_out, k=3, seed) · conv2d(x, w, b, stride=1, padding=0) · max_pool2d(x, k=2) |
CNNs (x: N×C×H×W) |
layer_norm(x, gamma, beta, eps) · norm_init(d) · batch_norm(x, gamma, beta, eps, axis) · rms_norm(x, gamma, eps) |
normalization |
embedding_init(vocab, d, seed) |
embedding table (look up with E[idx]) |
attention(q, k, v, mask) · causal_mask(T) |
scaled dot-product attention |
dropout(x, p=0.1, train=true, seed) |
dropout |
lstm_init(n_in, n_hidden) · lstm_cell(p, x, h, c) · lstm(p, xs) |
LSTM (xs: B×T×F → B×T×H) |
gru_init(n_in, n_hidden) · gru_cell(p, x, h) · gru(p, xs) |
GRU |
mse(pred, target) · cross_entropy(logits, labels) · accuracy(logits, labels) · one_hot(labels, k) |
losses and metrics |
sgd(lr, momentum) · adam(lr, b1, b2, eps) · adamw(…, weight_decay) · rmsprop(lr, decay, eps) · adagrad(lr, eps) |
optimizers: params = opt.step(params, grads) |
set_lr(opt, lr) · cosine_lr(step, total, base, warmup, minimum) · step_lr(step, base, every, factor) · linear_warmup(step, base, warmup) |
learning-rate schedules |
clip_grad_norm(grads, max_norm=1) · global_norm(grads) |
gradient clipping |
train(loss_fn, params, data, opt, epochs, batch_size, seed, verbose, clip, schedule, eval_fn) |
ready-made training loop; returns record(params, history) |
save_params(path, params) · load_params(path, like) |
save / load parameters (.npz) |
numeric_grad(f, x, eps) |
finite-difference gradient check |
spiral_data(n_per_class, classes, noise, seed) · make_moons · make_blobs · make_regression |
sample datasets |
train_test_split(X, y, test=0.2, seed) · standardize(X, axis) · batches(X, y, size, seed) |
data preparation |
relu gelu sigmoid tanh softmax log_softmax |
activations (also built-ins) |
layers — any neural-network architecture
Layers, composition (sequential, residual, parallel, dag), graph neural networks (graph, gcn, sage,
gat, gin, graph_pool, segment_sum/mean/max/softmax), transformers, a model zoo (mlp, resnet_mlp, cnn,
lstm_classifier, transformer_lm, transformer_classifier, autoencoder, vae, gcn_net, gat_net, gin_net,
siamese) and training helpers (fit, predict, summary, count_params). Every layer has init(seed) and is
applied as layer(params, x, train=false, graph=…). More layers: mean_pool(axis), bilinear, scale, select.
Backends: fit(…, backend="numpy"|"torch"|"auto", device="auto"|"cpu"|"cuda"|"mps"|"xpu", precision="fp64"|"fp32"|"tf32"|"bf16"|"fp16", compile=false), predict(…, backend=…), devices(),
torch_module(model, params) → torch.nn.Module, params_from_torch(model, module), check_torch(device).
The torch backend needs --allow-python.
Complete reference with shapes, parameters and recipes: LAYERS.md; backends: BACKENDS.md.
linalg
inv det solve eigh eig svd trace norm — units are preserved (eigenvalues of a Hamiltonian are energies).
random
seed randn rand randint permutation
sci — scientific computing
| Function | Description |
|---|---|
einsum("ij,jk->ik", A, B) |
Einstein summation (differentiable, multiplies units) |
fft ifft rfft irfft(x, n, axis) · fftfreq(n, d) · rfftfreq(n, d) · power_spectrum(x, d) · real imag conj |
Fourier transforms (frequencies in Hz when d is a time) |
integrate(f, a, b, points=64) |
Gauss–Legendre quadrature (unit [f]·[x]) |
trapz(y, x, dx) · cumtrapz(y, x) |
integrate sampled data |
interp(x, xp, fp) |
linear interpolation |
find_root(f, x0, tol, max_iter) · bisect(f, a, b, tol, max_iter) |
root finding (Newton with exact autodiff derivatives) |
minimize(f, x0, method="lbfgs"|"adam", steps, tol, lr) |
minimize over any parameter tree; returns record(x, value, steps, converged) |
curve_fit(model, x, y, p0, sigma) |
Levenberg–Marquardt; returns record(params, cov, chi2, dof) with correlated ± parameters |
solve_ivp(f, [t0, t1], y0, method="bdf"|"rk45", rtol, atol, t_eval) |
initial value problems y' = f(t, y): bdf is TR-BDF2 (implicit, L-stable, adaptive) for stiff systems such as chemical kinetics and circuits; rk45 is adaptive Dormand–Prince; units are kept; returns record(t, y, steps, rejected, evaluations, success) |
precision
quantize dequantize round_to qmatmul nbytes quant_error format_info and the formats
f64 f32 f16 bf16 fp8_e4m3 fp8_e5m2 fp6_e3m2 fp6_e2m3 fp4_e2m1 int8 int4 uint8 (short names fp8 bf8 fp6 fp4 e4m3 e5m2 fp16 fp32 fp64).
data — pipelines and storage
| Kind | Functions |
|---|---|
| sources | from_tensors(*arrays, batch, shuffle, seed, drop_last) · from_list(xs) · from_range(n) · from_generator(f) · from_csv(path, skip_header, chunk, delimiter) · from_jsonl(path) · from_parquet(path, columns, rows) (needs apsi pkg install data) · from_npy(path) · from_files(pattern) · interleave([pipelines]) |
steps (methods or |>) |
map(f, workers, ordered) · filter(f) · batch(size, drop_last) · unbatch() · shuffle(buffer, seed) · prefetch(n) · take(n) · skip(n) · repeat(times) · cache(directory, shard) · window(size, shift) (sliding windows for time series) · shard(count, index) (split across workers or machines) |
| results | .to_list() · len(ds) · throughput(ds, limit) · iterate with for |
| storage | open_tensor(path, shape, dtype, mode) (memory-mapped) · load_tensor(path, mmap) · save_tensor(path, x) · flush(x) · @disk_cache(directory) |
parallel — concurrency
spawn(f, *args) → task (.wait(), .done()) · wait_all(tasks, timeout) · wait_any(tasks, timeout) ·
channel(capacity) (send, recv, close, iterate with for) · lock() (acquire, release, run(f)) ·
atomic(value) (add, get, set) · parallel_for(items, f, workers) · parallel_reduce(items, f, combine, init) ·
set_threads(n) · cpu_count() · sleep(t) · bench(f, repeat, warmup)
web — HTTP servers
server(name, max_body, workers, max_connections, log, handler_timeout, header_timeout, body_timeout, keepalive_timeout, security_headers) ·
response(body, status, headers, type) · redirect(location, status) · abort(status, message) ·
jwt_sign(claims, secret, expires_in) · jwt_verify(token, secret, leeway) · bearer_token(req) ·
require_jwt(secret, exempt) · require_api_key(keys, header, exempt).
Server methods: get post put patch delete route(method, path) (as calls or decorators), before, after,
rate_limit, cors, static, health, expose_metrics, stats, handle (call without the network, for tests),
run(host, port, tls_cert, tls_key, processes, workers), run_background(port).
Requests: req.method path params query headers header(name) body text json() client id user.
Full guide: SERVER.md.
models — model files
load_safetensors(path, dtype, names, nested) · save_safetensors(path, params, metadata) · safetensors_info(path) ·
quantize_model(params, dtype, min_size) · dequantize_model(params) · model_size(params) · qlinear(x, W, b) ·
onnx_model(path, providers) · hf_pipeline(task, model, device) · torch_load(path) (the last three need
--allow-python). Guide: AI.md.
gpu and sys
device() · to_device(x, dev) · to_gpu(x) · to_cpu(x) · gpu_available() · gpu_info() · hardware() (CPU,
SIMD, GPU, RAM) · cpu_threads()
crypto
| Function | Description |
|---|---|
hash(data, algo="sha256") |
sha256, sha512, sha3_256, sha3_512, blake2b, blake2s |
hmac_sign(key, message) · hmac_verify(key, message, signature) |
message signatures |
password_hash(password) · password_verify(password, stored) |
salted scrypt |
secure_equal(a, b) |
constant-time comparison |
random_token(n) · random_hex(n) · secure_randint(lo, hi) |
randomness from the operating system's CSPRNG |
b64encode(text) · b64decode(text) |
base64 |
io, ffi, python, net
Group the built-ins of 21.8 and 21.9 so they can be used under a name: use io as fs → fs.read_text(path).
ffi also has the C types f64 f32 i32 i64 void cstr ptr_f64 ptr_f32 ptr_i32 ptr_i64.
23. The REPL
Start it with apsi (or apsi repl). Type Apsi code; the value of an expression is printed, and _ holds the last value.
apsi> let g = 9.81[m/s^2]
apsi> 0.5 * g * (2s) ** 2
19.62 m
apsi> fn sq(x) =
... x * x
apsi> cd C:\Users\me\project
C:\Users\me\project
apsi> run src\main.apsi
apsi> :load src\main.apsi
Multi-line input continues automatically after a line ending in : (a block — finish it with an empty line),
inside open brackets or a triple-quoted string, and after a trailing \, =, ,, |> or operator. :paste
accepts any block until a line containing :end.
Shell words behave like a terminal when they are not names you defined: cd PATH (Windows paths, quotes,
~, %VAR%/$VAR and cd - work), pwd, ls/dir [PATH], clear/cls, run FILE, help, exit/quit.
!COMMAND runs any operating-system command (!git status).
Commands (:help lists them; most have short aliases):
| Command | Does |
|---|---|
:help [NAME] (:h, :?) |
help, or documentation for NAME |
:quit (:q, exit, Ctrl+D) |
leave |
:vars (:v) |
variables and functions of the session with their types |
:type EXPR (:t) |
type of an expression |
:doc NAME |
signature and documentation |
:time [CODE] |
time one input, or toggle timing of every input |
:bench [N] CODE |
benchmark code (N runs, or about one second) |
:load FILE (:l) · :reload (:r) |
run a file inside the session, keeping its definitions |
:run FILE |
run a file in a fresh interpreter |
:check FILE… · :test [PATH…] · :fmt FILE… |
toolchain commands |
:edit [FILE] |
open $EDITOR (Notepad on Windows), then run what you wrote |
:paste |
paste a large block, finish with :end |
:save [FILE] |
save every successful input of the session as a .apsi file |
:history [N] (:hist) |
the last N inputs |
:ast CODE · :tokens CODE |
syntax tree / tokens |
:reset |
clear all variables and functions |
:perms · :env |
permissions / versions and current folder |
:cd :pwd :ls :clear |
the shell words, as commands |
Editing: Tab completes names, fields (p.<Tab>), :commands and paths; Up/Down recall history (kept in
~/.apsi/repl_history); Ctrl+C cancels the current input. Colours turn off with NO_COLOR=1.
The REPL uses the reference interpreter and the same permissions flags as apsi run
(apsi repl --allow-net).
24. The command line
apsi [COMMAND] [OPTIONS]
24.1 Shortcuts
| You type | Means |
|---|---|
apsi |
REPL (or, when a program is piped in, run it: cat app.apsi | apsi) |
apsi FILE.apsi [ARGS…] |
apsi run FILE.apsi ARGS… |
apsi -e "CODE" (-c, --eval) |
apsi eval "CODE" |
apsi -i |
apsi repl |
apsi TASK [ARGS…] |
apsi task TASK ARGS… when TASK is in apsi.toml |
apsi -V, apsi --version |
version |
A mistyped command prints suggestions ("did you mean: test").
24.2 Running
| Command | Description |
|---|---|
apsi run [FILE] [ARGS…] |
check, then run. Without FILE: the project's main. - reads the program from stdin. Options: --no-check, --time, -w/--watch, --engine compile|interp, security flags |
apsi eval CODE… |
evaluate code; the value of a final expression is printed. ; or \n separate statements |
apsi repl |
interactive REPL (chapter 23) |
apsi watch [PATHS…] [--do run|test|check] [--ext .apsi,.csv] [--interval S] [--no-clear] [-- FLAGS] |
re-run whenever a watched file changes |
apsi bench FILE [-n RUNS] [--warmup N] |
best / median / mean ± sd / worst over several runs (output suppressed) |
apsi build FILE [-o DIR] [--name N] [--allow-…] [--standalone] [--include PKG] [--add PATH] [--windowed] |
make an executable — see Executables and the compile cache below |
apsi compile [PATHS] [--runtime] [--stats] [--clear] |
parse, check and compile programs now, so later runs start from the compile cache |
apsi serve FILE [--host H] [--port P] [--only NAMES…] |
expose the program's functions as a JSON HTTP API |
Executables and the compile cache
How a program runs. apsi app.apsi parses the program, checks it, compiles it to Python bytecode
(engine compile, the default) and runs it. Parsed programs are kept in the compile cache — in memory, so
the Guard scan, the checker, imports and the run parse each file only once, and on disk in
~/.apsi/cache/compiled, so the next run of an unchanged file skips parsing. Disk entries are signed with your
per-user key (~/.apsi/cache.key, HMAC-SHA256); an entry that was modified or copied from another computer is
deleted, never loaded. The cache is keyed by the file's contents and by the Apsi version, so it can never serve a
stale result. apsi compile PATHS fills it ahead of time (and checks every file), apsi compile --runtime also
compiles Apsi itself to bytecode, apsi compile --stats / --clear show and empty it, and APSI_NO_CACHE=1
turns it off.
Executables. apsi build app.apsi turns a program into one executable file:
apsi build app.apsi # app.exe next to app.apsi (Windows), ./app (Linux)
apsi build train.apsi --allow-python -o dist # the permissions are fixed when it is built
apsi build app.apsi --standalone # Windows: dist folder with its own Python, for PCs without Apsi
apsi build server.apsi --windowed --allow-net # no console window
apsi build app.apsi --add data # pack a data folder (it sits next to the program when it runs)
The executable contains the program and every .apsi file it imports, the already parsed and checked program,
Apsi itself compiled to bytecode (so it runs exactly the Apsi version it was built with) and the permissions given
to apsi build. A program with check errors is never built. Arguments given to the executable go to the program
(args()), all of them — app.exe --verbose x passes ["--verbose", "x"]. On the first start the program's
files are unpacked once into ~/.apsi/apps/NAME-ID; later starts use them directly.
| Option | Meaning |
|---|---|
-o DIR, --name N |
output folder and name |
--allow-read, --allow-net, --allow-python, --allow-all, --profile, … |
permissions of the built program |
--standalone |
Windows: DIR\NAME\NAME.exe plus python\ with the Python runtime, numpy and the Python packages the program uses (pyimport("…"), the torch backend — detected automatically; --include PKG adds more). Copy the whole folder to another PC. |
--windowed |
Windows: no console window |
--add PATH |
pack a data file or folder (inside the program's folder) |
Without --standalone the executable uses the Python runtime of the Apsi installation that built it, so it runs on
computers where Apsi is installed in the same place.
24.3 Checking, testing, formatting
| Command | Description |
|---|---|
apsi check [PATHS…] [-q] |
check files and folders without running (default: current folder) |
apsi test [PATHS…] |
run test blocks (default: the project's tests/); accepts security flags and --engine |
apsi fmt [PATHS…] [--check] |
format in place; --check only reports (exit 1 when something would change) |
apsi audit FILES… |
the capabilities a program uses and the minimal --allow-* flags |
apsi ast FILE · apsi tokens FILE |
syntax tree / tokens |
24.4 Projects and documentation
| Command | Description |
|---|---|
apsi new NAME [-t app|lib|physics|ai|server] [--no-vscode] [--force] |
create a project folder |
apsi init [PATH] [--name N] [-t TEMPLATE] |
turn a folder into a project |
apsi task [NAME] [ARGS…] |
list or run apsi.toml tasks |
apsi doc [NAME] [FILTER] |
modules, built-ins, or one name: apsi doc, apsi doc nn, apsi doc nn adam, apsi doc sqrt |
apsi help [COMMAND] · apsi COMMAND --help |
help |
apsi completion bash|zsh|fish|powershell |
shell completion script |
apsi convert PATHS… [-o OUT] [--run] [--stdout] [--no-check] |
translate Python files or folders into Apsi, mapping numpy/math/torch/… onto Apsi (see PYTHON.md) |
apsi editors [list|install|uninstall|path] [NAMES…] [--json] |
Apsi support for VS Code, Cursor, Windsurf, VSCodium, VS Code Insiders, Trae, Sublime Text, Neovim, Vim and Notepad++ (see EDITORS.md) |
Install completion: bash eval "$(apsi completion bash)" in ~/.bashrc; zsh eval "$(apsi completion zsh)";
fish apsi completion fish > ~/.config/fish/completions/apsi.fish; PowerShell
apsi completion powershell | Out-String | Invoke-Expression in $PROFILE.
24.5 Security, packages and the machine
| Command | Description |
|---|---|
apsi scan PATHS… [--av] [--json] [--fail-on LEVEL] |
malware scan of programs and folders |
apsi verify |
compare the installed runtime with its release manifest |
apsi config [--profile sandbox|strict|standard|developer] |
show or set the default profile |
apsi pkg list | install NAMES | remove NAMES | lock | audit |
optional Python libraries (gpu onnx torch transformers sci data plot crypto audit, groups ai ai-gpu science all) |
apsi doctor [--json] |
check this machine against Apsi's hardware targets |
apsi lsp |
language server over stdio (for editors) |
apsi version |
version |
24.6 Security flags (run, test, repl, eval, bench, serve)
--profile NAME · --allow-read[=DIRS] · --allow-write[=DIRS] · --allow-net[=HOSTS] · --allow-env[=NAMES] ·
--allow-python[=all] · --allow-ffi[=LIB@SHA256] · --allow-all · --sandbox · --trust (skip Apsi Guard) ·
--max-memory SIZE · --timeout TIME · --engine compile|interp
24.7 Exit status
0 success · 1 errors (check failed, test failed, program error) · 2 usage error (bad arguments, missing file) ·
3 blocked by Apsi Guard · 130 interrupted (Ctrl+C) · any value passed to exit(code).
24.8 Environment variables
| Variable | Effect |
|---|---|
APSI_HOME |
settings folder (default ~/.apsi: config, REPL history, cache key) |
NO_COLOR / APSI_NO_COLOR |
no colours; FORCE_COLOR forces them |
EDITOR / VISUAL |
editor for :edit |
25. Projects and apsi.toml
apsi new orbit --template physics creates:
orbit/
apsi.toml project settings and tasks
src/main.apsi entry point
tests/main_test.apsi tests (import "../src/main.apsi")
README.md .gitignore .editorconfig
.vscode/ tasks.json (run, test, check, format, watch), settings.json, extensions.json
Templates: app (default), lib, physics, ai, server.
[project]
name = "orbit"
version = "0.1.0"
description = ""
main = "src/main.apsi" # what `apsi run` runs when no file is given
[run]
profile = "standard" # sandbox | strict | standard | developer
allow = ["net"] # extra permissions for run/test in this project: read write net env python ffi
[tasks] # apsi task NAME — or simply apsi NAME
start = "apsi run src/main.apsi"
test = "apsi test tests"
check = "apsi check src"
fmt = "apsi fmt src tests"
ci = ["apsi check src", "apsi fmt --check src tests", "apsi test tests"]
apsi run,apsi testandapsi taskfindapsi.tomlin the current folder or any parent folder.- A task is a command line or a list of command lines run in order (stopping at the first failure), in the project folder.
- Command lines that start with
apsiuse the same Apsi that runs the task, so tasks work even whenapsiis not on PATH. Other command lines run in the system shell. - Extra arguments go to the last step:
apsi task start --time. - Flags given on the command line override
[run].
26. Style guide
apsi fmt applies these rules automatically.
- 4 spaces per indentation level; no tabs.
snake_casefor variables and functions,PascalCasefor structs,UPPER_CASEfor module-level constants that you want to stand out; single-letter physics names (m,v,F,T) are fine.- spaces around
= == -> => |> ±and binary operators; none inside unit literals (9.81[m/s^2]), ranges (0..10) or keyword arguments (f(x=1)). - annotate the units of public functions:
fn kinetic(m: Mass, v: Velocity) -> Energy. - put tests next to the code they test.
- prefer
let; usevaronly for values that change.
27. Grammar
The grammar in EBNF ({ } = repeat, [ ] = optional, | = choice). INDENT/DEDENT/NEWLINE come from the
indentation rules of chapter 3.
program = { statement } ;
statement = let | fn_def | struct_def | if | while | for | evolve | test
| "return" [ expr_list ] | "break" | "continue" | "pass"
| "use" dotted [ "as" NAME ] | "import" STRING [ "as" NAME ]
| "assert" expr [ "," expr ] | NAME "'" "=" expr
| expr_list [ ( "=" | augop ) expr_list ] ;
let = ( "let" | "var" ) NAME { "," NAME } [ ":" type ] "=" expr_list ;
fn_def = { "@" expr NEWLINE } "fn" NAME [ "[" names "]" ] "(" params ")" [ "->" type ]
( "=" expr | block ) ;
params = [ param { "," param } ] ;
param = NAME [ ":" type ] [ "=" expr ] ;
struct_def = "struct" NAME ":" NEWLINE INDENT { NAME ":" type [ "=" expr ] NEWLINE | fn_def } DEDENT ;
if = "if" expr block { "elif" expr block } [ "else" block ] ;
while = "while" expr block ;
for = "for" names "in" expr block ;
evolve = "evolve" names "over" NAME "in" expr "step" expr [ "using" NAME [ "tol" expr ] ]
[ "record" [ "every" expr ] "into" NAME ] block ;
test = "test" STRING block ;
block = ":" NEWLINE INDENT { statement } DEDENT | ":" statement ;
type = NAME [ "[" type_arg { "," type_arg } "]" ] | "[" unit "]" ;
augop = "+=" | "-=" | "*=" | "/=" | "//=" | "%=" | "**=" | "@=" ;
expr = "fn" "(" params ")" "=>" expr | pipe ;
pipe = ternary { "|>" ternary } ;
ternary = or [ "if" or "else" ternary ] ;
or = and { "or" and } ;
and = not { "and" not } ;
not = "not" not | comparison ;
comparison = range { cmp_op range } ;
cmp_op = "==" | "!=" | "<" | "<=" | ">" | ">=" | "~=" | "in" | "not" "in" ;
range = additive [ ( ".." | "..=" ) additive ] { "as" "[" unit "]" } ;
additive = term { ( "+" | "-" ) term } ;
term = unary { ( "*" | "/" | "//" | "%" | "@" ) unary } ;
unary = ( "-" | "+" ) unary | plusminus ;
plusminus = power [ ( "±" | "+/-" ) power ] ;
power = postfix [ ( "**" | "^" ) unary ] ;
postfix = primary { "(" args ")" | "[" subscripts "]" | "." NAME } ;
primary = NUMBER [ unit ] | STRING | FSTRING | NAME | "true" | "false" | "none"
| "(" [ expr_list ] ")" | "[" list_or_comp "]" | "{" dict_or_comp "}" ;
args = [ arg { "," arg } ] ;
arg = expr | NAME "=" expr ;
Found a mistake? Report it · Markdown source