Skip to content

Releases: rubendahan/conatus

Conatus 0.1.2

Choose a tag to compare

@rubendahan rubendahan released this 21 Oct 22:31

A correction release for the findings of an external review of 0.1.1 and of a review of its
documentation. Each fix has a test against an independent reference value. Upgrading is
recommended: some results of 0.1.1 were wrong (see Correctness). Results that can differ from
0.1.1: large finite eigenvalues of dense QZ, parametric pencils with small parameter terms,
contour results with close roots, critical(over=) and the Hopf curves of continue_codim2.

Security

  • Local Studio: the session cookie is cn_session_<port> with --local and __Host-cn_session
    over HTTPS, and each session is bound to the Host it was opened at. The cookie was valid when
    replayed from any port of 127.0.0.1. On plain HTTP, another service on the same host name and
    port can still read it; the Studio guide says so.
  • Telemetry spools, Studio audit logs and token files are created 0600 in 0700 directories,
    whatever the umask; they took the umask's mode. Run records and outputs keep the umask's mode.
  • hub serve --local --state hub_state uses the directory given; the explicit spelling of the
    default was replaced by a temporary directory.

Correctness

  • Dense QZ (solve_dense, m.eigenvalues(method="dense")): with a nonsingular M every
    eigenvalue is finite and returned; L = -diag(1, 1e13), M = I lost 1e13 as infinite. The
    rank of M is measured on M as given, so a row of rounding noise where M has a zero row
    does not make M look nonsingular (its infinite eigenvalue was returned as a finite value near
    1e17). The log states the rank and names the pairs it cannot classify.
  • Parametric pencils (m.parametric): a coefficient entry is discarded only at rounding level
    for that entry; a whole matrix 1e-14 times the pencil's largest entry was deleted as
    "identically zero" (eigenvalue 1e-14 r read 0 at r = 1e14). An evaluation where the discarded
    entries could matter is refused.
  • continue_codim2(kind="hopf") stops at the Bogdanov–Takens point (κ = ω² = 0) and keeps only
    points whose Jacobian has the pair ±iω; it continued into real-saddle points. Tutorial 10, its
    figure and its checks are corrected.
  • cn.contour.beyn: close distinct roots (0.2 and 0.2000001) are returned separately, each
    counted on its own square; they were one root "of multiplicity 2" at their mean.
    multiplicity is the number of eigenvalues in a square of half-side enclosure (new field)
    around each value.
  • cn.contour.beyn(counted=...) refuses a count of another operator (it evaluates det T again
    at points the count recorded); it accepted any count of the same box.
  • cn.contour.count is described as a test on samples of the boundary. For a function T(z)
    with poles, a zero and a pole on opposite sides of the boundary, closer to it than a step, are
    not seen; count_method, explain() and the new CountResult.scope and
    poles_by_construction say so. The contour results no longer use the word "certified".
  • BeynResult.residual_definition states the scale of the residuals; the guide's formula for a
    function T(z) was not the one computed.
  • critical(over=) computes the neutral value at over_points points of the interval (default
    9) and refines every local minimum of that scan; one bounded Brent search could return a higher
    local minimum as the critical value. The log lists the minima found, and a point where the
    pencil is refused is logged as unchecked.
  • The run cache refuses a function whose code reads a value chosen at run time (getattr,
    vars, os.environ, open, np.load, ...); such a read kept the cache key, and a changed
    configuration returned the old value.

Checks that reported success without measuring it

  • verify() measures the right and left residuals of the vectors the mode holds when it is
    called; it printed the residual stored by track(), so a mode whose vector had been changed
    still passed. A zero vector, or a vector of the wrong size, is not a pass. A point where the
    parametric pencil is refused is reported as a failed check instead of an exception.
  • resolution_report is ok only when every variable of the plan was measured; an empty or
    partial state is reported as not measured (complete, unmeasured). An array of the wrong
    shape is refused with the expected shape instead of an IndexError.
  • python -m conatus agent check checks the argument values of Simulation.run and
    IVPSolver.run at the stop (run(steps=-1) was reported ok); the new validated field says
    whether the values of the stopped call were checked.
  • The acceptance scripts shipped with the package check the mathematical requirement of each
    case, and their runner counts a script that crashes or prints no result as a failure.
  • uncertainty(...).explain() prints tuple resolutions such as N=[(8, 8), (12, 12), (16, 16)]
    (it raised TypeError).

Documentation and website

  • The home page and the README start with one complete calculation, the critical Reynolds number
    of plane Poiseuille flow, shown with its recorded output. The site and the documentation share
    one navigation and one getting-started route.
  • The code shown on the guide and tutorial pages runs when copied in order, and the outputs shown
    are what it prints; a test runs every page.
  • Tutorial figures redrawn: tables where a plot carried little information, captions computed
    from the plotted data, values below the axis floor drawn as upper limits.
  • Wording: claims are stated with their scope and threshold, without slogans; the time-stepper
    orders are given per scheme; the pages no longer compare Conatus with other codes.
  • The Validation page lists published values and closed forms; the values are labelled as
    reported by the author, since their run records are not public.
  • Pages no longer cite examples or test files that the package does not ship; the deprecation
    versions and the release status are read from the package.
  • SHA256SUMS lists bare file names, so sha256sum -c SHA256SUMS works in the directory that
    holds the downloaded wheels.

