Skip to content

Choose a tag to compare

@yannrichet-asnr yannrichet-asnr released this 04 Sep 10:50
90053a7

Claude Code plugin: slash commands added

  • The fz Claude Code plugin (install with /plugin marketplace add Funz/fz
    then /plugin install fz@funz) now ships four slash commands alongside the
    Agent Skill: /fz:wrap (wrap a simulation code and verify it step by step),
    /fz:run (run a parametric study), /fz:design (adaptive design of
    experiments / optimization / calibration with fzd) and /fz:install
    (find and install an official fz-<code> wrapper or algorithm). Plugin
    bumped to 1.2.0, aligned with the package release.

Default run timeout raised to 1h, per-model override

  • FZ_RUN_TIMEOUT's default changed from 600 seconds (10 minutes) to 3600
    seconds (1 hour).
  • Models can now set their own "timeout" entry (int seconds) to override
    FZ_RUN_TIMEOUT for that model specifically. Setting it to None/null
    or 0 disables the timeout entirely for that model. An explicit
    timeout= argument to fzr()/fzc() still takes precedence over both.

--input_variables no longer required for variable-free datasets

  • fzc/fzr CLI (standalone and fz compile/fz run) no longer require
    --input_variables when the input files declare no variables. If they
    omit it and the model does declare variables, the CLI now errors out
    listing the variable(s) it found, instead of failing the generic
    "required argument" check before even looking at the input files.

