Execution, diagnostics, and arrays
Current-source Python API. Install this source revision, then run and inspect. Canonical guide source.
One run lifecycle
Blocking and awaitable execution use the same native worker, state machine, and once-materialized result:
field = model.field(model.field_ids[0])plan = eqiora.resolve( model, temporal=eqiora.time.Tsitouras45( initial_step_s=0.01, relative_tolerance=1.0e-9, absolute_tolerances={field: 1.0e-11}, ),)state = eqiora.State.initial(plan)run = eqiora.submit( plan, state=state, until_s=1.0, output_times_s=(1.0,),)print(run.status, run.progress)result = run.result()
# The blocking convenience uses the same lifecycle.same_kind_of_result = eqiora.run( plan, state=state, until_s=1.0, output_times_s=(1.0,),)RunStatus records a finite accepted history from creation through one
terminal state. progress is an execution-family-specific coalesced snapshot,
not a percentage or event log. Repeated result() calls return the same
immutable Python result object.
Awaiting does not introduce another native runtime:
async def simulate(plan): run = eqiora.submit( plan, state=eqiora.State.initial(plan), until_s=1.0, output_times_s=(1.0,), ) try: return await run finally: if not run.done: run.cancel()Cancelling the surrounding asyncio task and dropping a Run do not implicitly
cancel native work. Call run.cancel() explicitly. Cancellation is
cooperative at accepted execution boundaries, publishes typed cancellation
evidence, and never exposes a partial result. A request after the last
cancellable boundary may still complete.
Long native waits release the ordinary CPython GIL only after inputs are owned. Solver iterations do not call Python. Free-threaded Python and subinterpreter shutdown remain separate capabilities.
Structured failures
Eqiora model and execution failures derive from EqioraError. Stable
subclasses distinguish validation, compatibility, capability, execution,
cancellation, and internal failures. Every Eqiora-raised error retains
structured diagnostics:
try: result = eqiora.run( plan, state=eqiora.State.initial(plan), until_s=-1.0, output_times_s=(-1.0,), )except eqiora.EqioraError as error: print(error.category) for diagnostic in error.diagnostics: print(diagnostic.code, diagnostic.severity, diagnostic.message) print(diagnostic.graph_path, diagnostic.source_span)Python call-shape mistakes remain ordinary TypeError rather than fabricated
model diagnostics. Guarded native boundaries sanitize unwinding Rust panics as
InternalError; process abort and memory exhaustion are not recoverable
claims.
NumPy ownership
An Array owns a dense, native-endian, rank-one CPU float64 allocation.
Inspecting descriptors does not import NumPy.
array = result["x"].valuesview = array.numpy(copy=False) # `None` has the same meaningwritable = array.numpy(copy=True)
assert not view.flags.writeableassert writable.flags.writeableThe first no-copy projection transfers the native allocation once into an
opaque owner. The resulting C-contiguous NumPy array is irreversibly read-only
and remains alive independently of the Result and Array handles. copy=True
returns an independent writable allocation.
DLPack
Eqiora exports an independent versioned CPU snapshot:
import numpy as np
snapshot = np.from_dlpack(array)The snapshot never aliases immutable result evidence. Legacy capsule requests,
non-CPU transfers, non-None streams, and copy=False fail closed because
consumer enforcement of DLPack’s advisory read-only flag is not universal.
Differentiable-program inputs may arrive from a complete CPU:0 DLPack producer. Eqiora requests a no-transfer view, validates dtype, rank, length, byte order, alignment, and contiguity, then makes one documented owned staging copy before native execution. This is not a zero-copy execution-input claim. GPU streams, sparse/distributed arrays, and general Run inputs remain separate contracts.
Fixed arrays in sampled sessions
The bounded reference Model.sampled_session path accepts invariant real or exact
integer channel arrays through its existing typed input tables. For a Model with
drive: array<1, 2> at tick, pass one complete tuple per tick:
session = model.sampled_session( end_time_s=2, max_step_s=0.1, inputs={"drive": ("tick", [(3, 4), (5, 6), (7, 8)])},)session.advance_ticks(1)resumed = model.resume_sampled(session.checkpoint())Array State values and accepted output values are complete nested tuples; exact
integer components remain Python integers, including values above 2**53.
A failed tick commits neither a partial array nor another State update. Restart
preserves the accepted clock position and previously absent or present outputs.
The immutable Model fixes every extent. Scalar broadcasting, partial indexed
writes, spatial tensors and complex execution are not admitted by this path.
Input and retained output limits count scalar components, including nested arrays.