Known limitations

  • Unchanged from 0.1.1 (see below).
  • A contour count for a user function T(z) with poles is a test on samples, not a proof.
  • Local Studio over plain HTTP: another service on the same host name and port can replay the
    session cookie.
  • Dense QZ: a pencil whose M is singular and which also has finite eigenvalues larger than
    about 1e12 times the scale of the pencil can have pairs that cannot be classified; they are
    kept as finite and named in the log.

Conatus 0.1.1

Choose a tag to compare

@rubendahan rubendahan released this 20 Oct 23:41

A correction release: every finding of an external review of 0.1.0, and of our own audits of
security, input validation, shared state, invariance and assurances (checks that
reported success without measuring it), is fixed, each with a test. Upgrading is
recommended: some results of 0.1.0 were wrong (see Correctness). Changes that can alter a result
you computed with 0.1.0: the default dealiasing grid of some models, complex fields, the modes
kept by the eigenvalue filter, and the random draws of Lyapunov spectra (all below).

Added

  • Conatus Studio, local: python -m conatus studio serves live runs, telemetry and controls on
    your machine (pip install "conatus[studio]"; on a cluster, a batch job and an ssh tunnel). It
    is experimental (see below); a hosted hub is not part of this release.
  • An interface for AI agents: python -m conatus agent (info, explain <name>,
    check <script.py>), with AGENTS.md at the root of the repository.
  • Eigenvalues in a region of the complex plane:
    m.eigenvalues(N, ..., region=cn.box(re=(a, b), im=(c, d))) returns every eigenvalue of the
    discretised problem in the box, with res.count certified by the argument principle (computed
    from the sparse LU, never the determinant itself) and the eigenvalues from Beyn's
    contour-integral method, each validated by its residual. cn.contour.count and
    cn.contour.beyn do the same for a pencil, a Floquet-Hill operator or a function z -> T(z)
    (nonlinear eigenproblems).
  • Experimental features are marked: cn.errors.ExperimentalWarning, once per process, on the
    first use of a feature that is shipped and tested but not promised (the Studio,
    a spherical shell larger than 64 × 48 × 48, ShootingRPO, PipeTravellingWave); the guide
    page "Experimental features" lists them.
  • One-line repr of simulations, solvers and NewtonKrylovHookstep; the under-resolved warning
    of an eigenvalue states the accuracy to expect (about sqrt(tail) relative, an order of
    magnitude, not a bound).

Deprecated

  • conatus.expert exports only the names the guide documents. Any other name written as
    conatus.expert.<name> still works, with a DeprecationWarning naming the module to import it
    from, and stops working in 0.3. The Studio protocol moved to conatus.telemetry, the BibTeX
    strings to conatus.methods.BIBTEX.
  • The __all__ of the public modules lists documented names only; the other names stay
    importable at the same path.

Security

  • run.reproduce() of a decorated call no longer imports and calls the module:function named by
    a run record that another account can write (the record, its run directory or runs/ not owned
    by you, or writable by group or others): a typed OptionError names the two ways out (fn=,
    chmod go-w). New run directories are created without group/other write whatever the umask, so
    your own records are never refused; a deliberately group-writable shared registry needs fn=.
  • from_dedalus refuses private and dunder names of dedalus.public (d3.__dict__, d3.tr,
    d3.__reduce_ex__) and attributes of the distributor outside a whitelist: they handed the
    script the translator's own interpreter.
  • Run records redact the value of credential-like command-line options (--token X,
    --api-key=X) in invocation.argv.
  • File names in the run registry (Registry.run_dir, Run.file_path, save_file) cannot leave
    the run directory; a forged inline array in a record (shape not matching its values, too long,
    negative dimension, non-numeric dtype) is refused; temporary .npz names are random
    (mkstemp); the adjoint journal refuses file names that are not its own generation files.

Correctness (wrong results now fixed)

