You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
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).
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.