For AI agents¶
What an AI coding agent reads before using Conatus, and the JSON interface it uses while it works.
An agent that writes and runs Conatus code should be given one page,
AGENTS.md, at the root of the repository. It lists the whole usable surface, one call per line, grouped by
task, and the rules that keep an agent from the usual mistakes: reading a refusal and its
suggested fix instead of working around it, writing every tolerance that decides digits,
validating by residuals and explain(), checking cost and memory before a large run. Its content
is checked against the library by the test suite: every name it cites exists, every major public
element is there, and its example runs.
Ask the library, in JSON¶
Three commands answer an agent without any prose to parse. Each prints one JSON object (keys
sorted, "api": 1; within an API version fields are only added) and says what happened by its
exit code.
python -m conatus agent # versions, CPU/GPU, the user layer, watched runs
python -m conatus agent explain cn.Model.simulate # signature, required arguments and tolerances
# (and "deprecated": former argument names)
python -m conatus agent explain cn.problems.native.rayleigh_benard # a ready model: its unknowns too
python -m conatus agent check run.py # dry run of a script: what Conatus would refuse
check runs the script for real up to its first expensive call (a time integration, an
eigenvalue solve, a Newton search, a continuation…) and stops there. On the way, simulate builds
its solver, so a search that takes sim.solver is reached too; calls that write files or announce
a Studio run (watch, output, checkpoints, save) are skipped. At the stop, the call’s
arguments are checked against its signature, the model is discretised at the requested resolution
(so an under-resolved coefficient or a missing boundary condition is refused here, with its fix),
and the explain() texts are collected. With backend="cuda", simulate itself is the stop (no
device memory is allocated) and the device memory of the simulation is predicted; --memory does
the same prediction for a CPU simulation. No intercepted call integrates or solves (the list is
conatus.agent.EXPENSIVE).
check is not a sandbox. The script runs in the current process with your privileges: its own
code (writing a file, starting a subprocess) runs as written, and only the Conatus calls of that
list are intercepted, in that process only (not in a child process or an MPI launch). A script
that catches the stop (a bare except: or except BaseException) and goes on is reported as an
error, since the lines after it ran for real. The timeout cannot interrupt a long call inside
compiled code. Check only scripts you would run yourself.
For this script, where the drift test of an eigenvalue solve was asked for without its tolerance,
1import conatus as cn
2
3m = cn.Model(cn.Box(y=cn.walls(-1, 1)), name="Orr-Sommerfeld", Re=1e4, alpha=1.0)
4m.scalar("v")
5m.given(U=lambda y: 1 - y**2)
6m.eigenvalue("c", growth="imag")
7m.define("L2(f) = dy(dy(f)) - alpha**2*f")
8m.equations("L2(L2(v))/(1j*alpha*Re) - U*L2(v) + dy(dy(U))*v + c*L2(v) = 0")
9m.at("walls", "v = 0", "dy(v) = 0")
10res = m.eigenvalues(N=64, drift=96) # drift test without its tolerance
the report says what is missing, where, and the value Conatus suggests (exit code 1):
{
"api": 1,
"calls": [
{
"action": "stopped",
"call": "Model.eigenvalues",
"line": 10
}
],
"command": "check",
"errors": [],
"explain": {
"model": "Model 'Orr-Sommerfeld' ...",
"plan": "Discretization plan for EVP 'Orr-Sommerfeld' ..."
},
"memory": null,
"missing": [
{
"argument": "drift_tol",
"call": "Model.eigenvalues",
"kind": "tolerance",
"suggest": "1e-6",
"when": "drift=N2"
}
],
"notes": [],
"ok": false,
"plan": {
"groups": 1,
"type": "Plan"
},
"refusals": [],
"script": "os_check.py",
"stopped_at": {
"call": "Model.eigenvalues",
"line": 10
},
"warnings": []
}
exit code |
|
|---|---|
0 |
done; for |
1 |
|
2 |
usage error; |
4 |
|
The same functions are available from Python: conatus.agent.info(), conatus.agent.describe(name)
and conatus.agent.check(path) return the dictionaries printed above.
Follow a run that is already going¶
A long run declared with sim.watch(...) is followed and piloted with
python -m conatus studio status|watch|send|frame|log, with its own exit codes and per-agent
tokens: see Pilot a run from a script or an agent.
AGENTS.md¶
The page itself, as an agent reads it:
Conatus: spectral PDEs and dynamical systems on CPU and GPU (v0.1, API frozen; additions only). You write equations as on paper; Conatus discretises (Fourier/Chebyshev/curvilinear bases, taus placed for you), integrates, solves eigenproblems and finds structure (orbits, bifurcations, Floquet, optimal perturbations). It never guesses and never changes a numerical choice silently: it refuses, and says what to write.
1. Install, verify, orient (machine-readable)¶
pip install ".[tutorials]" # or installed (Python 3.11-3.13; h5py + matplotlib); writes only under ~/.cache/conatus
python -m conatus doctor --json # what this machine can run; exit 1 if something required is missing
python -m conatus agent # JSON: versions, CPU/GPU, user-layer names, watched runs
python -m conatus agent explain cn.Model.simulate # signature, required args, required tolerances (+ unknowns of a ready model)
python -m conatus agent check run.py # dry run (no solve): refusals + fix, missing tolerances, memory
2. Mental model¶
import conatus as cn: native layer. Geometry →cn.Model(unknowns, parameters, equations as text, conditions by boundary name) → numerics given at the call (N,scheme,dt,backend) → result object with.explain().import conatus.expert as X: expert layer (IR: bases, fields,X.IVP/X.EVP/…,X.discretize→X.Plan,X.IVPSolver). Every model compiles to it:m.compile(N).problem,sim.solver,sim.plan. A name present in both layers has one meaning.One word per notion:
steps(number of time steps),k(a wavenumber keyword; a problem parameter keeps its literature name, e.g.alphaof Orr–Sommerfeld),eta(radius ratio),n_modes,max_iterations,tol(exceptions until v0.2: the fieldsContinuationSettings(tolerance=, max_newton=), also read ascfg.tol,cfg.max_iterations); former names warn (conatus._deprecation.RENAMES).Names below:
m=cn.Model,sim=cn.model.Simulation,lin=cn.model.Linearization,X=conatus.expert.
3. Minimal working example (executed by the test suite)¶
import numpy as np
import conatus as cn
m = cn.Model(cn.Box(x=cn.periodic(22.0)), name="Kuramoto-Sivashinsky")
m.scalar("u")
m.equations("dt(u) + lap(u) + lap(lap(u)) = -u*dx(u)")
sim = m.simulate(N=32, scheme="RK443", dt=0.01) # numerics explicit, never defaulted
sim.initial(u=lambda x: np.cos(2 * np.pi * x / 22.0))
sim.run(steps=20) # run(until=T) must land on a whole step
print(sim.explain()) # short summary; full=True: every decision
os_ = cn.Model(cn.Box(y=cn.walls(-1, 1)), name="Orr-Sommerfeld", Re=1e4, alpha=1.0)
os_.scalar("v")
os_.given(U=lambda y: 1 - y**2)
os_.eigenvalue("c", growth="imag")
os_.define("L2(f) = dy(dy(f)) - alpha**2*f")
os_.equations("L2(L2(v))/(1j*alpha*Re) - U*L2(v) + dy(dy(U))*v + c*L2(v) = 0")
os_.at("walls", "v = 0", "dy(v) = 0")
res = os_.eigenvalues(N=64, drift=96, drift_tol=1e-6) # drift test removes spurious modes
i = int(res.order()[0]) # least stable first (growth = Im c)
assert res.residuals[i] < 1e-12 # validate by the residual, not the value
Critical point of a ready linearization, and its mode (Rayleigh–Bénard, no-slip walls):
import numpy as np
import conatus as cn
import conatus.expert as X
lin = cn.problems.native.rayleigh_benard_evp(k=3.1, Ra=1708.0, Pr=1.0)
P = lin.parametric(24, over=("Ra", "k"), drift=32, drift_tol=1e-6) # k: the wavenumber
crit = X.critical(P, "Ra", bracket=(1500.0, 2000.0), over={"k": (2.5, 3.8)}, xtol=1e-9)
assert abs(crit.value - 1707.762) < 1e-3 and abs(crit.over["k"] - 3.1163) < 1e-3
kc = crit.over["k"]
res = cn.problems.native.rayleigh_benard_evp(k=kc, Ra=crit.value, Pr=1.0).eigenvalues(
24, k=kc, drift=32, drift_tol=1e-6)
md = res.mode(int(res.order()[0]))
T = md.at("T", z=np.linspace(-0.5, 0.5, 101))[0] # exact, walls included; row x = 0
4. The surface, by task (one minimal call each)¶
Task |
Call |
|---|---|
Geometries |
|
Axes |
|
Unknowns, data |
|
Equations |
|
Boundary conditions |
|
Inspect before running |
|
Expert problems |
|
Parameters |
|
Time integration |
|
Boundary-value |
|
Eigenvalues |
|
Eigenvalues in a region |
|
Parametric, thresholds |
|
Transient growth |
|
Floquet / Hill (periodic base) |
|
Invariant solutions (ECS) |
|
Continuation, bifurcation |
|
Manifolds, slices, chaos |
|
Lyapunov, covariant vectors |
|
Particles, dye |
|
Adjoint, optimal perturbations |
|
Adjoint, optimal perturbations |
|
Symmetry |
native parities: |
GPU, distributed |
install: |
Memory, cost |
|
Resolution |
|
I/O, restart |
|
Uncertainty |
|
Data-driven |
|
Ready problems |
|
Provenance, runs |
|
Dedalus scripts |
|
Studio (watch live) |
|
Notebook, units |
|
Errors, tolerances |
|
5. Rules that prevent agent mistakes¶
Nothing is silent. A refusal is a
cn.errors.ConatusErrorwith.message,.where,.why,.fix,.doc. Readfix, apply it yourself only if it is what you mean; never wrap Conatus calls inexcept Exception: pass.Accuracy tolerances have no default (
REQUIRED):drift_tol,xtolofX.critical,tolofmethod="hill"/X.shell_floquet/nl.linear_gain/NewtonKrylovHookstep,toleranceofContinuationSettings,tolofX.NewtonSolver,arnoldi_tolofX.equilibrium_eigenvalues(omitted: v0.1 default 1e-12 with a DeprecationWarning),safetyofsim.cfland of adaptiveX.EmbeddedError. Write a value and say why;agent explain <name>lists them. Algorithm controls (trust radii, step growth, Krylov and iteration budgets, FD step) have declared defaults (cn.tolerances.explain()). Renamed arguments (nmodes/k→n_modes,X0→x0,xatol→xtol,maxit→max_iterations,nsteps→steps,radius_ratio→eta, …) still work with aDeprecationWarning; write the new names. A statement that the answer may not be the one you meant is a visiblecn.errors.CorrectnessWarning(BaseStateWarning,GroupWarning,DefaultChoiceWarning), never aDeprecationWarning(which Python hides outside__main__): treat it as an error and declare the choice it names. Never loosen a tolerance to make a refusal go away: find what is under-resolved.Under-resolved coefficients are refused (non-constant coefficient tail >
cn.tolerances.NCC_TAIL_TOL): raiseN, or declarencc_cutoff=/ncc_tail_tol=explicitly. Never truncate in your code. Callable initial data are refused when aliased, not periodic on a periodic axis or not finite, and warn (cn.errors.InitialDataWarning) when only not fully resolved (a coarseN);initial(..., check=False)only if you mean it; a run warns once (cn.errors.UnderResolvedWarning) when its spectral tail exceedsRUN_TAIL_TOL, and an ill-posed implicit term (backward diffusion) warns at compile (cn.errors.IllPosedWarning): treat both as errors in your script.Validate by residuals:
res.residuals,sol.residual,E1.residual,rank_report(),resolution_report(), convergence inNanddt(cn.uncertainty). A bare number is not a result.Read
explain()of every object you report (m,sim,res,lin, plan): a short summary by default (problem, scheme, checks, warnings);explain(full=True)states bases, tau placement, implicit/explicit split, dealiasing (default (p+1)/2 for the highest polynomial degree p, at least 3/2: declared), filters, residuals.run(until=T)must land on a whole number of steps (refused otherwise);dtis never adapted unless you build an adaptive controller; CFL is a stability indicator, not error control.Restarts are bit-for-bit only with identical numerics (scheme,
dt,N, options) and the same problem (model, equations, parameters, box): a mismatch is refused with the diff; a deliberate change issim.restart(path, allow=("kappa",)). Usesim.resume_or_start(dir, ...)(orcn.io.latest(dir)+sim.restart(...), which find.h5and.npz) for multi-job runs; both.h5and.npzrestart bit for bit on CPU and GPU. Root searches raise when unconverged (allow_unconverged=Truereturns the flagged iterate); a Newton result withtrivial=Trueis u = 0, not a new branch.Cost first:
python -m conatus agent check run.py --memoryandplan_memorybefore a GPU job;X.gpu_available()beforebackend="cuda". SmallNon the CPU first, then scale. A second identicalm.simulate(...)in the same process reuses the assembled operators (sim.explain()says “reused”; any change of parameters,N, dealiasing, equations, conditions or data rebuilds them).A solution found in a symmetry subspace is not declared stable until checked in the full space.
Transposed matrix ≠ physical adjoint; use
left=True,X.compare_adjoints,fm.vjpas documented.Experimental (shipped, tested, not promised; may change without deprecation): the Studio, the local hub, shells larger than 64 × 48 × 48, 3-D channel and pipe orbits (
conatus.ecs.shooting.ShootingRPO,conatus.ecs.pipe.PipeTravellingWave). First use warnscn.errors.ExperimentalWarning; say so in what you report.Xexports documented names only: anything else isconatus.<module>.<name>(X.<name>warnsDeprecationWarning, gone in 0.3).
6. Driving a run live (Studio channel, JSON, no server needed on a cluster)¶
python -m conatus studio runs # every run, live first (JSON, "api": 1)
python -m conatus studio status RUN # state, t, iteration, dt, scalars, alerts, commands, settable
python -m conatus studio watch RUN --for 600 # one JSON line per change; always bounded
python -m conatus studio send RUN pause|resume|checkpoint
python -m conatus studio send RUN set dt=2e-3 # only if declared settable, within bounds
python -m conatus studio send RUN stop # exit 5 + confirm_token; resend with --confirm TOKEN
python -m conatus studio log RUN -n 50 ; python -m conatus studio frame RUN --png out.png
Exit codes: 0 done (command written; confirm via events command_received → param_set/set_refused),
1 refused, 2 usage, 3 not authorised, 4 no such run, 5 confirmation needed, 6 hub unreachable.
Alerts: non_finite, silent, set_refused, failed, log_errors, … Act on one alert at a time,
check it was applied, then send the next command. Python: from conatus.studio.client import connect
(cl.status, cl.send, cl.wait). Hub tokens are per agent with a scope (read | command) and
run patterns (python -m conatus.studio.hub --state DIR addagent NAME --scope command --runs 'host:kh_*' --token-out FILE).
On a cluster: run agents and servers in compute-node jobs, never on login nodes; no background
process left behind; never poll the scheduler in a loop.
7. python -m conatus agent — JSON contract (api 1; fields only added within a version)¶
info [--no-probe] [--no-runs]→api, command, conatus, python, platform, host, cpus, packages, gpu{probed, available, backend, cupy, core, visible_devices, how}, surface{cn, modules, expert}, runs{available, runs[]}, commands, docs, rules.explain NAME(cn.<name>,expert.<name>,conatus.<module>.<name>) →qualified, kind, signature, summary, value, required, required_tolerances, conditional[{argument, when, suggest, kind}], deprecated{old: new}, methods | names, explain. Exit 4 if the name does not exist;explain FILE.pyis refused (exit 2: it never runs a script, usecheck).check SCRIPT.py [--memory] [--timeout S] [-- script args]runs the script for real up to its first expensive call (a CPUsimulateis built, a CUDA one stops; thenrun/eigenvalues/solve/ Newton/continuation/… stop it, listconatus.agent.EXPENSIVE); Conatus calls that write files (watch,output,checkpoints,save) are skipped. Not a sandbox: the script’s own code (file writes, subprocesses) runs with your privileges; never catchBaseExceptionaround Conatus calls. →ok, calls[{call, line, action}], stopped_at, missing[{call, argument, kind, suggest}], refusals[{type, message, where, why, fix, doc, line}], errors, warnings, explain{model, simulation, plan, ...}, plan{type, groups}, memory{peak_bytes (allocators), peak_with_process_floor_bytes (+ the declared CUDA context/kernels floor, 512 MiB measured), report} | null, notes, elapsed_s. Exit 0 ok, 1 refusal/missing/error, 2 usage. Python:conatus.agent.check(path),conatus.agent.info(),conatus.agent.describe(name).
8. Where the full documentation is¶
User guide docs/site/guide/ (geometries, equations, boundary_conditions, timesteppers, gpu, io,
studio_agents, explain, errors, dynamics/*), tutorials docs/site/tutorials/, API reference
docs/site/api/, frozen surface python -m conatus._surface. Web: /docs/guide/agents.html.