Simulation and models:

  • A field declared complex is solved in a complex system (its imaginary part was dropped).
  • The default dealiasing factor counts spatially varying given fields as factors of a product
    (U*u*u is cubic: factor 2, not 3/2), and a function of given fields alone (exp(G), 1/G) as
    one factor (exp(G)*u**3: 5/2); explain() states the condition (fields truncated at N modes)
    instead of a universal "exact (alias-free)". This changes the default grid of a model whose
    equations multiply given fields with a total degree of 3 or more (printed by explain()); pass
    dealias=3/2 to keep the 0.1.0 grid.
  • plan.solve(data) with new data for a coefficient of the operator (q in q*u = f) rebuilds
    the operator instead of changing only the right-hand side, and the residual is that of the
    updated problem (mapped, Cartesian, polar and spherical plans). Data are compared by value (an
    array changed in place is new data); the last rebuilt plan is reused when the same coefficient
    is passed again.
  • Integrals on a sine/cosine axis (walls(a, b, basis="sincos")) are over the physical interval
    [a, b], exact for both parities, in evaluate(), the dye diagnostics and equations (they were
    over the doubled period).
  • EmbeddedError(adapt=True) rejects a step whose error estimate exceeds tol and retries it with
    a smaller dt (the step used to be kept, only the next dt changed); ConvergenceError when
    tol cannot be met at dt_min.
  • SBDF3 and SBDF4 with the default startup="lower_order" converge at order 2, and now say so:
    solver.effective_order and explain() give the effective order,
    scheme_description(name, effective=) gives the formal and the effective order;
    startup="rk443" keeps the nominal order.
  • A failing model declaration (equations, at, given, dye, define, function,
    eigenvalue) leaves the model unchanged, so the corrected call works.
  • User functions (m.function) work on the sphere; tensor boundary conditions keep their rows
    whatever their scale.
  • A user function name belongs to its model: another model, or a re-run notebook cell, can declare
    its own g.
  • No false "under-resolved" warning for a field exactly represented on very few modes.
  • sim.particles: splitting a run into several run() calls no longer lowers the order.
  • Ensembles stop exactly at max_steps; counts, indices and labels are never truncated;
    Ensemble.states() and velocity snapshots are copies.
  • Checks that reported success without having measured it now say what they found: NaN or
    infinite data make resolution_report and estimate_modes say "non-finite" (an all-NaN field
    was "all resolved") and give a NaN or infinite CFL; verify() measures the tail of every
    component of a vector unknown and fails a tail it could not measure; critical(over=) says
    when the minimum sits at an end of over; time_average flags an unreliable batch-means error;
    NaN residuals are kept, never shown as small.

Analysis:

  • Floquet–Hill operators: a singular intermediate block no longer gives NaN (a pivoted banded LU
    takes over), and solve checks its backward error.
  • Generalized eigenvalue filtering is invariant under a scaling of the equations.
  • uncertainty(): the spatial error uses the actual resolution increments; trusted requires
    scheme_order and a consistent refinement.
  • max_growth() compares the ends of the interval; a CertifiedRoot is never certified when
    unconverged.
  • POD reports the fraction of the total energy; the low-level optimal response is the propagated
    state.
  • Continuation of boundary-value problems converges at the fold of a uniform state (the residual
    scale includes the parameter terms).
  • from_dedalus: **options in calls are expanded, a problem's own namespace={...} resolves its
    equations, NumPy keywords in initial data are kept, and complete is true only without
    unsupported or approximated lines. DedalusTranslation.to_script() writes a runnable program (it
    replays the translation and runs it).

Results that changed silently with unchecked inputs (now refused with a typed error naming the fix):

  • ncc_cutoff=nan reduced every non-constant coefficient to its mean; P.least_stable(re=...) on
    a pencil in Re used the declared Re; time_parameter= naming no parameter froze the forcing;
    the CFL step of a 1D scalar velocity was computed from its first grid value; a complex M in
    generalized_eigenvalues was cast to float; switches given as text were truthy
    (complex="no" was True); 2D or complex series in cn.time_average were flattened or cast;
    decreasing snapshot times gave a negative dt; Decimal parameter values compiled as complex;
    a shift-invert n_modes above the pencil size was clipped.
  • plan.params writes are checked, and a change after the group matrices were assembled is
    refused (the cached groups kept the old value).
  • A model that declares t itself and uses it in a time-dependent model gets a
    DefaultChoiceWarning (its sin(t) is constant).

State shared between calls:

  • A solver keeps at most FACTOR_CACHE_SIZE (16) factorizations, least recently used dropped
    first: an adaptive step (EmbeddedError(adapt=True)) or a search over the period kept one more
    set per step until memory ran out (CPU Cartesian, polar, spherical and batched solvers).
  • Package sources (or the compiled core) changed after import conatus are declared by
    provenance.code_state() (changed_since_import, not reproducible, "restart the interpreter"),
    and such a run is never a cache source or a cache hit: the hashes named the files on disk, not
    the code that ran. The change is judged by the content of the modules the process imported
    (sha256 recorded at import), not by modification times: an immediate edit or a deleted module is
    seen, a touch or a checkout back to the same text is not a change.
  • diagnostics.Grid.weights() and cell_volumes(), sim.coordinate("theta") on the sphere,
    sim.coeffs() on a mapped domain, EVPMode.grid/coeffs/coordinate and
    PipeTravellingWave.phi return the caller's own arrays (an in-place edit changed later
    inte...
Read more

Conatus 0.1.0

Choose a tag to compare

@rubendahan rubendahan released this 12 Oct 18:08

First public release 🎉

Install (CPU): pip install conatus

GPU (beta): download the +cu12 wheel matching your Python below, then
pip install "./conatus-0.1.0+cu12-cp312-....whl[gpu]" and check with python -m conatus doctor.
Built for A100 (sm_80) and L40S (sm_89), needs a driver supporting CUDA 12.9.

Linux x86-64, CPython 3.11, 3.12 and 3.13. Checksums in SHA256SUMS.

See the changelog and the
website.