Formula number formatting (@{expr | pattern})

  • Formula format specifiers now support the full java.text.DecimalFormat
    subset used by the original Java Funz, not just fixed-decimal patterns:
    # digits strip insignificant trailing zeros (@{3.1 | #.###}3.1,
    @{3.0 | #.###}3) and scientific notation is supported
    (@{123456.789 | 0.00E00}1.23E05). 0 digits still zero-pad as
    before (@{1/3 | 0.0000}0.3333). Documented in
    doc/formulas-and-interpreters.md ("Basic Formula Syntax" → "Number
    Formatting").

Shared static files across cases (input_static)

  • fzr()/fzc()/fzi()/fzd() gain an input_static parameter (CLI
    --input_static, repeatable or an inline JSON list): files identical
    across every case (e.g. a shared weather CSV or a large reference
    dataset) that are never templated/substituted, never re-hashed per case,
    and (for relative paths) not duplicated on disk per case. This is a
    function argument, not a model field — the model itself doesn't need to
    know about it.
    • Absolute path entries are assumed already present at that same path
      on the calculator side too (shared/mounted storage); fz never copies,
      symlinks, or transfers them - only hashes them (once per fzr()/fzd()
      call), so cache:// still reacts if the shared file's content changes.
    • Relative path entries are resolved against the cwd fzr()/fzd()
      was called from, identified by basename, and symlinked into every case's
      result/temp directory (falling back to a real copy if the platform
      doesn't allow symlinks, e.g. Windows without developer mode/admin).
      Explicitly transferred to ssh://, slurm:// (remote), and funz://
      calculators, since they live outside input_path and wouldn't otherwise
      be found by the normal per-case file transfer.
    • fzi() never scans them for $variables; .fz_hash always includes
      them (once, memoized) so cache matching stays correct.
    • fzd() passes input_static through unchanged to each iteration's
      internal fzr() call.
    • See doc/core-functions.md ("fzr" → input_static) for the full write-up.
    • fzr() now logs a one-time warning (per file, not per case) when an
      input_path file has no variables and is at least
      FZ_STATIC_CANDIDATE_MIN_SIZE bytes (default 1 MiB), suggesting it be
      passed via input_static instead; set FZ_STATIC_CANDIDATE_MIN_SIZE=0
      to disable.
    • New tests/test_static_files.py (8 tests, sh://),
      tests/test_static_files_ssh.py (real SFTP transfer over ssh:// to
      localhost, wired into ssh-localhost.yml), and
      tests/test_static_files_warning.py (4 tests for the new warning).

Configurable case directory naming (case_naming), thread-safe signal handling

  • fzr()/CLI fzr/fz run gain a case_naming parameter (--case_naming,
    env FZ_CASE_NAMING): "path" (default, unchanged var1=val1,var2=val2,...
    subdirectories), "hash" (short content hash of the variable combination),
    or "index" (case_<i>). "path" can exceed filesystem filename length
    limits (~255 chars) with many input variables; "hash"/"index" avoid
    that. With "hash"/"index", a single cases.csv manifest is written
    at the results root mapping each case directory to its variables; each
    case's own info.txt still has them too, as a fallback if the manifest
    is missing or incomplete. fzo() now recovers variable columns from
    whichever is available when a directory name doesn't parse as
    key=val,....
  • Fixed: fzr/fzd installed a SIGINT handler unconditionally, which
    raises ValueError when called from a non-main thread (e.g. Streamlit
    reruns, a ThreadPoolExecutor worker, or a background thread embedding
    fz). Signal handler install/restore is now skipped outside the main
    thread instead of raising.
  • fzd() now runs its internal per-iteration fzr() calls (file-based
    models) with case_naming="index" rather than the default "path":
    algorithm-generated design points can carry many variables with long
    float values, so iter<NNN>/case_<i>/ avoids filename length limits.
    cache:// matching is by .fz_hash content, not directory name, so
    cross-iteration cache reuse is unaffected.

Multi-objective (vector) objectives in fzd

  • fzd()'s output_expression now also accepts a list of expressions:
    each case then yields a list of scalars (one per expression, same order),
    passed as-is to the algorithm's get_next_design()/get_analysis().
    A plain string keeps the legacy single-scalar behavior unchanged.
    This completes the vector-output work of #75/#76: #76 reduces vector
    outputs to a scalar objective; this change allows the objective itself
    to be a vector, enabling native multi-objective algorithms.
  • New evaluate_output_expressions() in fz/algorithms.py (str or list of
    str); XY DataFrame and Y_<iteration>.csv gain one column per objective.
  • New example algorithm examples/algorithms/nsga2.py: NSGA-II (Deb 2002)
    at the fzd plugin format — batch-parallel generations, SBX + polynomial
    mutation, Pareto front written to nsga2_pareto.csv and returned in the
    analysis data (pareto_X/pareto_F). Objectives are all minimized;
    negate an expression to maximize. Validated against the analytic Pareto
    front of the Binh-Korn problem (objective-space deviation < 3%).
  • 8 new tests in tests/test_fzd_multiobjective.py; no regressions on
    test_fzd.py, test_fzd_vector_outputs.py, test_algorithm_options.py,
    test_algorithm_plugins.py (85 passed).

Native shell-free output extraction: python://, jq://, yq://, xpath://

  • Model output values can now be native Python expressions, marked with the
    python:// prefix, e.g. "pressure": "python://grep(r'pressure = (\S+)', 'output.txt')".
    Expressions are evaluated in the case result directory with built-in helpers
    (read, lines, line, grep, json_file, csv_file) and the re,
    json, math, statistics, np, pd modules — no bash/grep/awk needed,
    fully portable on Windows without FZ_SHELL_PATH.
  • New jq:// output prefix for JSON extraction with the
    jq command-line tool, e.g.
    "energy": "jq://.energy results.json". Requires the jq executable on
    PATH; no bash/shell otherwise involved.
  • New yq:// output prefix for YAML (and, via extension auto-detection,
    JSON/XML/TOML) extraction with the mikefarah/yq
    command-line tool, e.g. "version": "yq://.metadata.version config.yaml".
    Requires the yq executable on PATH; no bash/shell otherwise involved.
  • New xpath:// output prefix for XML extraction with xmllint --xpath
    (libxml2), e.g. "pressure": "xpath://'//pressure/text()' output.xml".
    Requires the xmllint executable on PATH; no bash/shell otherwise
    involved. The matched text is cast to int/float when possible, like grep.
  • New bash:// output prefix to explicitly mark a legacy shell-command
    output, alongside the implicit default (a plain string with no recognized
    prefix is still treated as a shell command, unchanged, for backward
    compatibility). All five forms (bash:///implicit, python://, jq://,
    yq://, xpath://, plus Python callables) can be freely mixed in the
    same model.
  • From the Python API, output values can also be callables receiving the case
    result directory as a pathlib.Path.
  • hdf5_file(path, dataset=None) helper for HDF5 results (optional h5py
    dependency); values are converted to native Python types.
  • fz is now importable on Windows without bash: the import-time check warns
    instead of raising. Only genuinely shell-dependent features (legacy
    shell-command outputs, sh:// calculators) raise a helpful error with
    installation instructions at use time; shell-free workflows (python://,
    jq://, yq://, xpath://) need no bash at all. A dedicated CI workflow
    (shell-free-outputs.yml) exercises these output kinds cross-platform,
    including Windows, without provisioning bash.
  • The python:// prefix also works with fzo --output-cmd NAME="python://..."
    on the CLI (as do jq://, yq://, xpath:// and bash://).

Vector (array) output support in fzr/fzo

  • Output entries can resolve to a Python list, not just a scalar — a
    natural fit for time series, per-node profiles, spectra, etc. Supported
    via python://grep(..., all=True), csv_file(column=...),
    hdf5_file(dataset=...), jq:///yq:// filters selecting an array,
    xpath:// matching several XML nodes, or a plain shell command printing
    a JSON array. fzr/fzo store the full list per case unmodified — no
    flattening, truncation or padding, so cases can have vectors of
    different lengths.
  • Fixed xpath://: an expression matching more than one XML node used to
    return a single string with the matched nodes' text concatenated by
    xmllint with no reliable separator, instead of a vector. It now
    returns a list of per-node values (each cast like grep's default);
    single-node and zero-node matches are unaffected (same scalar behavior
    as before).
  • New tests/test_vector_outputs.py and examples/vector_outputs_example.md
    cover vector outputs end-to-end across fzo/fzr, all extraction forms,
    and fzo/fzr coherence. See doc/model-definition.md ("output" → "Vector /
    array outputs") for the full write-up, including the CSV/JSON
    persistence caveat (to_csv() stringifies lists; prefer --format json,
    to_pickle, or to_parquet for a lossless round trip).
  • fzd (design of experiments / optimization) still expects a scalar
    objective per case; vector-output support there is tracked as a
    follow-up.

Vector-valued outputs as fzd objectives

  • output_expression (the expression fzd evaluates to get its scalar
    objective per case) can now reduce a vector-valued output (a time
    series, a per-node profile, ...) down to that scalar: sum(), len(),
    sorted(), mean(), median(), stdev(), variance() join the
    existing math functions and indexing/slicing (series[-1]) available in
    evaluate_output_expression.
  • Referencing a vector-valued output without reducing it (e.g.
    output_expression="T_series" on its own) used to fail with a bare
    float() argument must be a string or a real number, not 'list'
    TypeError. It now raises a clear ValueError naming the offending
    output(s) and suggesting a reduction (e.g. mean(T_series)); the
    affected point is reported as a failed evaluation (None), like any
    other per-case error — it does not stop the run.
  • Two different vector outputs can be combined in the same expression:
    plain + concatenates two lists (mean(a + b) pools both before
    averaging), and the new zip() helper combines them element-wise, e.g.
    sqrt(sum((x - y) ** 2 for x, y in zip(sim, ref)) / len(sim)) for an
    RMSE/residual between a simulated and a reference series.
  • Fixed a related bug this uncovered: evaluate_output_expression() used
    to evaluate with a split globals/locals dict
    (eval(expr, {"__builtins__": {}}, safe_dict)), which made any
    generator-expression or comprehension body unable to see the output
    variables or helper functions (Python resolves names inside a nested
    comprehension/genexp scope through globals only, never through a
    separately-passed locals dict) — e.g. max(abs(x - y) for x, y in zip(a, b)) used to fail with a spurious name 'abs' is not defined.
    Now uses a single combined globals dict, so any output variable or
    helper function works the same whether referenced directly or from
    inside a generator/comprehension body.
  • fzd's objective itself is still a single scalar per case (no
    multi-objective / vector-objective optimization) — this only concerns
    reducing a vector-valued model output to that scalar.