Tenax Integration¶
Graphcal can serve a checked engineering calculation as a persistent
Tenax model. The process compiles
once, receives Arrow record batches on stdin, evaluates each row through the
same typed DAG used by graphcal eval, and writes Arrow results on stdout.
The initial interface is intentionally strict: Tenax stdio protocol version 1 and Arrow schema version 2. Unsupported Graphcal values are rejected during startup rather than flattened, renamed, or converted without an explicit type.
1. Define a model¶
Declare finite sampling domains on every numeric input, use a concrete named index for each categorical input, and make selected Boolean outputs public:
pub index Mode = { Nominal, Degraded };
param load: Force(min: 0.0 N, max: 10_000.0 N);
param cycles: Int(min: 0, max: 100_000);
param mode: Key<Mode>;
pub node failure: Bool =
@load > 8_000.0 N
&& @cycles > 50_000
&& @mode == Mode.Degraded;
The interface above discovers as:
load: continuousFloat64, bounds0..10000, unitN;cycles: integerInt64, bounds0..100000;mode:Dictionary<Int32, Utf8>, categoriesDegraded,Nominalin lexical order;failure: Boolean output.
Dictionary codes are not semantic identities. Graphcal always resolves a categorical request by its dictionary value, so a sender may assign codes in any valid order.
2. Start the persistent server¶
Repeat --output to select multiple public Boolean nodes. Discovery preserves
source declaration order even when flags are given in another order.
Do not run this command as an interactive text program. Stdout consists of two concatenated Arrow IPC streams and must be connected to Tenax or another protocol-compatible client. Logs and errors use stderr.
3. Spawn it from Tenax¶
A Rust Tenax client can spawn the command, inspect discovery, and reuse the same child for multiple requests:
use std::process::Command;
use tenax::{
EvalRequest, EvaluationId, Evaluator, Feature, InputChunk, StdioEvaluator,
};
let mut command = Command::new("graphcal");
command.args([
"model", "serve", "reliability.gcl",
"--output", "failure",
]);
let evaluator = StdioEvaluator::spawn(command)?;
let schema = evaluator.schema();
let inputs = InputChunk::new(
schema,
vec![
Feature::continuous("load", vec![2_000.0, 9_000.0])?,
Feature::integer("cycles", vec![10_000, 80_000])?,
Feature::categorical(
"mode",
vec!["Nominal".to_owned(), "Degraded".to_owned()],
)?,
],
)?;
let request = EvalRequest::new(EvaluationId::new(1), 42, inputs);
let result = evaluator.evaluate(vec![request]).next().unwrap()?;
// More evaluate(...) calls reuse the compiled Graphcal process.
let status = evaluator.shutdown()?;
assert!(status.success());
# Ok::<(), Box<dyn std::error::Error>>(())
Graphcal models are deterministic, so the request seed is validated for protocol consistency and otherwise ignored.
4. Sample and run PRIM¶
Tenax sampling and analysis use the discovered domains directly:
use tenax::{
Evaluator, Objective, Prim, PrimConfig, evaluation_to_dataset,
sample_latin_hypercube,
};
let request = sample_latin_hypercube(evaluator.schema(), 5_000, 0x5eed, 1)?;
let retained = request.clone();
let result = evaluator.evaluate(vec![request]).next().unwrap()?;
let failure = evaluator.schema().output_position("failure")?;
let dataset = evaluation_to_dataset(
evaluator.schema(), retained, result, failure,
)?;
let config = PrimConfig::new(0.05, 0.05, 0.05, Objective::Lenient1)?;
let discovered = Prim::new(&dataset, config).find_box();
# Ok::<(), Box<dyn std::error::Error>>(())
Always call shutdown() when finished. Dropping an incomplete result iterator
makes stream reuse ambiguous, so Tenax terminates that child.
Supported interface¶
Inputs¶
| Graphcal type | Requirement | Arrow type |
|---|---|---|
Quantity / Dimensionless |
Finite closed min and max; finite width |
Float64 |
Int |
Closed i64 min and max |
Int64 |
Key<I> |
Concrete non-empty named index | Dictionary<Int32, Utf8> |
Dimensioned quantities are exposed in a canonical scale-1 SI unit expression;
bounds and request values use that same unit. Dimensionless omits unit
metadata. Parameters with defaults remain inputs because every Tenax v2 request
contains every discovered input.
Outputs¶
Each selected value must be a directly declared, explicitly public, scalar
Bool node. Graphcal preserves source order and rejects duplicate names across
the complete input/output interface.
Graphcal's prepared evaluator itself also supports Boolean, datetime, complex, algebraic, key, and recursively indexed bindings and outputs. Those richer families are not silently forced through schema v2; they require the planned recursive Arrow protocol.
Failure behavior¶
One valid request batch produces exactly one result batch with the same row count, ID, and row order.
- Success: status
0, every output non-null, no failure message. - Ordinary Graphcal runtime/domain/assertion failure: status
1, every output null, diagnostic present. - Defensive binding rejection beyond the shared protocol checks: status
4.
Malformed IPC, an incompatible request schema, null input/context values, non-finite or out-of-domain numeric data, unknown categorical values, invalid dictionary keys, inconsistent IDs/seeds, duplicate IDs, and internal evaluator invariants are process errors. They do not become synthetic failed rows.
Protocol lifecycle¶
Startup order is fixed to avoid pipe deadlocks:
- Graphcal loads, checks, and prepares the project.
- Stdout receives a schema-only discovery stream and its EOS marker.
- Stdout receives the result-stream schema.
- Graphcal opens and reads the stdin request stream.
- Each non-empty request batch is evaluated sequentially.
- Request EOS finishes the result stream and exits successfully.
A reader for the first stdout stream must not buffer bytes past its EOS unless it can return those bytes for the second stream. Arrow's unbuffered stream reader is suitable.
Startup troubleshooting¶
graphcal model serve reports configuration failures before writing stdout.
Common errors include:
- a numeric parameter has no
min, nomax, or a one-sided/non-finite range; - an input is
Bool, datetime, complex, algebraic, indexed, a coordinate key, or a finite key, none of which schema v2 can represent; - the selected output is unknown, private, duplicated, or not scalar
Bool; - an input and output share a field name;
- project dependencies or WASM plugins fail normal Graphcal loading checks.
Use graphcal check reliability.gcl first for source diagnostics, then inspect
graphcal model serve ... stderr for interface-capability errors. Never decode
stdout as text.