From c4f2528c98aebd6b5fc197eb90f3aa941887fe51 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 20:28:46 +0000 Subject: [PATCH 1/3] Restructure and correct the documentation site Structure - User Guide split into Templates & Models, Core Functions, Calculators, Running Studies, Design of Experiments, Installing, AI Agents - new pages: template syntax, output extraction, timeouts, results & traceability (layout, case naming, manifest, RO-Crate, input_static), writing algorithms, installing models, AI agents (plugin, fz-mcp), CLI reference, constraints & limits, security model - moved pages redirected (mkdocs-redirects); strict build with link and anchor validation; requirements.txt pins MkDocs < 2; nav check script Corrections (each checked by running fz) - fzr examples passing calculators positionally (4th slot is results_dir) - callbacks are a dict; fzi returns formulas and static objects too; fzc writes one sub-directory per case; fzo must target case dirs - default delimiters: variables () / formulas {} without a delim key - FZ_RUN_TIMEOUT=0 times out immediately; no timeout for ssh/slurm - parallelism = number of calculator entries; FZ_MAX_WORKERS only caps - nonexistent FZ_CACHE_DIR, FZ_UDP_DISCOVERY_PORT, CRITICAL log level, --timeout flag, slurm_options, n_parallel, funz auto-discovery token - SLURM (srun / sbatch arrays, resources), funz:// (UDP port), SSH auth and host keys, cache key (SHA-256 + code_id), cache://_ resume - plugins installed with fz install model, not pip; per-plugin pages generated from the wrappers' actual model files - removed obsolete SUMMARY.md Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_012GxLbauyVHBQPCeSow8hdh --- .github/workflows/deploy.yml | 5 +- README.md | 106 +-- SUMMARY.md | 150 ---- docs/contributing/development.md | 36 +- docs/contributing/testing.md | 10 +- docs/examples/colab.md | 39 +- docs/examples/hpc.md | 15 +- docs/examples/modelica.md | 60 +- docs/examples/perfectgas.md | 2 +- docs/getting-started/concepts.md | 490 ++----------- docs/getting-started/installation.md | 287 ++------ docs/getting-started/quickstart.md | 415 +++-------- docs/index.md | 241 ++---- docs/plugins/cathare.md | 53 +- docs/plugins/cristal.md | 59 +- docs/plugins/index.md | 52 +- docs/plugins/mcnp.md | 53 +- docs/plugins/moret.md | 53 +- docs/plugins/scale.md | 59 +- docs/plugins/telemac.md | 53 +- docs/reference/api.md | 99 +-- docs/reference/cli.md | 80 ++ docs/reference/configuration.md | 118 +-- docs/reference/environment.md | 266 ++----- docs/reference/limitations.md | 186 +++++ docs/reference/releases.md | 36 +- docs/reference/security.md | 55 ++ docs/reference/troubleshooting.md | 110 +-- docs/user-guide/advanced/caching.md | 78 -- docs/user-guide/advanced/interrupts.md | 46 -- docs/user-guide/advanced/parallel.md | 75 -- docs/user-guide/ai-agents.md | 62 ++ docs/user-guide/calculators/cache.md | 107 ++- docs/user-guide/calculators/funz.md | 330 +-------- docs/user-guide/calculators/overview.md | 102 ++- docs/user-guide/calculators/shell.md | 104 ++- docs/user-guide/calculators/slurm.md | 235 ++---- docs/user-guide/calculators/ssh.md | 99 ++- docs/user-guide/core-functions/fzc.md | 90 +-- docs/user-guide/core-functions/fzd.md | 470 +++--------- docs/user-guide/core-functions/fzi.md | 82 +-- docs/user-guide/core-functions/fzl.md | 144 ++-- docs/user-guide/core-functions/fzo.md | 103 +-- docs/user-guide/core-functions/fzr.md | 688 +++--------------- docs/user-guide/design/algorithms.md | 117 +++ docs/user-guide/installing.md | 94 +++ docs/user-guide/model-definition.md | 38 - docs/user-guide/models/definition.md | 103 +++ docs/user-guide/models/outputs.md | 108 +++ docs/user-guide/running/caching.md | 76 ++ docs/user-guide/running/interrupts.md | 43 ++ docs/user-guide/running/parallel.md | 82 +++ docs/user-guide/running/results.md | 101 +++ docs/user-guide/running/timeouts.md | 47 ++ .../{advanced => templates}/formulas.md | 19 +- docs/user-guide/templates/syntax.md | 123 ++++ mkdocs.yml | 68 +- requirements.txt | 5 + test_site_structure.py | 76 +- 59 files changed, 3077 insertions(+), 4026 deletions(-) delete mode 100644 SUMMARY.md create mode 100644 docs/reference/cli.md create mode 100644 docs/reference/limitations.md create mode 100644 docs/reference/security.md delete mode 100644 docs/user-guide/advanced/caching.md delete mode 100644 docs/user-guide/advanced/interrupts.md delete mode 100644 docs/user-guide/advanced/parallel.md create mode 100644 docs/user-guide/ai-agents.md create mode 100644 docs/user-guide/design/algorithms.md create mode 100644 docs/user-guide/installing.md delete mode 100644 docs/user-guide/model-definition.md create mode 100644 docs/user-guide/models/definition.md create mode 100644 docs/user-guide/models/outputs.md create mode 100644 docs/user-guide/running/caching.md create mode 100644 docs/user-guide/running/interrupts.md create mode 100644 docs/user-guide/running/parallel.md create mode 100644 docs/user-guide/running/results.md create mode 100644 docs/user-guide/running/timeouts.md rename docs/user-guide/{advanced => templates}/formulas.md (65%) create mode 100644 docs/user-guide/templates/syntax.md create mode 100644 requirements.txt diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 943350e..1be2f6b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -28,11 +28,12 @@ jobs: - name: Install dependencies run: | - pip install mkdocs mkdocs-material pymdown-extensions + pip install -r requirements.txt - name: Build documentation run: | - mkdocs build + mkdocs build --strict + python test_site_structure.py - name: Deploy to GitHub Pages if: github.event_name == 'push' && github.ref == 'refs/heads/main' diff --git a/README.md b/README.md index ff244a1..cf8bb71 100644 --- a/README.md +++ b/README.md @@ -1,87 +1,47 @@ # FZ Documentation Website -This repository hosts the documentation website for [FZ - Parametric Scientific Computing Framework](https://github.com/Funz/fz). +Source of the documentation site of [FZ](https://github.com/Funz/fz) (PyPI `funz-fz`), +published at **https://funz.github.io/fz.github.io**. -## ๐Ÿ“š Documentation Site +## Structure -Visit the live documentation at: **https://funz.github.io/fz.github.io** +| Section | Directory | Content | +|---------|-----------|---------| +| Getting Started | `docs/getting-started/` | Installation, quick start, concepts | +| User Guide โ€” Templates & Models | `docs/user-guide/templates/`, `docs/user-guide/models/` | Template syntax, formulas, model fields, output extraction | +| User Guide โ€” Core Functions | `docs/user-guide/core-functions/` | `fzi`, `fzc`, `fzo`, `fzr`, `fzd`, `fzl` | +| User Guide โ€” Calculators | `docs/user-guide/calculators/` | `sh://`, `ssh://`, `slurm://`/`slurm-array://`, `funz://`, `cache://`, aliases | +| User Guide โ€” Running Studies | `docs/user-guide/running/` | Parallelism, timeouts, caching, results/manifest, interrupts | +| User Guide โ€” other | `docs/user-guide/` | Writing algorithms, installing models, AI agents | +| Plugins | `docs/plugins/` | `fz-` wrappers and algorithms | +| Examples | `docs/examples/` | Perfect gas, Modelica, HPC, Colab | +| Reference | `docs/reference/` | CLI, Python API, `.fz` directory, environment variables, constraints, security, troubleshooting, release notes | +| Contributing | `docs/contributing/` | Development and testing of fz | -## ๐Ÿš€ Quick Links +Notebooks for Colab are in `notebooks/`. Moved pages are redirected (`redirects` plugin +in `mkdocs.yml`). -- [Installation Guide](https://funz.github.io/fz.github.io/getting-started/installation/) -- [Quick Start](https://funz.github.io/fz.github.io/getting-started/quickstart/) -- [Core Functions](https://funz.github.io/fz.github.io/user-guide/core-functions/fzr/) -- [Plugins](https://funz.github.io/fz.github.io/plugins/) -- [Google Colab Notebooks](https://funz.github.io/fz.github.io/examples/colab/) - -## ๐Ÿ““ Google Colab Examples - -Try FZ directly in your browser: - -- [Perfect Gas Example](https://colab.research.google.com/github/Funz/fz.github.io/blob/main/notebooks/perfectgas_example.ipynb) -- [OpenModelica Integration](https://colab.research.google.com/github/Funz/fz.github.io/blob/main/notebooks/modelica_example.ipynb) - -## ๐Ÿ”Œ FZ Plugins - -- [FZ-Moret](https://github.com/Funz/fz-moret) - Moret model plugin -- [FZ-MCNP](https://github.com/Funz/fz-mcnp) - Monte Carlo N-Particle Transport -- [FZ-Cathare](https://github.com/Funz/fz-cathare) - Thermal-hydraulic system code -- [FZ-Cristal](https://github.com/Funz/fz-cristal) - Cristal simulation plugin -- [FZ-Scale](https://github.com/Funz/fz-scale) - Scale nuclear analysis code -- [FZ-Telemac](https://github.com/Funz/fz-telemac) - Hydrodynamics simulation system - -## ๐Ÿ› ๏ธ Building the Documentation - -This site is built with [MkDocs](https://www.mkdocs.org/) and the [Material theme](https://squidfunk.github.io/mkdocs-material/). - -### Prerequisites +## Build ```bash -pip install mkdocs mkdocs-material pymdown-extensions +pip install -r requirements.txt # MkDocs 1.x + Material; MkDocs 2.0 is not supported +mkdocs serve # http://127.0.0.1:8000 +mkdocs build --strict # fails on broken links or anchors +python test_site_structure.py # every navigation page and redirect was built ``` -### Local Development - -```bash -# Clone the repository -git clone https://github.com/Funz/fz.github.io.git -cd fz.github.io - -# Serve locally with live reload -mkdocs serve - -# Open http://127.0.0.1:8000 in your browser -``` - -### Build - -```bash -# Build static site -mkdocs build - -# Output in site/ directory -``` - -### Deploy - -The documentation is automatically deployed to GitHub Pages when changes are pushed to the `main` branch via GitHub Actions. - -## ๐Ÿ“ Contributing - -Contributions to the documentation are welcome! Please: - -1. Fork this repository -2. Create a feature branch -3. Make your changes -4. Test locally with `mkdocs serve` -5. Submit a pull request +Pushes to `main` are built with `--strict` and deployed to GitHub Pages by +`.github/workflows/deploy.yml`. -### Adding Content +## Writing rules -- Documentation pages are in `docs/` -- Notebooks are in `notebooks/` -- Configuration is in `mkdocs.yml` +- Content must match the code of fz: check behavior by running it, not only by reading + other docs. Constraints that surprise users go to `docs/reference/limitations.md` + (mirrors `doc/limitations.md` in the fz repository). +- Python examples call `fz.fzr(...)` with `calculators=` and `results_dir=` as keywords. +- Models in examples set `"delim"` explicitly. +- One topic per page; link instead of repeating. -## ๐Ÿ“„ License +## License -BSD 3-Clause License - see the [FZ repository](https://github.com/Funz/fz) for details. \ No newline at end of file +BSD 3-Clause, as [FZ](https://github.com/Funz/fz). diff --git a/SUMMARY.md b/SUMMARY.md deleted file mode 100644 index 9ee7845..0000000 --- a/SUMMARY.md +++ /dev/null @@ -1,150 +0,0 @@ -# FZ Documentation Website - Summary - -## What Was Built - -A comprehensive ReadTheDocs-like documentation website for the FZ parametric scientific computing framework. - -## Key Features - -### 1. MkDocs with Material Theme -- Professional, modern design -- Responsive layout for all devices -- Dark/light mode toggle -- Advanced search functionality -- Beautiful code highlighting - -### 2. Comprehensive Documentation (34 Pages) - -#### Getting Started -- Installation guide (multiple methods, OS-specific) -- Quick start with complete example -- Core concepts and fundamentals - -#### User Guide -- Core functions: fzi, fzc, fzo, fzr -- Model definition -- Calculator types (shell, SSH, cache) -- Advanced features (parallel, caching, formulas, interrupts) - -#### Plugins (6 Plugins) -- FZ-Moret - Moret model plugin -- FZ-MCNP - Monte Carlo N-Particle Transport -- FZ-Cathare - Thermal-hydraulic system code -- FZ-Cristal - Cristal simulation plugin -- FZ-Scale - Scale nuclear analysis code -- FZ-Telemac - Hydrodynamics simulation system - -#### Examples -- Perfect Gas pressure study (complete) -- Modelica/OpenModelica integration -- Remote HPC execution -- Google Colab notebooks - -#### Reference -- API reference -- Configuration -- Environment variables -- Troubleshooting - -### 3. Google Colab Notebooks (2 Notebooks) - -1. **perfectgas_example.ipynb** - - Basic parametric study - - Ideal gas law calculations - - Visualization with matplotlib - - Ready to run in browser - -2. **modelica_example.ipynb** - - OpenModelica integration - - Dynamic system simulations - - Harmonic oscillator example - - Parameter sweep and analysis - -### 4. GitHub Pages Deployment - -- Automated deployment with GitHub Actions -- Builds on every push to main -- Published to https://funz.github.io -- Continuous integration/deployment - -## File Structure - -``` -fz.github.io/ -โ”œโ”€โ”€ .github/ -โ”‚ โ””โ”€โ”€ workflows/ -โ”‚ โ””โ”€โ”€ deploy.yml # GitHub Actions deployment -โ”œโ”€โ”€ docs/ # Documentation source -โ”‚ โ”œโ”€โ”€ index.md # Homepage -โ”‚ โ”œโ”€โ”€ getting-started/ # 3 pages -โ”‚ โ”œโ”€โ”€ user-guide/ # 12 pages -โ”‚ โ”œโ”€โ”€ plugins/ # 7 pages -โ”‚ โ”œโ”€โ”€ examples/ # 4 pages -โ”‚ โ”œโ”€โ”€ reference/ # 4 pages -โ”‚ โ””โ”€โ”€ contributing/ # 2 pages -โ”œโ”€โ”€ notebooks/ # Google Colab notebooks -โ”‚ โ”œโ”€โ”€ perfectgas_example.ipynb -โ”‚ โ””โ”€โ”€ modelica_example.ipynb -โ”œโ”€โ”€ mkdocs.yml # MkDocs configuration -โ”œโ”€โ”€ .gitignore # Git ignore rules -โ””โ”€โ”€ README.md # Repository README -``` - -## Technologies Used - -- **MkDocs**: Static site generator for documentation -- **Material for MkDocs**: Beautiful, responsive theme -- **Python Markdown Extensions**: Enhanced markdown features -- **GitHub Actions**: Automated deployment -- **GitHub Pages**: Free hosting -- **Jupyter Notebooks**: Interactive examples - -## How to Use - -### Local Development -```bash -pip install mkdocs mkdocs-material pymdown-extensions -mkdocs serve -# Open http://127.0.0.1:8000 -``` - -### Build -```bash -mkdocs build -# Output in site/ directory -``` - -### Deploy -Automatically deployed via GitHub Actions when pushing to main branch. - -## Success Metrics - -โœ… 34 documentation pages created -โœ… 2 Google Colab notebooks -โœ… All plugins documented -โœ… Complete examples with code -โœ… Professional design with Material theme -โœ… Automated deployment configured -โœ… Mobile-responsive -โœ… Search functionality -โœ… Dark/light mode - -## Next Steps (Optional Future Enhancements) - -- Add more Google Colab notebooks for each plugin -- Create video tutorials -- Add interactive examples -- Expand API reference with auto-generated docs -- Add versioning support -- Create tutorials section -- Add FAQ page - -## Links - -- **Repository**: https://github.com/Funz/fz.github.io -- **Live Site**: https://funz.github.io (once deployed) -- **Main FZ Repo**: https://github.com/Funz/fz - -## Contact - -For questions or contributions, please open an issue in the repository. diff --git a/docs/contributing/development.md b/docs/contributing/development.md index 4b5207a..982140d 100644 --- a/docs/contributing/development.md +++ b/docs/contributing/development.md @@ -13,22 +13,32 @@ python -m venv venv && source venv/bin/activate pip install -e ".[dev]" ``` -Optional extras: `paramiko` (SSH/SLURM), `pandas` (DataFrame output), `rpy2` + R -(R interpreter and R algorithm plugins), `h5py` (HDF5 outputs). +`paramiko` and `pandas` are required dependencies. Optional: `rpy2` + R (R formulas and +algorithms, extra `[r]`), `mcp` (extra `[mcp]`, Python โ‰ฅ 3.10), `h5py`, `jq`, `yq`, +`xmllint`. ## Package Layout | Module | Responsibility | |--------|----------------| -| `fz/core.py` | The public functions `fzi`, `fzc`, `fzo`, `fzr`, `fzd` | +| `fz/core.py` | Public functions `fzi`, `fzc`, `fzo`, `fzr`, `fzd`, `fzl` | +| `fz/cli.py` | Entry points `fz`, `fzi`, `fzc`, `fzo`, `fzr`, `fzd`, `fzl` | | `fz/interpreter.py` | Variable parsing, formula evaluation (Python / R) | -| `fz/runners.py` | Calculator backends โ€” `sh://`, `ssh://`, `slurm://`, `funz://`, `cache://` | -| `fz/helpers.py` | Parallel scheduling, retry, interrupt handling | -| `fz/io.py` | File staging, hashing, `.fz_hash` caching | -| `fz/algorithms.py` | Algorithm framework for `fzd` | -| `fz/shell.py` | Shell utilities, `FZ_SHELL_PATH` binary resolution | -| `fz/cli.py` | `fz`, `fzi`, `fzc`, `fzo`, `fzr`, `fzd`, `fzl` entry points | -| `fz/config.py` | Environment-variable configuration | +| `fz/outparsers.py` | `python://`, `jq://`, `yq://`, `xpath://` output extractors | +| `fz/runners/` | Calculator backends: `sh`, `ssh`, `slurm`, `slurm_array`, `funz`, `cache`, plus `dispatch`, `manager`, `resolve`, `base` | +| `fz/slurm_async.py` | Job-array batching and `sacct` monitoring | +| `fz/helpers.py` | Case scheduling, retries, calculator/model resolution | +| `fz/io.py` | File staging, `.fz_hash`, cache matching | +| `fz/manifest.py`, `fz/uri.py` | `manifest.json` / RO-Crate, URI password redaction | +| `fz/algorithms.py` | `fzd` algorithm loading and output expressions | +| `fz/installer.py` | `fz install` / `fz uninstall` | +| `fz/mcp_server.py` | `fz-mcp` | +| `fz/config.py`, `fz/logging.py`, `fz/shell.py` | Configuration (`FZ_*`), logs, bash / `FZ_SHELL_PATH` resolution | + +Documentation lives in three places that must stay consistent when the API or the CLI +changes: `README.md`, `doc/`, and the agent skill `skills/fz/` (tested by +`tests/test_skill_static.py`). This website is a separate repository, +[Funz/fz.github.io](https://github.com/Funz/fz.github.io). ## Workflow @@ -41,13 +51,13 @@ Optional extras: `paramiko` (SSH/SLURM), `pandas` (DataFrame output), `rpy2` + R ## Releasing -Version lives in `fz/__init__.py` (`pyproject.toml` reads it dynamically). A release -commit bumps that, folds `## Unreleased` into a dated `## Version X.Y` section in +`fz/_version.py` is stamped by CI (`scripts/stamp_version.py`) and must not be edited by +hand; `pyproject.toml` reads the version dynamically. A release commit folds `## Unreleased` into a dated `## Version X.Y` section in `NEWS.md`, and aligns the Claude Code plugin version. Publishing a GitHub Release with the matching tag triggers `release.yml`, which pushes to PyPI. ## See Also - [Testing](testing.md) -- [Writing Custom Algorithms](../user-guide/core-functions/fzd.md#writing-custom-algorithms) +- [Writing Algorithms](../user-guide/design/algorithms.md) - [Plugin templates](../plugins/index.md#creating-your-own-plugin) โ€” `fz-Model`, `fz-Algorithm`, `fz-AlgorithmR` diff --git a/docs/contributing/testing.md b/docs/contributing/testing.md index 9f2aa77..6ff4221 100644 --- a/docs/contributing/testing.md +++ b/docs/contributing/testing.md @@ -10,14 +10,20 @@ pip install -e ".[dev]" python -m pytest tests/ -v # everything python -m pytest tests/test_fzd.py -v # one file python -m pytest tests/ -k parallel -v # by keyword +python -m pytest tests/test_skill_static.py # agent skill claims vs code FZ_LOG_LEVEL=DEBUG python -m pytest tests/test_interrupt_handling.py -v ``` +Every test runs in a fresh temporary directory under `./tmp` (autouse fixture in +`tests/conftest.py`): reference test data by absolute path. Markers (`slow`, +`integration`, `requires_ssh`, `requires_docker`, ...) are declared in `pytest.ini` +(`--strict-markers`). SSH, SLURM, Funz and example tests run in dedicated CI workflows. + ## Notable Test Areas | File(s) | Covers | |---------|--------| -| `test_parallel.py` | Concurrent execution, load balancing | +| `test_parallel_simple.py`, `test_complete_parallel_execution.py` | Concurrent execution, load balancing | | `test_interrupt_handling.py` | Ctrl+C graceful shutdown, resume | | `test_fzd*.py` | Design of experiments, vector / multi-objective outputs | | `test_static_files*.py` | `input_static` (local and real SFTP over `ssh://`) | @@ -35,7 +41,7 @@ from pathlib import Path def test_my_model(): with tempfile.TemporaryDirectory() as tmp: inp = Path(tmp) / "input.txt" - inp.write_text("Parameter: $param\n") + inp.write_text("param=$param\n") calc = Path(tmp) / "calc.sh" calc.write_text('#!/bin/bash\nsource "$1"\necho "result=$param" > output.txt\n') diff --git a/docs/examples/colab.md b/docs/examples/colab.md index 0e8a14c..3fea269 100644 --- a/docs/examples/colab.md +++ b/docs/examples/colab.md @@ -35,7 +35,7 @@ All variable syntaxes, `@{}` formula expressions, `#@` context code, and delimit - `$name`, `${name}`, `${name~default}` variable forms - `@{expr}` inline formula evaluation (Python & R) - `#@ code` context blocks and `#@: static` constants -- Legacy `?(name)` syntax +- `?(name)` templates (model `varprefix: "?"`, `delim: "()"`) - Custom delimiter styles: `()`, `{}`, `[]`, `<>` ### 3. Parametric Studies (fzr) @@ -87,13 +87,13 @@ Cache reuse, multi-output models, logging, coarse-to-fine DOE. Add this cell at the beginning: ```python -!pip install git+https://github.com/Funz/fz.git +!pip install funz-fz ``` For plugins: ```python -!pip install git+https://github.com/Funz/fz-moret.git +!fz install model Moret ``` ### Step 2: Install Dependencies @@ -159,7 +159,7 @@ Complete notebook for dynamic system simulations: !apt-get install -y omc # Install FZ -!pip install git+https://github.com/Funz/fz.git +!pip install funz-fz # Create Modelica model %%writefile Oscillator.mo @@ -230,34 +230,21 @@ plt.show() ## Plugins in Colab -### Installing Plugins - ```python -# Install base FZ -!pip install git+https://github.com/Funz/fz.git - -# Install plugins -!pip install git+https://github.com/Funz/fz-moret.git -!pip install git+https://github.com/Funz/fz-mcnp.git -# etc. +!pip install funz-fz +!fz install model Moret # model + runner script + localhost_Moret alias in ./.fz/ ``` -### Using Plugin Models - ```python -from fz_moret import get_model - -# Use plugin model -model = get_model('moret') - -results = fz.fzr( - "input.txt", - variables, - model, - calculators="sh://bash run_moret.sh" -) +import fz +results = fz.fzr("input.inp", {"e": [1, 2]}, "Moret", + calculators="localhost_Moret", results_dir="results") ``` +The simulation code itself (MORET, MCNP, ...) must also be available in the Colab +runtime, which is rarely possible for licensed codes; Colab suits open codes such as +OpenModelica. + ## Accessing Files in Colab ### Upload Files diff --git a/docs/examples/hpc.md b/docs/examples/hpc.md index 893f2c2..f43728b 100644 --- a/docs/examples/hpc.md +++ b/docs/examples/hpc.md @@ -12,7 +12,7 @@ import fz model = { "varprefix": "$", - "output": {"keff": "python://grep(r'k-eff = (\\S+)', 'out.txt')"}, + "output": {"keff": "python://grep(r'k-eff = (\\S+)', 'solver.out')"}, } results = fz.fzr( @@ -29,7 +29,7 @@ print(results[["enrichment", "radius", "keff", "status"]]) #!/bin/bash source reactor.inp module load gcc/11.2 openmpi/4.1 -mpirun -np 32 ./solver reactor.inp > out.txt +mpirun -np 32 ./solver reactor.inp > solver.out # not out.txt: reserved by fz ``` ## Through SLURM @@ -58,8 +58,8 @@ results = fz.fzr( model, calculators=[ "cache://slurm_results", # reuse anything already done - "slurm://user@cluster.edu:compute/bash run_case.sh", # then submit - "slurm://user@cluster.edu:compute/bash run_case.sh", # 2 concurrent jobs + "slurm://user@cluster.edu:compute/bash /scratch/user/run_case.sh", # then submit + "slurm://user@cluster.edu:compute/bash /scratch/user/run_case.sh", # 2 concurrent jobs "ssh://user@fallback.edu/bash /scratch/run_case.sh", # last resort ], results_dir="slurm_results", @@ -67,18 +67,19 @@ results = fz.fzr( ``` Shared, read-only inputs (a common cross-section library, a mesh) belong in -[`input_static`](../user-guide/core-functions/fzr.md#shared-static-files-new-in-12) โ€” +[`input_static`](../user-guide/running/results.md#shared-static-files-input_static) โ€” give an **absolute path** when the file is already on the cluster's shared storage, so FZ only hashes it rather than transferring a copy per case. ## Tips - Use **absolute paths** in remote calculator commands. -- Set `FZ_SSH_KEEPALIVE=300` for long jobs; `FZ_RUN_TIMEOUT` bounds each case (default 1 h since 1.2). +- `ssh://` and `slurm://` have **no default timeout**: set `FZ_RUN_TIMEOUT` or the model's + `timeout` to bound each case. `FZ_SSH_KEEPALIVE` (default 300 s) keeps idle connections alive. - Test the script by hand first: `ssh user@host "bash /scratch/user/run_case.sh reactor.inp"`. - **Ctrl+C** cancels submitted jobs and cleans up remote temp dirs; resume with a `cache://` entry. ## See Also - [SSH Remote Calculator](../user-guide/calculators/ssh.md) ยท [SLURM Calculator](../user-guide/calculators/slurm.md) -- [Parallel Execution](../user-guide/advanced/parallel.md) ยท [Caching Strategy](../user-guide/advanced/caching.md) +- [Parallelism & Retries](../user-guide/running/parallel.md) ยท [Caching](../user-guide/running/caching.md) diff --git a/docs/examples/modelica.md b/docs/examples/modelica.md index 9fb12f2..0b18998 100644 --- a/docs/examples/modelica.md +++ b/docs/examples/modelica.md @@ -1,21 +1,59 @@ -# Modelica/OpenModelica Integration +# Modelica / OpenModelica -Use FZ with OpenModelica for dynamic system simulations. +The [FZ-Modelica](https://github.com/Funz/fz-Modelica) wrapper runs OpenModelica +models as parametric studies. -## Installation +## Install ```bash -# Install OpenModelica -sudo apt-get install omc +sudo apt-get install omc # OpenModelica compiler (or the installer for your OS) +pip install funz-fz +fz install model Modelica # model "Modelica" + localhost_Modelica alias in ./.fz/ +``` + +## Template + +Variables use `${...}` (the model sets `delim: "{}"`) and comments are `//`: + +```modelica title="NewtonCooling.mo" +model NewtonCooling + parameter Real T_inf = 25; + parameter Real T0 = 90; + parameter Real h = ${convection~0.7}; + parameter Real m = 0.1; + parameter Real c_p = 1.2; + parameter Real A = 1.0; + Real T; +initial equation + T = T0; +equation + m*c_p*der(T) = h*A*(T_inf-T); +end NewtonCooling; +``` -# Install FZ -pip install git+https://github.com/Funz/fz.git +## Run + +```python +import fz + +results = fz.fzr( + "NewtonCooling.mo", + {"convection": [0.3, 0.7, 1.5]}, + "Modelica", + calculators=["localhost_Modelica"] * 3, + results_dir="results_modelica", +) ``` -## Example Model +The model's single output `res` is a dict of the simulation's CSV results; `fzr` +expands it into columns `res__` (e.g. `res_NewtonCooling_T`), each +holding the time series of one case. + +## Notebooks -See the [Google Colab notebook](colab.md#openmodelica-integration) for a complete working example. +- [fz_modelica_projectile.ipynb](https://github.com/Funz/fz/blob/main/examples/fz_modelica_projectile.ipynb) in the fz repository +- [Google Colab Notebooks](colab.md) -## Repository +## Links -[OpenModelica](https://openmodelica.org/) +[OpenModelica](https://openmodelica.org/) ยท [Funz/fz-Modelica](https://github.com/Funz/fz-Modelica) diff --git a/docs/examples/perfectgas.md b/docs/examples/perfectgas.md index 08e50e7..94a7775 100644 --- a/docs/examples/perfectgas.md +++ b/docs/examples/perfectgas.md @@ -339,5 +339,5 @@ python run_study.py - [Modelica Example](modelica.md) - OpenModelica integration - [HPC Example](hpc.md) - Remote cluster execution -- [Advanced Features](../user-guide/advanced/parallel.md) - Master parallel execution +- [Parallelism & Retries](../user-guide/running/parallel.md) - parallel execution - [Plugins](../plugins/index.md) - Explore FZ plugins diff --git a/docs/getting-started/concepts.md b/docs/getting-started/concepts.md index cf3cfba..625fc29 100644 --- a/docs/getting-started/concepts.md +++ b/docs/getting-started/concepts.md @@ -1,459 +1,91 @@ # Core Concepts -Understanding these fundamental concepts will help you use FZ effectively. - -## The FZ Workflow - -FZ follows a simple four-step workflow: +FZ separates **what** varies (the template), **how** to read and parse it (the model), +**where** it runs (the calculator) and **which** values to run (the design). Each part +is defined independently and can be reused with the others. ```mermaid graph LR - A[Input Template] -->|fzi| B[Parse Variables] - B -->|fzc| C[Compile Cases] - C -->|Calculator| D[Execute] - D -->|fzo| E[Parse Results] - - style A fill:#e1f5fe - style E fill:#c8e6c9 -``` - -1. **Parse** - Identify variables in input templates -2. **Compile** - Substitute values and evaluate formulas -3. **Execute** - Run calculations -4. **Parse** - Extract results - -The `fzr` function orchestrates all four steps automatically for fixed parametric studies. For adaptive, algorithm-driven exploration, `fzd` iteratively selects new parameter points based on previous results. - -## Variables - -Variables are placeholders in input templates that get replaced with actual values. - -### Variable Syntax - -```text -temperature = $temp -pressure = $press -concentration = $conc -``` - -The `$` prefix marks a variable (customizable via `varprefix`). - -### Variable Defaults (New in 0.9.1) - -Specify default values using the `${var~default}` syntax: - -```text -host = ${hostname~localhost} -port = ${port~8080} -debug = ${debug_mode~false} -``` - -When a variable is not provided in `input_variables`, the default value is used. A warning is issued when defaults are applied. - -### Variable Types - -FZ supports scalar and list values: - -```python -# Scalar variable (single value) -{"temperature": 100} - -# List variable (multiple values) -{"temperature": [100, 200, 300]} - -# Mixed -{ - "temperature": [100, 200, 300], # 3 cases - "pressure": 1.0 # Fixed -} -``` - -## Parametric Studies - -When you provide lists of values, FZ creates the **Cartesian product**: - -```python -input_variables = { - "temp": [10, 20], # 2 values - "volume": [1, 2, 3], # 3 values - "amount": 1.0 # Fixed -} -# Creates 2 ร— 3 = 6 cases: -# temp=10, volume=1, amount=1.0 -# temp=10, volume=2, amount=1.0 -# temp=10, volume=3, amount=1.0 -# temp=20, volume=1, amount=1.0 -# temp=20, volume=2, amount=1.0 -# temp=20, volume=3, amount=1.0 -``` - -## Formulas - -Formulas are evaluated during compilation to create calculated values. - -### Formula Syntax - -```text -# Simple formula -result = @($a + $b) - -# With functions -#@ def square(x): -#@ return x * x -area = @(square($width)) - -# Multi-line -#@ import math -#@ radius = $diameter / 2 -#@ area = math.pi * radius**2 -circle_area = @(area) -``` - -### Formula Features - -- **Python or R** expressions (set with `FZ_INTERPRETER` env var) -- **Variable substitution** - Use variables with `$` in formulas -- **Function definitions** - Define reusable functions -- **Context sharing** - Variables defined in one formula available in others - -## Models - -A model defines how to parse inputs and extract outputs. - -### Basic Model - -```python -model = { - "varprefix": "$", - "output": { - "result": "cat output.txt" - } -} -``` - -### Complete Model - -```python -model = { - # Input parsing - "varprefix": "$", # Variable marker - "formulaprefix": "@", # Formula marker - "delim": "()", # Formula delimiters - "commentline": "#", # Comment lines - - # Output extraction - "output": { - "pressure": "grep 'P:' out.txt | awk '{print $2}'", - "temp": "grep 'T:' out.txt | awk '{print $2}'", - "energy": "python extract_energy.py" - }, - - # Optional identifier - "id": "mymodel" -} -``` - -### Model Aliases - -Store models in `.fz/models/mymodel.json` and use by name: - -```python -results = fz.fzr("input.txt", variables, "mymodel") -``` - -## Calculators - -Calculators define **where** and **how** calculations are executed. - -### Calculator Types - -| Type | URI Format | Purpose | -|------|------------|---------| -| **Shell** | `sh://command args` | Local execution | -| **SSH** | `ssh://user@host/command` | Remote execution | -| **SLURM** | `slurm://partition/command` | HPC cluster execution | -| **Funz** | `funz://host:port` | Legacy Funz server | -| **Cache** | `cache://directory` | Reuse previous results | - -### Calculator Examples - -```python -# Local shell -calculators = "sh://bash script.sh" - -# Remote SSH -calculators = "ssh://user@server.com/bash /path/to/script.sh" - -# Cache with fallback -calculators = [ - "cache://previous_results", - "sh://bash script.sh" -] -``` - -### Multiple Calculators - -Provide a list for parallel execution or failover: - -```python -# Parallel execution (4 workers) -calculators = [ - "sh://bash calc.sh", - "sh://bash calc.sh", - "sh://bash calc.sh", - "sh://bash calc.sh" -] - -# Failover chain -calculators = [ - "cache://results", # Try cache first - "sh://bash fast_method.sh", # Fast but unstable - "sh://bash robust_method.sh", # Slow but reliable - "ssh://user@hpc/bash calc.sh" # Remote fallback -] + T["Input template: $x, @{f}"] -->|fzi| V["Variables found"] + T -->|"fzc + values"| C["One directory per case"] + C -->|"calculator: sh, ssh, slurm, funz"| R["Case results"] + K[("cache://")] -.->|hit| R + R -->|"fzo + model outputs"| D["DataFrame"] ``` -## Results Structure +`fzr` chains all steps for a list of cases; `fzd` repeats them in a loop, asking an +algorithm for the next cases after each batch. -FZ organizes results in a clear directory structure: +## Vocabulary -``` -results/ -โ”œโ”€โ”€ T_celsius=10,V_L=1/ -โ”‚ โ”œโ”€โ”€ input.txt # Compiled input -โ”‚ โ”œโ”€โ”€ output.txt # Calculation output -โ”‚ โ”œโ”€โ”€ log.txt # Execution metadata -โ”‚ โ”œโ”€โ”€ out.txt # Standard output -โ”‚ โ”œโ”€โ”€ err.txt # Standard error -โ”‚ โ””โ”€โ”€ .fz_hash # Input file hashes -โ”œโ”€โ”€ T_celsius=10,V_L=2/ -โ”‚ โ””โ”€โ”€ ... -โ””โ”€โ”€ T_celsius=20,V_L=1/ - โ””โ”€โ”€ ... -``` +| Term | Definition | Page | +|------|------------|------| +| **Template** | The code's own input file(s), with `$variables`, `@{formulas}` and `#@` context lines. A file or a whole directory tree. | [Template syntax](../user-guide/templates/syntax.md) | +| **Model** | A dict (or JSON alias) giving the template syntax and the `output` extractors. It never says how to run the code. | [Model definition](../user-guide/models/definition.md) | +| **Calculator** | A URI saying where and how to run one case: `sh://`, `ssh://`, `slurm://`, `slurm-array://`, `funz://`, or `cache://`. | [Calculators](../user-guide/calculators/overview.md) | +| **Case** | One combination of values: one compiled copy of the template, one run, one result directory, one DataFrame row. | [Results](../user-guide/running/results.md) | +| **Design** | The set of cases: a dict of lists (full factorial), a DataFrame (one row per case), or an algorithm (`fzd`). | [fzr](../user-guide/core-functions/fzr.md), [fzd](../user-guide/core-functions/fzd.md) | +| **Alias** | A named model, calculator or algorithm stored under `./.fz/` or `~/.fz/`. | [.fz directory](../reference/configuration.md) | -### DataFrame Output - -Results are returned as a pandas DataFrame: +## Designs ```python - T_celsius V_L n_mol pressure status calculator error command -0 10.0 1.0 1.0 2353.58 done sh:// None bash... -1 10.0 2.0 1.0 1176.79 done sh:// None bash... -2 20.0 1.0 1.0 2437.30 done sh:// None bash... -``` - -Columns include: - -- **Input variables** - All parameters -- **Output variables** - Extracted results -- **Metadata** - Status, calculator used, errors, command - -## Caching - -FZ uses MD5 hashes of input files for intelligent caching. +# Full factorial: Cartesian product of the lists; scalars are fixed values +{"T": [10, 20, 30], "V": [1, 2], "n": 1.0} # 3 x 2 = 6 cases -### How Caching Works +# Explicit list of cases: one row = one case (LHS, imported plans, constrained designs) +import pandas as pd +pd.DataFrame({"T": [10, 20, 10], "V": [1, 1, 2], "n": 1.0}) # 3 cases -1. **Hash Generation** - MD5 hash of all input files stored in `.fz_hash` -2. **Cache Check** - Compare hash with cached results -3. **Reuse** - If match found and outputs valid, reuse results -4. **Fallback** - If no match, proceed to next calculator - -### Cache Strategy - -```python -# First run -results1 = fz.fzr( - "input.txt", - {"param": [1, 2, 3]}, - model, - calculators="sh://expensive_calc.sh", - results_dir="run1" -) - -# Add more cases - reuse previous -results2 = fz.fzr( - "input.txt", - {"param": [1, 2, 3, 4, 5]}, # 2 new cases - model, - calculators=[ - "cache://run1", # Reuse 1, 2, 3 - "sh://expensive_calc.sh" # Calculate 4, 5 - ], - results_dir="run2" -) +# Adaptive (fzd): ranges and fixed values as strings, the algorithm picks the points +{"T": "[0;100]", "V": "[1;5]", "n": "1"} ``` -## Parallel Execution - -FZ automatically parallelizes when multiple calculators are available. - -### How It Works - -1. **Round-robin distribution** - Cases distributed to calculators -2. **Thread-safe locking** - Each calculator locked during execution -3. **Load balancing** - Available calculators pick up new cases -4. **Progress tracking** - ETA calculated based on completed cases - -### Controlling Parallelism +## What happens for one case -```python -# Environment variable -import os -os.environ['FZ_MAX_WORKERS'] = '8' - -# Or duplicate calculators -calculators = ["sh://bash calc.sh"] * 8 -``` +1. The template is compiled with the case's values into the results directory + (`results/T=10,V=1,n=1.0/` by default) and the SHA-256 of the inputs is written to + `.fz_hash`. +2. If a `cache://` calculator finds a previous case with the same hash and valid outputs, + its results are copied and the case is `done`. +3. Otherwise a free calculator runs the command in a temporary directory, with the + compiled input file names appended to the command line; stdout and stderr go to + `out.txt` and `err.txt`. +4. Result files are copied back, the model's `output` extractors run in the case + directory, and the values become the case's row. +5. On failure the case is retried, possibly on another calculator, up to + `FZ_MAX_RETRIES` (5) failures. -## Error Handling +## Parallelism -FZ provides robust error handling and retry mechanisms. - -### Retry Strategy +Each non-cache calculator entry runs one case at a time. The number of entries is the +number of cases running concurrently: ```python -import os -os.environ['FZ_MAX_RETRIES'] = '3' - -results = fz.fzr( - "input.txt", - variables, - model, - calculators=[ - "sh://unreliable.sh", - "sh://backup.sh" - ] -) +calculators = ["sh://bash calc.sh"] * 4 # 4 local cases at a time +calculators = ["cache://previous", "ssh://u@a/bash /x/run.sh", "ssh://u@b/bash /x/run.sh"] ``` -Process: -1. Try first calculator -2. On failure, try next calculator -3. Repeat up to `MAX_RETRIES` times -4. Report final status in DataFrame +`FZ_MAX_WORKERS` only caps this number. See +[Parallelism & Retries](../user-guide/running/parallel.md). -### Graceful Interrupts +## Results -Press Ctrl+C to stop gracefully: - -- First Ctrl+C: Complete current calculations, save partial results -- Second Ctrl+C: Force quit (not recommended) - -Resume with cache: - -```python -results = fz.fzr( - "input.txt", - variables, - model, - calculators=[ - "cache://interrupted_run", - "sh://bash calc.sh" - ] -) -``` +`fzr` returns a pandas DataFrame with the variables, the outputs, and the columns +`status` (`done`, `failed`, `error`, `timeout`, `interrupted`), `calculator`, `error`, +`command`. Every case also leaves a directory with its inputs, outputs and logs, and the +results root holds `manifest.json` for traceability. See +[Results & Traceability](../user-guide/running/results.md). ## Configuration -FZ can be configured via: - -### Environment Variables - -```bash -export FZ_LOG_LEVEL=DEBUG -export FZ_MAX_RETRIES=5 -export FZ_MAX_WORKERS=4 -export FZ_INTERPRETER=python -``` - -### Configuration Files - -Store models and calculators in `.fz/`: - -``` -.fz/ -โ”œโ”€โ”€ models/ -โ”‚ โ”œโ”€โ”€ model1.json -โ”‚ โ””โ”€โ”€ model2.json -โ””โ”€โ”€ calculators/ - โ”œโ”€โ”€ cluster1.json - โ””โ”€โ”€ cluster2.json -``` - -### Python API - -```python -from fz import get_config - -config = get_config() -config.max_retries = 10 -config.max_workers = 8 -``` - -## Best Practices - -### 1. Start Small - -Test with a few cases first: - -```python -# Development -results = fz.fzr("input.txt", {"param": [1, 2]}, model, ...) - -# Production -results = fz.fzr("input.txt", {"param": range(1000)}, model, ...) -``` - -### 2. Use Caching - -Always include cache in calculator chain: - -```python -calculators = [ - "cache://previous_results", - "sh://bash calc.sh" -] -``` - -### 3. Handle Failures - -Check status column: - -```python -failed = results[results['status'] != 'done'] -if len(failed) > 0: - print(f"Failed cases: {len(failed)}") - print(failed[['status', 'error']]) -``` - -### 4. Organize Results - -Use descriptive directory names: - -```python -results_dir = f"results_{model_name}_{timestamp}" -``` - -### 5. Document Models - -Include comments in model definitions: - -```json -{ - "varprefix": "$", - "output": { - "pressure": "grep 'P:' output.txt | awk '{print $2}' # Extract pressure in Pa" - } -} -``` - -## Next Steps +Defaults come from `FZ_*` environment variables, read when `fz` is imported (call +`fz.reload_config()` after changing `os.environ`). Aliases come from `./.fz/` then +`~/.fz/`. See [Environment Variables](../reference/environment.md) and +[.fz Directory & Aliases](../reference/configuration.md). -Now that you understand the core concepts: +## Next -- [Core Functions](../user-guide/core-functions/fzi.md) - Deep dive into fzi, fzc, fzo, fzr, fzd, fzl -- [Model Definition](../user-guide/model-definition.md) - Advanced model configuration -- [Calculators](../user-guide/calculators/overview.md) - Master calculator types -- [Examples](../examples/perfectgas.md) - See concepts in action +- [Input Template Syntax](../user-guide/templates/syntax.md) +- [Model Definition](../user-guide/models/definition.md) +- [Constraints & Limits](../reference/limitations.md) diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 8190fe2..212a5cc 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -1,259 +1,118 @@ # Installation -FZ is a Python package that requires Python 3.8 or later. This guide covers different installation methods and optional dependencies. - ## Requirements -- **Python**: 3.8 or later -- **Operating System**: Linux, macOS, or Windows -- **Optional**: SSH access for remote calculators, pandas for DataFrame output - -## Installation Methods - -### From PyPI (Recommended) - -Install the latest stable version from PyPI: - -```bash -pip install funz-fz -``` - -Or using pipx for isolated CLI tools: - -```bash -pipx install funz-fz -``` - -This installs the `fz` command along with standalone commands: `fzi`, `fzc`, `fzo`, `fzr`, `fzd`, and `fzl`. - -### From Source - -Install the latest development version from GitHub: +| Item | Requirement | +|------|-------------| +| Python | โ‰ฅ 3.9 (CI tests 3.9 to 3.13; 3.14 as pre-release only) | +| Required packages | `paramiko`, `pandas`, `charset-normalizer` (installed automatically) | +| bash | Needed by `sh://` calculators and shell output commands. Native on Linux/macOS; MSYS2 or Git Bash on Windows | +| Operating system | Linux, macOS, Windows | -```bash -git clone https://github.com/Funz/fz.git -cd fz -pip install -e . -``` +Optional components, needed only for the matching feature: -The `-e` flag installs in editable mode, which is useful for development. +| Feature | Install | +|---------|---------| +| R formulas (`interpreter: "R"`) and R algorithms | R + `pip install 'funz-fz[r]'` (rpy2) | +| MCP server `fz-mcp` | `pip install 'funz-fz[mcp]'` โ€” Python โ‰ฅ 3.10 only | +| `hdf5_file()` output helper | `pip install h5py` | +| `jq://`, `yq://`, `xpath://` outputs | `jq`, [mikefarah/yq](https://github.com/mikefarah/yq), `xmllint` on `PATH` | +| `slurm://`, `slurm-array://` | SLURM client commands (`srun`; `sbatch`/`sacct` for arrays) | +| `funz://` | A running Java Funz calculator | -### Using Virtual Environment (Recommended) +## Install -It's best practice to use a virtual environment: +=== "pip" -```bash -# Create virtual environment -python -m venv fz-env + ```bash + pip install funz-fz + ``` -# Activate it -# On Linux/macOS: -source fz-env/bin/activate -# On Windows: -fz-env\Scripts\activate +=== "pipx (CLI only)" -# Install FZ -pip install -e /path/to/fz -``` + ```bash + pipx install funz-fz + ``` -## Optional Dependencies +=== "virtual environment" -FZ has several optional dependencies for additional features: + ```bash + python3 -m venv .venv + source .venv/bin/activate # Windows: .venv\Scripts\activate + pip install funz-fz + ``` -### SSH Support + Use this form on systems that refuse `pip install` with + `error: externally-managed-environment` (PEP 668). -For remote calculator execution via SSH: +=== "from source" -```bash -pip install paramiko -``` + ```bash + git clone https://github.com/Funz/fz.git + cd fz + pip install -e ".[dev]" # editable, with test dependencies + ``` -### DataFrame Support +This installs the Python package `fz` and the commands `fz`, `fzi`, `fzc`, `fzo`, `fzr`, +`fzd`, `fzl` (and `fz-mcp` when the `mcp` extra is installed). -For pandas DataFrame output (highly recommended): +## Verify ```bash -pip install pandas +fz --version +python -c "import fz; print(fz.__version__)" +fz list --check # models/calculators found in ./.fz and ~/.fz ``` -### All Optional Dependencies +## Windows -Install everything at once: +- Install [MSYS2](https://www.msys2.org/) or Git Bash, then point `FZ_SHELL_PATH` at the + directories containing `bash` and the Unix tools, before starting Python: -```bash -pip install paramiko pandas -``` + ```powershell + $env:FZ_SHELL_PATH = "C:\msys64\usr\bin;C:\msys64\mingw64\bin" + ``` -## Verify Installation +- `import fz` works without bash; only `sh://` calculators and shell output commands need + it. The `python://`, `jq://`, `yq://` and `xpath://` output forms do not. +- Relative `input_static` files are symlinked into each case; without symlink permission + (no developer mode/admin) they are copied. +- Write templates with Unix line endings when they are sourced by bash scripts. -Test that FZ is properly installed: +## HPC login nodes ```bash -python -c "import fz; print('FZ version:', fz.__version__)" +module load python/3.11 # site-specific +python3 -m venv ~/fz-venv && source ~/fz-venv/bin/activate +pip install funz-fz ``` -You should see output like: -``` -FZ version: 1.2 -``` +Calculator scripts executed remotely through `ssh://` or `slurm://` do not need fz on the +remote side: fz only needs to be installed where the study is launched. ## Google Colab -To use FZ in Google Colab, add this to your notebook: - ```python !pip install funz-fz ``` -Or install from GitHub for the latest development version: - -```python -!pip install git+https://github.com/Funz/fz.git -``` - -## Installing Plugins - -FZ plugins are separate packages. Install them as needed: - -### FZ-Moret - -```bash -git clone https://github.com/Funz/fz-moret.git -cd fz-moret -pip install -e . -``` - -### FZ-MCNP - -```bash -git clone https://github.com/Funz/fz-mcnp.git -cd fz-mcnp -pip install -e . -``` - -### Other Plugins - -Follow the same pattern for other plugins: - -- [FZ-Cathare](https://github.com/Funz/fz-cathare) -- [FZ-Cristal](https://github.com/Funz/fz-cristal) -- [FZ-Scale](https://github.com/Funz/fz-scale) -- [FZ-Telemac](https://github.com/Funz/fz-telemac) - -## Development Installation - -For FZ development, install additional dependencies: - -```bash -# Clone the repository -git clone https://github.com/Funz/fz.git -cd fz - -# Install with development dependencies -pip install -e ".[dev]" - -# Run tests to verify -pytest tests/ -``` - -## Troubleshooting - -### Import Error - -If you get `ModuleNotFoundError: No module named 'fz'`: - -1. Verify installation: `pip list | grep fz` -2. Check your Python path: `python -c "import sys; print(sys.path)"` -3. Ensure you're using the correct Python environment - -### SSH Connection Issues - -If SSH calculators fail: - -1. Install paramiko: `pip install paramiko` -2. Test SSH manually: `ssh user@host` -3. Check host keys are accepted -4. Verify network connectivity - -### Permission Errors - -On Linux/macOS, if you get permission errors: - -```bash -# Use --user flag -pip install --user -e . - -# Or use sudo (not recommended) -sudo pip install -e . -``` - -## System-Specific Notes - -### Windows - -- Use PowerShell or Command Prompt -- Since **1.2**, `import fz` works on Windows without bash. Only genuinely shell-dependent - features (legacy shell-command outputs, `sh://` calculators) need it; shell-free - workflows (`python://`, `jq://`, `yq://`, `xpath://` outputs) run with no bash at all. -- Shell calculators and `bash://` outputs still require MSYS2 or Git Bash โ€” set - `FZ_SHELL_PATH` to point to the binaries: - ```powershell - $env:FZ_SHELL_PATH = "C:\msys64\usr\bin;C:\msys64\mingw64\bin" - ``` -- Path separators are backslashes (`\`) instead of forward slashes (`/`) -- Write input files with Unix line endings (`newline='\n'`) to avoid issues - -### macOS - -- May need Xcode Command Line Tools: `xcode-select --install` -- Use Homebrew to install Python if needed: `brew install python` - -### Linux - -- Use your distribution's package manager for Python: - - Ubuntu/Debian: `sudo apt install python3 python3-pip` - - Fedora/RHEL: `sudo dnf install python3 python3-pip` - - Arch: `sudo pacman -S python python-pip` +See [Google Colab Notebooks](../examples/colab.md). -## HPC Environments - -For HPC clusters, you may need to: - -1. Load Python module: `module load python/3.9` -2. Install to user directory: `pip install --user -e .` -3. Add to PATH: `export PATH=$HOME/.local/bin:$PATH` - -## Docker Installation (Advanced) - -Create a Dockerfile for containerized FZ: - -```dockerfile -FROM python:3.10-slim - -# Install dependencies -RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/* - -# Install FZ -RUN pip install funz-fz - -# Set working directory -WORKDIR /workspace - -# Default command -CMD ["python"] -``` +## Models and algorithms for specific codes -Build and run: +Ready-made wrappers (`fz-` repositories) are installed with fz itself, not with +pip: ```bash -docker build -t fz-env . -docker run -it -v $(pwd):/workspace fz-env +fz install model Moret # -> https://github.com/Funz/fz-Moret, into ./.fz/ +fz install algorithm brent # -> https://github.com/Funz/fz-brent ``` -## Next Steps +See [Installing Models & Algorithms](../user-guide/installing.md) and +[Plugins](../plugins/index.md). -Once installed, proceed to: +## Next steps -- [Quick Start Guide](quickstart.md) - Your first FZ calculation -- [Core Concepts](concepts.md) - Understand FZ fundamentals -- [Examples](../examples/perfectgas.md) - See FZ in action +- [Quick Start](quickstart.md) +- [Core Concepts](concepts.md) +- [Constraints & Limits](../reference/limitations.md) diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md index 6c5b872..4e13cc3 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -1,353 +1,164 @@ # Quick Start -This guide will get you up and running with FZ in just a few minutes. We'll create a simple parametric study for the ideal gas law. +This page runs a complete study: the pressure of a perfect gas, `P = nRT/V`, for 4 +temperatures ร— 3 volumes = 12 cases. Every file below is complete; the output shown was +produced by running them. -## The Complete Example +## 1. Input template -We'll calculate pressure for different temperatures and volumes using the ideal gas law: `PV = nRT` +A template is the code's normal input file with `$variables` and `@{formulas}`. -### Step 1: Create Input Template - -Create a file named `input.txt`: - -```text -# input file for Perfect Gas Pressure, with variables n_mol, T_celsius, V_L +```text title="input.txt" +# Perfect gas: n_mol, T_celsius, V_L are variables n_mol=$n_mol -T_kelvin=@($T_celsius + 273.15) +T_kelvin=@{$T_celsius + 273.15} #@ def L_to_m3(L): -#@ return(L / 1000) -V_m3=@(L_to_m3($V_L)) +#@ return L / 1000 +V_m3=@{L_to_m3($V_L)} ``` -**What's happening here?** - -- `$n_mol`, `$T_celsius`, `$V_L` are **variables** (marked with `$`) -- `@(...)` are **formulas** that are evaluated during compilation -- `#@` lines define Python functions available to formulas +- `$n_mol`, `$T_celsius`, `$V_L`: variables, replaced by a value in each case. +- `@{...}`: formulas, evaluated in Python when the case is compiled. +- `#@` lines: code made available to formulas (here a function). -### Step 2: Create Calculation Script +## 2. Calculation script -Create a file named `calculate.sh`: +The "simulation" reads the compiled input and writes a result file. -```bash +```bash title="calculate.sh" #!/bin/bash - -# Read input file -source $1 - -# Simulate calculation time -sleep 1 - -# Calculate pressure using ideal gas law -# P = nRT/V (R = 8.314 J/(molยทK)) -echo 'pressure = '`echo "scale=4;$n_mol*8.314*$T_kelvin/$V_m3" | bc` > output.txt - -echo 'Done' +# $1 is the compiled input file, in the case directory +source "$1" +P=$(python3 -c "print($n_mol * 8.314 * $T_kelvin / $V_m3)") +echo "pressure = $P" > output.txt ``` -Make it executable: - -```bash -chmod +x calculate.sh -``` +fz runs the script inside a fresh directory per case and appends the compiled input +file names to the command line, so `$1` is `input.txt`. -### Step 3: Run Parametric Study +!!! warning "Do not name result files `out.txt`, `err.txt`, `log.txt`, `info.txt`, `history.txt`" + fz writes these files in every case directory (stdout, stderr, metadata) and would + overwrite a result file of the same name. -Create a file named `run_study.py`: +## 3. Model and run -```python +```python title="run_study.py" import fz -# Define the model model = { - "varprefix": "$", # Variables are marked with $ - "formulaprefix": "@", # Formulas are marked with @ - "delim": "()", # Formula delimiters - "commentline": "#", # Comment character - "output": { - "pressure": "grep 'pressure = ' output.txt | awk '{print $3}'" - } -} - -# Define parameter values -input_variables = { - "T_celsius": [10, 20, 30, 40], # 4 temperatures - "V_L": [1, 2, 5], # 3 volumes - "n_mol": 1.0 # fixed amount -} - -# Run all combinations (4 ร— 3 = 12 cases) -results = fz.fzr( - "input.txt", - input_variables, - model, - calculators="sh://bash calculate.sh", - results_dir="results" -) - -# Display results -print(results) -print(f"\nCompleted {len(results)} calculations") -``` - -### Step 4: Execute - -Run the study: - -```bash -python run_study.py -``` - -**Expected output:** - -``` - T_celsius V_L n_mol pressure status calculator error command -0 10 1.0 1.0 2353.58 done sh:// None bash... -1 10 2.0 1.0 1176.79 done sh:// None bash... -2 10 5.0 1.0 470.72 done sh:// None bash... -3 20 1.0 1.0 2437.30 done sh:// None bash... -... - -Completed 12 calculations -``` - -## Understanding the Results - -The results DataFrame contains: - -- **Input variables**: `T_celsius`, `V_L`, `n_mol` -- **Output variables**: `pressure` -- **Metadata**: `status`, `calculator`, `error`, `command` - -You can use pandas to analyze: - -```python -# Find maximum pressure -max_pressure = results['pressure'].max() -print(f"Maximum pressure: {max_pressure}") - -# Filter results -high_temp = results[results['T_celsius'] > 25] -print(high_temp) - -# Plot results -import matplotlib.pyplot as plt - -for volume in results['V_L'].unique(): - data = results[results['V_L'] == volume] - plt.plot(data['T_celsius'], data['pressure'], - marker='o', label=f'V={volume} L') - -plt.xlabel('Temperature (ยฐC)') -plt.ylabel('Pressure (Pa)') -plt.legend() -plt.show() -``` - -## What Just Happened? - -Let's break down the workflow: - -1. **fzi (Parse Input)** - FZ identified variables `$n_mol`, `$T_celsius`, `$V_L` in `input.txt` - -2. **fzc (Compile)** - For each parameter combination, FZ: - - Created a directory (e.g., `results/T_celsius=10,V_L=1`) - - Substituted variable values - - Evaluated formulas - - Saved compiled input file - -3. **Calculator Execution** - For each case, FZ: - - Ran `bash calculate.sh input.txt` in the case directory - - Captured output and errors - - Logged execution metadata - -4. **fzo (Parse Output)** - FZ: - - Ran the output command to extract `pressure` - - Collected results from all cases - - Built a pandas DataFrame - -5. **fzr (Complete Run)** - FZ orchestrated all steps automatically! - -## Next Steps - -### Try Different Calculators - -Run on a remote server: - -```python -results = fz.fzr( - "input.txt", - input_variables, - model, - calculators="ssh://user@server.com/bash /path/to/calculate.sh", - results_dir="remote_results" -) -``` - -Use caching to avoid recalculation: - -```python -results = fz.fzr( - "input.txt", - input_variables, - model, - calculators=[ - "cache://results", # Check cache first - "sh://bash calculate.sh" # Run if not cached - ], - results_dir="cached_results" -) -``` - -### Run in Parallel - -Use multiple calculators for parallel execution: - -```python -results = fz.fzr( - "input.txt", - input_variables, - model, - calculators=[ - "sh://bash calculate.sh", - "sh://bash calculate.sh", - "sh://bash calculate.sh", - "sh://bash calculate.sh" - ], # 4 parallel workers - results_dir="parallel_results" -) -``` - -### Save Model as Alias - -Create `.fz/models/perfectgas.json`: - -```json -{ - "varprefix": "$", + "varprefix": "$", # these four values are the defaults "formulaprefix": "@", - "delim": "()", + "delim": "{}", "commentline": "#", "output": { - "pressure": "grep 'pressure = ' output.txt | awk '{print $3}'" + "pressure": "python://grep(r'pressure = (\\S+)', 'output.txt')", }, - "id": "perfectgas" } -``` -Then use by name: - -```python results = fz.fzr( "input.txt", - input_variables, - "perfectgas", # Model name instead of dict - calculators="sh://bash calculate.sh", - results_dir="results" + {"T_celsius": [10, 20, 30, 40], "V_L": [1, 2, 5], "n_mol": 1.0}, # 4 x 3 = 12 cases + model, + calculators=["sh://bash calculate.sh"] * 2, # 2 cases at a time + results_dir="results", ) +print(results[["T_celsius", "V_L", "n_mol", "pressure", "status"]].head()) ``` -## Common Patterns +```text title="output" + T_celsius V_L n_mol pressure status +0 10 1 1.0 2354109.10 done +1 10 2 1.0 1177054.55 done +2 10 5 1.0 470821.82 done +3 20 1 1.0 2437249.10 done +4 20 2 1.0 1218624.55 done +``` -### Single Parameter Study +!!! danger "Pass `calculators=` and `results_dir=` by keyword" + The 4th positional parameter of `fz.fzr` is `results_dir`, not `calculators`. + `fz.fzr("input.txt", vars, model, "sh://bash calculate.sh")` creates a directory + named `sh:/bash calculate.sh` and runs without calculator: every case fails. -Vary one parameter: +## 4. What was produced -```python -results = fz.fzr( - "input.txt", - {"temperature": [100, 200, 300, 400, 500]}, - model, - calculators="sh://bash calc.sh" -) +```text +results/ +โ”œโ”€โ”€ manifest.json # campaign record (versions, model, calculators, cases) +โ”œโ”€โ”€ ro-crate-metadata.json # same, as RO-Crate metadata +โ”œโ”€โ”€ T_celsius=10,V_L=1,n_mol=1.0/ +โ”‚ โ”œโ”€โ”€ input.txt # compiled input +โ”‚ โ”œโ”€โ”€ output.txt # written by calculate.sh +โ”‚ โ”œโ”€โ”€ out.txt err.txt # stdout / stderr of the command +โ”‚ โ”œโ”€โ”€ log.txt info.txt history.txt +โ”‚ โ””โ”€โ”€ .fz_hash # SHA-256 of the inputs (cache key) +โ””โ”€โ”€ ... # 11 more case directories ``` -### Full Factorial Design +The returned DataFrame has one row per case: the variables, the outputs, and +`status`, `calculator`, `error`, `command`. A case whose `status` is not `done` has its +diagnosis in that case's `err.txt` and `log.txt`. -Vary multiple parameters: +## 5. The same from the command line -```python -results = fz.fzr( - "input.txt", - { - "param1": [1, 2, 3], # 3 values - "param2": [10, 20], # 2 values - "param3": [0.1, 0.5, 1.0] # 3 values - }, # Total: 3 ร— 2 ร— 3 = 18 cases - model, - calculators="sh://bash calc.sh" -) +```bash +fzr input.txt \ + --model '{"output": {"pressure": "python://grep(r\"pressure = (\\S+)\", \"output.txt\")"}}' \ + --input_variables '{"T_celsius": [10, 20, 30, 40], "V_L": [1, 2, 5], "n_mol": 1.0}' \ + --calculators "sh://bash calculate.sh" \ + --results_dir results --format json ``` -### Mixed Fixed and Variable Parameters +Data goes to stdout, logs and progress to stderr; the exit status is 1 when no case +succeeds. -```python -results = fz.fzr( - "input.txt", - { - "variable_param": [1, 2, 3, 4], # Variable - "fixed_param": 100 # Fixed - }, - model, - calculators="sh://bash calc.sh" -) -``` +## 6. Check each step before a large run -## Troubleshooting - -**Issue**: Calculation fails with "command not found" +For a new code, verify the steps separately; each one isolates a class of errors. ```python -# Use absolute paths -calculators="sh://bash /full/path/to/calculate.sh" -``` +fz.fzi("input.txt", model) +# {'T_celsius': None, 'V_L': None, 'n_mol': None, +# 'T_celsius + 273.15': None, 'L_to_m3(V_L)': None} variables and formulas found -**Issue**: Output not parsed correctly - -```python -# Test your output command manually -import subprocess -result = subprocess.run( - "grep 'pressure = ' output.txt | awk '{print $3}'", - shell=True, capture_output=True, text=True -) -print(result.stdout) +fz.fzc("input.txt", {"T_celsius": 10, "V_L": 1, "n_mol": 1.0}, model, "compiled") +# compiled/T_celsius=10,V_L=1,n_mol=1.0/input.txt one sub-directory per case +# T_kelvin=283.15, V_m3=0.001 compilation correct? ``` -**Issue**: Formulas not evaluating - -```python -# Check formula syntax -# Ensure variables are marked with $ and formulas with @ -# Check that commentline is correct +```bash +(cd compiled/*/ && bash ../../calculate.sh input.txt) # does the code run? ``` -## Google Colab Quick Start - -Want to try FZ without installing anything locally? Use Google Colab: - -[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Funz/fz/blob/main/notebooks/quickstart.ipynb) - -## Beyond Fixed Grids: Adaptive Design - -For optimization and uncertainty quantification, use `fzd` instead of `fzr`. Instead of specifying exact parameter values, you specify ranges and let an algorithm choose points adaptively: - ```python -result = fz.fzd( - "input.txt", - {"T_celsius": "[0;100]", "V_L": "[1;5]"}, # Ranges, not lists - model, - output_expression="pressure", - algorithm="examples/algorithms/bfgs.py", - calculators="sh://bash calculate.sh" -) -``` - -See [fzd - Design of Experiments](../user-guide/core-functions/fzd.md) for details. - -## Further Reading - -- [Core Concepts](concepts.md) - Understand FZ fundamentals -- [Core Functions](../user-guide/core-functions/fzi.md) - Deep dive into fzi, fzc, fzo, fzr, fzd, fzl -- [Model Definition](../user-guide/model-definition.md) - Learn about model configuration -- [Examples](../examples/perfectgas.md) - More complete examples +fz.fzo("compiled/*", model) # does parsing find the value? +# path=compiled/T_celsius=10,V_L=1,n_mol=1.0, pressure=2354109.1, T_celsius=10, ... +``` + +`fzo` must target the case directory (or a glob of case directories): `fz.fzo("compiled", +model)` looks for `output.txt` in `compiled/` itself and returns `None`. + +## 7. Next steps + +- Reuse finished cases: add `"cache://results"` first in `calculators` + ([Caching](../user-guide/running/caching.md)). +- Run elsewhere: [SSH](../user-guide/calculators/ssh.md), + [SLURM](../user-guide/calculators/slurm.md). +- Let an algorithm choose the points (optimization, sampling): + [fzd](../user-guide/core-functions/fzd.md). + + ```python + fz.fzd("input.txt", + {"T_celsius": "[0;100]", "V_L": "[1;5]", "n_mol": "1"}, # ranges, fixed values + model, + output_expression="pressure", + algorithm="randomsampling", # .fz/algorithms/randomsampling.py + calculators="sh://bash calculate.sh", + algorithm_options={"nvalues": 5}) + # summary: "randomsampling completed: 1 iterations, 5 evaluations (5 valid)" + ``` + + `randomsampling.py` is copied from fz's + [`examples/algorithms/`](https://github.com/Funz/fz/tree/main/examples/algorithms); + `algorithm=` also accepts a path to a `.py`/`.R` file. + +- Read the [Constraints & Limits](../reference/limitations.md) before wrapping a real code. diff --git a/docs/index.md b/docs/index.md index 79b5937..3480f91 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,228 +1,145 @@ # FZ - Parametric Scientific Computing Framework [![CI](https://github.com/Funz/fz/workflows/CI/badge.svg)](https://github.com/Funz/fz/actions/workflows/ci.yml) +[![PyPI](https://img.shields.io/pypi/v/funz-fz.svg)](https://pypi.org/project/funz-fz/) [![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) -[![Version](https://img.shields.io/badge/version-1.2-blue.svg)](https://github.com/Funz/fz/releases) -A powerful Python package for parametric simulations and computational experiments. **FZ** wraps your simulation codes to automatically run parametric studies, manage input/output files, handle parallel execution, and collect results in structured DataFrames. +**FZ** wraps any simulation code that reads input files and writes output files, and runs +it as a parametric study: variables in the input files are substituted for each case, +cases run in parallel (locally, over SSH, on SLURM or on Funz servers), and the outputs +are parsed back into a pandas DataFrame. FZ is the Python rewrite of the Java +[Funz](https://github.com/Funz) framework. PyPI package: `funz-fz`. -!!! info "What's New in 1.2" - - **Shell-free output extraction**: `python://`, `jq://`, `yq://`, `xpath://` output prefixes โ€” no bash/grep/awk, fully portable on Windows - - **Vector (array) outputs**: an output entry can resolve to a full list (time series, profiles, spectra) โ€” stored as-is by `fzr`/`fzo` - - **Multi-objective `fzd`**: `output_expression` also accepts a list of expressions; new NSGA-II example algorithm - - **Shared static files**: new `input_static` parameter for files identical across every case โ€” never re-hashed or duplicated per case - - **Configurable case naming**: `case_naming` = `"path"` / `"hash"` / `"index"` to avoid filesystem filename-length limits - - **1 h default run timeout**: `FZ_RUN_TIMEOUT` default is now 3600 s, with a per-model `"timeout"` override - - **Formula number formatting**: full `DecimalFormat` subset โ€” `@{3.0 | #.###}` โ†’ `3`, `@{123456.789 | 0.00E00}` โ†’ `1.23E05` - - **Claude Code plugin**: four slash commands โ€” `/fz:wrap`, `/fz:run`, `/fz:design`, `/fz:install` - - [See full release notes](reference/releases.md) - -## What is FZ? - -FZ is a framework that simplifies running parametric computational studies. Whether you're working with scientific simulations, engineering calculations, or any computational model, FZ helps you: - -- ๐Ÿ”„ **Run parametric studies** - Automatically generate and execute all combinations of parameter values -- โšก **Parallelize execution** - Run multiple cases concurrently across multiple calculators -- ๐Ÿ’พ **Cache results** - Reuse previous calculations based on input file hashes -- ๐ŸŒ **Execute remotely** - Run calculations on remote servers via SSH -- ๐Ÿ“Š **Structure output** - Get results as pandas DataFrames with automatic type conversion - -## Six Core Functions - -FZ provides six functions that cover the entire workflow: - -| Function | Purpose | Description | -|----------|---------|-------------| -| **[fzi](user-guide/core-functions/fzi.md)** | Parse **I**nput | Identify variables in input files | -| **[fzc](user-guide/core-functions/fzc.md)** | **C**ompile | Substitute variable values in templates | -| **[fzo](user-guide/core-functions/fzo.md)** | Parse **O**utput | Extract results from output files | -| **[fzr](user-guide/core-functions/fzr.md)** | **R**un | Execute complete parametric studies | -| **[fzd](user-guide/core-functions/fzd.md)** | **D**esign | Iterative design of experiments with adaptive algorithms | -| **[fzl](user-guide/core-functions/fzl.md)** | **L**ist | List and validate installed models and calculators | - -## Quick Example +```bash +pip install funz-fz +``` -Here's a simple parametric study in just a few lines: +## Minimal example ```python import fz -# Define the model -model = { - "varprefix": "$", - "output": { - "pressure": "grep 'pressure = ' output.txt | awk '{print $3}'" - } -} +model = {"output": {"pressure": "python://grep(r'pressure = (\\S+)', 'output.txt')"}} -# Run all combinations (4 ร— 3 = 12 cases) results = fz.fzr( - "input.txt", - { - "T_celsius": [10, 20, 30, 40], # 4 temperatures - "V_L": [1, 2, 5], # 3 volumes - "n_mol": 1.0 # fixed amount - }, + "input.txt", # template containing $T_celsius, $V_L + {"T_celsius": [10, 20, 30], "V_L": [1, 2]}, # 3 x 2 = 6 cases model, - calculators="sh://bash calculate.sh", - results_dir="results" + calculators="sh://bash calculate.sh", # always pass by keyword + results_dir="results", ) - -print(results) # pandas DataFrame with all results +print(results[["T_celsius", "V_L", "pressure", "status"]]) ``` -## Key Features - -### Parametric Studies -Generate and run all combinations of parameter values automatically. FZ creates the Cartesian product of your parameter lists and manages execution. +The full walk-through is in the [Quick Start](getting-started/quickstart.md). -### Multiple Calculators -Execute calculations using different methods: +## The six functions -- **Local shell** - Run scripts and executables locally -- **SSH remote** - Execute on remote servers with automatic file transfer -- **SLURM** - Submit jobs to HPC clusters with workload management (New in 0.9.1) -- **Funz server** - Connect to Java Funz calculator servers (New in 0.9.1) -- **Cache** - Reuse previous results based on input hashes +Each function exists in Python (`fz.fzr(...)`) and as a command (`fzr ...` or +`fz run ...`). -### Smart Parallel Execution -FZ automatically parallelizes your calculations across available calculators with: +| Function | Role | Page | +|----------|------|------| +| `fzi` | List the variables of an input template | [fzi](user-guide/core-functions/fzi.md) | +| `fzc` | Compile templates with given values | [fzc](user-guide/core-functions/fzc.md) | +| `fzo` | Parse output files into a table | [fzo](user-guide/core-functions/fzo.md) | +| `fzr` | Run a full parametric study (grid or list of cases) | [fzr](user-guide/core-functions/fzr.md) | +| `fzd` | Run an adaptive design of experiments (optimization, sampling, calibration) | [fzd](user-guide/core-functions/fzd.md) | +| `fzl` | List and check installed models and calculators | [fzl](user-guide/core-functions/fzl.md) | -- Load balancing -- Automatic retry on failures -- Progress tracking with ETA -- Graceful interrupt handling (Ctrl+C) - -### Formula Evaluation -Use Python or R expressions directly in input templates for calculated parameters: - -```text -Temperature: $T_celsius C -# Calculated value, formatted with a DecimalFormat pattern -T_kelvin: @{$T_celsius + 273.15 | 0.00} K -``` - -### Shell-Free Output Extraction (New in 1.2) -Pull results out of output files with native, portable extractors โ€” no bash/grep/awk needed: - -```python -model = { - "output": { - "pressure": "python://grep(r'Pressure: (\\S+)', 'output.txt')", - "energy": "jq://.energy results.json", - "T_series": "python://csv_file('temps.csv', column='T')", # vector output - } -} -``` - -## Getting Started - -Ready to get started? Check out our guides: +## How the documentation is organized
-- :material-rocket-launch:{ .lg .middle } __Quick Start__ +- :material-rocket-launch:{ .lg .middle } __Getting Started__ --- - Get up and running with FZ in minutes + Installation, a first study end to end, and the vocabulary (template, model, + calculator, case). [:octicons-arrow-right-24: Quick Start](getting-started/quickstart.md) -- :material-book-open-variant:{ .lg .middle } __User Guide__ +- :material-file-document-edit:{ .lg .middle } __Templates & Models__ --- - Learn about core functions, models, and calculators + How to mark variables and formulas in input files, and how to declare the outputs to + extract. - [:octicons-arrow-right-24: User Guide](user-guide/core-functions/fzi.md) + [:octicons-arrow-right-24: Template syntax](user-guide/templates/syntax.md) -- :material-puzzle:{ .lg .middle } __Plugins__ +- :material-server-network:{ .lg .middle } __Calculators__ --- - Explore FZ plugins for specific simulation codes + Where cases run: local shell, SSH, SLURM (per case or job arrays), Funz servers, and + the result cache. - [:octicons-arrow-right-24: Plugins](plugins/index.md) + [:octicons-arrow-right-24: Calculators](user-guide/calculators/overview.md) -- :material-code-braces:{ .lg .middle } __Examples__ +- :material-play-speed:{ .lg .middle } __Running Studies__ --- - See FZ in action with complete examples and Google Colab notebooks - - [:octicons-arrow-right-24: Examples](examples/perfectgas.md) - -
- -## Plugins - -FZ includes plugins for various simulation codes: + Parallelism, retries, timeouts, caching, results layout, manifest, interrupt and + resume. -- **[FZ-Moret](plugins/moret.md)** - Moret model plugin -- **[FZ-MCNP](plugins/mcnp.md)** - Monte Carlo N-Particle Transport Code -- **[FZ-Cathare](plugins/cathare.md)** - Thermal-hydraulic system code -- **[FZ-Cristal](plugins/cristal.md)** - Cristal simulation plugin -- **[FZ-Scale](plugins/scale.md)** - Scale nuclear analysis code -- **[FZ-Telemac](plugins/telemac.md)** - Hydrodynamics simulation system + [:octicons-arrow-right-24: Running studies](user-guide/running/parallel.md) -## AI Agent Skill (Claude Code) +- :material-chart-bell-curve:{ .lg .middle } __Design of Experiments__ -FZ ships a **Claude Code plugin** that teaches AI coding agents the full fz workflow โ€” parameterizing input files, defining models, choosing calculators, and running parametric studies or optimizations. - -Install it directly from Claude Code: - -``` -/plugin marketplace add Funz/fz -/plugin install fz@funz -``` + --- -Since **1.2** the plugin also provides four slash commands alongside the Agent Skill: + `fzd` with built-in or installed algorithms, and how to write your own. -| Command | Purpose | -|---------|---------| -| `/fz:wrap` | Wrap a simulation code and verify it step by step | -| `/fz:run` | Run a parametric study (`fzr`) | -| `/fz:design` | Adaptive design of experiments / optimization / calibration (`fzd`) | -| `/fz:install` | Find and install an official `fz-` wrapper or algorithm | + [:octicons-arrow-right-24: fzd](user-guide/core-functions/fzd.md) -Or just describe what you want in plain language โ€” *"wrap my simulation and run a parameter sweep over mesh_size and timestep"* โ€” and the agent handles the rest. +- :material-alert-circle-outline:{ .lg .middle } __Constraints & Limits__ -The skill covers the complete workflow: `fzi` โ†’ `fzc` โ†’ `fzo` โ†’ `fzr`/`fzd`, calculator selection (local, SSH, SLURM), caching, and writing custom model wrappers or algorithms. + --- -## Google Colab Integration + Behaviors that most often surprise users, checked against the code. Read before + writing a first model. -Try FZ directly in your browser with our Google Colab notebooks: + [:octicons-arrow-right-24: Constraints](reference/limitations.md) -- [Basic Example - Perfect Gas](examples/colab.md#perfect-gas-example) -- [OpenModelica Integration](examples/colab.md#openmodelica-example) -- [Plugin Examples](examples/colab.md#plugin-examples) + -## Use Cases +## Capabilities at a glance -FZ is perfect for: +| Area | What FZ provides | Details | +|------|------------------|---------| +| Templates | `$var`, `${var~default}`, `@{formula}` in Python or R, `#@` context lines, DecimalFormat number formatting, Java Funz `$(var)` templates | [Syntax](user-guide/templates/syntax.md), [Formulas](user-guide/templates/formulas.md) | +| Designs | Full factorial (dict of lists), explicit case list (DataFrame), adaptive (`fzd`) incl. multi-objective | [fzr](user-guide/core-functions/fzr.md), [fzd](user-guide/core-functions/fzd.md) | +| Outputs | Shell commands, shell-free `python://`, `jq://`, `yq://`, `xpath://`, Python callables; scalar or vector values | [Output extraction](user-guide/models/outputs.md) | +| Execution | `sh://`, `ssh://`, `slurm://`, `slurm-array://`, `funz://`, `cache://`; aliases in `.fz/calculators/` | [Calculators](user-guide/calculators/overview.md) | +| Robustness | Retries across calculators, timeouts, graceful Ctrl+C, resume from cache | [Running studies](user-guide/running/parallel.md) | +| Traceability | Per-case logs, `manifest.json`, RO-Crate metadata, cache identity (`code_id`) | [Results & traceability](user-guide/running/results.md) | +| Packaging | `fz install model|algorithm ` from the `fz-` repositories | [Installing](user-guide/installing.md), [Plugins](plugins/index.md) | +| AI agents | Claude Code plugin (skill + slash commands), `fz-mcp` MCP server | [AI agents](user-guide/ai-agents.md) | -- **Sensitivity Analysis** - Understand how parameters affect your results -- **Design of Experiments** - Systematically explore the parameter space -- **Optimization Studies** - Find optimal parameter combinations -- **Uncertainty Quantification** - Propagate uncertainties through your model -- **Model Validation** - Compare model outputs against experimental data +## Requirements -## Community and Support +- Python โ‰ฅ 3.9 (tested 3.9โ€“3.13); dependencies `paramiko`, `pandas`, `charset-normalizer`. +- **bash** for shell calculators and shell output commands (MSYS2 or Git Bash on + Windows, located with `FZ_SHELL_PATH`). +- Optional: `rpy2` + R (R formulas), `mcp` on Python โ‰ฅ 3.10 (`fz-mcp`), `h5py`, `jq`, + `yq`, `xmllint` (corresponding output extractors). -- **GitHub**: [Funz/fz](https://github.com/Funz/fz) -- **Issues**: [Report bugs or request features](https://github.com/Funz/fz/issues) -- **Documentation**: You're reading it! +!!! warning "Security" + Templates, formulas, output commands and calculator commands run as code with your + privileges. Only use models, algorithms and calculator aliases from sources you + trust. See [Security Model](reference/security.md). -## License +## Links -FZ is released under the [BSD 3-Clause License](https://opensource.org/licenses/BSD-3-Clause). +- Source and issues: [github.com/Funz/fz](https://github.com/Funz/fz) +- Release notes: [Release Notes](reference/releases.md) +- License: [BSD 3-Clause](https://opensource.org/licenses/BSD-3-Clause) ## Citation -If you use FZ in your research, please cite: - ```bibtex @software{fz, title = {FZ: Parametric Scientific Computing Framework}, diff --git a/docs/plugins/cathare.md b/docs/plugins/cathare.md index 390fd7b..a2173fd 100644 --- a/docs/plugins/cathare.md +++ b/docs/plugins/cathare.md @@ -1,15 +1,52 @@ -# FZ-Cathare Plugin +# FZ-Cathare -CATHARE thermal-hydraulic system code support for FZ. +CATHARE thermal-hydraulic system code. -## Installation +- **Simulation type**: Thermal-hydraulics for reactor safety +- **Use cases**: Reactor safety, accident analysis, transient simulations + +## Install ```bash -git clone https://github.com/Funz/fz-cathare.git -cd fz-cathare -pip install -e . +pip install funz-fz +fz install model Cathare +``` + +Installs into `./.fz/` (`--global`: `~/.fz/`, see the +[warning on runner paths](../user-guide/installing.md)). **Requirements of the code +itself**: CATHARE installation. + +## Models + +| Model alias | Variables | Formulas | Comment | Outputs | +|-------------|-----------|----------|---------|---------| +| `Cathare` | `$(x)` | `@(...)` | `*` | `*` | + +## Calculator alias + +`localhost_Cathare` (`sh://`) maps each model to its runner script: + +| Model | Command | +|-------|---------| +| `Cathare` | `bash .fz/calculators/Cathare.sh` | + +## Use + +```python +import fz + +results = fz.fzr( + "my_input", # template using the syntax above + {"param": [1.0, 2.0, 3.0]}, + "Cathare", + calculators=["localhost_Cathare"] * 2, # 2 cases at a time; omit it to run one by one + results_dir="results_cathare", +) ``` -## Repository +Check the variables of a template first: `fzi my_input --model Cathare --format json`. + +## Links -[Funz/fz-cathare](https://github.com/Funz/fz-cathare) +- Repository and README: [Funz/fz-Cathare](https://github.com/Funz/fz-Cathare) +- [Plugins overview](index.md) ยท [Installing Models & Algorithms](../user-guide/installing.md) diff --git a/docs/plugins/cristal.md b/docs/plugins/cristal.md index 00636e2..615c3da 100644 --- a/docs/plugins/cristal.md +++ b/docs/plugins/cristal.md @@ -1,15 +1,58 @@ -# FZ-Cristal Plugin +# FZ-Cristal -Cristal simulation support for FZ. +French criticality package (V1 & V2). -## Installation +- **Simulation type**: Criticality calculations (SN KEFF, SN Normes, Pij-MC, AP2M5) +- **Use cases**: French nuclear code criticality studies + +## Install ```bash -git clone https://github.com/Funz/fz-cristal.git -cd fz-cristal -pip install -e . +pip install funz-fz +fz install model Cristal +``` + +Installs into `./.fz/` (`--global`: `~/.fz/`, see the +[warning on runner paths](../user-guide/installing.md)). **Requirements of the code +itself**: Set `CRISTAL_HOME` and `CRISTAL_VERSION`. + +## Models + +| Model alias | Variables | Formulas | Comment | Outputs | +|-------------|-----------|----------|---------|---------| +| `CRISTAL-AP2M5` | `${x}` | `@{...}` | `#` | `mean_keff`, `sigma_keff`, `dkeff_pertu`, `sigma_dkeff_pertu`, `cU`, `cPU`, `M2`, `B2`, `KINF` | +| `Cristal-Pij-MC` | `${x}` | `@{...}` | `*` | `mean_keff`, `sigma_keff`, `dkeff_pertu`, `sigma_dkeff_pertu`, `cU`, `cPU`, `M2`, `B2`, `KINF` | +| `Cristal-SnKeff` | `${x}` | `@{...}` | `*` | `keff`, `kinf`, `slowing_down`, `M2`, `B2` | +| `Cristal-SnNormes` | `${x}` | `@{...}` | `*` | `keff`, `kinf`, `slowing_down`, `M2`, `B2`, `dimension`, `cx`, `hx` | + +## Calculator alias + +`localhost_Cristal` (`sh://`) maps each model to its runner script: + +| Model | Command | +|-------|---------| +| `Cristal-SnKeff` | `bash .fz/calculators/Cristal.sh` | +| `Cristal-SnNormes` | `bash .fz/calculators/Cristal.sh` | +| `Cristal-Pij-MC` | `bash .fz/calculators/Cristal.sh` | +| `CRISTAL-AP2M5` | `bash .fz/calculators/Cristal.sh` | + +## Use + +```python +import fz + +results = fz.fzr( + "my_input", # template using the syntax above + {"param": [1.0, 2.0, 3.0]}, + "CRISTAL-AP2M5", + calculators=["localhost_Cristal"] * 2, # 2 cases at a time; omit it to run one by one + results_dir="results_cristal", +) ``` -## Repository +Check the variables of a template first: `fzi my_input --model CRISTAL-AP2M5 --format json`. + +## Links -[Funz/fz-cristal](https://github.com/Funz/fz-cristal) +- Repository and README: [Funz/fz-Cristal](https://github.com/Funz/fz-Cristal) +- [Plugins overview](index.md) ยท [Installing Models & Algorithms](../user-guide/installing.md) diff --git a/docs/plugins/index.md b/docs/plugins/index.md index cde3b8d..ec0556c 100644 --- a/docs/plugins/index.md +++ b/docs/plugins/index.md @@ -11,7 +11,7 @@ Monte Carlo N-Particle Transport Code support. - **Simulation type**: Radiation transport, criticality calculations - **Repository**: [Funz/fz-MCNP](https://github.com/Funz/fz-MCNP) -- **Installation**: `pip install git+https://github.com/Funz/fz.git` + Set `MCNP_PATH` environment variable +- **Installation**: `fz install model MCNP` + Set `MCNP_PATH` environment variable - **Input syntax**: Variables `%(...)`, Formulas `@{...}`, Comments `C ` - **Main outputs**: `mean_keff`, `sigma_keff` - **Use cases**: Shielding, criticality, dose calculations @@ -21,7 +21,7 @@ MORET Monte Carlo criticality safety calculations. - **Simulation type**: Reactor physics criticality - **Repository**: [Funz/fz-Moret](https://github.com/Funz/fz-Moret) -- **Installation**: `fz.install('Moret')` + Install MORET at `/opt/MORET/scripts/moret.py` +- **Installation**: `fz install model Moret` + MORET at `/opt/MORET/scripts/moret.py` - **Input syntax**: Variables `${...}`, Formulas `@{...}`, Comments `*` - **Main outputs**: `mean_keff`, `sigma_keff`, `dkeff_pertu`, `sigma_dkeff_pertu` - **Use cases**: Criticality safety, parametric reactor studies @@ -31,7 +31,7 @@ French criticality package (V1 & V2). - **Simulation type**: Criticality calculations (SN KEFF, SN Normes, Pij-MC, AP2M5) - **Repository**: [Funz/fz-Cristal](https://github.com/Funz/fz-Cristal) -- **Installation**: `pip install git+https://github.com/Funz/fz.git` + Set `CRISTAL_HOME` and `CRISTAL_VERSION` +- **Installation**: `fz install model Cristal` + Set `CRISTAL_HOME` and `CRISTAL_VERSION` - **Input syntax**: Variables `${...}`, Formulas `@{...}`, Comments `*` (or `#` for XML) - **Main outputs**: `keff`, `kinf`, `M2`, `B2`, `mean_keff`, `sigma_keff` (model dependent) - **Use cases**: French nuclear code criticality studies @@ -41,8 +41,8 @@ SCALE nuclear analysis code system. - **Simulation type**: Nuclear criticality, shielding, isotopic analysis, sensitivity - **Repository**: [Funz/fz-Scale](https://github.com/Funz/fz-Scale) -- **Installation**: `pip install git+https://github.com/Funz/fz.git` + SCALE 6.2+ at `/SCALE/scale6.2` or set `SCALE_HOME` -- **Input syntax**: Variables `&{...}`, Formulas `@{...}`, Comments `'` +- **Installation**: `fz install model Scale` + SCALE 6.2+ at `/SCALE/scale6.2` or set `SCALE_HOME` +- **Input syntax**: Variables `&(...)`, Formulas `@{...}`, Comments `'` - **Main outputs**: `mean_keff`, `sigma_keff`, `mean_E_lethargy`, `mean_nubar`, `mean_free_path`, `lambda` (XSDRNPM) - **Models**: Scale-keno, Scale-shift, Scale-tsunami, Scale-xsdrnpm - **Use cases**: Reactor physics, fuel cycle, depletion, sensitivity analysis @@ -52,7 +52,7 @@ Serpent Monte Carlo reactor physics code. - **Simulation type**: Continuous-energy Monte Carlo reactor physics - **Repository**: [Funz/fz-Serpent](https://github.com/Funz/fz-Serpent) -- **Installation**: `pip install git+https://github.com/Funz/fz.git` + `pip install serpentTools` + Serpent2 installation +- **Installation**: `fz install model Serpent` + `pip install serpentTools` + Serpent2 installation - **Input syntax**: Variables `${...}`, Formulas `@{...}`, Comments `%` - **Main outputs**: `absKeff`, `anaKeff`, `colKeff`, `impKeff`, `burnup`, `burnDays` (JSON arrays) - **Use cases**: Detailed reactor physics, fuel depletion, advanced Monte Carlo simulations @@ -62,7 +62,7 @@ CASMO5 lattice physics code. - **Simulation type**: Light water reactor lattice physics - **Repository**: [Funz/fz-Casmo](https://github.com/Funz/fz-Casmo) -- **Installation**: `pip install git+https://github.com/Funz/fz.git` + CASMO5 license & set `CASMO_PATH` +- **Installation**: `fz install model Casmo` + CASMO5 license & set `CASMO_PATH` - **Input syntax**: Variables `${...}`, Formulas `@{...}`, Comments `*` - **Main outputs**: `k_inf`, `m2`, `burnup`, `u235_wt_pct`, `fissile_pu_wt_pct`, `pin_power_peak` (depletion arrays) - **Use cases**: PWR/BWR assembly analysis, fuel depletion studies @@ -74,7 +74,7 @@ CATHARE thermal-hydraulic system code. - **Simulation type**: Thermal-hydraulics for reactor safety - **Repository**: [Funz/fz-Cathare](https://github.com/Funz/fz-Cathare) -- **Installation**: `pip install fz` + CATHARE installation +- **Installation**: `fz install model Cathare` + CATHARE installation - **Input syntax**: Variables `$(...)`, Formulas `@(...)`, Comments `*` - **Main outputs**: EVOLUTION data from FORT07 (TIME_*, Z_* variables with time series) - **Use cases**: Reactor safety, accident analysis, transient simulations @@ -86,7 +86,7 @@ TELEMAC-MASCARET hydrodynamics suite. - **Simulation type**: Free surface flow, sediment transport - **Repository**: [Funz/fz-Telemac](https://github.com/Funz/fz-Telemac) -- **Installation**: `pip install git+https://github.com/Funz/fz.git` + `pip install PyTelTools` + Telemac (or Docker) +- **Installation**: `fz install model Telemac` + `pip install PyTelTools` + Telemac (or Docker) - **Input syntax**: Variables `$(...)`, Formulas `@(...)`, Comments `/` - **Main outputs**: `S`, `H` (water surface, depth time series at POI from CSV) - **Use cases**: River flow, coastal modeling, dam breaks, flood analysis @@ -98,7 +98,7 @@ Cast3m finite element software. - **Simulation type**: Structural and fluid mechanics FEM - **Repository**: [Funz/fz-Cast3M](https://github.com/Funz/fz-Cast3M) -- **Installation**: `pip install git+https://github.com/Funz/fz.git` + Cast3m (castem2000/cast3m in PATH) +- **Installation**: `fz install model Cast3M` + Cast3m (castem2000/cast3m in PATH) - **Input syntax**: Variables `$(...)`, Formulas `%(...)`, Comments `*` - **Main outputs**: MESS variables, text files (*.txt), CSV files (*.csv) - **Use cases**: Structural mechanics, thermal analysis, coupled simulations @@ -108,7 +108,7 @@ OpenModelica multi-physics simulation. - **Simulation type**: Multi-domain modeling (mechanics, thermodynamics, electrical, control) - **Repository**: [Funz/fz-Modelica](https://github.com/Funz/fz-Modelica) -- **Installation**: `pip install git+https://github.com/Funz/fz.git` + OpenModelica installation +- **Installation**: `fz install model Modelica` + OpenModelica installation - **Input syntax**: Variables `${...~default}`, Formulas `@{...}`, Comments `//` - **Main outputs**: `res` (JSON dictionary with all CSV simulation results) - **Use cases**: Physical system modeling, control systems, thermal analysis @@ -214,7 +214,7 @@ Template repositories for writing custom `fzd` algorithm plugins. - **Purpose**: Starting point for new optimization, sampling, or calibration algorithms - **Interface**: implement `get_initial_design`, `get_next_design`, `get_analysis` -See also [Writing Custom Algorithms](../user-guide/core-functions/fzd.md#writing-custom-algorithms) in the fzd docs. +See also [Writing Algorithms](../user-guide/design/algorithms.md). ## Plugin Architecture @@ -246,30 +246,18 @@ results = fz.fzr( ## Installing Plugins -Most plugins are used directly by cloning their repositories: - ```bash -# Clone plugin repository -git clone https://github.com/Funz/fz-.git -cd fz- - -# The .fz/ directory is automatically detected by fz -# Install simulation code separately (MCNP, OpenModelica, etc.) -``` - -Some plugins provide Python installation via `fz.install()`: - -```python -import fz -fz.install('Moret') # Installs Moret plugin +pip install funz-fz # fz itself +fz install model MCNP # -> github.com/Funz/fz-MCNP, into ./.fz/ +fz install model MCNP --global # into ~/.fz/ for all projects ``` -## Quick Start with a Plugin +This copies the model, the runner script and a `localhost_` calculator alias. +The simulation code itself is not installed: follow the plugin's README (paths, +environment variables, licenses). Details: [Installing Models & Algorithms](../user-guide/installing.md). -1. **Install fz framework**: `pip install git+https://github.com/Funz/fz.git` -2. **Clone plugin**: `git clone https://github.com/Funz/fz-.git` -3. **Install simulation code**: Follow plugin's README for code installation -4. **Run example**: Check plugin's `examples/` directory or README +A clone of a plugin repository also works: fz reads the `.fz/` directory of the current +directory, so running from inside the clone uses its models and aliases. ## Using Plugins diff --git a/docs/plugins/mcnp.md b/docs/plugins/mcnp.md index 8aabf5b..e8a27c0 100644 --- a/docs/plugins/mcnp.md +++ b/docs/plugins/mcnp.md @@ -1,15 +1,52 @@ -# FZ-MCNP Plugin +# FZ-MCNP -Monte Carlo N-Particle Transport Code support for FZ. +Monte Carlo N-Particle Transport Code support. -## Installation +- **Simulation type**: Radiation transport, criticality calculations +- **Use cases**: Shielding, criticality, dose calculations + +## Install ```bash -git clone https://github.com/Funz/fz-mcnp.git -cd fz-mcnp -pip install -e . +pip install funz-fz +fz install model MCNP +``` + +Installs into `./.fz/` (`--global`: `~/.fz/`, see the +[warning on runner paths](../user-guide/installing.md)). **Requirements of the code +itself**: Set `MCNP_PATH` environment variable. + +## Models + +| Model alias | Variables | Formulas | Comment | Outputs | +|-------------|-----------|----------|---------|---------| +| `MCNP` | `%(x)` | `@(...)` | `C ` | `mean_keff`, `sigma_keff` | + +## Calculator alias + +`localhost_MCNP` (`sh://`) maps each model to its runner script: + +| Model | Command | +|-------|---------| +| `MCNP` | `bash .fz/calculators/MCNP.sh` | + +## Use + +```python +import fz + +results = fz.fzr( + "my_input", # template using the syntax above + {"param": [1.0, 2.0, 3.0]}, + "MCNP", + calculators=["localhost_MCNP"] * 2, # 2 cases at a time; omit it to run one by one + results_dir="results_mcnp", +) ``` -## Repository +Check the variables of a template first: `fzi my_input --model MCNP --format json`. + +## Links -[Funz/fz-mcnp](https://github.com/Funz/fz-mcnp) +- Repository and README: [Funz/fz-MCNP](https://github.com/Funz/fz-MCNP) +- [Plugins overview](index.md) ยท [Installing Models & Algorithms](../user-guide/installing.md) diff --git a/docs/plugins/moret.md b/docs/plugins/moret.md index 21349f8..5a2cc72 100644 --- a/docs/plugins/moret.md +++ b/docs/plugins/moret.md @@ -1,15 +1,52 @@ -# FZ-Moret Plugin +# FZ-Moret -Moret model plugin for FZ. +MORET Monte Carlo criticality safety calculations. -## Installation +- **Simulation type**: Reactor physics criticality +- **Use cases**: Criticality safety, parametric reactor studies + +## Install ```bash -git clone https://github.com/Funz/fz-moret.git -cd fz-moret -pip install -e . +pip install funz-fz +fz install model Moret +``` + +Installs into `./.fz/` (`--global`: `~/.fz/`, see the +[warning on runner paths](../user-guide/installing.md)). **Requirements of the code +itself**: MORET at `/opt/MORET/scripts/moret.py`. + +## Models + +| Model alias | Variables | Formulas | Comment | Outputs | +|-------------|-----------|----------|---------|---------| +| `Moret` | `${x}` | `@{...}` | `*` | `mean_keff`, `sigma_keff`, `dkeff_pertu`, `sigma_dkeff_pertu` | + +## Calculator alias + +`localhost_Moret` (`sh://`) maps each model to its runner script: + +| Model | Command | +|-------|---------| +| `Moret` | `bash .fz/calculators/Moret.sh` | + +## Use + +```python +import fz + +results = fz.fzr( + "my_input", # template using the syntax above + {"param": [1.0, 2.0, 3.0]}, + "Moret", + calculators=["localhost_Moret"] * 2, # 2 cases at a time; omit it to run one by one + results_dir="results_moret", +) ``` -## Repository +Check the variables of a template first: `fzi my_input --model Moret --format json`. + +## Links -[Funz/fz-moret](https://github.com/Funz/fz-moret) +- Repository and README: [Funz/fz-Moret](https://github.com/Funz/fz-Moret) +- [Plugins overview](index.md) ยท [Installing Models & Algorithms](../user-guide/installing.md) diff --git a/docs/plugins/scale.md b/docs/plugins/scale.md index b0e28f0..4994c31 100644 --- a/docs/plugins/scale.md +++ b/docs/plugins/scale.md @@ -1,15 +1,58 @@ -# FZ-Scale Plugin +# FZ-Scale -SCALE nuclear analysis code system support for FZ. +SCALE nuclear analysis code system. -## Installation +- **Simulation type**: Nuclear criticality, shielding, isotopic analysis, sensitivity +- **Use cases**: Reactor physics, fuel cycle, depletion, sensitivity analysis + +## Install ```bash -git clone https://github.com/Funz/fz-scale.git -cd fz-scale -pip install -e . +pip install funz-fz +fz install model Scale +``` + +Installs into `./.fz/` (`--global`: `~/.fz/`, see the +[warning on runner paths](../user-guide/installing.md)). **Requirements of the code +itself**: SCALE 6.2+ at `/SCALE/scale6.2` or set `SCALE_HOME`. + +## Models + +| Model alias | Variables | Formulas | Comment | Outputs | +|-------------|-----------|----------|---------|---------| +| `Scale-keno` | `&(x)` | `@{...}` | `'` | `mean_keff`, `sigma_keff`, `mean_E_lethargy`, `sigma_E_lethargy`, `mean_nubar`, `sigma_nubar`, `mean_free_path`, `sigma_free_path` | +| `Scale-shift` | `&(x)` | `@{...}` | `'` | `mean_keff`, `sigma_keff`, `mean_E_lethargy`, `sigma_E_lethargy`, `mean_nubar`, `sigma_nubar`, `mean_free_path`, `sigma_free_path` | +| `Scale-tsunami` | `&(x)` | `@{...}` | `'` | `mean_keff`, `sigma_keff`, `mean_E_lethargy`, `sigma_E_lethargy`, `mean_nubar`, `sigma_nubar`, `mean_free_path`, `sigma_free_path`, `mean_sens_h1_total`, `sigma_sens_h1_total`, `mean_sens_h1_scatter`, `sigma_sens_h1_scatter`, `mean_sens_h1_capture`, `sigma_sens_h1_capture`, `mean_sens_u235_total`, `sigma_sens_u235_total`, `mean_sens_u235_fission`, `sigma_sens_u235_fission`, `mean_sens_u238_total`, `sigma_sens_u238_total` | +| `Scale-xsdrnpm` | `&(x)` | `@{...}` | `'` | `lambda` | + +## Calculator alias + +`localhost_Scale` (`sh://`) maps each model to its runner script: + +| Model | Command | +|-------|---------| +| `Scale-keno` | `bash .fz/calculators/Scale-keno.sh` | +| `Scale-shift` | `bash .fz/calculators/Scale-shift.sh` | +| `Scale-tsunami` | `bash .fz/calculators/Scale-tsunami.sh` | +| `Scale-xsdrnpm` | `bash .fz/calculators/Scale-xsdrnpm.sh` | + +## Use + +```python +import fz + +results = fz.fzr( + "my_input", # template using the syntax above + {"param": [1.0, 2.0, 3.0]}, + "Scale-keno", + calculators=["localhost_Scale"] * 2, # 2 cases at a time; omit it to run one by one + results_dir="results_scale", +) ``` -## Repository +Check the variables of a template first: `fzi my_input --model Scale-keno --format json`. + +## Links -[Funz/fz-scale](https://github.com/Funz/fz-scale) +- Repository and README: [Funz/fz-Scale](https://github.com/Funz/fz-Scale) +- [Plugins overview](index.md) ยท [Installing Models & Algorithms](../user-guide/installing.md) diff --git a/docs/plugins/telemac.md b/docs/plugins/telemac.md index 9546fc2..05a559a 100644 --- a/docs/plugins/telemac.md +++ b/docs/plugins/telemac.md @@ -1,15 +1,52 @@ -# FZ-Telemac Plugin +# FZ-Telemac -TELEMAC-MASCARET hydrodynamics suite support for FZ. +TELEMAC-MASCARET hydrodynamics suite. -## Installation +- **Simulation type**: Free surface flow, sediment transport +- **Use cases**: River flow, coastal modeling, dam breaks, flood analysis + +## Install ```bash -git clone https://github.com/Funz/fz-telemac.git -cd fz-telemac -pip install -e . +pip install funz-fz +fz install model Telemac +``` + +Installs into `./.fz/` (`--global`: `~/.fz/`, see the +[warning on runner paths](../user-guide/installing.md)). **Requirements of the code +itself**: `pip install PyTelTools` + Telemac (or Docker). + +## Models + +| Model alias | Variables | Formulas | Comment | Outputs | +|-------------|-----------|----------|---------|---------| +| `Telemac` | `$(x)` | `@(...)` | `/` | `S`, `H` | + +## Calculator alias + +`localhost_Telemac` (`sh://`) maps each model to its runner script: + +| Model | Command | +|-------|---------| +| `Telemac` | `bash .fz/calculators/Telemac.sh` | + +## Use + +```python +import fz + +results = fz.fzr( + "my_input", # template using the syntax above + {"param": [1.0, 2.0, 3.0]}, + "Telemac", + calculators=["localhost_Telemac"] * 2, # 2 cases at a time; omit it to run one by one + results_dir="results_telemac", +) ``` -## Repository +Check the variables of a template first: `fzi my_input --model Telemac --format json`. + +## Links -[Funz/fz-telemac](https://github.com/Funz/fz-telemac) +- Repository and README: [Funz/fz-Telemac](https://github.com/Funz/fz-Telemac) +- [Plugins overview](index.md) ยท [Installing Models & Algorithms](../user-guide/installing.md) diff --git a/docs/reference/api.md b/docs/reference/api.md index 5c4375b..f1cf127 100644 --- a/docs/reference/api.md +++ b/docs/reference/api.md @@ -1,46 +1,61 @@ -# API Reference - -`import fz` exposes six functions. Each has a dedicated guide with full parameters and -examples โ€” this page is the at-a-glance summary. - -## Functions - -| Function | Signature (essentials) | Returns | -|----------|------------------------|---------| -| [`fzi`](../user-guide/core-functions/fzi.md) | `fzi(input_path, model, input_static=None)` | `dict` โ€” variable names โ†’ `None` | -| [`fzc`](../user-guide/core-functions/fzc.md) | `fzc(input_path, input_variables, model, output_dir, input_static=None)` | `None` (writes compiled files) | -| [`fzo`](../user-guide/core-functions/fzo.md) | `fzo(output_dir, model)` | `DataFrame` โ€” one row per case | -| [`fzr`](../user-guide/core-functions/fzr.md) | `fzr(input_path, input_variables, model, calculators, results_dir="results", *, input_static=None, case_naming="path", timeout=None, callbacks=None)` | `DataFrame` โ€” inputs + outputs + `status`/`calculator`/`error`/`command` | -| [`fzd`](../user-guide/core-functions/fzd.md) | `fzd(input_path, input_variables, model, output_expression, algorithm, calculators=None, algorithm_options=None, analysis_dir="analysis", *, input_static=None)` | `dict` โ€” `XY`, `analysis`, `iterations`, `total_evaluations`, `summary` | -| [`fzl`](../user-guide/core-functions/fzl.md) | `fzl(models="*", calculators="*", check=False)` | `dict` โ€” `{"models": ..., "calculators": ...}` | - -## Common Arguments - -| Argument | Accepted values | -|----------|-----------------| -| `model` | `dict` definition, or a `str` alias resolved from `.fz/models/` | -| `calculators` | a URI `str`, an alias `str`, or a `list` of them โ€” `sh://`, `ssh://`, `slurm://`, `funz://`, `cache://` | -| `input_variables` | `{"x": [1, 2, 3]}` (list โ†’ varied), `{"x": 5}` (fixed); for `fzd`, `{"x": "[min;max]"}` ranges | -| `input_static` | `list` of paths identical across every case โ€” never templated or re-hashed per case *(1.2)* | -| `output_expression` | (`fzd`) a `str` expression, or a `list` of expressions for a vector objective *(1.2)* | - -## Config Helper +# Python API ```python -from fz import get_config -config = get_config() # reads FZ_* environment variables -config.max_workers = 4 # override for the current process +import fz ``` -## CLI - -Every function has a matching command โ€” `fzi`, `fzc`, `fzo`, `fzr`, `fzd`, `fzl` โ€” plus -`fz install` / `fz uninstall` for plugins. Results go to **stdout**, logs and the -progress bar to **stderr**. See [Configuration](configuration.md#argument-formats-cli) -for the accepted argument formats. - -## See Also - -- [Configuration](configuration.md) ยท [Environment Variables](environment.md) -- [Model Definition](../user-guide/model-definition.md) ยท [Calculators Overview](../user-guide/calculators/overview.md) -- Deep dives in the [main FZ repository docs](https://github.com/Funz/fz/tree/main/doc) +## Core functions + +| Function | Signature | Returns | +|----------|-----------|---------| +| [`fzi`](../user-guide/core-functions/fzi.md) | `fzi(input_path, model, input_static=None)` | `dict` of variables / static objects / formulas | +| [`fzc`](../user-guide/core-functions/fzc.md) | `fzc(input_path, input_variables=None, model=None, output_dir="output", input_static=None)` | `None` (writes files) | +| [`fzo`](../user-guide/core-functions/fzo.md) | `fzo(output_path, model)` | `DataFrame`, one row per directory | +| [`fzr`](../user-guide/core-functions/fzr.md) | `fzr(input_path, input_variables=None, model=None, results_dir="results", calculators=None, callbacks=None, timeout=None, case_naming=None, input_static=None)` | `DataFrame`, one row per case | +| [`fzd`](../user-guide/core-functions/fzd.md) | `fzd(input_path, input_variables, model, output_expression, algorithm, calculators=None, algorithm_options=None, analysis_dir="analysis", input_static=None)` | `dict` with `XY`, `analysis`, `algorithm`, `iterations`, `total_evaluations`, `summary` | +| [`fzl`](../user-guide/core-functions/fzl.md) | `fzl(models="*", calculators="*", check=False)` | `dict` with `models`, `calculators` | + +!!! danger "Keyword arguments" + In `fzr`, `results_dir` precedes `calculators`. Always write `calculators=...` and + `results_dir=...`. + +Invalid argument types raise `TypeError`; invalid values (unknown `case_naming`, bad +`delim`, unknown callback name, duplicate DataFrame rows, missing `input_variables` for a +template with variables) raise `ValueError`; a missing `input_path` raises +`FileNotFoundError`. + +## Installation of models and algorithms + +| Function | Role | +|----------|------| +| `install_model(source, global_install=False)` | Install from name, URL or zip; returns names and paths | +| `install(model, global_install=False)` | Same as `install_model` | +| `uninstall_model(model_name, global_uninstall=False)` | Remove | +| `list_installed_models(global_list=False)` | Installed models | +| `install_algorithm(source, global_install=False)` | Install an algorithm | +| `uninstall_algorithm(algorithm_name, global_uninstall=False)` | Remove | +| `list_installed_algorithms(global_list=False)` | Installed algorithms | +| `list_models(global_list=False)` | Model aliases | + +## Configuration and logging + +| Function | Role | +|----------|------| +| `get_config()` | Current configuration object (`max_workers`, `max_retries`, `run_timeout`, `case_naming`, ...), modifiable at runtime | +| `reload_config()` | Re-read the `FZ_*` environment variables (they are otherwise read once, at import) | +| `print_config()` | Print the effective configuration | +| `set_log_level(level)` / `get_log_level()` | `"QUIET"`, `"ERROR"`, `"WARNING"`, `"INFO"`, `"DEBUG"` | +| `set_interpreter(name)` / `get_interpreter()` | Default formula interpreter (`"python"` or `"R"`) | + +## Other + +| Name | Role | +|------|------| +| `discover_funz_servers(udp_port, listen_duration=10.0, stop_when=None)` | List Java Funz calculators broadcasting on a UDP port | +| `FunctionModelParallelError` | Raised by `fzd` when a function model fails while evaluated in parallel | + +Everything else (`fz.helpers`, `fz.runners`, `fz.io`, ...) is internal and may change. + +## See also + +[CLI Reference](cli.md) ยท [Environment Variables](environment.md) diff --git a/docs/reference/cli.md b/docs/reference/cli.md new file mode 100644 index 0000000..0703d96 --- /dev/null +++ b/docs/reference/cli.md @@ -0,0 +1,80 @@ +# CLI Reference + +Every core function has a command, available standalone or as a subcommand of `fz`: + +| Standalone | Subcommand | Function | +|------------|------------|----------| +| `fzi` | `fz input` | [fzi](../user-guide/core-functions/fzi.md) | +| `fzc` | `fz compile` | [fzc](../user-guide/core-functions/fzc.md) | +| `fzo` | `fz output` | [fzo](../user-guide/core-functions/fzo.md) | +| `fzr` | `fz run` | [fzr](../user-guide/core-functions/fzr.md) | +| `fzd` | `fz design` | [fzd](../user-guide/core-functions/fzd.md) | +| `fzl` | `fz list` | [fzl](../user-guide/core-functions/fzl.md) | +| โ€” | `fz install model\|algorithm [--global]` | [Installing](../user-guide/installing.md) | +| โ€” | `fz uninstall model\|algorithm [--global]` | [Installing](../user-guide/installing.md) | +| `fz-mcp` | โ€” | [MCP server](../user-guide/ai-agents.md#mcp-server-fz-mcp) | + +All commands accept `--help` and `--version`. + +## Options + +```text +fzi [input_path] [-i PATH] [-m MODEL] [MODEL FIELDS] [--input_static F] [-f FORMAT] +fzc [input_path] [-i PATH] [-m MODEL] [MODEL FIELDS] [-v VARS] [--input_static F] [-o DIR] +fzo [output_path] [-o PATH] [-m MODEL] [MODEL FIELDS] [-f FORMAT] +fzr [input_path] [-i PATH] [-m MODEL] [MODEL FIELDS] [-v VARS] [-r DIR] + [--case_naming {path,hash,index}] [-c CALC]... [--input_static F]... [-f FORMAT] +fzd -i PATH -v VARS -m MODEL -e EXPR -a ALGO [-r DIR] [-c CALC] [-o OPTIONS] [--input_static F] +fzl [-m PATTERN] [-c PATTERN] [--check] [-f {json,markdown,table}] +``` + +| Option | Aliases | Commands | Value | +|--------|---------|----------|-------| +| `input_path` (positional) | `--input_path`, `-i` | fzi, fzc, fzr | File or directory | +| `output_path` (positional) | `--output_path`, `-o` | fzo | Directory or quoted glob | +| `--model` | `-m` | all but fzl | Alias, `.json` file, or inline JSON | +| `--input_variables` | `--variables`, `-v` | fzc, fzr | JSON dict (inline or file) or `a=1,b=[1,2]` | +| `--output_dir` | `--output`, `-o` | fzc | Default `output` | +| `--results_dir` | `--results`, `-r` | fzr | Default `results` | +| `--calculators` | `--calculator`, `-c` | fzr, fzd | URI, alias, JSON file or JSON list; repeatable (fzr) | +| `--case_naming` | | fzr | `path` (default), `hash`, `index` | +| `--input_static` | | fzi, fzc, fzr, fzd | Path or JSON list; repeatable | +| `--format` | `-f` | fzi, fzo, fzr | `json`, `csv`, `html`, `markdown` (default), `table` | +| `--format` | `-f` | fzl | `json`, `markdown` (default), `table` | +| `--input_dir` | `--input_path`, `-i` | fzd | Required | +| `--input_vars` | `--input_variables`, `--variables`, `-v` | fzd | JSON with `"[min;max]"` strings, or `x=[0;1],y=2` | +| `--output_expression` | `-e` | fzd | One expression | +| `--algorithm` | `-a` | fzd | Name or path | +| `--options` | `-o` | fzd | JSON (inline or file) | +| `--results_dir` | `-r` | fzd | Default `results_fzd` | + +**Model fields** (fzi, fzc, fzo, fzr) define or override the model inline: +`--varprefix`, `--formulaprefix`, `--delim`, `--commentline`, `--interpreter`, and +repeatable `--output-cmd NAME=COMMAND`. Without `--model`, these start from +`{"varprefix": "$", "formulaprefix": "@", "delim": "{}", "commentline": "#"}`. + +## Conventions + +- Results go to **stdout**; logs (`FZ_LOG_LEVEL`), progress and errors to **stderr**. + Use `--format json` for machine-readable output. +- Exit status is non-zero on error; `fzr` exits 1 when no case ends `done`. +- `--model`, `--input_variables`, `--options` accept inline JSON or a JSON file; + `--model` and `--calculators` also accept an alias name from `.fz/`. + +## Differences with the Python API + +| Feature | Python | CLI | +|---------|--------|-----| +| Explicit list of cases (DataFrame) | yes | no (dict only, full factorial) | +| Multi-objective `fzd` (list of expressions) | yes | no | +| Function model for `fzd` | yes | no | +| Callbacks | yes | no | +| `timeout` per call | `timeout=` | no option: model `timeout` or `FZ_RUN_TIMEOUT` | +| `fzd` default directory | `analysis` | `results_fzd` | +| `fzd` output format | dict | printed summary, no `--format` | +| Model without `delim` | variables use `()` | `{}` when `--model` is absent; `()` when `--model` gives a model without `delim` | + +## Shell completion + +Completion scripts for bash and zsh are in the fz repository under +[`completions/`](https://github.com/Funz/fz/tree/main/completions). diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index a1c59fe..9f1e99a 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -1,105 +1,67 @@ -# Configuration - -FZ is configured through **environment variables**, **`.fz/` alias directories**, and -per-call arguments. Nothing needs to be configured to get started โ€” every setting has a -default. - -## Environment Variables - -The full list, with defaults, is on the [Environment Variables](environment.md) page. -The most common ones: - -| Variable | Purpose | Default | -|----------|---------|---------| -| `FZ_LOG_LEVEL` | `DEBUG` / `INFO` / `WARNING` / `ERROR` / `CRITICAL` | `ERROR` | -| `FZ_MAX_WORKERS` | Thread-pool size for parallel cases | CPU count | -| `FZ_MAX_RETRIES` | Calculator retry attempts per case | `5` | -| `FZ_RUN_TIMEOUT` | Per-case timeout, seconds (`0` disables) | `3600` | -| `FZ_INTERPRETER` | Default formula interpreter (`python` / `R`) | `python` | -| `FZ_SHELL_PATH` | Extra search path for shell binaries | system `PATH` | -| `FZ_CASE_NAMING` | Case dir naming (`path` / `hash` / `index`) | `path` | -| `FZ_CACHE_DIR` | Cache location | `.fz/cache` | - -```python -from fz import get_config - -config = get_config() -print(config.max_workers, config.max_retries) -config.max_workers = 4 # override at runtime -``` +# .fz Directory & Aliases -## `.fz/` Directory +fz looks for named models, calculators and algorithms in two `.fz/` directories: -FZ looks for aliases in `./.fz/` (project) then `~/.fz/` (user); project wins. +1. `./.fz/` โ€” the directory from which fz is run (project); +2. `~/.fz/` โ€” the user's home (global, `--global` for `fz install`). -``` +The project directory wins when both contain the same name. + +```text .fz/ -โ”œโ”€โ”€ models/ # model aliases โ†’ fz.fzr(..., model="perfectgas") -โ”‚ โ””โ”€โ”€ perfectgas.json -โ”œโ”€โ”€ calculators/ # calculator aliases โ†’ fz.fzr(..., calculators="cluster") -โ”‚ โ””โ”€โ”€ cluster.json -โ”œโ”€โ”€ algorithms/ # fzd algorithm plugins -โ”‚ โ”œโ”€โ”€ brent.py -โ”‚ โ””โ”€โ”€ pso.R -โ””โ”€โ”€ tmp/ # per-run scratch dirs (auto-created) +โ”œโ”€โ”€ models/ # .json -> model="name" +โ”œโ”€โ”€ calculators/ # .json (+ scripts) -> calculators="name" +โ”œโ”€โ”€ algorithms/ # .py / .R -> algorithm="name" +โ””โ”€โ”€ tmp/ # per-run temporary directories (created by fz) ``` -### Model Alias +## Model alias ```json title=".fz/models/perfectgas.json" { - "varprefix": "$", - "formulaprefix": "@", + "id": "perfectgas", "delim": "{}", - "commentline": "#", - "interpreter": "python", - "output": { - "pressure": "python://grep(r'P = (\\S+)', 'output.txt')" - } + "output": {"pressure": "python://grep(r'pressure = (\\S+)', 'output.txt')"} } ``` -### Calculator Alias +All fields: [Model Definition](../user-guide/models/definition.md). The file name is +the alias; `id` must be set for calculator aliases to match the model. + +## Calculator alias ```json title=".fz/calculators/cluster.json" { - "uri": "ssh://user@hpc.university.edu", - "models": { - "perfectgas": "bash /home/user/codes/perfectgas/run.sh", - "cfd": "bash /home/user/codes/cfd/run.sh" - } + "uri": "ssh://user@hpc.example.edu", + "models": {"perfectgas": "bash /home/user/codes/perfectgas/run.sh"}, + "code_id": "perfectgas@2.1" } ``` -```python -fz.fzr("input.txt", variables, "perfectgas", calculators="cluster") -# resolves to: ssh://user@hpc.university.edu/bash /home/user/codes/perfectgas/run.sh -``` +Keys: `uri`, `models` (model `id` โ†’ command), `code_id`, `version_cmd`. See +[Calculators](../user-guide/calculators/overview.md#aliases). When `calculators` is +omitted, every alias supporting the model's `id` is used. -Inspect and validate what is installed with [`fzl`](../user-guide/core-functions/fzl.md): +## Algorithms -```bash -fzl --models "*" --calculators "*" --check --format markdown -``` - -## Timeout Resolution Order - -Highest priority first: +`algorithm="brent"` loads `.fz/algorithms/brent.py` (or `.R`), then +`~/.fz/algorithms/`. Globs are accepted. See +[Writing Algorithms](../user-guide/design/algorithms.md). -1. `timeout=` argument to `fzr()` / `fzc()` (or `--timeout` on the CLI) -2. Model field `model["timeout"]` (int seconds; `None`/`0` disables) -3. `FZ_RUN_TIMEOUT` (default `3600`) +## `.fz/tmp` -## Argument Formats (CLI) +Each run creates temporary case directories under `./.fz/tmp/fz_temp_*` (and, for +`ssh://`, under `.fz/tmp/fz_calc_*` in the remote home). Their content is removed after +the run, but empty `fz_temp_*` directories remain and accumulate; `.fz/tmp/` can be +deleted when no run is active. Remote cleanup is restricted to fz's own directories. -`--model`, `--calculator`, and `--variables` each accept, tried in this order: +## Inspecting -1. **Alias** โ€” `--model perfectgas` (from `.fz/models/`) -2. **JSON file** โ€” `--model model.json` -3. **Inline JSON** โ€” `--model '{"varprefix": "$"}'` +```bash +fz list --format json # models and calculators (not algorithms) +ls .fz/algorithms ~/.fz/algorithms +``` -## See Also +## See also -- [Environment Variables](environment.md) โ€” every variable in detail -- [Model Definition](../user-guide/model-definition.md) -- [Calculators Overview](../user-guide/calculators/overview.md) +[Environment Variables](environment.md) ยท [Installing Models & Algorithms](../user-guide/installing.md) diff --git a/docs/reference/environment.md b/docs/reference/environment.md index 39310f5..0f0538a 100644 --- a/docs/reference/environment.md +++ b/docs/reference/environment.md @@ -1,225 +1,87 @@ # Environment Variables -FZ can be configured using several environment variables to customize its behavior. +The variables of the General, Execution, Cache and SSH tables are read **once, when +`fz` is imported**: set them before starting Python, or call `fz.reload_config()` after +changing `os.environ`. `fz.print_config()` shows the effective values. SLURM array +variables are read when the first `slurm-array://` case is submitted, MCP variables when +`fz-mcp` starts. -## Core Configuration +## General -### `FZ_LOG_LEVEL` -**Description**: Controls the verbosity of FZ logging output. +| Variable | Default | Meaning | +|----------|---------|---------| +| `FZ_LOG_LEVEL` | `ERROR` | `QUIET`, `ERROR`, `WARNING`, `INFO`, `DEBUG`. Logs go to stderr | +| `FZ_INTERPRETER` | `python` | Default formula interpreter (`python` or `R`); a model's `interpreter` wins | +| `FZ_SHELL_PATH` | unset | Directories searched first for `bash` and the commands of shell calculators/extractors (`;`-separated on Windows, `:` elsewhere). Needed on Windows (MSYS2/Git Bash) | -**Values**: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` +## Execution -**Default**: `ERROR` +| Variable | Default | Meaning | +|----------|---------|---------| +| `FZ_MAX_WORKERS` | unset | Upper bound on concurrent cases; never above the number of non-cache calculator entries (except `slurm-array://`) | +| `FZ_MAX_RETRIES` | `5` | Calculator failures tolerated per case before `failed` | +| `FZ_RUN_TIMEOUT` | `3600` for `sh://`/`funz://`, none for `ssh://`/`slurm://` | Per-case timeout in seconds; when set, applies to all calculators. **`0` is not "unlimited"**: cases time out immediately ([Timeouts](../user-guide/running/timeouts.md)) | +| `FZ_CASE_NAMING` | `path` | Case directory naming: `path`, `hash`, `index` (invalid values fall back to `path`) | +| `FZ_STATIC_CANDIDATE_MIN_SIZE` | `1048576` | Size (bytes) above which a variable-free input file triggers the `input_static` suggestion; `0` disables | +| `FZ_RO_CRATE` | `1` | `0` disables `ro-crate-metadata.json` (`manifest.json` is always written) | -**Example**: -```bash -export FZ_LOG_LEVEL=DEBUG -``` +## Cache -### `FZ_INTERPRETER` -**Description**: Default formula interpreter for evaluating expressions. +| Variable | Default | Meaning | +|----------|---------|---------| +| `FZ_CACHE_STRICT` | `0` | `1` refuses cache matches whose code identity cannot be verified | +| `FZ_CACHE_ACCEPT_LEGACY` | `0` | `1` lets `cache://` use caches written in the old MD5 format | -**Values**: `python`, `R` +## SSH -**Default**: `python` +| Variable | Default | Meaning | +|----------|---------|---------| +| `FZ_SSH_KEEPALIVE` | `300` | Keepalive interval (s) | +| `FZ_SSH_AUTO_ACCEPT_HOSTKEYS` | `0` | `1` accepts unknown host keys without prompting (the prompt only occurs with a password in the URI; key authentication already adds unknown hosts) | +| `SSH_USER` | local user | User name when the URI has no `user@` (read at connection time) | -**Example**: -```bash -export FZ_INTERPRETER=R -``` +## SLURM job arrays -## Execution Configuration +| Variable | Default | Meaning | +|----------|---------|---------| +| `FZ_SLURM_ARRAY_WINDOW` | `1` | Seconds during which cases are gathered into one `sbatch --array` | +| `FZ_SLURM_POLL_INTERVAL` | `2` | Seconds between `sacct`/`squeue` polls | -### `FZ_RUN_TIMEOUT` (renamed in 1.2, default raised to 1 h) +## MCP server -**Description**: Default timeout in seconds for a single case run. A model can override -it with its own `"timeout"` entry (`None`/`null`/`0` disables the timeout for that -model); an explicit `timeout=` argument to `fzr()` / `fzc()` overrides both. +| Variable | Default | Meaning | +|----------|---------|---------| +| `FZ_MCP_ROOT` | working directory | Root to which all file paths are confined | +| `FZ_MCP_TRUSTED` | `1` | `0`: models/calculators must be installed aliases | +| `FZ_MCP_TRANSPORT` | `stdio` | `sse` or `streamable-http` (requires the next variable) | +| `FZ_MCP_ALLOW_NETWORK_TRANSPORT` | `0` | `1` allows a network transport | -**Values**: Positive integer (seconds), or `0` to disable +## Examples -**Default**: `3600` (1 hour โ€” was `600` before 1.2) +=== "Linux / macOS" -**Example**: -```bash -export FZ_RUN_TIMEOUT=300 # 5 minutes -``` + ```bash + export FZ_LOG_LEVEL=INFO FZ_MAX_WORKERS=8 FZ_RUN_TIMEOUT=7200 + python run_study.py + ``` -!!! note - Earlier releases named this variable `FZ_EXECUTION_TIMEOUT`. Use `FZ_RUN_TIMEOUT`. +=== "Windows (PowerShell)" -### `FZ_MAX_RETRIES` -**Description**: Maximum number of retry attempts when a calculator fails. + ```powershell + $env:FZ_SHELL_PATH = "C:\msys64\usr\bin;C:\msys64\mingw64\bin" + python run_study.py + ``` -**Values**: Non-negative integer +=== "Python" -**Default**: `5` + ```python + import os, fz + os.environ["FZ_MAX_RETRIES"] = "3" + fz.reload_config() + fz.set_log_level("DEBUG") # or directly, without environment variables + fz.get_config().max_workers = 4 + ``` -**Example**: -```bash -export FZ_MAX_RETRIES=3 -``` - -### `FZ_MAX_WORKERS` -**Description**: Maximum number of parallel workers for concurrent execution. - -**Values**: Positive integer - -**Default**: Number of CPU cores - -**Example**: -```bash -export FZ_MAX_WORKERS=8 -``` - -## Shell Configuration - -### `FZ_SHELL_PATH` (New in 0.9.1) -**Description**: Custom search path for shell commands and executables. Overrides system PATH for binary resolution. Essential for Windows users with MSYS2, Git Bash, or custom tool locations. - -**Format**: -- Windows: Semicolon-separated paths -- Unix/Linux: Colon-separated paths - -**Default**: System PATH - -**Example**: -```bash -# Windows -SET FZ_SHELL_PATH=C:\msys64\usr\bin;C:\msys64\mingw64\bin;C:\Python39 - -# Linux/macOS -export FZ_SHELL_PATH=/opt/tools/bin:/usr/local/bin -``` - -**Features**: -- Automatic `.exe` extension handling on Windows -- Binary path caching for performance -- Overrides system PATH priority - -## Case Layout Configuration - -### `FZ_CASE_NAMING` (New in 1.2) -**Description**: How each case's result/temp subdirectory is named. `"path"` produces -`var1=val1,var2=val2,...` (human-readable, but can exceed the ~255-char filename limit -with many variables); `"hash"` uses a short content hash of the variable combination; -`"index"` uses `case_`. With `"hash"` / `"index"` a `cases.csv` manifest mapping each -directory to its variables is written at the results root. Overridden by the -`case_naming=` argument / `--case_naming` flag. - -**Values**: `path`, `hash`, `index` - -**Default**: `path` - -**Example**: -```bash -export FZ_CASE_NAMING=hash -``` - -### `FZ_STATIC_CANDIDATE_MIN_SIZE` (New in 1.2) -**Description**: Size threshold in bytes above which an `input_path` file with no -variables triggers a one-time warning suggesting it be passed via `input_static` -instead (it is otherwise re-read, re-copied, and re-hashed on every case). Set to `0` -to disable the warning. - -**Values**: Non-negative integer (bytes) - -**Default**: `1048576` (1 MiB) - -**Example**: -```bash -export FZ_STATIC_CANDIDATE_MIN_SIZE=0 -``` - -## SSH Configuration - -### `FZ_SSH_KEEPALIVE` -**Description**: Interval in seconds for SSH keepalive packets to prevent connection timeout. - -**Values**: Positive integer (seconds) - -**Default**: `300` - -**Example**: -```bash -export FZ_SSH_KEEPALIVE=30 -``` - -## Cache Configuration - -### `FZ_CACHE_DIR` -**Description**: Directory for storing cached results. - -**Values**: Valid directory path - -**Default**: `.fz/cache` in working directory - -**Example**: -```bash -export FZ_CACHE_DIR=/tmp/fz_cache -``` - -## Discovery Configuration - -### `FZ_UDP_DISCOVERY_PORT` -**Description**: UDP port for Funz calculator auto-discovery. - -**Values**: Valid port number - -**Default**: `21001` - -**Example**: -```bash -export FZ_UDP_DISCOVERY_PORT=21001 -``` - -## Configuration Files - -### Model and Calculator Aliases - -FZ looks for configuration files in: - -- **Models**: `~/.fz/models/` and `./.fz/models/` -- **Calculators**: `~/.fz/calculators/` and `./.fz/calculators/` - -Configuration files use JSON format: - -**Model Example** (`~/.fz/models/perfectgas.json`): -```json -{ - "varprefix": "$", - "interpreter": "python", - "output": { - "pressure": "grep 'P =' output.txt | awk '{print $3}'" - } -} -``` - -**Calculator Example** (`~/.fz/calculators/compute.json`): -```json -{ - "uri": "ssh://user@cluster.example.edu/bash", - "models": ["perfectgas", "simulation"] -} -``` - -## Platform-Specific Notes - -### Windows - -- Use `SET` instead of `export` for environment variables -- Path separators are semicolons (`;`) in `FZ_SHELL_PATH` -- Consider setting `FZ_SHELL_PATH` for Git Bash or MSYS2 tools - -### Linux/macOS - -- Use `export` for environment variables -- Path separators are colons (`:`) in `FZ_SHELL_PATH` -- Environment variables can be set in `~/.bashrc` or `~/.zshrc` - -## See Also - -- [Configuration Guide](configuration.md) - Model and calculator configuration -- [Shell Calculator](../user-guide/calculators/shell.md) - Shell execution details -- [Troubleshooting](troubleshooting.md) - Common issues and solutions +## See also +[.fz Directory & Aliases](configuration.md) ยท [Constraints & Limits](limitations.md) diff --git a/docs/reference/limitations.md b/docs/reference/limitations.md new file mode 100644 index 0000000..663ec29 --- /dev/null +++ b/docs/reference/limitations.md @@ -0,0 +1,186 @@ +# Constraints & Limits + +This page lists the behaviors of fz that most often surprise users. Each item states the +rule, the consequence, and what to do instead. Items were checked against the code of fz (`fz/core.py`, +`fz/helpers.py`, `fz/runners/`, `fz/config.py`, `fz/cli.py`) and by running it. Source: +[`doc/limitations.md`](https://github.com/Funz/fz/blob/main/doc/limitations.md). + +## Platform and dependencies + +- **Python โ‰ฅ 3.9.** 3.9โ€“3.13 are tested in CI; 3.14 is exercised as a pre-release only. +- **bash is required everywhere.** Calculator commands and shell output parsers run + through bash. On Windows, install MSYS2 or Git Bash and point `FZ_SHELL_PATH` at its + `bin` directories (see [Environment Variables](environment.md#general)). Shell-free output parsers + (`python://`) remove the need for grep/awk, but not for bash in `sh://` calculators. +- **Required Python dependencies:** `paramiko`, `pandas`, `charset-normalizer`. + Optional: `rpy2` (R formulas, extra `[r]`), `mcp` (the `fz-mcp` server, extra `[mcp]`, + **Python โ‰ฅ 3.10 only**), `h5py` (the `hdf5_file()` output helper), `jq`, `yq` + (mikefarah), `xmllint` (for `jq://`, `yq://`, `xpath://` outputs). +- **R formulas:** `import rpy2` can succeed while `import rpy2.robjects` fails + (`ffi.error`, R/rpy2 version mismatch). fz then reports R as unavailable. + +## Templates and models + +- **Default delimiters differ for variables and formulas.** A model without `delim` + (nor `var_delim`) delimits variables with `()` and formulas with `{}`: `${x}` is then + not a variable. The CLI without `--model` uses `delim: "{}"`. Set `delim` explicitly. +- **No automatic `?var` conversion.** `?var` is a variable only with `"varprefix": "?"`. +- A variable absent from `input_variables` and without `~default` is left as-is in the + compiled file (no error from `fzc`). +- `fzi` returns variables *and* formula expressions (e.g. `'T_celsius + 273.15'`) as keys. + +## Python API + +- **`fzr` argument order is `(input_path, input_variables, model, results_dir, + calculators, ...)`.** `results_dir` comes *before* `calculators`. A call such as + `fz.fzr("input.txt", variables, model, "sh://bash calc.sh")` puts the calculator URI into + `results_dir` (a directory literally named `sh:/bash calc.sh` is created) and runs with + no calculator, so every case fails. **Always pass `calculators=` and `results_dir=` by + keyword.** +- **A DataFrame design must not contain duplicate rows** (`ValueError`): each row is one + case. Variables given but absent from the templates only trigger a warning. +- **`FZ_*` environment variables are read once, at `import fz`.** Setting + `os.environ["FZ_MAX_WORKERS"] = "8"` after the import has no effect until + `fz.reload_config()` is called. Alternatives: set the variable before starting Python, + call `fz.set_log_level("DEBUG")`, or change `fz.get_config().max_workers` directly. +- **`callbacks` is a dict**, not a list: keys `on_start`, `on_case_start`, + `on_case_complete`, `on_progress`, `on_complete`. Any other key raises `ValueError`. + Callbacks run in worker threads; an exception inside a callback is logged and ignored. +- **`fzd` with a Python function as model** (`input_path=None`): `calculators` must be a + positive int (number of concurrent evaluations, default 1). With `calculators > 1` the + function must be thread-safe; any error in parallel mode aborts the whole `fzd` with + `FunctionModelParallelError`. Callables bridged from R (reticulate) must use + `calculators=1`. + +## CLI + +- **CLI and Python differ for `fzd`:** + - `--output_expression` takes a single expression; a *list* of objectives + (multi-objective `fzd`) is only available from Python. + - The CLI default results directory is `results_fzd`; the Python default + `analysis_dir` is `analysis`. + - `fzd` has no `--format` option; function models are Python-only. +- **`fzl` / `fz list` formats** are `json`, `markdown` and `table` only (no `csv`/`html`). +- **Non-factorial designs are Python-only.** `--input_variables` takes a JSON dict + (inline or file) or the short form `'a=1,b=[4,5,6]'`, always crossed as a full + factorial; a list of cases requires a pandas DataFrame from Python. +- **No `--timeout` flag.** Use the model's `"timeout"` entry or `FZ_RUN_TIMEOUT`. +- **`fz list` / `fzl`** shows calculator aliases by their `uri`, not their file name, and + `--check` reports an alias whose command sits in its `models` map + (`{"uri": "sh://", "models": {...}}`, the layout of installed wrappers) as failed + (`Empty sh:// command`) although `fzr` uses it correctly. Algorithms are not listed. +- **`fzr` exits with status 1 when no case succeeds**; data goes to stdout, logs and + progress to stderr. Use `--format json` for machine-readable output. + +## Parallelism and retries + +- **Parallel workers = number of non-cache calculator entries** (capped by the number of + cases). One entry runs cases sequentially; `["sh://bash calc.sh"] * 4` runs four at a + time. `FZ_MAX_WORKERS` only **caps** that number; it never adds workers. Exception: + `slurm-array://` uses one waiting thread per case (capped by `FZ_MAX_WORKERS`) so that + cases can be batched into one job array. +- **Case โ†’ calculator assignment** prefers `case_index mod n_calculators` and falls back + to the first free calculator. +- **`FZ_MAX_RETRIES` (default 5)** is the number of calculator failures tolerated per + case; after that the case is marked `failed`. With several calculators, a failed + attempt moves to another calculator. +- **Case `status` values:** `done`, `failed`, `error`, `timeout`, `interrupted`. A cache + hit is `done` with a `calculator` value starting with `cache://`. + +## Timeouts + +- **Resolution order:** `timeout=` argument of `fzr()` > model `"timeout"` entry > + `FZ_RUN_TIMEOUT`. +- **Default:** 3600 s for `sh://` and `funz://`; **no timeout** for `ssh://` and + `slurm://` unless `FZ_RUN_TIMEOUT` is set explicitly (a warning is logged). +- **Only a model `"timeout"` of `null`/`None` or `0` disables the timeout.** + `FZ_RUN_TIMEOUT=0` or `timeout=0` does *not* disable it: every case times out + immediately. + +## `sh://` command line + +- **The command runs inside a per-case temporary directory**, and the case's input file + names are **appended to the end of the whole command line** (`.` when there are none). + With pipes or redirections, they are appended after the last element. +- **Relative paths in the command are made absolute against the directory `fzr` was + launched from**, not the case directory. Every token that looks like a file name is + rewritten (e.g. `run.sh`, `data/mesh.msh`, `result.txt`; a fixed list of common tools + such as `bash`, `python`, `cat`, `cp`, `grep` is left alone). Consequence: + `sh://cat input.txt > res.txt` reads the *uncompiled* template of the launch directory + and writes `res.txt` outside the case. **Put the work in a script** and launch it with + `sh://bash run.sh`: inside the script, relative paths refer to the case directory and + `$1`, `$2`, ... are the compiled input files. +- `ssh://` and `slurm://` commands run on the remote side: use absolute remote paths. + +## Files and directories + +- **Reserved names in each case directory:** `out.txt` (stdout), `err.txt` (stderr), + `log.txt`, `info.txt`, `history.txt` and `.fz_hash` are written by fz. A simulation + output with one of these names is **overwritten** (e.g. a code writing `out.txt` loses + it to the captured stdout). Name code outputs differently. +- **Reserved names at the results root:** `manifest.json`, `ro-crate-metadata.json` + (disable with `FZ_RO_CRATE=0`) and, with `case_naming="hash"`/`"index"`, `cases.csv`. +- **Case directory names (`case_naming="path"`, default)** are `var1=val1,var2=val2,...`. + The characters `/ \ : * ? " < > | %` and control characters are percent-encoded, and a + value of exactly `.` or `..` is encoded. Names can exceed the ~255-character filename + limit with many variables or long values: use `case_naming="hash"` or `"index"` + (always used internally by `fzd`). +- **`fzc` always writes one sub-directory per case** (`output_dir/var1=val1,.../`) when + the input declares variables, even when all values are scalars. Run the code and + `fzo` inside that sub-directory (or on the glob `output_dir/*`); `fzo output_dir` + itself returns a row of `None`. +- **Global wrapper installs:** `fz install model --global` copies the wrapper to `~/.fz/`, but its calculator + alias keeps the command `bash .fz/calculators/.sh`, which `sh://` resolves against the + **launch directory**: runs from any other directory fail (`Command not found locally: + '/.fz/calculators/.sh'`). Prefer project-local installs, or edit + `~/.fz/calculators/localhost_.json` to use the absolute path of the script (`~` is + not expanded). +- **Existing results directories are not overwritten in place**: an existing + `results_dir` is renamed with a timestamp suffix before the new run (and can be reused + via `cache://`). +- **`input_static` absolute paths** must exist at the same path on the calculator side + (shared storage); fz never copies them. Relative paths are symlinked (copied where + symlinks are unavailable) and transferred to remote calculators. + +## Cache (`cache://`) + +- **The cache key is the SHA-256 of the case's input files** (plus `input_static`), and, + when declared, the calculator's `code_id`. It **does not include the calculator + command or the output parsers**: changing the simulation script without changing the + inputs still hits the cache. Declare a `code_id` (or `version_cmd`) in calculator + aliases, or use a fresh `results_dir` without `cache://`, to force recomputation. +- A match between calculators with no declared `code_id` is accepted with a warning; + `FZ_CACHE_STRICT=1` refuses it. +- **Resuming into the same `results_dir`** requires the special entry `cache://_` (the + renamed previous content). `cache://` points to the new, empty directory + and never hits. +- Caches written before the v2 hash format (MD5) are ignored unless + `FZ_CACHE_ACCEPT_LEGACY=1`. +- A cached case is reused only if its outputs, parsed with the *current* model, are all + non-`None`. + +## Remote calculators + +- **`slurm-array://` is local only** (the machine running fz must have `sbatch`/`sacct`). + `slurm://` supports both local and SSH-remote SLURM. +- **SSH host keys:** with key authentication, an unknown host key is added + automatically (paramiko `AutoAddPolicy`, no fingerprint check). With a password in the + URI, fz asks interactively on stdin, which blocks unattended runs, unless + `FZ_SSH_AUTO_ACCEPT_HOSTKEYS=1`. Pre-populate `~/.ssh/known_hosts` when host identity + matters. A password embedded in the URI is masked in results and logs but stays in + memory and in your scripts: prefer keys. +- **No interactive SSH password prompt.** Use keys, or a password in the URI (keys are + then not tried). Without `user@`, the user is `$SSH_USER`, else the local user. +- **Remote cleanup** (`rm -rf`) is refused outside fz's own `.fz/tmp/fz_calc_*` / + `fz_slurm_*` directories. +- **`funz://`** needs a running legacy Java Funz calculator (TCP), optionally discovered + by UDP broadcast (`fz.discover_funz_servers`). + +## Security + +Templates, `#@` formula-context lines, `@{...}` formulas, output commands and calculator +commands are **executed as code with the user's privileges**. There is no sandbox. Do not +run a model, algorithm or calculator alias obtained from an untrusted source without +reading it. `fz-mcp` confines file paths to `FZ_MCP_ROOT` and can restrict models and +calculators to installed aliases (`FZ_MCP_TRUSTED=0`), but its tool annotations are +advisory, not a security boundary. See [Security Model](security.md) and [AI Agents](../user-guide/ai-agents.md). diff --git a/docs/reference/releases.md b/docs/reference/releases.md index dbe40e2..9de5c73 100644 --- a/docs/reference/releases.md +++ b/docs/reference/releases.md @@ -1,5 +1,39 @@ # Release Notes +Complete, authoritative notes: [NEWS.md](https://github.com/Funz/fz/blob/main/NEWS.md) +in the fz repository. + +## Unreleased (main branch, after 1.2) + +!!! warning "Breaking changes" + - Python 3.8 is no longer supported (`requires-python >= 3.9`). + - `"path"` case directory names percent-encode `/ \ : * ? " < > | %`, control + characters and `.`/`..` values: names containing such values differ from 1.2. + - `cache://` uses a SHA-256 `.fz_hash` ("v2") format; caches written by older versions + are ignored unless `FZ_CACHE_ACCEPT_LEGACY=1`. + - `ssh://` and `slurm://` have no default timeout (was 3600 s); `sh://`/`funz://` keep + 3600 s. + - `fz-mcp`: `FZ_MCP_TRUSTED=0` restricted mode now works at the MCP layer only. + +- **`slurm-array://`**: all cases batched in one `sbatch --array` job, one shared + `sacct` monitor; resources in the URI (`?cores=4&mem=8G&time=01:00:00&maxrunning=M`), + also accepted by `slurm://` ([SLURM](../user-guide/calculators/slurm.md)). +- **Campaign manifest**: `manifest.json` and RO-Crate `ro-crate-metadata.json` written by + `fzr` and `fzd` ([Results & Traceability](../user-guide/running/results.md)). +- **Cache identity**: calculator aliases may declare `code_id` or `version_cmd`; + `FZ_CACHE_STRICT=1` refuses unverifiable matches + ([Caching](../user-guide/running/caching.md)). +- **`fz-mcp` MCP server** (`pip install 'funz-fz[mcp]'`, Python โ‰ฅ 3.10) with tool + annotations, stdio-only by default ([AI Agents](../user-guide/ai-agents.md)). +- **Security**: threat model documented; remote commands built by fz are shell-quoted; + remote cleanup restricted to fz's directories; passwords in URIs masked everywhere + ([Security Model](security.md)). +- `fz install model` installs every model of a repository. +- `fzc`/`fzr` evaluate formulas using inline variable defaults, as `fzi` does. +- Error reports no longer blame the command for a "not found" printed by the code. +- `fz/runners.py` split into the `fz.runners` package (no behavior change; code patching + `fz.runners.run_command` must patch `fz.runners.sh.run_command`). + ## Version 1.2 (2026-09-04) ### New Features @@ -282,7 +316,7 @@ If `analysis_dir` already exists it is renamed with a timestamp suffix and its c - Warning issued when default value is used #### Old Funz Syntax Compatibility -- Support for legacy Java Funz variable syntax: `?var` (equivalent to `$var`) +- Support for legacy Java Funz variable syntax: `?var` (note: as of the current code, only with `"varprefix": "?"`; there is no automatic conversion to `$var`) - Backward compatibility for existing Funz users migrating to Python - Automatic detection and replacement - Example: `Temperature: ?T_celsius` โ†’ `Temperature: 25` diff --git a/docs/reference/security.md b/docs/reference/security.md new file mode 100644 index 0000000..ac12fad --- /dev/null +++ b/docs/reference/security.md @@ -0,0 +1,55 @@ +# Security Model + +fz does **not** sandbox what it runs. This is a deliberate choice of the project: a +restricted formula language was evaluated and rejected because it would break existing +usage (`#@ import`, multi-line Python/R formulas, shell output parsers, `python -c` +calculators). + +## What runs as code, with your privileges + +| Element | Where it comes from | Executed by | +|---------|---------------------|-------------| +| `#@` context lines and `@{...}` formulas | Input templates | Python `exec`/`eval`, or R | +| Output extractors (shell, `python://`, callables) | Model `output` | bash / Python | +| Calculator commands | Calculator URIs and aliases, installed wrappers' scripts | bash, locally or remotely | +| Algorithm classes | `fzd` algorithm files | Python / R | +| `#require:` lines of algorithms | Algorithm files | `pip install` of the listed packages | + +Running a model, an algorithm or a calculator alias from a source is equivalent to +running a shell script from that source. `fz install` from a network source prints a +reminder of this. + +## Measures in place + +- Case directory names are percent-encoded, and fz refuses to create a case directory + outside `results_dir` (protects against values such as `../../x`). +- Remote commands built by fz (`mkdir`, `cd`, `rm -rf`, file names) are shell-quoted; + remote cleanup is refused outside fz's `.fz/tmp/fz_calc_*` / `fz_slurm_*` directories. +- Remote `out.txt`/`err.txt`/`log.txt` are written from the fetched data, not through a + remote heredoc. +- Passwords embedded in `ssh://` / `slurm://` URIs are masked in the DataFrame, + `info.txt`, `history.txt`, logs and `manifest.json`. +- SLURM resource values in URIs are validated against a whitelist. + +These measures protect fz's own command construction. They do not restrict the user's +commands, formulas or extractors. + +## SSH + +- With key authentication, unknown host keys are added without fingerprint check; + populate `~/.ssh/known_hosts` when host identity matters. +- A password in a URI remains in your scripts and in memory; prefer keys. + +## AI agents (`fz-mcp`) + +Access to `fz-mcp` in its default mode is equivalent to shell access for the agent, +including through prompt injection in files it reads. Mitigations: `FZ_MCP_ROOT` +(path confinement, always on), `FZ_MCP_TRUSTED=0` (only installed aliases), stdio only +unless a network transport is explicitly allowed. Tool annotations are advisory. See +[AI Agents](../user-guide/ai-agents.md#mcp-server-fz-mcp). + +## Recommendations + +- Read third-party models, calculator aliases, runner scripts and algorithms before use. +- Run untrusted studies in a container or a dedicated account. +- Keep `.fz/` directories under version control to see what changed. diff --git a/docs/reference/troubleshooting.md b/docs/reference/troubleshooting.md index d88ba76..3f32cae 100644 --- a/docs/reference/troubleshooting.md +++ b/docs/reference/troubleshooting.md @@ -1,86 +1,46 @@ # Troubleshooting -## Common Issues - -**Calculations fail with "command not found"** - -Use absolute paths in calculator URIs: - -```bash -calculators = "sh://bash /full/path/to/script.sh" -``` - -**SSH calculations hang** - -Increase timeout or verify SSH connectivity: - -```bash -# Test manually -ssh user@host "bash script.sh" -``` - -**Cache not working** - -Check `.fz_hash` files exist in cache directories. Enable debug logging: +First read the failing case's `err.txt`, `log.txt` and `history.txt` (in its result +directory), and the `error` column of the DataFrame. For more detail: ```python -import os -os.environ['FZ_LOG_LEVEL'] = 'DEBUG' +fz.set_log_level("DEBUG") # or FZ_LOG_LEVEL=DEBUG before starting Python ``` -**Out of memory with many parallel cases** - -Limit parallel workers: +## Symptoms and causes + +| Symptom | Likely cause | Fix | +|---------|--------------|-----| +| A directory named `sh:/...` appears; every case fails | Calculator passed as 4th positional argument of `fzr` (that slot is `results_dir`) | `calculators=...` by keyword | +| `Permission denied ... ./input.txt` | No calculator: fz fell back to an empty `sh://` | Give `calculators`, or install a calculator alias matching the model `id` | +| `fzi` misses `${x}` variables; `${x}` left in compiled files | Model has no `delim`: variables use `()` | Add `"delim": "{}"` | +| `fzi` reports unexpected variables | `varprefix` collides with the code's syntax | Change `varprefix` (e.g. `%`) | +| `status` `done` but output `None`, `error` = `Missing output: ...` | Extractor found nothing: wrong file/pattern, file written elsewhere, locale | Test the extractor with `fzo` on the case directory | +| Output value equals the program's stdout | The code writes `out.txt` (reserved, overwritten by stdout) | Rename the code's output file | +| Result file appears in the launch directory, not in the case | File names in the `sh://` command line are made absolute against the launch directory | Move file handling into a script run as `sh://bash script.sh` | +| Every case `timeout` immediately | `FZ_RUN_TIMEOUT=0` or `timeout=0` | Use model `"timeout": null`, or a positive value | +| `ssh://`/`slurm://` run never ends | No default timeout for these calculators | Set `FZ_RUN_TIMEOUT` or the model's `timeout` | +| Changing `os.environ["FZ_..."]` has no effect | Configuration is read at import | `fz.reload_config()` | +| `FZ_MAX_WORKERS=8` but cases still run one at a time | Parallelism = number of calculator entries | `calculators=["sh://bash calc.sh"] * 8` | +| `cache://results` with `results_dir="results"` never hits | The old directory was renamed before the lookup | Use `cache://_` | +| Cache hit although the code changed | The command/script is not part of the cache key | Declare `code_id` in calculator aliases, or use a fresh `results_dir` | +| `fzo results/` returns one row of `None` | `fzo` targets the directory itself | `fzo "results/*"` | +| After `fzc`, `compiled/input.txt` does not exist | `fzc` writes `compiled//input.txt` | Look into the case sub-directory | +| `fz list --check` marks an installed calculator `failed` (`Empty sh:// command`) | Known `fz list` limitation for `{"uri": "sh://", "models": {...}}` aliases | Check with a real `fzr` run | +| SSH run blocks at start | Host-key prompt (password in URI, unknown host) | Add the host to `known_hosts` or `FZ_SSH_AUTO_ACCEPT_HOSTKEYS=1` | +| `bash: not found` / shell errors on Windows | bash not found | Install MSYS2/Git Bash, set `FZ_SHELL_PATH` | +| `ValueError: signal only works in main thread` | fz < 1.2 called from a thread | Upgrade; since 1.2 the handler is skipped outside the main thread | +| R formulas unavailable although `rpy2` imports | `rpy2.robjects` fails to load (R/rpy2 version mismatch) | Align R and rpy2 versions | + +## Verify step by step ```bash -export FZ_MAX_WORKERS=2 -``` - -## Windows / Cross-Platform - -**Shell commands fail on Windows** - -Install MSYS2 and set `FZ_SHELL_PATH` to point to its binaries: - -```powershell -$env:FZ_SHELL_PATH = "C:\msys64\usr\bin;C:\msys64\mingw64\bin" +fzi input.txt --model m --format json # variables found? +fzc input.txt --model m --input_variables '{"x": 1}' --output_dir compiled +(cd compiled/*/ && bash /abs/path/run.sh input.txt) # code runs? +fzo 'compiled/*' --model m --format json # outputs parsed? ``` -See [FZ_SHELL_PATH configuration](https://github.com/Funz/fz/blob/main/examples/shell_path_example.md) for details. - -**Line ending issues on Windows** - -Write input files with Unix line endings: - -```python -with open("input.txt", "w", newline='\n') as f: - f.write(content) -``` - -**`chmod` has no effect on Windows** - -This is expected โ€” Windows does not support Unix file permissions. Shell scripts run via `sh://` do not need `chmod` on Windows. - -## Debug Mode - -Enable detailed logging: - -```python -import os -os.environ['FZ_LOG_LEVEL'] = 'DEBUG' - -results = fz.fzr(...) # Will show detailed execution logs -``` - -Debug output includes: - -- Calculator selection and locking -- File operations and command execution -- Cache matching -- Thread pool management - -## See Also +## See also -- [Environment Variables](environment.md) ยท [Configuration](configuration.md) -- [`fzl --check`](../user-guide/core-functions/fzl.md) โ€” validate installed models and calculators -- [Interrupt Handling](../user-guide/advanced/interrupts.md) +[Constraints & Limits](limitations.md) ยท [Environment Variables](environment.md) diff --git a/docs/user-guide/advanced/caching.md b/docs/user-guide/advanced/caching.md deleted file mode 100644 index e528b84..0000000 --- a/docs/user-guide/advanced/caching.md +++ /dev/null @@ -1,78 +0,0 @@ -# Caching Strategy - -FZ caches results by the **MD5 hash of a case's input files**. A -[`cache://`](../calculators/cache.md) calculator reuses a previous result whenever the -input hashes match and the stored outputs are valid โ€” no recomputation. - -```title=".fz_hash" -a1b2c3d4e5f6... input.txt -f6e5d4c3b2a1... config.dat -``` - -Matching is by `.fz_hash` content, so it is independent of the -[`case_naming`](../core-functions/fzr.md#case-directory-naming-new-in-12) scheme used to -write the cache. - -## Strategies - -### Resume an interrupted run - -```python -fz.fzr("input.txt", {"param": list(range(100))}, model, - "sh://bash slow_calc.sh", results_dir="run1") -# interrupted with Ctrl+C after ~50 cases - -fz.fzr("input.txt", {"param": list(range(100))}, model, - ["cache://run1", "sh://bash slow_calc.sh"], results_dir="run1_resumed") -``` - -### Expand the parameter space - -```python -fz.fzr("input.txt", {"temp": [100, 200, 300], "pressure": [1, 10, 100]}, model, - "sh://bash calc.sh", results_dir="study1") # 9 cases - -fz.fzr("input.txt", - {"temp": [100, 200, 300, 400, 500], "pressure": [1, 10, 100, 1000, 10000]}, - model, ["cache://study1", "sh://bash calc.sh"], results_dir="study2") # reuses 9, runs 16 -``` - -### Multi-tier cache - -```python -calculators = [ - "cache://latest_run", - "cache://archive/2024-*", - "cache://archive/*/*", - "sh://bash calc.sh", # last resort -] -``` - -## What the Cache Keys On - -Only the **input files**. It does **not** consider the calculator command, so: - -- Changing the calculation script but not the inputs โ†’ still a cache hit. -- To force recomputation, run into a fresh `results_dir` with no `cache://` entry. - -## `fzd` Caching - -[`fzd`](../core-functions/fzd.md) adds two automatic layers on top: - -- **Cross-iteration caching** โ€” a point evaluated in one iteration is never re-run in a - later one. -- **Re-run resume** โ€” an existing `analysis_dir` is renamed with a timestamp and its - iteration directories are added to the cache, so a re-run reuses all prior work. - -## Housekeeping - -```bash -# Drop result payloads older than 30 days but keep the cache keys -find results/ -type d -mtime +30 -exec rm -rf {} + -find results/ -type f ! -name '.fz_hash' -delete -``` - -## See Also - -- [Cache Calculator](../calculators/cache.md) ยท [Interrupt Handling](interrupts.md) ยท [Parallel Execution](parallel.md) -- [`FZ_CACHE_DIR`](../../reference/environment.md#fz_cache_dir) diff --git a/docs/user-guide/advanced/interrupts.md b/docs/user-guide/advanced/interrupts.md deleted file mode 100644 index 17bacae..0000000 --- a/docs/user-guide/advanced/interrupts.md +++ /dev/null @@ -1,46 +0,0 @@ -# Interrupt Handling - -`fzr` and `fzd` install a `SIGINT` handler so **Ctrl+C** stops a run cleanly instead of -leaving orphaned processes or half-written directories. - -## What Happens on Ctrl+C - -``` -โš ๏ธ Interrupt received (Ctrl+C). Gracefully shutting down... -``` - -1. Cases already running are allowed to finish. -2. No new cases are started. -3. Results collected so far are written out. -4. For `ssh://` / `slurm://` / `funz://`, the remote job is signalled to terminate and - the remote temp directory is cleaned up. - -A second Ctrl+C forces an immediate exit. - -## Resuming - -Because completed cases are on disk with their `.fz_hash`, add a -[`cache://`](../calculators/cache.md) entry pointing at the interrupted run: - -```python -try: - fz.fzr("input.txt", variables, model, "sh://bash calc.sh", results_dir="run1") -except KeyboardInterrupt: - print("Interrupted โ€” partial results saved") - -fz.fzr("input.txt", variables, model, - ["cache://run1", "sh://bash calc.sh"], results_dir="run1_resumed") -``` - -## Calling From a Background Thread (1.2) - -Signal handlers can only be installed from the main thread. Since **1.2**, `fzr` / `fzd` -detect when they are called off the main thread (Streamlit reruns, a -`ThreadPoolExecutor` worker, an app embedding fz) and **skip** the handler install -instead of raising `ValueError: signal only works in main thread`. Ctrl+C handling is -simply unavailable in that case; everything else works normally. - -## See Also - -- [Caching Strategy](caching.md) ยท [Cache Calculator](../calculators/cache.md) -- [Parallel Execution](parallel.md) diff --git a/docs/user-guide/advanced/parallel.md b/docs/user-guide/advanced/parallel.md deleted file mode 100644 index 84ad2f3..0000000 --- a/docs/user-guide/advanced/parallel.md +++ /dev/null @@ -1,75 +0,0 @@ -# Parallel Execution - -FZ runs cases concurrently across the calculators you provide. Each calculator entry is -locked to one case at a time, so **N calculator entries = N parallel workers**. - -## Turning On Parallelism - -```python -# Sequential โ€” one calculator entry -fz.fzr("input.txt", {"t": [100, 200, 300, 400, 500]}, model, - calculators="sh://bash calc.sh") - -# Parallel โ€” repeat the entry -fz.fzr("input.txt", {"t": [100, 200, 300, 400, 500]}, model, - calculators=["sh://bash calc.sh"] * 3) # 3 cases at once -``` - -The entries do not have to be identical โ€” mix local, SSH, and SLURM freely: - -```python -calculators = [ - "sh://bash calc.sh", - "sh://bash calc.sh", - "ssh://user@node1/bash /path/calc.sh", - "ssh://user@node2/bash /path/calc.sh", -] -``` - -## Load Balancing - -Cases are handed out round-robin. With 10 cases and 3 calculators: - -| Calculator | Cases | -|------------|-------| -| 0 | 0, 3, 6, 9 | -| 1 | 1, 4, 7 | -| 2 | 2, 5, 8 | - -## Controlling the Worker Count - -| Method | Example | -|--------|---------| -| Number of calculator entries | `["sh://bash calc.sh"] * 8` | -| Environment variable | `export FZ_MAX_WORKERS=8` | -| Config object | `from fz import get_config; get_config().max_workers = 8` | - -`FZ_MAX_WORKERS` caps the thread pool regardless of how many calculator entries you pass. - -### Choosing a Number - -- **CPU-bound**: about `os.cpu_count()` workers. -- **I/O-bound / remote**: more than the core count can help. -- **Memory-bound**: `available_RAM / RAM_per_case`. - -## Progress and Interrupts - -A progress bar with ETA is shown on **stderr** (auto-disabled when stderr is not a -terminal, e.g. in CI or when redirected). Press **Ctrl+C** for a graceful shutdown: -running cases finish, no new cases start, partial results are saved. See -[Interrupt Handling](interrupts.md). - -## Combine With Cache - -```python -calculators = [ - "cache://previous_run", # instant on a hit - *["sh://bash calc.sh"] * 4, # otherwise 4 parallel workers -] -``` - -## See Also - -- [Caching Strategy](caching.md) ยท [Interrupt Handling](interrupts.md) -- [Calculators Overview](../calculators/overview.md) -- [`FZ_MAX_WORKERS`](../../reference/environment.md#fz_max_workers) diff --git a/docs/user-guide/ai-agents.md b/docs/user-guide/ai-agents.md new file mode 100644 index 0000000..674cb54 --- /dev/null +++ b/docs/user-guide/ai-agents.md @@ -0,0 +1,62 @@ +# AI Agents (Claude Code, MCP) + +fz provides two integrations for AI coding agents: a **Claude Code plugin** (an Agent +Skill and slash commands) and an **MCP server** (`fz-mcp`) usable by any MCP client. + +## Claude Code plugin + +```text +/plugin marketplace add Funz/fz +/plugin install fz@funz +``` + +| Component | Content | +|-----------|---------| +| Agent Skill `fz` | The wrapping workflow (check for an existing `fz-` wrapper, parameterize, define the model, verify `fzi` โ†’ `fzc` โ†’ one manual run โ†’ `fzo`, then `fzr`/`fzd`), a condensed API/CLI reference, the algorithm interface, a wrapper authoring guide | +| `/fz:wrap` | Wrap a simulation code and verify it step by step | +| `/fz:run` | Run a parametric study (`fzr`) and report the results | +| `/fz:design` | Adaptive design of experiments, optimization, calibration (`fzd`) | +| `/fz:install` | Find and install an official `fz-` wrapper or algorithm | + +The skill loads automatically when a request mentions fz, Funz, parameter sweeps, +design of experiments over a simulation, or running many cases locally, over SSH or on +SLURM. Its content is in the fz repository under +[`skills/fz/`](https://github.com/Funz/fz/tree/main/skills/fz) and is tested against the +code (CLI flags, environment variables, defaults, signatures). + +The skill can also be copied into a project without the plugin system: +`cp -r fz/skills/fz .claude/skills/`. + +## MCP server (`fz-mcp`) + +```bash +pip install 'funz-fz[mcp]' # Python >= 3.10 +claude mcp add fz -- fz-mcp # example: register with Claude Code +``` + +`fz-mcp` exposes `fzi`, `fzc`, `fzr`, `fzo` and `fzl` as MCP tools over **stdio**. + +!!! danger "Trusted mode = shell access" + By default the agent can pass any model and calculator: templates, formulas, output + commands and calculator commands run as code with your privileges. Register it only + for agents and inputs you trust. + +| Setting | Effect | +|---------|--------| +| `FZ_MCP_ROOT` | All file paths are confined to this directory (default: working directory) | +| `FZ_MCP_TRUSTED=0` | Restricted mode: models and calculators must be installed aliases (no inline model, no `sh://`/`ssh://` URI). Does not sandbox formula evaluation inside fz | +| `FZ_MCP_TRANSPORT` + `FZ_MCP_ALLOW_NETWORK_TRANSPORT=1` | Required together to start a network transport (`sse`, `streamable-http`); otherwise fz-mcp refuses to start. It does not authenticate clients | + +Tool annotations: `fzc`/`fzr` are `destructiveHint`/`openWorldHint`, `fzi`/`fzo`/`fzl` +are `readOnlyHint`. They are hints for well-behaved clients, not a security boundary. +`fzd` is not exposed. + +## Machine-readable documentation + +The fz repository ships [`llms.txt`](https://github.com/Funz/fz/blob/main/llms.txt), an +index of the documentation for LLMs, and the modular +[`doc/`](https://github.com/Funz/fz/tree/main/doc) directory. + +## See also + +[Security Model](../reference/security.md) ยท [Environment Variables](../reference/environment.md#mcp-server) diff --git a/docs/user-guide/calculators/cache.md b/docs/user-guide/calculators/cache.md index a3e090f..5346164 100644 --- a/docs/user-guide/calculators/cache.md +++ b/docs/user-guide/calculators/cache.md @@ -1,84 +1,75 @@ -# Cache Calculator (`cache://`) +# Cache (`cache://`) -The `cache://` calculator does not run anything. It looks in one or more existing result -directories for a case whose **input files hash identically** to the current case and, -if it finds one with valid outputs, copies those results in โ€” skipping the computation. +`cache://` runs nothing. For each case it looks in previous result directories for a +case with the **same input hash** (and compatible code identity) whose outputs are all +valid, and copies its results. On a miss, the next calculator in the list runs the case. -Put it **first** in a calculator list so real calculators only run on cache misses. - -## URI Syntax - -``` -cache://path/to/results +```text +cache://path # a results directory, or a glob ``` ```python -calculators = "cache://previous_run" - -calculators = ["cache://run1", "cache://archive/results"] # several caches - -calculators = ["cache://archive/2024-*/results"] # glob patterns - -calculators = [ - "cache://previous_results", # try cache first - "sh://bash calculate.sh", # compute on miss -] +calculators = ["cache://run1", "sh://bash calc.sh"] # reuse, else compute +calculators = ["cache://run1", "cache://archive/2024-*", "sh://bash calc.sh"] ``` -## How It Works +Put `cache://` entries **first**. They do not count as parallel workers. -1. Compute the MD5 hash of every input file for the current case. -2. Search the cache directories for a `.fz_hash` file whose entries all match. -3. Check that the cached outputs are non-`None`. -4. On a hit, copy the cached result files in and mark the case `done`; on a miss, fall - through to the next calculator. +## Matching rules -Matching is by `.fz_hash` **content**, not directory name โ€” so a cache written with any -[`case_naming`](../core-functions/fzr.md#case-directory-naming-new-in-12) scheme -(`path` / `hash` / `index`) still matches. +1. The case's `.fz_hash` โ€” SHA-256 of every compiled input file and of the + `input_static` files โ€” must equal a cached case's `.fz_hash`. +2. Code identity: if both calculators declare a `code_id`, they must be equal; if the + identity cannot be verified (no `code_id`), the match is accepted with a one-time + warning, or refused with `FZ_CACHE_STRICT=1`. +3. The cached outputs, re-parsed with the **current** model, must all be non-`None`. -```title=".fz_hash" -a1b2c3d4e5f6... input.txt -f6e5d4c3b2a1... config.dat -``` +Directory names do not matter: a cache written with any `case_naming` matches. + +!!! warning "What is *not* in the cache key" + The calculator command, the script content and the output extractors are not part + of the key. Changing `calc.sh` without changing the inputs still gives cache hits. + Declare a `code_id` / `version_cmd` in calculator aliases + ([Caching](../running/caching.md#cache-identity-code_id)), or run into a fresh + `results_dir` without `cache://`. -!!! note - The cache keys on **input files only**, not on the calculator command. Editing your - calculation script but not the inputs will still produce a cache hit โ€” use a fresh - `results_dir` without `cache://` to force recomputation. +- Caches written by fz versions using the previous MD5 format are ignored unless + `FZ_CACHE_ACCEPT_LEGACY=1`. +- `fzr` pointed at an existing `results_dir` renames it with a timestamp + (`run1_2026-09-30_20-17-13`) before running. The special entry **`cache://_`** means + "the previous content of `results_dir`" and is redirected to that renamed copy. + `cache://run1` with `results_dir="run1"` finds nothing: it points to the new, empty + directory. -## Common Uses +## Common uses === "Resume an interrupted run" ```python - fz.fzr("input.txt", {"p": range(100)}, model, "sh://bash calc.sh", "run1") - # ... Ctrl+C after 50 cases ... - fz.fzr("input.txt", {"p": range(100)}, model, - ["cache://run1", "sh://bash calc.sh"], "run1_resumed") + fz.fzr("input.txt", {"p": list(range(100))}, model, + calculators="sh://bash calc.sh", results_dir="run1") + # Ctrl+C after 50 cases ... + fz.fzr("input.txt", {"p": list(range(100))}, model, + calculators=["cache://_", "sh://bash calc.sh"], results_dir="run1") + # cache://_ = previous content of run1 (renamed run1_) ``` -=== "Expand the parameter space" +=== "Extend a design" ```python - fz.fzr("input.txt", {"t": [100, 200, 300]}, model, "sh://bash calc.sh", "study1") + fz.fzr("input.txt", {"t": [100, 200, 300]}, model, + calculators="sh://bash calc.sh", results_dir="study1") fz.fzr("input.txt", {"t": [100, 200, 300, 400, 500]}, model, - ["cache://study1", "sh://bash calc.sh"], "study2") # reuses 3, runs 2 + calculators=["cache://study1", "sh://bash calc.sh"], results_dir="study2") + # 3 reused, 2 computed ``` -=== "Multi-tier cache" +=== "Re-parse without re-running" - ```python - calculators = [ - "cache://latest_run", - "cache://archive/2024-*", - "cache://archive/*/*", - "sh://bash calc.sh", - ] - ``` + To only change output extractors, `fzo` on the old results is simpler than a + cached `fzr`: `fz.fzo("study1/*", new_model)`. -## See Also +## See also -- [Caching Strategy](../advanced/caching.md) โ€” deeper patterns -- [Interrupt Handling](../advanced/interrupts.md) -- [`fzd` cross-iteration caching](../core-functions/fzd.md#cross-iteration-caching) +[Caching](../running/caching.md) ยท [Interrupt & Resume](../running/interrupts.md) ยท +[Results & Traceability](../running/results.md) diff --git a/docs/user-guide/calculators/funz.md b/docs/user-guide/calculators/funz.md index 7b64ae4..af31d67 100644 --- a/docs/user-guide/calculators/funz.md +++ b/docs/user-guide/calculators/funz.md @@ -1,316 +1,56 @@ -# Funz Calculator +# Funz Server (`funz://`) -The Funz calculator provides compatibility with legacy Java Funz calculator servers, enabling FZ to leverage existing Funz infrastructure. +`funz://` sends cases to a legacy **Java Funz calculator** (the execution daemon of the +Java Funz framework). The calculator is located by **UDP discovery**, then driven over +TCP. -## Overview - -Funz is a Java-based framework for computational experiments. The FZ Funz calculator allows Python FZ to communicate with Java Funz calculator servers, providing backward compatibility and integration with existing Funz deployments. - -## URI Format - -``` -funz://[host]:/ +```text +funz://[host]:/ ``` -### Components - -- **host** (optional): Hostname of the Funz server (default: localhost) -- **port** (required): TCP port number of the Funz server -- **code**: Model/language code supported by the server (e.g., R, Python, bash) - -## Examples - -### Local Funz Server +| Part | Meaning | +|------|---------| +| `host` | Host for the TCP connection (default `localhost`) | +| `udp_port` | **UDP port on which calculators broadcast their availability** (required). The TCP port is read from the broadcast | +| `code` | Code the calculator must offer (`R`, `Python`, `Modelica`, `bash`, ...) | ```python -import fz - -results = fz.fzr( - "input.txt", - {"param1": [1, 2, 3]}, - model, - calculators="funz://:5555/R", - results_dir="results" -) +calculators = "funz://:19001/R" +calculators = ["funz://:19001/R"] * 3 # up to 3 calculators in parallel ``` -### Remote Funz Server - -```python -calculators = "funz://server.example.com:5555/Python" -``` +## How a case runs -### Multiple Funz Servers +1. **Discovery**: listen on `udp_port` for up to 10 s; pick an idle calculator offering + `code` (else any offering it, else the first seen). +2. **Reservation** on the advertised TCP port. +3. Upload of the input files, execution, download of the results. +4. Release of the calculator. -Load balance across multiple calculator servers: +## Discovery from Python ```python -calculators = [ - "funz://server1.example.com:5555/R", - "funz://server2.example.com:5555/R", - "funz://server3.example.com:5555/R" -] -``` - -## Protocol - -The Funz calculator uses TCP socket communication with a specific protocol: +from fz import discover_funz_servers -### Reservation +servers = discover_funz_servers(19001, listen_duration=10) +# [{'host': '192.168.1.100', 'tcp_port': 5555, 'name': 'calc1', 'os': 'Linux 6.1', +# 'activity': 'idle', 'idle': True, 'codes': ['R', 'Python']}, ...] -Before executing a calculation, FZ reserves the calculator: - -``` -RESERVE -``` - -Server responds with: +idle_r = [s for s in servers if s["idle"] and "R" in s["codes"]] ``` -OK -``` - -### Execution - -Files are uploaded and execution is requested: - -``` -EXECUTE -FILE - -... -``` - -### Result Retrieval - -After execution, results are downloaded: - -``` -GET -``` - -Server responds with file content. - -### Unreservation - -After completion, the calculator is released: - -``` -UNRESERVE -``` - -## UDP Discovery - -Funz servers can advertise themselves via UDP broadcast. FZ can automatically discover available Funz calculators on the local network. - -### Discovery Process - -1. FZ sends UDP broadcast on port 21001: `WHO` -2. Funz servers respond with: `FUNZ : ` -3. FZ automatically configures discovered calculators - -### Automatic Discovery - -```python -# FZ automatically discovers Funz servers -results = fz.fzr( - "input.txt", - variables, - model, - calculators="funz", # Auto-discover - results_dir="results" -) -``` - -## Calculator Configuration - -Funz calculators can be configured via JSON files in `.fz/calculators/`: - -```json -{ - "uri": "funz://:5555", - "models": { - "R": "R", - "Python": "Python" - } -} -``` - -## Features - -### File Transfer - -- Automatic upload of input files to Funz server -- Download of output files after execution -- Binary file support - -### Job Management - -- Calculator reservation system prevents conflicts -- Automatic retry on calculator failure -- Graceful cleanup on interruption (Ctrl+C) - -### Multi-Language Support -Funz servers can support multiple languages/codes: - -- R statistical computing -- Python scripting -- Bash shell scripts -- MATLAB/Octave -- Custom calculation codes +A broadcast is a newline-separated message: calculator name, TCP port, start timestamp, +operating system, activity (`idle` when free), number of codes, then one code per line. ## Requirements -- Java Funz calculator server running and accessible -- Network connectivity to Funz server -- Port not blocked by firewall - -## Setting Up a Funz Server - -To start a Java Funz calculator server: - -```bash -java -jar Funz-Calculator.jar \ - -code R \ - -port 5555 \ - -verbose -``` - -For multiple codes: - -```bash -java -jar Funz-Calculator.jar \ - -code R,Python,bash \ - -port 5555 -``` - -## Model Compatibility - -The Funz calculator requires that the model is compatible with the specified code: - -```python -model = { - "varprefix": "$", - "code": "R", # Must match calculator code - "output": { - "result": "cat output.txt" - } -} - -calculators = "funz://:5555/R" # Code must match -``` - -## Examples - -### R Statistical Analysis - -```python -import fz - -model = { - "varprefix": "$", - "code": "R", - "output": { - "mean": "grep 'Mean:' output.txt | awk '{print $2}'" - } -} - -results = fz.fzr( - "analysis.R", - { - "sample_size": [100, 500, 1000], - "mean": [0, 10, 20], - "sd": [1, 2, 5] - }, - model, - calculators="funz://:5555/R", - results_dir="r_results" -) -``` - -### Python Simulation - -```python -calculators = "funz://compute-server.local:5555/Python" - -results = fz.fzr( - "simulation.py", - {"iterations": [1000, 5000, 10000]}, - model, - calculators=calculators, - results_dir="sim_results" -) -``` - -### Load Balancing - -Distribute calculations across multiple Funz servers: - -```python -calculators = [ - "funz://node1:5555/R", - "funz://node2:5555/R", - "funz://node3:5555/R", - "funz://node4:5555/R" -] - -results = fz.fzr( - "model.R", - large_parameter_grid, - model, - calculators=calculators, - n_parallel=4 # Use all 4 servers -) -``` - -## Troubleshooting - -### Connection Refused - -Check that the Funz server is running: - -```bash -telnet hostname 5555 -``` - -Verify firewall rules allow the connection. - -### Code Not Supported - -Ensure the Funz server supports the requested code: - -```bash -echo "WHO" | nc -u hostname 21001 -``` - -Check the server's code list in the response. - -### File Transfer Fails - -Large files may require timeout adjustments: - -```python -model = { - "varprefix": "$", - "timeout": 300 # 5 minutes -} -``` - -### Calculator Reserved - -If a calculator is stuck in reserved state, restart the Funz server: - -```bash -# Kill existing server -pkill -f "Funz-Calculator" - -# Restart -java -jar Funz-Calculator.jar -code R -port 5555 -``` +- A running Java Funz calculator: helper scripts `tools/setup_funz_calculator.sh`, + `tools/start_funz_calculator.sh` in the fz repository. +- UDP broadcasts from the calculator must reach the machine running fz (same network, + firewall open), and its TCP port must be reachable. +- Default timeout 3600 s ([Timeouts](../running/timeouts.md)). -## See Also +## See also -- [SSH Calculator](ssh.md) - For remote SSH execution -- [SLURM Calculator](slurm.md) - For HPC cluster execution -- [Calculator Overview](overview.md) - Overview of all calculator types -- [Funz Protocol Documentation](https://github.com/Funz/fz/blob/main/FUNZ_UDP_DISCOVERY.md) - Detailed protocol specification +- [Funz protocol description](https://github.com/Funz/fz/blob/main/doc/funz-protocol.md) +- [Calculators overview](overview.md) diff --git a/docs/user-guide/calculators/overview.md b/docs/user-guide/calculators/overview.md index 83e6d16..a51a617 100644 --- a/docs/user-guide/calculators/overview.md +++ b/docs/user-guide/calculators/overview.md @@ -1,33 +1,91 @@ -# Calculator Overview +# Calculators -Calculators define where and how calculations are executed. FZ supports multiple calculator types for different execution environments. +A calculator says **where and how** one case runs. It is given as a URI string, an alias +name, a dict, or a list of these, in the `calculators` argument of `fzr` / `fzd`. -## Available Calculator Types +## Types -### Local Execution -- **[Local Shell](shell.md)** - Execute on local machine using shell commands -- **[Cache](cache.md)** - Reuse previous results based on input hashes +| URI | Runs | Default timeout | Page | +|-----|------|-----------------|------| +| `sh://command` | Local shell, in a temporary directory per case | 3600 s | [Local shell](shell.md) | +| `ssh://[user[:pw]@]host[:port]/command` | Remote host; files transferred by SFTP | none | [SSH](ssh.md) | +| `slurm://[user@host[:port]]:partition/command` | `srun` on a SLURM partition, local or through SSH | none | [SLURM](slurm.md) | +| `slurm-array://:partition/command` | Local SLURM; all cases batched in one job array | none | [SLURM](slurm.md#job-arrays-slurm-array) | +| `funz://[host]:udp_port/code` | Java Funz calculator, found by UDP discovery | 3600 s | [Funz](funz.md) | +| `cache://path` | Nothing: reuses results of previous runs | โ€” | [Cache](cache.md) | -### Remote Execution -- **[SSH Remote](ssh.md)** - Execute on remote servers via SSH -- **[SLURM](slurm.md)** - Execute on HPC clusters with SLURM workload manager -- **[Funz Server](funz.md)** - Connect to legacy Java Funz calculator servers +## Several calculators -## URI Format +A list serves two purposes at once: -Each calculator type uses a specific URI format: +- **Parallelism**: each non-cache entry runs one case at a time, so N entries run up to + N cases concurrently (`["sh://bash calc.sh"] * 4`). +- **Failover**: a case whose run fails is retried on the calculators, up to + `FZ_MAX_RETRIES` (default 5) failures. -- Shell: `sh://[path/to/]script` -- SSH: `ssh://user@host[:port]/script` -- SLURM: `slurm://[user@host[:port]]:partition/script` -- Funz: `funz://[host]:/` -- Cache: `cache://` +```python +calculators = [ + "cache://previous_run", # reused when inputs match + "sh://bash calc.sh", # local + "sh://bash calc.sh", # 2nd local slot + "ssh://user@node1/bash /opt/code/run.sh", # remote slot +] +``` -## New in Version 0.9.1 +Case *i* first tries entry *i mod n*, then the first free one. See +[Parallelism & Retries](../running/parallel.md). -- **SLURM Calculator**: Full support for HPC workload management -- **Funz Calculator**: Backward compatibility with Java Funz servers -- **Shell Path Configuration**: Custom binary resolution via `FZ_SHELL_PATH` +## Default when `calculators` is omitted -See individual calculator documentation for detailed usage and examples. +`fzr` and `fzd` look for aliases in `.fz/calculators/` supporting the model's `id`; if +none is found they use `sh://` with no command, which fails (it tries to execute the +input file: `Permission denied ... ./input.txt`). +## Aliases + +An alias is a JSON file in `./.fz/calculators/` (then `~/.fz/calculators/`), used by its +file name without `.json`. + +```json title=".fz/calculators/cluster.json" +{ + "uri": "ssh://user@hpc.example.edu", + "models": { + "perfectgas": "bash /home/user/codes/perfectgas/run.sh", + "cfd": "bash /home/user/codes/cfd/run.sh" + }, + "code_id": "perfectgas@2.1" +} +``` + +```python +fz.fzr("input.txt", variables, "perfectgas", calculators="cluster") +# runs: ssh://user@hpc.example.edu/bash /home/user/codes/perfectgas/run.sh +``` + +| Key | Role | +|-----|------| +| `uri` | Scheme and location; may contain the command directly (`"sh://bash run.sh"`) | +| `models` | Model `id` โ†’ command on that calculator. Only models listed here are run on it | +| `code_id` | Identity of the installed code, used by `cache://` to decide whether results of another calculator are reusable ([Caching](../running/caching.md#cache-identity-code_id)) | +| `version_cmd` | Command whose output gives the `code_id` (run once per calculator per session) | + +The model must carry the same `id` for the `models` map to apply. `calculators` also +accepts a glob or regex on alias names (`"local*"`, `"^hpc"`) and a path to a JSON file. + +Installed wrappers ship an alias `localhost_.json` of the form +`{"uri": "sh://", "models": {"": "bash .fz/calculators/.sh"}}`. + +## Rules common to all calculators + +- The command receives the **compiled input file names appended to its command line**. +- stdout/stderr of the command are saved as `out.txt` / `err.txt` in the case directory; + `log.txt`, `info.txt`, `history.txt`, `.fz_hash` are also written by fz. A code + writing results under one of these names loses them. +- A password in a URI is masked in results, logs and manifests, but prefer SSH keys. +- Calculator commands are executed as written: only use aliases you trust + ([Security](../../reference/security.md)). + +## See also + +[Parallelism & Retries](../running/parallel.md) ยท [Timeouts](../running/timeouts.md) ยท +[.fz Directory & Aliases](../../reference/configuration.md) diff --git a/docs/user-guide/calculators/shell.md b/docs/user-guide/calculators/shell.md index 67be3dd..a329364 100644 --- a/docs/user-guide/calculators/shell.md +++ b/docs/user-guide/calculators/shell.md @@ -1,86 +1,68 @@ -# Local Shell Calculator (`sh://`) +# Local Shell (`sh://`) -The `sh://` calculator runs your computational code locally, in a shell, in a private -temporary directory per case. It is the default calculator and the one used in most -examples. +`sh://` runs the case on the local machine, through bash, in a temporary directory +created for the case. -## URI Syntax - -``` +```text sh://command [arguments] ``` -The command is whatever you would type to run one case by hand โ€” a script, an -interpreter plus a script, or a compiled binary: - ```python calculators = "sh://bash calculate.sh" calculators = "sh://python3 simulate.py --verbose" -calculators = "sh://./run_simulation" -calculators = "sh://bash run.sh --method=fast --tolerance=1e-6" +calculators = ["sh://bash calculate.sh"] * 4 # 4 cases at a time ``` -## How It Works +## Execution of one case -1. FZ creates a unique temporary directory for the case. -2. All compiled input files (and any [`input_static`](../core-functions/fzr.md#shared-static-files-new-in-12) files) are copied/linked in. -3. The command runs in that directory, receiving the input file name(s) as arguments: - ```bash - bash calculate.sh input.txt # single input file - bash calculate.sh file1.txt cfg.ini # multiple input files - ``` -4. Outputs are extracted with the model's [`output`](../model-definition.md#output-extraction-updated-in-12) specs. -5. The temp directory is removed (kept when `FZ_LOG_LEVEL=DEBUG`). +1. The compiled input files (and `input_static` files) are placed in a temporary + directory under `.fz/tmp/`. +2. The command runs **in that directory**, with the input file names appended to the + end of the command line: `bash /abs/path/calculate.sh input.txt`. +3. stdout โ†’ `out.txt`, stderr โ†’ `err.txt`. +4. Files are copied to the case result directory and the outputs are parsed there. -## Example Calculator Script +Exit status 0 means the run succeeded; the case is then `done` if outputs can be +parsed. A non-zero status is a failure and the case is retried. -```bash title="calculate.sh" -#!/bin/bash -INPUT_FILE=$1 +## Path rewriting in the command line -# Read input variables written by the compiled template -source "$INPUT_FILE" +!!! warning "Relative file names are resolved against the launch directory" + Before running, every token of the command that looks like a file name + (`calculate.sh`, `data/mesh.msh`, `result.txt`) is made **absolute relative to the + directory from which `fzr` was called**, not the case directory. Common tools + (`bash`, `sh`, `python`, `python3`, `cat`, `cp`, `mv`, `rm`, `grep`, `awk`, `sed`, + ...), flags (`-x`, `--opt=v`) and numbers are left unchanged. -echo "Running with temp=$temp, pressure=$pressure" -result=$(echo "scale=2; $temp * $pressure / 100" | bc) + `sh://cat input.txt > res.txt` therefore reads the **uncompiled template** of the + launch directory and writes `res.txt` outside the case; the case ends `done` with + empty outputs. -echo "result=$result" > output.txt -``` +**Rule:** put the work in a script and launch it with `sh://bash script.sh`. Inside the +script, relative paths are the case directory, and `$1`, `$2`, ... are the compiled +input files. -```python -model = { - "varprefix": "$", - "output": {"result": "grep 'result=' output.txt | cut -d= -f2"}, -} - -results = fz.fzr( - "input.txt", - {"temp": [100, 200, 300], "pressure": [1, 10]}, - model, - calculators="sh://bash calculate.sh", -) +```bash title="calculate.sh" +#!/bin/bash +# runs in the case directory; $1 = compiled input file +source "$1" +./solver --in "$1" --out result.dat > solver.log 2>&1 || exit 1 ``` -## Parallel Execution +The rewritten command is logged at INFO level and stored in the `command` column. -Each calculator entry handles one case at a time. Pass several to run cases concurrently: - -```python -calculators = ["sh://bash calc.sh"] * 4 # 4 workers -``` +## Windows -See [Parallel Execution](../advanced/parallel.md) for details. +`sh://` needs bash (MSYS2 or Git Bash). Set `FZ_SHELL_PATH` to their `bin` directories +before starting Python; commands found there are resolved to absolute paths. See +[Environment Variables](../../reference/environment.md#general). -## Windows +## Timeout and interrupts -`sh://` needs a POSIX shell and the usual Unix utilities. Install MSYS2 or Git Bash and -point [`FZ_SHELL_PATH`](../../reference/environment.md#fz_shell_path-new-in-091) at their -`bin` directories. Since 1.2, `import fz` itself works without bash โ€” only `sh://` and -`bash://` outputs require it; the [`python://` / `jq://` / `yq://` / `xpath://`](../core-functions/fzo.md#output-command-forms) -output forms do not. +Default timeout 3600 s ([Timeouts](../running/timeouts.md)). On Ctrl+C the process is +terminated (then killed after 5 s) and the case is `interrupted`. -## See Also +## See also -- [SSH Remote Calculator](ssh.md) ยท [SLURM](slurm.md) ยท [Funz Server](funz.md) ยท [Cache](cache.md) -- [Calculators Overview](overview.md) -- [Environment Variables](../../reference/environment.md) +[SSH](ssh.md) ยท [SLURM](slurm.md) ยท [Calculators overview](overview.md) ยท +[Constraints & Limits](../../reference/limitations.md) diff --git a/docs/user-guide/calculators/slurm.md b/docs/user-guide/calculators/slurm.md index 082cd31..2911a01 100644 --- a/docs/user-guide/calculators/slurm.md +++ b/docs/user-guide/calculators/slurm.md @@ -1,203 +1,92 @@ -# SLURM Calculator +# SLURM (`slurm://`, `slurm-array://`) -The SLURM calculator allows FZ to execute calculations on HPC clusters using the SLURM Workload Manager. +Two schemes submit cases to a SLURM cluster: -## Overview +| | `slurm://` | `slurm-array://` | +|--|-----------|------------------| +| Submission | One blocking `srun` per case | One `sbatch --array` job for all cases submitted within a short window | +| Where fz runs | On a node with SLURM commands, or anywhere through SSH | On a node with `sbatch`/`sacct` (local only) | +| Parallel cases | One per calculator entry | All cases from one URI (optionally throttled) | +| Monitoring | `srun` returns when the job ends | One shared thread polls `sacct` (fallback `squeue`) | +| Default timeout | none | none | -SLURM (Simple Linux Utility for Resource Management) is a widely-used job scheduler for HPC clusters. The FZ SLURM calculator provides seamless integration for submitting and managing jobs on SLURM-enabled systems. +## `slurm://` -## URI Format - -``` -slurm://[user@host[:port]]:partition/script -``` - -### Components - -- **user** (optional): Username for remote SLURM clusters -- **host** (optional): Hostname for remote SLURM clusters -- **port** (optional): SSH port for remote access (default: 22) -- **partition** (required): SLURM partition name (e.g., compute, gpu, debug) -- **script**: Shell command or script to execute - -## Local SLURM Execution - -For local SLURM clusters (when FZ runs on the cluster login node): - -```python -import fz - -results = fz.fzr( - "input.txt", - {"param1": [1, 2, 3]}, - model, - calculators="slurm://:compute/bash script.sh", - results_dir="results" -) +```text +slurm://:partition/command # local SLURM +slurm://user@host:partition/command # remote, through SSH +slurm://user@host:port:partition/command # remote, custom SSH port ``` -Example with GPU partition: +The partition is required and must be preceded by `:` (also in the local form). ```python -calculators = "slurm://:gpu/python simulation.py" +calculators = "slurm://:compute/bash /home/me/run.sh" +calculators = ["slurm://me@cluster.example.edu:compute/bash /home/me/run.sh"] * 8 # 8 jobs at a time ``` -## Remote SLURM Execution +- Local: fz runs `srun --partition= [resources] ` in the + case directory. +- Remote: fz connects by SSH (same authentication and host-key rules as + [`ssh://`](ssh.md)), uploads the inputs by SFTP, runs `srun` there, downloads the + results. +- Each entry runs one case at a time; repeat the URI to have several jobs in the queue. -For remote SLURM clusters accessed via SSH: +## Job arrays (`slurm-array://`) ```python -calculators = "slurm://username@cluster.example.edu:gpu/bash run.sh" +calculators = "slurm-array://:compute/bash /home/me/run.sh?cores=4&mem=8G&time=01:00:00&maxrunning=20" ``` -With custom SSH port: - -```python -calculators = "slurm://user@cluster.edu:2222:compute/python script.py" -``` - -## Features - -### Automatic Job Management +- Cases arriving within `FZ_SLURM_ARRAY_WINDOW` seconds (default 1) are submitted as one + array; one calculator URI runs all of them concurrently. +- `maxrunning=M` limits simultaneously running tasks (`--array=0-N%M`). +- Each task changes into its case directory listed in a manifest file: the case + directories must be on a filesystem **shared with the compute nodes**. +- `FZ_SLURM_POLL_INTERVAL` (default 2 s) sets the polling period. +- Remote job arrays are not supported: use `slurm://user@host:...` for a remote cluster. -- Submits jobs to SLURM scheduler using `sbatch` -- Monitors job status using `squeue` -- Retrieves results when jobs complete -- Handles job failures and retries +## Resources -### Interrupt Handling +Both schemes accept resources as a query string at the end of the URI: -Press `Ctrl+C` to gracefully terminate SLURM jobs: +| Key | SLURM option | +|-----|--------------| +| `cores` | `--cpus-per-task` | +| `mem` | `--mem` | +| `time` | `--time` | +| `nodes` | `--nodes` | +| `ntasks` | `--ntasks` | +| `gres` | `--gres` | +| `account` | `--account` | +| `qos` | `--qos` | +| `maxrunning` | array throttle (`slurm-array://` only) | -- Cancels all running SLURM jobs using `scancel` -- Cleans up temporary files -- Preserves completed results - -### File Transfer - -For remote execution: - -- Automatically uploads input files to the cluster -- Downloads output files after job completion -- Uses SSH/SCP for secure file transfer - -## Configuration - -### SLURM Script Headers - -The calculator automatically adds appropriate SLURM directives to job scripts: - -```bash -#!/bin/bash -#SBATCH --job-name=fz_case_001 -#SBATCH --output=output_%j.log -#SBATCH --error=error_%j.log -#SBATCH --partition=compute -``` - -### Custom SLURM Options - -You can specify additional SLURM options in your model configuration: - -```python -model = { - "varprefix": "$", - "slurm_options": { - "nodes": 1, - "ntasks": 4, - "time": "01:00:00", - "mem": "8GB" - } -} -``` +Unknown keys are rejected; values must match `[A-Za-z0-9_.:,=-/]+`. -## Examples +## Timeouts and interrupts -### Basic Parametric Study - -```python -import fz - -model = { - "varprefix": "$", - "output": { - "result": "grep 'Result:' output.txt | awk '{print $2}'" - } -} - -results = fz.fzr( - "simulation.input", - { - "temperature": [300, 350, 400, 450], - "pressure": [1.0, 2.0, 3.0] - }, - model, - calculators="slurm://:compute/bash run_simulation.sh", - results_dir="slurm_results", - n_parallel=6 # Submit up to 6 jobs simultaneously -) -``` - -### Multiple Partitions - -Use different partitions for different job types: - -```python -calculators = [ - "slurm://:compute/bash short_job.sh", # Quick jobs - "slurm://:gpu/bash gpu_job.sh" # GPU-intensive jobs -] -``` - -### Remote HPC Cluster - -```python -calculators = "slurm://myuser@hpc.university.edu:compute/python analyze.py" -``` +There is **no default timeout** for SLURM calculators (queue waits are unbounded); a +warning is logged. Set the model's `timeout` or `FZ_RUN_TIMEOUT` to bound a case, +including its time in the queue. Ctrl+C cancels the submitted jobs +([Interrupt & Resume](../running/interrupts.md)). ## Requirements -- SLURM commands must be available: `sbatch`, `squeue`, `scancel` -- For remote execution: SSH access with key-based authentication -- Python `paramiko` package for remote SSH connections - -## Limitations - -- Requires SLURM workload manager installed -- Job scheduling may introduce delays depending on cluster load -- Remote execution requires SSH key authentication (password auth not supported) +- Local: `srun` (and `sbatch`, `sacct`/`squeue` for arrays) on `PATH`. +- Remote: SSH access to a login node with `srun`. +- A job failing with a SLURM state such as `TIMEOUT`, `OUT_OF_MEMORY`, `NODE_FAIL`, + `PREEMPTED` marks the case as failed (then retried). -## Troubleshooting - -### Job Submission Fails - -Check that SLURM is available: - -```bash -which sbatch -sinfo # Check partition availability -``` - -### Partition Not Found - -Verify partition names: +## Check the cluster by hand first ```bash -sinfo -o "%P" +sinfo -o "%P" # partition names +srun --partition=compute bash /home/me/run.sh input.txt ``` -### Remote Connection Issues - -Test SSH connection: - -```bash -ssh user@cluster.example.edu -``` - -Ensure SSH key authentication is configured. - -## See Also +## See also -- [SSH Calculator](ssh.md) - For remote execution without SLURM -- [Local Shell Calculator](shell.md) - For local execution -- [Environment Variables](../../reference/environment.md) - Configuration options +[SSH](ssh.md) ยท [Remote HPC example](../../examples/hpc.md) ยท +[Timeouts](../running/timeouts.md) ยท +[Architecture note](https://github.com/Funz/fz/blob/main/doc/slurm-architecture.md) diff --git a/docs/user-guide/calculators/ssh.md b/docs/user-guide/calculators/ssh.md index d31ad33..889893e 100644 --- a/docs/user-guide/calculators/ssh.md +++ b/docs/user-guide/calculators/ssh.md @@ -1,82 +1,67 @@ -# SSH Remote Calculator (`ssh://`) +# SSH (`ssh://`) -The `ssh://` calculator runs each case on a remote host over SSH. Input files are -uploaded by SFTP, the command runs in a remote temporary directory, and result files are -downloaded back before the outputs are parsed. +`ssh://` runs each case on a remote host: inputs are uploaded by SFTP into a remote +temporary directory, the command runs there, results are downloaded, and the outputs +are parsed locally. Uses `paramiko` (installed with fz). fz itself is not needed on the +remote host. -Requires the `paramiko` package (`pip install paramiko`). - -## URI Syntax - -``` +```text ssh://[user[:password]@]host[:port]/command [arguments] ``` ```python -# Key-based auth (recommended) โ€” uses your ~/.ssh keys / agent -calculators = "ssh://john@compute.edu/bash /home/john/run.sh" - -# Custom port -calculators = "ssh://john@server.edu:2222/bash /path/to/script.sh" - -# Several remote nodes in parallel -calculators = [ - "ssh://user@node1.cluster.edu/bash /path/run.sh", - "ssh://user@node2.cluster.edu/bash /path/run.sh", +calculators = "ssh://john@compute.example.edu/bash /home/john/run.sh" +calculators = "ssh://john@compute.example.edu:2222/bash /home/john/run.sh" +calculators = [ # 2 hosts, 2 cases at a time + "ssh://user@node1/bash /opt/code/run.sh", + "ssh://user@node2/bash /opt/code/run.sh", ] ``` -!!! warning "Use absolute paths for remote commands" - `ssh://user@host/bash script.sh` may not resolve on the remote side. Prefer - `ssh://user@host/bash /absolute/path/to/script.sh`. - -## How It Works - -1. Open an SSH connection (key, agent, URI password, or interactive prompt). -2. Create a remote temporary directory. -3. SFTP-upload the compiled input files (and relative [`input_static`](../core-functions/fzr.md#shared-static-files-new-in-12) files). -4. Run the command remotely in that directory. -5. SFTP-download the result files. -6. Remove the remote temp directory. +Use **absolute remote paths** in the command. As with `sh://`, the input file names are +appended to the command line. ## Authentication | Method | How | |--------|-----| -| SSH key / agent | `ssh://user@host/...` โ€” keys from `~/.ssh/` are tried automatically (recommended) | -| Interactive password | `ssh://user@host/...` โ€” FZ prompts if key auth fails | -| Password in URI | `ssh://user:password@host/...` โ€” insecure, avoid in production | +| SSH key / agent (recommended) | `ssh://user@host/...` โ€” keys from `~/.ssh/` and the agent are used | +| Password in URI | `ssh://user:pw@host/...` โ€” keys and agent are then not tried; the password is masked in results, logs and manifests, but visible in your scripts | -### Host Key Verification +There is no interactive password prompt. When `user@` is omitted, the user name is +`$SSH_USER`, else the local user name. -On first connection to an unknown host, FZ shows the fingerprint and asks whether to -accept it. To skip the prompt (use with care): +## Host keys -```bash -export FZ_SSH_AUTO_ACCEPT_HOSTKEYS=1 -``` +| Situation | Behavior | +|-----------|----------| +| Key authentication, unknown host | Key added automatically (no fingerprint check) | +| Password in URI, unknown host | Interactive prompt `Accept this host key? [y/N/fingerprint]` โ€” blocks unattended runs | +| `FZ_SSH_AUTO_ACCEPT_HOSTKEYS=1` | Key added automatically in all cases | + +When host identity matters, fill `~/.ssh/known_hosts` beforehand and check the +fingerprint. -## Configuration +## Settings -| Variable | Purpose | Default | +| Variable | Default | Meaning | |----------|---------|---------| -| `FZ_SSH_KEEPALIVE` | Keepalive interval, seconds | `300` | -| `FZ_SSH_AUTO_ACCEPT_HOSTKEYS` | Auto-accept unknown host keys | `0` | -| `FZ_RUN_TIMEOUT` | Per-case timeout, seconds | `3600` | +| `FZ_SSH_KEEPALIVE` | `300` | Keepalive interval (s) | +| `FZ_SSH_AUTO_ACCEPT_HOSTKEYS` | `0` | See above | +| `FZ_RUN_TIMEOUT` | unset โ†’ **no timeout** for `ssh://` | Set it (or the model's `timeout`) to bound remote runs | -## Remote Script Example +## Safety measures -```bash title="/home/user/run.sh (on the server)" -#!/bin/bash -source input.txt +- Remote directory names and file names are shell-quoted. +- Remote cleanup (`rm -rf`) is refused outside fz's own `.fz/tmp/fz_calc_*` directories. +- The calculator command itself is run as written: it is your code. -module load gcc/11.2 openmpi/4.1 -mpirun -np 16 ./simulation input.txt -# results written to output.txt, downloaded by FZ -``` +## Static files + +Relative `input_static` files are uploaded for each case; absolute ones must already +exist at the same path on the remote host (shared storage). -## See Also +## See also -- [Local Shell](shell.md) ยท [SLURM](slurm.md) (SSH + `srun`) ยท [Funz Server](funz.md) ยท [Cache](cache.md) -- [Remote HPC Example](../../examples/hpc.md) -- [Environment Variables](../../reference/environment.md#ssh-configuration) +[SLURM](slurm.md) ยท [Remote HPC example](../../examples/hpc.md) ยท +[Timeouts](../running/timeouts.md) diff --git a/docs/user-guide/core-functions/fzc.md b/docs/user-guide/core-functions/fzc.md index 8465430..96cc834 100644 --- a/docs/user-guide/core-functions/fzc.md +++ b/docs/user-guide/core-functions/fzc.md @@ -1,72 +1,74 @@ -# fzc - Compile Input Files +# fzc - Compile -`fzc` turns a template into ready-to-run input files: it substitutes variable values and -evaluates formulas. With list-valued variables it writes one compiled case per -combination (the Cartesian product). - -## Function Signature +`fzc` substitutes values into a template and evaluates its formulas, writing one +compiled copy per case. It runs nothing: use it to check compilation before a study. ```python -fz.fzc(input_path, input_variables, model, output_dir, input_static=None) +fz.fzc(input_path, input_variables=None, model=None, output_dir="output", + input_static=None) -> None ``` -| Parameter | Type | Description | -|-----------|------|-------------| -| `input_path` | `str` | Template file or directory | -| `input_variables` | `dict` | Values โ€” scalar (fixed) or list (varied) | -| `model` | `dict` or `str` | Model definition or alias | -| `output_dir` | `str` | Where compiled files are written | -| `input_static` | `list`, optional | Shared files symlinked into `output_dir` rather than templated/duplicated *(1.2)* | +| Parameter | Description | +|-----------|-------------| +| `input_path` | Template file or directory | +| `input_variables` | `{"x": 1}` (fixed) or `{"x": [1, 2]}` (one case per value, Cartesian product); may be omitted when the template has no variables | +| `model` | Model dict, JSON string/file or alias | +| `output_dir` | Destination (default `output`) | +| `input_static` | Shared files, linked rather than templated | -Returns `None`; the result is the files written under `output_dir`. +## Output layout -## Single Case +When the template declares variables, **each case gets a sub-directory named after its +values, even when all values are scalars**: ```python -model = {"varprefix": "$", "formulaprefix": "@", "delim": "{}", "commentline": "#"} - -fz.fzc("input.txt", {"temp": 25, "pressure": 101.3}, model, "compiled/") -# compiled/input.txt โ€” values substituted +fz.fzc("input.txt", {"T": 25, "P": 1.0}, {"delim": "{}"}, "compiled") +# compiled/T=25,P=1.0/input.txt + +fz.fzc("input.txt", {"T": [10, 20], "P": [1, 10], "V": 1.0}, {"delim": "{}"}, "grid") +# grid/T=10,P=1,V=1.0/input.txt +# grid/T=10,P=10,V=1.0/input.txt +# grid/T=20,P=1,V=1.0/input.txt +# grid/T=20,P=10,V=1.0/input.txt ``` -## Grid of Cases +- An existing `output_dir` is renamed with a timestamp suffix before writing. +- Each case directory also receives `.fz_hash` (SHA-256 of the compiled files). +- A variable that is neither given nor defaulted is left unchanged in the file. +- A template without variables is compiled directly into `output_dir/`. -```python -fz.fzc( - "input.txt", - {"temp": [10, 20, 30], "pressure": [1, 10], "volume": 1.0}, # 3 ร— 2 ร— fixed - model, - "compiled_grid/", -) -# compiled_grid/temp=10,pressure=1/input.txt -# compiled_grid/temp=10,pressure=10/input.txt -# ... 6 directories total -``` +To test a compiled case by hand, run the code **inside the case sub-directory** and +point `fzo` at it (or at `compiled/*`), not at `compiled/`. ## Formulas ```text Temperature: $T_celsius C #@ T_kelvin = $T_celsius + 273.15 -Temperature: @{T_kelvin | 0.00} K +Calculated T: @{T_kelvin | 0.00} K +``` + +gives, for `T_celsius=25`: + +```text +Temperature: 25 C +#@ T_kelvin = 25 + 273.15 +Calculated T: 298.15 K ``` -Formula context lines (`#@ ...`) are evaluated first, then `@{...}` expressions are -replaced. See [Formula Evaluation](../advanced/formulas.md), including the 1.2 -`DecimalFormat` number-formatting patterns. +Context lines stay in the file (with variables substituted). See +[Formulas](../templates/formulas.md). ## CLI ```bash -fzc input.txt -m mymodel -v '{"temp": [10, 20, 30], "pressure": 1.0}' -o compiled/ +fzc input.txt --model mymodel \ + --input_variables '{"T": [10, 20], "P": 1.0}' --output_dir compiled/ ``` -Since **1.2**, `--input_variables` may be omitted when the template declares no -variables. +`--input_variables` accepts inline JSON or a JSON file and may be omitted for a +template without variables; `fzc` has no `--format` option. -## See Also +## See also -- [fzi](fzi.md) โ€” find the variables first -- [fzo](fzo.md) โ€” parse the outputs afterwards -- [fzr](fzr.md) โ€” do all of it in one call -- [Model Definition](../model-definition.md) +[fzi](fzi.md) ยท [fzo](fzo.md) ยท [fzr](fzr.md) ยท [Input Template Syntax](../templates/syntax.md) diff --git a/docs/user-guide/core-functions/fzd.md b/docs/user-guide/core-functions/fzd.md index aa0c28a..f5f79c8 100644 --- a/docs/user-guide/core-functions/fzd.md +++ b/docs/user-guide/core-functions/fzd.md @@ -1,407 +1,157 @@ # fzd - Design of Experiments -The `fzd` function (or `fz design` command) runs iterative design of experiments with adaptive algorithms. Unlike `fzr` which runs a fixed grid of parameter combinations, `fzd` lets algorithms intelligently choose which points to evaluate next based on previous results. +`fzd` runs an iterative loop: an **algorithm** proposes a batch of points, fz evaluates +them (with `fzr` for a file-based model, or by calling a Python function), and the +algorithm uses the results to propose the next batch or stop. Use it for optimization, +calibration/inversion, adaptive sampling, uncertainty propagation. -## When to Use fzd vs fzr +| | `fzr` | `fzd` | +|--|-------|-------| +| Values | Given by you (grid or list) | Chosen by the algorithm within ranges | +| `input_variables` | `{"x": [1, 2, 3]}` | `{"x": "[0;10]", "y": "2.5"}` (strings) | +| Result | DataFrame | Dict with `XY` DataFrame, analysis, summary | -| Feature | `fzr` | `fzd` | -|---------|-------|-------| -| **Parameter values** | You specify exact values | Algorithm chooses from ranges | -| **Design type** | Fixed factorial/custom grid | Adaptive, iterative | -| **Use case** | Parameter sweeps, sensitivity analysis | Optimization, uncertainty quantification | -| **Input format** | `{"x": [1, 2, 3]}` (values) | `{"x": "[0;10]"}` (ranges) | - -## Python API - -### Function Signature +## Signature ```python -import fz - -result = fz.fzd( - input_path, - input_variables, - model, - output_expression, - algorithm, +fz.fzd( + input_path, # template, or None for a Python function model + input_variables, # {"x": "[min;max]"} varied, {"z": "1.5"} fixed + model, # model dict/alias, or a Python callable + output_expression, # "pressure", "a + 2*b", or a list for multi-objective + algorithm, # installed name, glob, or path to a .py/.R file calculators=None, - algorithm_options=None, - analysis_dir="analysis" -) + algorithm_options=None, # dict, JSON string or JSON file path + analysis_dir="analysis", + input_static=None, +) -> dict ``` -### Parameters +| Parameter | Notes | +|-----------|-------| +| `input_variables` | `"[min;max]"` (or `"[min,max]"`) ranges are passed to the algorithm; plain value strings are fixed and merged into every point | +| `output_expression` | Evaluated on each case's outputs. A **list** of expressions gives a vector objective (multi-objective algorithms). May be `None` for a function model | +| `algorithm` | `"brent"` โ†’ `.fz/algorithms/brent.py` (then `~/.fz/algorithms/`), or a file path | +| `calculators` | File-based model: as in `fzr`; omitted โ†’ installed aliases matching the model `id`, else `sh://`. Function model: a positive int (concurrent evaluations, default 1) | +| `analysis_dir` | Output directory; an existing one is renamed with a timestamp and its results reused as cache | -| Parameter | Type | Description | -|-----------|------|-------------| -| `input_path` | `str` | Path to input file or directory | -| `input_variables` | `dict` | Variable ranges: `{"var": "[min;max]"}` or fixed: `{"var": "value"}` | -| `model` | `dict` or `str` | Model definition or alias | -| `output_expression` | `str` or `list` | Expression to evaluate (e.g., `"pressure"` or `"r1 + r2 * 2"`). A **list** of expressions (since 1.2) makes each case yield one scalar per expression โ€” a vector objective for multi-objective algorithms. | -| `algorithm` | `str` | Path to algorithm Python file | -| `calculators` | `str` or `list` | Calculator URI(s) (default: `["sh://"]`) | -| `algorithm_options` | `dict`, `str`, or `None` | Algorithm options as dict, JSON string, or JSON file path | -| `analysis_dir` | `str` | Analysis results directory (default: `"analysis"`) | +## Result -### Return Value +| Key | Content | +|-----|---------| +| `XY` | DataFrame of all evaluated points: inputs and objective(s) | +| `analysis` | Processed output of the algorithm's `get_analysis()` (text, data, HTML/JSON file names) | +| `algorithm` | Algorithm used | +| `iterations` | Number of iterations | +| `total_evaluations` | Number of evaluated points | +| `summary` | e.g. `"randomsampling completed: 1 iterations, 5 evaluations (5 valid)"` | -Returns a dictionary with: - -| Key | Type | Description | -|-----|------|-------------| -| `XY` | `DataFrame` | All sampled input/output values | -| `analysis` | varies | Algorithm analysis results (HTML, plots, metrics) | -| `algorithm` | `str` | Algorithm file path used | -| `iterations` | `int` | Number of algorithm iterations completed | -| `total_evaluations` | `int` | Total number of function evaluations | -| `summary` | `str` | Human-readable summary text | - -## CLI Usage - -### Command Signature - -```bash -fzd --input_path DIR --input_variables VARS --model MODEL \ - --output_expression EXPR --algorithm ALGO \ - [--results_dir DIR] [--calculators CALC] [--options OPTS] -``` +`analysis_dir` contains `X_.csv`, `Y_.csv`, `results_.html` (or `.json`/`.txt` +depending on the analysis content), one `iter/` directory per iteration (cases +named `case_`), and a campaign `manifest.json`. -Or using the main `fz` command: - -```bash -fz design --input_path DIR --input_variables VARS --model MODEL \ - --output_expression EXPR --algorithm ALGO [...] -``` - -### CLI Options - -| Option | Short | Required | Description | -|--------|-------|----------|-------------| -| `--input_path` / `--input_dir` | `-i` | Yes | Input file or directory path | -| `--input_variables` / `--input_vars` / `--variables` | `-v` | Yes | Variable ranges (JSON file or inline JSON) | -| `--model` | `-m` | Yes | Model definition (JSON file, inline JSON, or alias) | -| `--output_expression` | `-e` | Yes | Output expression to optimize | -| `--algorithm` | `-a` | Yes | Algorithm name (`randomsampling`, `brent`, `bfgs`, ...) or file path | -| `--results_dir` | `-r` | No | Results directory (default: `results_fzd`) | -| `--calculators` | `-c` | No | Calculator specifications (URI, alias, or JSON list) | -| `--options` | `-o` | No | Algorithm options (JSON file or inline JSON) | -| `--input_static` | | No | Shared static file (repeatable), never templated or re-hashed per case (new in 1.2) | - -!!! note "Flag aliases (since 1.1)" - `--input_path` and `--input_variables` are the preferred names, consistent with `fzi`, `fzc`, and `fzr`. - The old `--input_dir` and `--input_vars` names remain accepted for backward compatibility. - -## Examples - -### Example 1: Random Sampling - -Explore the parameter space with random sampling: +## Example ```python import fz model = { - "varprefix": "$", - "delim": "()", - "run": "bash -c 'source input.txt && result=$(echo \"scale=6; $x * $x + $y * $y\" | bc) && echo \"result = $result\" > output.txt'", - "output": { - "result": "grep 'result = ' output.txt | cut -d '=' -f2 | tr -d ' '" - } + "delim": "{}", + "output": {"pressure": "python://grep(r'pressure = (\\S+)', 'output.txt')"}, } result = fz.fzd( - input_path="input/", - input_variables={"x": "[-2;2]", "y": "[-2;2]"}, - model=model, - output_expression="result", - algorithm="examples/algorithms/randomsampling.py", - algorithm_options={"nvalues": 20, "seed": 42} -) - -print(f"Total evaluations: {result['total_evaluations']}") -df = result['XY'] -best = df.loc[df['result'].idxmin()] -print(f"Best: x={best['x']:.4f}, y={best['y']:.4f}, result={best['result']:.6f}") -``` - -### Example 2: 1D Optimization (Brent's Method) - -Find the minimum of a 1D function: - -```python -result = fz.fzd( - input_path="input/", - input_variables={"x": "[0;2]"}, - model=model_1d, - output_expression="result", - algorithm="examples/algorithms/brent.py", - algorithm_options={"max_iter": 20, "tol": 1e-3} + "input.txt", + {"T_celsius": "[0;100]", "V_L": "[1;5]", "n_mol": "1"}, + model, + output_expression="pressure", + algorithm="examples/algorithms/montecarlo_uniform.py", # path to the file + calculators=["sh://bash calculate.sh"] * 4, # 4 points at a time + algorithm_options={"batch_sample_size": 20, "max_iterations": 10, "seed": 123}, + analysis_dir="mc_analysis", ) - -df = result['XY'] -best = df.loc[df['result'].idxmin()] -print(f"Optimal x = {best['x']:.6f} (expected: 0.7)") +print(result["summary"]) +print(result["XY"].describe()) ``` -### Example 3: Multi-dimensional Optimization (BFGS) +## Output expressions -Find the minimum of a multi-dimensional function with parallel evaluations: +Available names: the model outputs, `abs`, `min`, `max`, `pow`, `sqrt`, `exp`, `log`, +`log10`, `sin`, `cos`, `tan`, `asin`, `acos`, `atan`, `atan2`, `pi`, `e`, and for vector +outputs `sum`, `len`, `sorted`, `mean`, `median`, `stdev`, `variance`, `zip`, indexing +and slicing. ```python -result = fz.fzd( - input_path="input/", - input_variables={"x": "[-2;2]", "y": "[-2;2]"}, - model=model, - output_expression="result", - algorithm="examples/algorithms/bfgs.py", - algorithm_options={"max_iter": 20, "tol": 1e-4}, - calculators=["sh://bash calc.sh"] * 4 # 4 parallel evaluators -) +output_expression = "r1 + 2 * r2" +output_expression = "T_series[-1]" # last value +output_expression = "sqrt(sum((x - y)**2 for x, y in zip(sim, ref)) / len(sim))" # RMSE +output_expression = ["f1", "-f2"] # multi-objective: minimize f1, maximize f2 ``` -### Example 4: Monte Carlo with Convergence +A vector output used without reduction makes that point fail (reported, non-fatal). +Multi-objective algorithms (e.g. `nsga2.py`) minimize every objective; negate an +expression to maximize it. -Run Monte Carlo sampling until a confidence interval target is reached: +## Python function as model -```python -result = fz.fzd( - input_path="input.txt", - input_variables={ - "n_mol": "[0;10]", - "T_celsius": "[0;100]", - "V_L": "[1;5]" - }, - model=perfectgas_model, - output_expression="pressure + 1", - algorithm="examples/algorithms/montecarlo_uniform.py", - calculators=["sh://bash PerfectGazPressure.sh"] * 10, - algorithm_options={ - "batch_sample_size": 20, - "max_iterations": 50, - "confidence": 0.90, - "target_confidence_range": 1000000, - "seed": 123 - }, - analysis_dir="fzd_analysis" -) -``` - -### Example 5: Custom Output Expression - -Combine multiple model outputs in an expression: +With a callable model, no files and no calculators are involved: ```python -result = fz.fzd( - input_path="input/", - input_variables={"x": "[-2;2]", "y": "[-2;2]"}, - model=model_multi_output, - output_expression="r1 + r2 * 2", # Custom expression - algorithm="examples/algorithms/randomsampling.py", - algorithm_options={"nvalues": 20, "seed": 42} -) +def branin(x, y): + import math + return (y - 5.1 / (4 * math.pi**2) * x**2 + 5 / math.pi * x - 6)**2 \ + + 10 * (1 - 1 / (8 * math.pi)) * math.cos(x) + 10 + +result = fz.fzd(None, {"x": "[-5;10]", "y": "[0;15]"}, branin, + output_expression=None, algorithm="examples/algorithms/bfgs.py", + calculators=1) ``` -Available expression operators: `+`, `-`, `*`, `/`, `**`, `abs()`, `min()`, `max()`, `sqrt()`, `exp()`, `log()`, `pi`, `e`. +- `input_path` must be `None`; `input_variables` keys are the function's parameters. +- `output_expression=None` takes the return value (or its first item/key). +- `calculators=1` (default) calls the function sequentially in the calling thread. + `calculators=N` uses N threads: the function must be thread-safe, and any error then + aborts `fzd` with `fz.FunctionModelParallelError`. +- Each iteration directory contains only `values.csv`. -Since **1.2**, if a model output is itself vector-valued (a time series, a profile), the -expression can reduce it to a scalar with `sum()`, `len()`, `sorted()`, `mean()`, -`median()`, `stdev()`, `variance()`, indexing/slicing (`T_series[-1]`), and `zip()` โ€” e.g. -`sqrt(sum((x - y) ** 2 for x, y in zip(sim, ref)) / len(sim))` for an RMSE against a -reference series. Referencing a vector output without reducing it raises a clear -`ValueError` for that point (reported as a failed evaluation, non-fatal). +## Behaviors -### Example 7: Multi-Objective (Vector Objective) +- **Deduplication**: identical points within a batch are evaluated once. +- **Cross-iteration cache**: a point already evaluated is not re-run. +- **Re-run**: an existing `analysis_dir` is renamed with a timestamp and its iterations + serve as cache for the new run. +- Iterations use `case_naming="index"` (`iter001/case_0/`), whatever `FZ_CASE_NAMING`. +- `input_static` is passed to every iteration's `fzr`. -Pass a **list** of expressions โ€” each case yields one scalar per expression, handed -as-is to a multi-objective algorithm such as NSGA-II: +## Algorithms -```python -result = fz.fzd( - input_path="input/", - input_variables={"x": "[-2;2]", "y": "[-2;2]"}, - model=model, - output_expression=["f1", "-f2"], # minimise f1, maximise f2 (negated) - algorithm="examples/algorithms/nsga2.py", - algorithm_options={"pop_size": 24, "generations": 15, "seed": 42}, -) -# result['XY'] gains one column per objective; -# the Pareto front is in result['analysis']['data'] and nsga2_pareto.csv -``` +| Source | How to use | +|--------|------------| +| Examples shipped in the fz repository: `randomsampling.py`, `montecarlo_uniform.py`, `brent.py`, `bfgs.py`, `nsga2.py` ([examples/algorithms](https://github.com/Funz/fz/tree/main/examples/algorithms)) | Pass the file path, or copy it to `.fz/algorithms/` and use its name | +| Installable `fz-` repositories (e.g. `fz-brent`, `fz-PSO`, `fz-gradientdescent`) | `fz install algorithm brent`, then `algorithm="brent"` | +| Your own | [Writing Algorithms](../design/algorithms.md) | -All objectives are minimised โ€” negate an expression to maximise it. +Each algorithm file lists its options and defaults in its `#options:` header. -### Example 6: CLI Usage +## CLI ```bash -# Random sampling -fzd --input_path input/ --model perfectgas \ - --input_variables '{"x": "[-2;2]", "y": "[-2;2]"}' \ - --output_expression "result" \ - --algorithm examples/algorithms/randomsampling.py \ - --options '{"nvalues": 20, "seed": 42}' - -# Algorithm options from a JSON file -fzd --input_path input/ --model perfectgas \ - --input_variables '{"x": "[-2;2]"}' \ - --output_expression "result" \ - --algorithm examples/algorithms/brent.py \ - --options algo_config.json \ - --results_dir optimization_results/ - -# As fz subcommand (short flags) -fz design -i input/ -m perfectgas \ - -v '{"x": "[-2;2]", "y": "[-2;2]"}' \ - -e "result" \ - -a examples/algorithms/bfgs.py +fzd --input_dir input.txt --model perfectgas \ + --input_vars '{"T_celsius": "[0;100]", "V_L": "[1;5]", "n_mol": "1"}' \ + --output_expression "pressure" \ + --algorithm examples/algorithms/montecarlo_uniform.py \ + --options '{"batch_sample_size": 20, "max_iterations": 10}' \ + --results_dir mc_analysis ``` -## Algorithm Options Formats - -Algorithm options can be provided in three formats: - -=== "Dict (Python API)" - - ```python - algorithm_options={"batch_size": 20, "max_iterations": 10, "seed": 42} - ``` - -=== "JSON String (CLI)" - - ```bash - --options '{"batch_size": 20, "max_iterations": 10, "seed": 42}' - ``` - -=== "JSON File" - - ```bash - --options algo_config.json - ``` - -## Input Variables: Ranges vs Fixed Values - -`input_variables` accepts two kinds of entries: - -| Format | Example | Behaviour | -|--------|---------|-----------| -| Range | `"[min;max]"` or `"[min,max]"` | Handed to the algorithm; it decides which values to sample | -| Fixed | `"5.0"` (a plain number string) | Constant โ€” merged into every design point unchanged, never varied | - -```python -# x and y are explored; z is fixed at 1.5 for every evaluation -result = fz.fzd( - input_path="input/", - input_variables={"x": "[-2;2]", "y": "[-2;2]", "z": "1.5"}, - ... -) -``` - -## Automatic Behaviors - -### Batch Deduplication - -Within each iteration, duplicate design points proposed by the algorithm are evaluated only once. The results are re-mapped so the algorithm receives the correct output for every point it requested, including duplicates. This prevents redundant expensive evaluations when an algorithm proposes the same point twice. - -### Cross-Iteration Caching - -Results from previous iterations are automatically reused โ€” a point evaluated in iteration 2 is never re-run in iteration 5. No extra configuration is required. - -### Re-Run Resume - -If `analysis_dir` already exists when `fzd` starts, it is **renamed** with a timestamp suffix (e.g., `analysis_2026-04-27_10-30-00`) and the original path is used for the new run. The renamed directory's iteration subdirectories are still added to the cache, so a re-run with different algorithm options benefits from all prior computations automatically. - -```python -# Re-run after an interrupted or exploratory first run โ€” prior results reused as cache -result = fz.fzd( - input_path="input/", - input_variables={"x": "[-2;2]"}, - algorithm="examples/algorithms/bfgs.py", - analysis_dir="my_analysis" # if exists โ†’ renamed; its cache still consulted -) -``` - -## Available Algorithms - -FZ ships with example algorithms in `examples/algorithms/`: - -| Algorithm | File | Type | Best For | -|-----------|------|------|----------| -| Random Sampling | `randomsampling.py` | Exploration | Initial exploration, baselines | -| Brent's Method | `brent.py` | 1D optimization | Precise 1D root finding/optimization | -| BFGS | `bfgs.py` | Multi-D optimization | Smooth multi-dimensional optimization | -| Monte Carlo | `montecarlo_uniform.py` | Integration | Uncertainty quantification | -| NSGA-II | `nsga2.py` | Multi-objective optimization | Pareto fronts โ€” requires a **list** `output_expression` (new in 1.2) | - -!!! tip "Choosing an Algorithm" - - **1D problems**: Use Brent's method - - **2-10D smooth problems**: Use BFGS - - **Exploratory / non-smooth**: Use random sampling - - **Uncertainty quantification**: Use Monte Carlo - -## Writing Custom Algorithms - -Custom algorithms must implement a Python class with the following interface: - -```python -class MyAlgorithm: - - def __init__(self, options): - """Initialize with algorithm-specific options dict.""" - self.batch_size = int(options.get("batch_size", 10)) - - def get_initial_design(self, input_variables, output_variables): - """Return initial list of sample points. - - Args: - input_variables: dict of {"var_name": "[min;max]"} ranges - output_variables: list of output variable names - - Returns: - List of dicts, each dict is one sample point: - [{"x": 1.0, "y": 2.0}, {"x": 3.0, "y": 4.0}, ...] - """ - pass - - def get_next_design(self, X, Y): - """Return next sample points or None to stop. - - Args: - X: list of input dicts evaluated so far - Y: list of output values evaluated so far - - Returns: - List of dicts for next batch, or None to stop iteration. - """ - pass - - def get_analysis(self, X, Y): - """Return analysis results (called at end). - - Args: - X: all input dicts - Y: all output values - - Returns: - HTML string, dict, or any serializable result. - """ - pass -``` - -### Algorithm File Header - -Algorithm files should include metadata in comments: - -```python -#title: My Algorithm Name -#author: Author Name -#type: sampling|optimization -#options: batch_size=10;max_iterations=100;seed=42 -#require: numpy;scipy -``` +- Also `fz design ...`. `--input_path`/`--input_variables`/`--variables` are accepted + aliases of `--input_dir`/`--input_vars`. +- Differences with Python: the default directory is `results_fzd` (Python: + `analysis`); `--output_expression` takes a single expression (no multi-objective + list); there is no `--format` option (a summary is printed); function models are not + available. -## See Also +## See also -- [fzr](fzr.md) - Run fixed parametric studies -- [fzi](fzi.md) - Parse input variables -- [fzl](fzl.md) - List available models/calculators -- [Algorithm Options Example](https://github.com/Funz/fz/blob/main/examples/algorithm_options_example.md) -- [FZD Examples](https://github.com/Funz/fz/blob/main/examples/fzd_example.md) +[Writing Algorithms](../design/algorithms.md) ยท [fzr](fzr.md) ยท +[Installing Models & Algorithms](../installing.md) ยท [Caching](../running/caching.md) diff --git a/docs/user-guide/core-functions/fzi.md b/docs/user-guide/core-functions/fzi.md index a1d0a04..49c9664 100644 --- a/docs/user-guide/core-functions/fzi.md +++ b/docs/user-guide/core-functions/fzi.md @@ -1,67 +1,57 @@ -# fzi - Parse Input Variables +# fzi - Parse Input -`fzi` scans an input file or directory and reports every **variable** it finds โ€” the -placeholders your templates expect you to fill in. It reads nothing else and runs -nothing; it is the discovery step. - -## Function Signature +`fzi` scans a template (file or directory) and reports the variables, static objects +and formulas it contains. It runs nothing. ```python -fz.fzi(input_path, model, input_static=None) +fz.fzi(input_path, model, input_static=None) -> dict ``` -| Parameter | Type | Description | -|-----------|------|-------------| -| `input_path` | `str` | Input file or directory (scanned recursively) | -| `model` | `dict` or `str` | Model definition or alias โ€” only the syntax fields matter here (`varprefix`, `delim`, `formulaprefix`, `commentline`) | -| `input_static` | `list`, optional | Shared files to ignore while scanning โ€” never templated *(1.2)* | - -## Returns - -A `dict` mapping each variable name to `None` (a template ready to be filled with -values): - -```python -model = {"varprefix": "$", "delim": "{}"} -# input.txt: Temperature: ${temp}, Pressure: ${pressure} +| Parameter | Description | +|-----------|-------------| +| `input_path` | Template file or directory (scanned recursively) | +| `model` | Model dict, JSON string/file or alias; only the syntax fields are used | +| `input_static` | Files excluded from scanning ([shared static files](../running/results.md#shared-static-files-input_static)) | -fz.fzi("input.txt", model) -# {'temp': None, 'pressure': None} -``` +## Result -## Variables vs Formulas +A dict whose keys are: -Names that appear only inside a formula (`@{...}`) or that are *defined* in a formula -context line (`#@ ...`) are **not** variables: +- each **variable** โ†’ `None`, or its `~default`; +- each **static object** declared with `#@:` โ†’ its value; +- each **formula expression** โ†’ its value when computable from defaults, else `None`. -```text -n_mol=$n_mol -T_celsius=$T_celsius -#@ T_kelvin = $T_celsius + 273.15 -T_kelvin=@{T_kelvin} +```text title="input.txt" +a=${a~3} +b=$b +#@: K = 10 +c=@{$a*2} ``` ```python -model = {"varprefix": "$", "formulaprefix": "@", "delim": "{}", "commentline": "#"} -fz.fzi("input.txt", model) -# {'n_mol': None, 'T_celsius': None} โ€” T_kelvin is a formula result, not an input +fz.fzi("input.txt", {"delim": "{}"}) +# {'K': '10', 'a': 3, 'b': None, 'a*2': 6} ``` -## Typical Uses - -- Discover the parameters of a legacy input deck. -- Validate that you are supplying every value a template needs before an `fzr` run. -- Auto-generate a parameter list for documentation. +The variables to provide to `fzc`/`fzr` are the variable names (`a`, `b`), not the +formula keys. ## CLI ```bash -fzi input.txt -m mymodel -fzi input_dir/ -m mymodel --format json +fzi input.txt --model mymodel --format json +fzi case_dir/ --delim '{}' --format json # inline model fields, no alias ``` -## See Also +`--format`: `json`, `csv`, `html`, `markdown` (default), `table`. + +## Use it to + +- check that the model's markers match the template: stray variables mean `varprefix` + collides with the code's syntax; missing `${x}` variables mean `delim` is not `{}` + ([defaults](../models/definition.md#default-delimiters)); +- list the parameters of an existing input deck. + +## See also -- [fzc](fzc.md) โ€” substitute values into the template -- [fzr](fzr.md) โ€” the full run that calls `fzi` โ†’ `fzc` โ†’ execute โ†’ `fzo` -- [Model Definition](../model-definition.md) ยท [Formula Evaluation](../advanced/formulas.md) +[Input Template Syntax](../templates/syntax.md) ยท [fzc](fzc.md) ยท [fzr](fzr.md) diff --git a/docs/user-guide/core-functions/fzl.md b/docs/user-guide/core-functions/fzl.md index fd05153..0dff967 100644 --- a/docs/user-guide/core-functions/fzl.md +++ b/docs/user-guide/core-functions/fzl.md @@ -1,98 +1,56 @@ -# fzl - List and Validate Models/Calculators +# fzl - List Models and Calculators -The `fzl` function (or `fz list` command) lists and validates installed models and calculators. +`fzl` (or `fz list`) lists the model and calculator aliases found in `./.fz/` and +`~/.fz/`, which calculators support which model, and optionally checks them. -## Command Signature - -```bash -fzl [--models PATTERN] [--calculators PATTERN] [--check] [--format FORMAT] +```python +fz.fzl(models="*", calculators="*", check=False) -> dict ``` -Or using the main `fz` command: - ```bash -fz list [--models PATTERN] [--calculators PATTERN] [--check] [--format FORMAT] -``` - -## Options - -- `--models PATTERN` - Glob pattern to filter models (default: "*") -- `--calculators PATTERN` - Glob pattern to filter calculators (default: "*") -- `--check` - Validate model/calculator integrity -- `--format FORMAT` - Output format: `markdown` (default), `json`, or `table` - -## Returns - -Lists installed models and calculators with: -- Model names and supported calculators -- Calculator URIs and supported models -- Validation status (when `--check` is used) - -## Examples - -### List All Models and Calculators - -```bash -fzl --models "*" --calculators "*" -``` - -### List Specific Models - -```bash -fzl --models "perfect*" -``` - -### Validate Models and Calculators - -```bash -fzl --models "*" --calculators "*" --check -``` - -### JSON Output - -```bash -fzl --models "*" --calculators "*" --format json -``` - -### Table Format - -```bash -fzl --models "*" --calculators "*" --format table -``` - -## Use Cases - -- **Discovery**: Find available models and calculators -- **Validation**: Check that models and calculators are properly configured -- **Integration**: Verify calculator-model compatibility -- **Debugging**: Identify configuration issues - -## Example Output - -```markdown -# Models - -## perfectgas -- Supported calculators: localhost_perfectgas, ssh://remote/perfectgas - -## mcnp -- Supported calculators: localhost_MCNP, ssh://remote/MCNP - -# Calculators - -## localhost_perfectgas -- URI: sh://bash calculate.sh -- Supported models: perfectgas - -## localhost_MCNP -- URI: sh://bash mcnp.sh -- Supported models: mcnp -``` - -## See Also - -- [fzi](fzi.md) - Parse input variables -- [fzc](fzc.md) - Compile input files -- [fzo](fzo.md) - Parse output files -- [fzr](fzr.md) - Run parametric study -- [fzd](fzd.md) - Design of experiments with adaptive algorithms +fzl [--models PATTERN] [--calculators PATTERN] [--check] [--format json|markdown|table] +fz list ... # same +``` + +| Option | Meaning | +|--------|---------| +| `--models`, `-m` | Glob on model alias names (default `*`) | +| `--calculators`, `-c` | Glob or regex on calculator alias names (default `*`) | +| `--check` | Validate each model (JSON structure) and calculator (test run) | +| `--format`, `-f` | `markdown` (default), `json`, `table` โ€” no `csv`/`html` | + +## Result + +```json +{ + "models": { + "perfectgas": { + "path": "/project/.fz/models/perfectgas.json", + "properties": {"id": "perfectgas", "delim": "{}", "output": {"pressure": "..."}}, + "supported_calculators": ["sh://"], + "check_status": "passed" + } + }, + "calculators": { + "sh://": {"supports_models": "all", "check_status": "failed", + "check_error": "Empty sh:// command"} + } +} +``` + +## Limitations + +- Calculators are listed by their `uri`, not by alias file name. +- An alias whose command is in its `models` map โ€” `{"uri": "sh://", "models": + {"perfectgas": "bash calculate.sh"}}`, the layout used by installed wrappers โ€” is + reported `failed` / `Empty sh:// command` by `--check`, although `fzr` runs it + correctly. Check such an alias with a real run: `fzr ... --model perfectgas` without + `--calculators`. +- Algorithms are not listed: use `ls .fz/algorithms ~/.fz/algorithms` or + `fz.list_installed_algorithms()`. + +## See also + +[.fz Directory & Aliases](../../reference/configuration.md) ยท +[Installing Models & Algorithms](../installing.md) ยท +[Calculators](../calculators/overview.md) diff --git a/docs/user-guide/core-functions/fzo.md b/docs/user-guide/core-functions/fzo.md index 45c2c19..5fe876c 100644 --- a/docs/user-guide/core-functions/fzo.md +++ b/docs/user-guide/core-functions/fzo.md @@ -1,86 +1,55 @@ -# fzo - Parse Output Files +# fzo - Parse Output -The `fzo` function reads and parses calculation results from output directories. - -## Function Signature +`fzo` runs the model's output extractors in one or several existing directories and +returns one row per directory. ```python -fz.fzo(output_dir, model) +fz.fzo(output_path, model) -> pandas.DataFrame ``` -## Parameters - -- `output_dir` (str): Path to results directory -- `model` (dict): Model definition with output commands - -## Returns - -pandas DataFrame with results - -## Example +| Parameter | Description | +|-----------|-------------| +| `output_path` | A directory, or a glob such as `results/*` | +| `model` | Model dict, JSON string/file or alias; only `output` is used | ```python -import fz +model = {"output": {"pressure": "python://grep(r'pressure = (\\S+)', 'output.txt')"}} -model = { - "output": { - "pressure": "grep 'Pressure:' output.txt | awk '{print $2}'" - } -} - -results = fz.fzo("results", model) -print(results) +fz.fzo("results/*", model) +# path pressure T_celsius V_L +# 0 results/T_celsius=10,V_L=1 2354109.1 10 1 +# ... ``` -## Output Command Forms - -Each entry in `model["output"]` names a result column and says how to extract it. Since -**1.2**, several forms are available and can be freely mixed in one model: +## Columns -| Form | Example | Requires | -|------|---------|----------| -| Shell command (implicit default, or `bash://`) | `"grep 'P:' out.txt \| awk '{print $2}'"` | bash + Unix tools | -| `python://` โ€” native Python expression | `"python://grep(r'P: (\\S+)', 'out.txt')"` | nothing | -| `jq://` โ€” JSON via a [jq](https://jqlang.org/) filter | `"jq://.energy results.json"` | `jq` on `PATH` | -| `yq://` โ€” YAML/JSON/XML/TOML via [mikefarah/yq](https://github.com/mikefarah/yq) | `"yq://.metadata.version config.yaml"` | `yq` on `PATH` | -| `xpath://` โ€” XML via `xmllint --xpath` | `"xpath://'//result/T/text()' out.xml"` | `xmllint` on `PATH` | -| Python callable (Python API only) | `lambda d: float((d / "final.txt").read_text())` | nothing | +- `path`: the directory parsed. +- One column per `output` entry ([Output Extraction](../models/outputs.md)). +- Variable columns recovered from the directory name `var1=val1,var2=val2,...`; with + `case_naming="hash"`/`"index"` they come from `cases.csv` at the results root, or from + each case's `info.txt`. +- `_output_error` when an extractor failed. -The `python://` helpers available in expressions: `read(path)`, `lines(path)`, -`line(path, n)`, `grep(pattern, path, group=None, all=False, cast=True)`, -`json_file(path)`, `csv_file(path, column=None)`, `hdf5_file(path, dataset=None)` (needs -the optional `h5py` package), plus the modules `re`, `json`, `math`, `statistics`, `np`, -`pd`, and `Path`. Relative paths resolve against the case result directory. `grep` -returns the first capture group, cast to int/float when possible; `all=True` returns a -list of every match. +!!! warning "Target the case directories" + `fzo("results", model)` runs the extractors in `results/` itself and returns one row + of `None`. Use `fzo("results/*", model)` or a single case directory. -The shell-free forms (`python://`, `jq://`, `yq://`, `xpath://`) need no bash and are -fully portable on Windows. +## CLI -### Vector (Array) Outputs +```bash +fzo 'results/*' --model mymodel --format json +fzo results/case_3 --output-cmd 'P=grep "P =" output.txt | cut -d= -f2' --format csv +``` -An output entry may resolve to a **list** rather than a scalar โ€” a time series, a -spatial profile, a spectrum. `fzo` / `fzr` store the full list per case as-is, with no -flattening, truncation, or padding; different cases may have different lengths. +`--format`: `json`, `csv`, `html`, `markdown` (default), `table`. Quote globs so that +fz, not the shell, expands them. -```python -model = { - "output": { - "T_series": "python://csv_file('temps.csv', column='T')", - "spectrum": "jq://.spectrum results.json", - "residuals": "python://grep(r'res=(\\S+)', 'log.txt', all=True)", - } -} -``` +## Use it to -!!! warning "Persisting vectors" - `--format csv` / `to_csv()` stringifies lists (`"[1, 2, 3]"`). Use `--format json`, - `to_pickle`, or `to_parquet` for a lossless round trip. The plain-shell / `bash://` - form also unwraps a single-element array to a scalar โ€” prefer `python://` / `jq://` / - `yq://` / `xpath://` when a vector's length can legitimately be 1. +- re-parse a finished study with a new or corrected output definition, without + re-running it; +- check an extractor on one manually run case before a study. -## See Also +## See also -- [Model Definition](../model-definition.md) โ€” the `output` field in context -- [fzr](fzr.md) โ€” runs `fzo` automatically at the end of a study -- [Output extraction reference in the FZ repo](https://github.com/Funz/fz/blob/main/doc/model-definition.md) +[Output Extraction](../models/outputs.md) ยท [fzr](fzr.md) ยท [Results & Traceability](../running/results.md) diff --git a/docs/user-guide/core-functions/fzr.md b/docs/user-guide/core-functions/fzr.md index 04ae2a2..4565662 100644 --- a/docs/user-guide/core-functions/fzr.md +++ b/docs/user-guide/core-functions/fzr.md @@ -1,635 +1,143 @@ -# fzr - Run Parametric Study +# fzr - Run a Parametric Study -The `fzr` function orchestrates complete parametric studies by combining all FZ capabilities: parsing inputs, compiling cases, executing calculations, and collecting results. - -## Function Signature +`fzr` compiles every case, runs it on the calculators, parses the outputs and returns a +DataFrame. It is `fzc` + execution + `fzo` for a whole design. ```python fz.fzr( input_path, - input_variables, - model, - calculators, + input_variables=None, + model=None, results_dir="results", - **kwargs -) -``` - -## Parameters - -| Parameter | Type | Required | Description | -|-----------|------|----------|-------------| -| `input_path` | `str` | Yes | Path to input file or directory | -| `input_variables` | `dict` | Yes | Dictionary of variable names and values | -| `model` | `dict` or `str` | Yes | Model definition or model alias name | -| `calculators` | `str` or `list` | Yes | Calculator URI(s) | -| `results_dir` | `str` | No | Output directory (default: "results") | -| `callbacks` | `list` | No | List of callback functions for progress monitoring (new in 0.9.1) | -| `input_static` | `list` | No | Files identical across every case โ€” never templated, re-hashed, or duplicated per case (new in 1.2) | -| `case_naming` | `str` | No | Case directory naming: `"path"` (default), `"hash"`, or `"index"` (new in 1.2) | -| `timeout` | `int` | No | Per-run timeout in seconds; overrides the model `"timeout"` and `FZ_RUN_TIMEOUT` (default `3600`) | - -## Returns - -**pandas.DataFrame** - Results with columns for: - -- All input variables -- All output variables defined in model -- Metadata: `status`, `calculator`, `error`, `command` - -## Basic Usage - -### Simple Parametric Study - -```python -import fz - -model = { - "varprefix": "$", - "output": { - "result": "cat output.txt" - } -} - -results = fz.fzr( - input_path="input.txt", - input_variables={"temperature": [100, 200, 300]}, - model=model, - calculators="sh://bash calculate.sh", - results_dir="results" -) - -print(results) + calculators=None, + callbacks=None, + timeout=None, + case_naming=None, + input_static=None, +) -> pandas.DataFrame ``` -### Full Factorial Design - -```python -results = fz.fzr( - "input.txt", - { - "pressure": [1, 10, 100], # 3 values - "temperature": [300, 400, 500], # 3 values - "concentration": 0.5 # Fixed - }, # Total: 3 ร— 3 = 9 cases - model, - calculators="sh://bash calc.sh" -) -``` +!!! danger "Pass `calculators` and `results_dir` by keyword" + `results_dir` is the **4th** positional parameter. `fz.fzr("in.txt", vars, model, + "sh://bash run.sh")` uses the URI as a directory name, runs without calculator and + every case fails. -## Variable Handling - -### Scalar Variables - -Fixed values for all cases: - -```python -results = fz.fzr( - "input.txt", - { - "param1": 100, # Fixed - "param2": "value", # Fixed string - "param3": [1, 2, 3] # Variable - }, - model, - calculators="sh://bash calc.sh" -) -# Creates 3 cases -``` +## Parameters -### List Variables +| Parameter | Description | +|-----------|-------------| +| `input_path` | Template file or directory | +| `input_variables` | Design: dict (factorial) or DataFrame (one row per case). Optional when the template has no variables โ€” then pass `model=` by keyword | +| `model` | Model dict, JSON string/file or alias ([Model Definition](../models/definition.md)) | +| `results_dir` | Results root (default `results`); an existing one is renamed with a timestamp | +| `calculators` | URI, alias, dict, or list of them. Omitted: installed aliases matching the model `id`, else `sh://` ([Calculators](../calculators/overview.md)) | +| `callbacks` | Dict of progress callbacks (below) | +| `timeout` | Seconds per case; overrides the model's `timeout` and `FZ_RUN_TIMEOUT` ([Timeouts](../running/timeouts.md)) | +| `case_naming` | `"path"` (default, or `FZ_CASE_NAMING`), `"hash"`, `"index"` ([Results](../running/results.md#case-directory-naming)) | +| `input_static` | Files identical for every case, never templated ([Results](../running/results.md#shared-static-files-input_static)) | -Creates Cartesian product: +## Designs ```python -results = fz.fzr( - "input.txt", - { - "x": [1, 2], # 2 values - "y": [10, 20, 30] # 3 values - }, - model, - calculators="sh://bash calc.sh" -) -# Creates 2 ร— 3 = 6 cases -``` +# Full factorial: lists are crossed, scalars are fixed (3 x 2 = 6 cases) +fz.fzr("input.txt", {"T": [10, 20, 30], "P": [1, 10], "V": 1.0}, model, + calculators="sh://bash calc.sh") -### Large Parameter Spaces +# Explicit cases: one row per case (LHS, imported plan, constrained combinations) +import pandas as pd +design = pd.DataFrame({"T": [10, 20, 10], "P": [1, 1, 10]}) +fz.fzr("input.txt", design, model, calculators="sh://bash calc.sh") -```python +# numpy arrays are accepted as lists import numpy as np - -results = fz.fzr( - "input.txt", - { - "param1": np.linspace(0, 10, 50), # 50 values - "param2": np.logspace(-3, 3, 20), # 20 values - "param3": [0.1, 0.5, 1.0] # 3 values - }, # Total: 50 ร— 20 ร— 3 = 3000 cases - model, - calculators=["sh://bash calc.sh"] * 8 # 8 parallel workers -) +fz.fzr("input.txt", {"T": np.linspace(0, 100, 11)}, model, calculators="sh://bash calc.sh") ``` -## Calculator Options +## Result -### Single Calculator +One row per case, in design order: -```python -results = fz.fzr( - "input.txt", - variables, - model, - calculators="sh://bash calculate.sh" -) -``` - -### Multiple Calculators (Parallel) +| Column | Content | +|--------|---------| +| variables | The case's values | +| outputs | One column per `output` entry (dict outputs expand to `name_key` columns) | +| `path` | Case result directory | +| `status` | `done`, `failed`, `error`, `timeout` or `interrupted` | +| `calculator` | Calculator used (`cache://...` for a cache hit), with a short id suffix | +| `error` | Error message, including `Missing output: ...` when an extractor failed | +| `command` | Command actually executed (paths made absolute) | ```python -results = fz.fzr( - "input.txt", - variables, - model, - calculators=[ - "sh://bash calc.sh", - "sh://bash calc.sh", - "sh://bash calc.sh", - "sh://bash calc.sh" - ] # 4 parallel workers -) +failed = results[results["status"] != "done"] +print(failed[["path", "status", "error"]]) ``` -### Failover Chain - -```python -results = fz.fzr( - "input.txt", - variables, - model, - calculators=[ - "cache://previous_results", # Try cache - "sh://bash fast_method.sh", # Fast method - "sh://bash robust_method.sh", # Backup - "ssh://user@hpc/bash remote.sh" # Remote fallback - ] -) -``` +Each case directory contains the compiled inputs, the files written by the code, and +`out.txt`, `err.txt`, `log.txt`, `info.txt`, `history.txt`, `.fz_hash`. The results root +contains `manifest.json` and `ro-crate-metadata.json`. See +[Results & Traceability](../running/results.md). -### Remote Execution +## Calculators ```python -results = fz.fzr( - "input.txt", - variables, - model, - calculators="ssh://user@server.com/bash /path/to/calculate.sh" -) +calculators="sh://bash calc.sh" # 1 case at a time +calculators=["sh://bash calc.sh"] * 4 # 4 at a time +calculators=["cache://previous", "sh://bash calc.sh"] # reuse, then compute +calculators="cluster" # alias in .fz/calculators/ ``` -## Model Options +Failed attempts are retried on the calculators, up to `FZ_MAX_RETRIES` (default 5) +failures per case. See [Parallelism & Retries](../running/parallel.md). -### Dictionary Model +## Callbacks -```python -model = { - "varprefix": "$", - "formulaprefix": "@", - "delim": "()", - "commentline": "#", - "output": { - "pressure": "grep 'P:' output.txt | awk '{print $2}'", - "temperature": "grep 'T:' output.txt | awk '{print $2}'" - } -} +`callbacks` is a **dict** with any of these keys (others raise `ValueError`): -results = fz.fzr("input.txt", variables, model, calculators) -``` - -### Model Alias - -Save model to `.fz/models/mymodel.json`: - -```json -{ - "varprefix": "$", - "output": { - "result": "cat output.txt" - } -} -``` - -Use by name: - -```python -results = fz.fzr("input.txt", variables, "mymodel", calculators) -``` - -## Results Analysis - -### Basic Analysis - -```python -results = fz.fzr(...) - -# Summary statistics -print(results.describe()) - -# Check for failures -failed = results[results['status'] != 'done'] -print(f"Failed: {len(failed)}") - -# Group by variable -grouped = results.groupby('temperature').agg({ - 'pressure': ['mean', 'std', 'min', 'max'] -}) -print(grouped) -``` - -### Filtering Results +| Key | Arguments | +|-----|-----------| +| `on_start` | `(total_cases, calculators)` | +| `on_case_start` | `(case_index, total_cases, var_combo)` | +| `on_case_complete` | `(case_index, total_cases, var_combo, status, result)` | +| `on_progress` | `(completed, total, eta_seconds)` | +| `on_complete` | `(total_cases, completed_cases, results_df)` | ```python -# Filter by condition -high_pressure = results[results['pressure'] > 1000] +def done(i, n, combo, status, result): + print(f"[{i + 1}/{n}] {combo} -> {status}") -# Filter by multiple conditions -subset = results[ - (results['temperature'] > 300) & - (results['pressure'] < 2000) -] - -# Filter by status -successful = results[results['status'] == 'done'] +fz.fzr("input.txt", {"x": [1, 2, 3]}, model, + calculators="sh://bash calc.sh", + callbacks={"on_case_complete": done}) ``` -### Visualization - -```python -import matplotlib.pyplot as plt +Callbacks run in worker threads; an exception raised in a callback is logged and the run +continues. -# Line plot -for temp in results['temperature'].unique(): - data = results[results['temperature'] == temp] - plt.plot(data['pressure'], data['result'], label=f'T={temp}') -plt.legend() -plt.show() - -# Scatter plot -plt.scatter(results['temperature'], results['pressure'], - c=results['result'], cmap='viridis') -plt.colorbar(label='Result') -plt.show() -``` +## Interrupting -## Advanced Features - -### Progress Callbacks (New in 0.9.1) - -Monitor execution progress in real-time with custom callback functions: - -```python -def progress_callback(event_type, case_info): - """ - Callback function for monitoring progress. +The first Ctrl+C stops starting new cases, terminates the running ones, and makes `fzr` +**return** the DataFrame (interrupted cases have `status="interrupted"`); a second Ctrl+C +raises `KeyboardInterrupt`. Resume with `cache://`. See +[Interrupt & Resume](../running/interrupts.md). - Args: - event_type: 'case_start', 'case_complete', or 'case_failed' - case_info: Dictionary with case details - """ - if event_type == "case_start": - print(f"โณ Starting {case_info['case_name']}") - elif event_type == "case_complete": - print(f"โœ“ Completed {case_info['case_name']}") - elif event_type == "case_failed": - print(f"โœ— Failed {case_info['case_name']}: {case_info.get('error')}") +## CLI -results = fz.fzr( - "input.txt", - variables, - model, - calculators="sh://bash calc.sh", - callbacks=[progress_callback] -) +```bash +fzr input.txt --model mymodel \ + --input_variables '{"T": [10, 20, 30], "P": [1, 10]}' \ + --calculators '["cache://results_v1", "sh://bash calc.sh"]' \ + --results_dir results_v2 --case_naming hash --format json ``` -Multiple callbacks can be registered: - -```python -def logger_callback(event_type, case_info): - with open('execution.log', 'a') as f: - f.write(f"{event_type}: {case_info}\n") - -def metrics_callback(event_type, case_info): - # Send metrics to monitoring system - send_metric(event_type, case_info) - -results = fz.fzr( - "input.txt", - variables, - model, - calculators="sh://bash calc.sh", - callbacks=[logger_callback, metrics_callback] -) -``` - -### Parallel Execution Control - -```python -import os - -# Set maximum workers -os.environ['FZ_MAX_WORKERS'] = '16' - -results = fz.fzr( - "input.txt", - large_variables, - model, - calculators=["sh://bash calc.sh"] * 16 -) -``` - -### Retry Configuration - -```python -import os - -# Set retry limit -os.environ['FZ_MAX_RETRIES'] = '5' - -results = fz.fzr( - "input.txt", - variables, - model, - calculators=[ - "sh://unreliable_method.sh", - "sh://backup_method.sh" - ] -) -``` - -### Interrupt Handling - -```python -try: - results = fz.fzr( - "input.txt", - {"param": list(range(1000))}, - model, - calculators="sh://bash slow_calc.sh" - ) -except KeyboardInterrupt: - print("Interrupted! Partial results saved.") - # Resume with cache - results = fz.fzr( - "input.txt", - {"param": list(range(1000))}, - model, - calculators=[ - "cache://results", - "sh://bash slow_calc.sh" - ], - results_dir="results_resumed" - ) -``` - -### Caching Strategy - -```python -# First run -results1 = fz.fzr( - "input.txt", - {"param": [1, 2, 3, 4, 5]}, - model, - calculators="sh://bash expensive.sh", - results_dir="run1" -) - -# Extend with caching -results2 = fz.fzr( - "input.txt", - {"param": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}, - model, - calculators=[ - "cache://run1", # Reuse 1-5 - "sh://bash expensive.sh" # Calculate 6-10 - ], - results_dir="run2" -) -``` - -### Shared Static Files (New in 1.2) - -Pass files that are identical for every case โ€” a shared mesh, a reference dataset, a -weather series โ€” via `input_static` instead of putting them in `input_path`. They are -never scanned for variables, never re-hashed per case, and (for relative paths) not -copied into every case directory: - -```python -results = fz.fzr( - "input.txt", - {"x": [1, 2, 3]}, - model, - calculators="sh://bash calc.sh", - input_static=["reference_data.csv", "/shared/big_mesh.msh"], -) -``` - -- **Relative paths** are resolved against the current directory, symlinked into each - case directory (real copy where symlinks are unavailable), and explicitly transferred - to `ssh://`, `slurm://` (remote), and `funz://` calculators. -- **Absolute paths** are assumed to already exist at the same path on the calculator - side (shared/mounted storage) โ€” fz only hashes them so `cache://` still notices - content changes. - -`fzr` logs a one-time warning when a variable-free `input_path` file is at least -`FZ_STATIC_CANDIDATE_MIN_SIZE` bytes (default 1 MiB), suggesting `input_static`. - -### Case Directory Naming (New in 1.2) - -`case_naming` controls how each case subdirectory is named: - -| Value | Example directory | Notes | -|-------|-------------------|-------| -| `"path"` (default) | `x=1,y=2/` | Readable; can exceed the ~255-char filename limit with many variables | -| `"hash"` | `a1b2c3d4/` | Short content hash of the variable combination | -| `"index"` | `case_0/` | Shortest | - -With `"hash"` / `"index"`, a `cases.csv` manifest at the results root maps each -directory to its variables (each case's `info.txt` also carries them). Set globally with -the `FZ_CASE_NAMING` environment variable. - -## Output Directory Structure - -``` -results/ -โ”œโ”€โ”€ param=1/ # or a1b2c3d4/ (hash) / case_0/ (index) -โ”‚ โ”œโ”€โ”€ input.txt # Compiled input -โ”‚ โ”œโ”€โ”€ output.txt # Calculation output -โ”‚ โ”œโ”€โ”€ log.txt # Execution metadata -โ”‚ โ”œโ”€โ”€ out.txt # Standard output -โ”‚ โ”œโ”€โ”€ err.txt # Standard error -โ”‚ โ””โ”€โ”€ .fz_hash # File checksums -โ”œโ”€โ”€ param=2/ -โ”‚ โ””โ”€โ”€ ... -โ””โ”€โ”€ param=3/ - โ””โ”€โ”€ ... -``` - -## Complete Examples - -### Example 1: Sensitivity Analysis - -```python -import fz -import numpy as np - -model = { - "varprefix": "$", - "output": { - "result": "grep 'Result:' output.txt | awk '{print $2}'" - } -} - -# Vary one parameter at a time -baseline = {"A": 1.0, "B": 2.0, "C": 3.0} - -for param in ['A', 'B', 'C']: - variables = baseline.copy() - variables[param] = np.linspace(0.5, 1.5, 20) - - results = fz.fzr( - "model.txt", - variables, - model, - calculators="sh://bash simulate.sh", - results_dir=f"sensitivity_{param}" - ) - - print(f"Sensitivity to {param}:") - print(results[[param, 'result']].corr()) -``` - -### Example 2: Design of Experiments - -```python -import fz -from itertools import combinations - -model = { - "varprefix": "$", - "output": {"response": "cat response.txt"} -} - -# Full factorial -variables = { - "factor1": [-1, 0, 1], - "factor2": [-1, 0, 1], - "factor3": [-1, 0, 1] -} - -results = fz.fzr( - "experiment.txt", - variables, - model, - calculators="sh://bash run_experiment.sh", - results_dir="doe_results" -) - -# Analyze main effects -for factor in ['factor1', 'factor2', 'factor3']: - effect = results.groupby(factor)['response'].mean() - print(f"\n{factor} effect:") - print(effect) - -# Analyze interactions -for f1, f2 in combinations(['factor1', 'factor2', 'factor3'], 2): - interaction = results.groupby([f1, f2])['response'].mean() - print(f"\n{f1} ร— {f2} interaction:") - print(interaction) -``` - -### Example 3: Optimization Search - -```python -import fz -import numpy as np - -model = { - "varprefix": "$", - "output": {"objective": "cat objective.txt"} -} - -# Initial grid search -results = fz.fzr( - "optimize.txt", - { - "x": np.linspace(-10, 10, 20), - "y": np.linspace(-10, 10, 20) - }, - model, - calculators="sh://bash evaluate.sh", - results_dir="grid_search" -) - -# Find best region -best = results.loc[results['objective'].idxmin()] -print(f"Best found: x={best['x']}, y={best['y']}, obj={best['objective']}") - -# Refine search around optimum -results2 = fz.fzr( - "optimize.txt", - { - "x": np.linspace(best['x']-1, best['x']+1, 20), - "y": np.linspace(best['y']-1, best['y']+1, 20) - }, - model, - calculators=[ - "cache://grid_search", - "sh://bash evaluate.sh" - ], - results_dir="refined_search" -) -``` - -## Error Handling - -```python -import fz - -try: - results = fz.fzr( - "input.txt", - variables, - model, - calculators="sh://bash calc.sh" - ) -except FileNotFoundError: - print("Input file not found") -except ValueError as e: - print(f"Invalid configuration: {e}") -except Exception as e: - print(f"Unexpected error: {e}") - -# Check results -if 'status' in results.columns: - failures = results[results['status'] != 'done'] - if len(failures) > 0: - print(f"\nFailed cases: {len(failures)}") - print(failures[['status', 'error']]) -``` - -## Performance Tips - -1. **Use caching** for expensive calculations -2. **Parallelize** with multiple calculators -3. **Batch similar cases** for better locality -4. **Filter early** to reduce data processing -5. **Save checkpoints** for long runs +- `--calculators` / `-c` is repeatable and also accepts an alias, a JSON file or an + inline JSON list. +- `--input_variables` accepts a JSON dict (inline or file) or the short form + `'T=[10,20,30],P=1'`. A list of cases (non-factorial design) is Python-only: pass a + DataFrame. +- No option sets a timeout: use the model's `timeout` or `FZ_RUN_TIMEOUT`. +- Exit status 1 when no case ends with `status="done"`. -## See Also +## See also -- [fzi](fzi.md) - Parse input variables -- [fzc](fzc.md) - Compile input files -- [fzo](fzo.md) - Parse output files -- [Calculators](../calculators/overview.md) - Calculator types -- [Parallel Execution](../advanced/parallel.md) - Parallelization guide +[fzc](fzc.md) ยท [fzo](fzo.md) ยท [fzd](fzd.md) ยท [Calculators](../calculators/overview.md) ยท +[Constraints & Limits](../../reference/limitations.md) diff --git a/docs/user-guide/design/algorithms.md b/docs/user-guide/design/algorithms.md new file mode 100644 index 0000000..cb19a72 --- /dev/null +++ b/docs/user-guide/design/algorithms.md @@ -0,0 +1,117 @@ +# Writing Algorithms for fzd + +An `fzd` algorithm is a Python (or R) file defining **one class** that proposes points, +receives their results, and decides when to stop. Examples: +[`examples/algorithms/`](https://github.com/Funz/fz/tree/main/examples/algorithms) in the +fz repository. + +## File template + +```python title=".fz/algorithms/myalgo.py" +#title: My Algorithm +#author: Name +#type: optimization +#options: max_iter=100;tol=1e-6 +#require: numpy;scipy + +class MyAlgo: + def __init__(self, **options): + # values from #options and algorithm_options arrive as strings: cast them + self.max_iter = int(options.get("max_iter", 100)) + self.tol = float(options.get("tol", 1e-6)) + self.iteration = 0 + + def get_initial_design(self, input_vars, output_vars): + """input_vars: {"x": (min, max), ...}; output_vars: list of output names. + Returns a list of points: [{"x": 0.5, "y": 1.0}, ...]""" + self.bounds = input_vars + return [{v: (lo + hi) / 2 for v, (lo, hi) in input_vars.items()}] + + def get_next_design(self, previous_input_vars, previous_output_values): + """All points evaluated so far and their outputs (None = failed point). + Returns the next list of points, or [] to stop.""" + self.iteration += 1 + if self.iteration >= self.max_iter: + return [] + valid = [(x, y) for x, y in zip(previous_input_vars, previous_output_values) + if y is not None] + ... + return [next_point] + + def get_analysis(self, input_vars, output_values): + """Final result: a dict with "text" (summary) and "data" (values).""" + valid = [(y, x) for x, y in zip(input_vars, output_values) if y is not None] + best_y, best_x = min(valid, key=lambda t: t[0]) + return {"text": f"best {best_y} at {best_x}", + "data": {"best_output": best_y, "best_input": best_x}} + + def get_analysis_tmp(self, input_vars, output_values): # optional + """Called after each iteration to report progress.""" + return {"text": f"{len(input_vars)} points evaluated"} +``` + +## Header lines + +| Header | Effect | +|--------|--------| +| `#title`, `#author`, `#type` | Descriptive metadata | +| `#options: k=v;k2=v2` | Default options, passed to `__init__(**options)` as strings; overridden by `algorithm_options` | +| `#require: pkg1;pkg2` | Python packages imported by the algorithm. **Missing packages are pip-installed automatically** into the current environment when the algorithm is loaded | + +## Rules + +- Points are plain dicts of floats; outputs are floats (or lists of floats for a + list-valued `output_expression`), and `None` for failed points โ€” always filter them. +- Several points per batch are evaluated in parallel on the available calculators; + duplicates are evaluated once. +- Already evaluated points are served from cache, across iterations and across re-runs. +- Fixed variables (non-range strings in `input_variables`) are not passed to the + algorithm; fz merges them into every point. + +## Analysis content + +`get_analysis()` / `get_analysis_tmp()` return a dict. Its `"text"` is inspected and +saved in `analysis_dir`: HTML โ†’ `analysis_.html`, JSON โ†’ `analysis_.json`, +`key=value` lines โ†’ parsed dict, otherwise plain text; `"data"` is returned as-is in +`result["analysis"]`. Details: +[fzd content formats](https://github.com/Funz/fz/blob/main/doc/fzd_content_format.md). + +## R algorithms + +An `.R` file defining the same methods is supported through `rpy2` (install +`funz-fz[r]` and R). Template: [Funz/fz-AlgorithmR](https://github.com/Funz/fz-AlgorithmR). + +## Packaging as `fz-` + +To make the algorithm installable with `fz install algorithm `: + +```text +fz-myalgo/ # GitHub repository, default branch "main" +โ”œโ”€โ”€ .fz/algorithms/myalgo.py +โ”œโ”€โ”€ tests/ # a small end-to-end fzd case +โ””โ”€โ”€ README.md # options, what it optimizes/samples, quick start +``` + +`fz install algorithm myalgo` downloads `https://github.com/Funz/fz-myalgo` (any git URL +or local zip also works) and copies the files of `.fz/algorithms/` into +`./.fz/algorithms/` (`--global`: `~/.fz/algorithms/`). Template repository: +[Funz/fz-Algorithm](https://github.com/Funz/fz-Algorithm). + +## Testing + +```bash +fz install algorithm ./fz-myalgo.zip +ls .fz/algorithms/ # fz list shows models and calculators only +fzd --input_dir tests/input.txt --model MyCode \ + --input_vars '{"x": "[0;10]"}' --output_expression result \ + --algorithm myalgo --options '{"max_iter": 20}' +``` + +Choose a test problem with a known answer (e.g. a quadratic with a known minimum) and +check it in `result["analysis"]`. + +## See also + +[fzd](../core-functions/fzd.md) ยท [Installing Models & Algorithms](../installing.md) ยท +[Security Model](../../reference/security.md) โ€” an algorithm file is executed code, and +`#require` installs packages diff --git a/docs/user-guide/installing.md b/docs/user-guide/installing.md new file mode 100644 index 0000000..dc7e6e1 --- /dev/null +++ b/docs/user-guide/installing.md @@ -0,0 +1,94 @@ +# Installing Models & Algorithms + +Ready-made **models** (wrappers of simulation codes) and **fzd algorithms** are +published as GitHub repositories named `fz-` under the +[Funz organization](https://github.com/orgs/Funz/repositories?q=fz-). fz installs them +into a `.fz/` directory. + +## Commands + +```bash +fz install model Moret # -> https://github.com/Funz/fz-Moret +fz install model https://github.com/you/fz-mycode +fz install model ./fz-mycode.zip # local archive +fz install model Moret --global # into ~/.fz/ instead of ./.fz/ (see warning below) + +fz install algorithm brent # -> https://github.com/Funz/fz-brent +fz uninstall model Moret +fz uninstall algorithm brent --global +``` + +```python +fz.install_model("Moret") # also fz.install("Moret") +fz.install_algorithm("brent", global_install=True) +fz.list_installed_models() +fz.list_installed_algorithms() +fz.uninstall_model("Moret") +``` + +| Source | Resolved to | +|--------|-------------| +| Name `X` | `https://github.com/Funz/fz-X`, branch `main` archive | +| GitHub URL | That repository's `main` archive | +| Local `.zip` | The archive itself | + +Installing from a network source prints a reminder: the model's templates, formulas and +output commands will run as code with your privileges ([Security](../reference/security.md)). + +## What gets installed + +A repository ships a `.fz/` tree; every file is copied into `./.fz/` (or `~/.fz/`): + +```text +.fz/ +โ”œโ”€โ”€ models/Moret.json # model (id, syntax, output extractors); a repo may ship several +โ”œโ”€โ”€ calculators/Moret.sh # runner script +โ”œโ”€โ”€ calculators/localhost_Moret.json # local alias: {"uri": "sh://", "models": {"Moret": "bash .fz/calculators/Moret.sh"}} +โ””โ”€โ”€ algorithms/*.py|*.R # for algorithm repositories +``` + +The model is then used by its alias, and the local calculator alias is found +automatically from the model `id`: + +```bash +fzr input.inp --model Moret --input_variables '{"e": [1, 2]}' --format json +``` + +The simulation code itself (MORET, MCNP, ...) is **not** installed: each wrapper's +README states what it expects (path, environment variables). + +!!! warning "`--global` installs and runner scripts" + `fz install model --global` copies the wrapper to `~/.fz/`, but its calculator + alias keeps the command `bash .fz/calculators/.sh`, which `sh://` resolves against the + **launch directory**: runs from any other directory fail (`Command not found locally: + '/.fz/calculators/.sh'`). Prefer project-local installs, or edit + `~/.fz/calculators/localhost_.json` to use the absolute path of the script (`~` is + not expanded). + +## Available packages + +- Models: see [Plugins](../plugins/index.md) (Moret, MCNP, Cathare, Cristal, Scale, + Telemac, Modelica, Serpent, Casmo, Cast3M, ...). +- Algorithms: `fz-brent`, `fz-PSO`, `fz-gradientdescent`; the fz repository also ships + examples in [`examples/algorithms/`](https://github.com/Funz/fz/tree/main/examples/algorithms) + (random sampling, Monte Carlo, BFGS, Brent, NSGA-II) to copy into `.fz/algorithms/`. + +## Checking an installation + +```bash +fz list --models Moret --check --format json +``` + +The model must report `check_status: passed`. The calculator line shows the alias's +`uri` (`sh://`) and may report `Empty sh:// command` for this alias layout โ€” a known +`fz list` limitation ([fzl](core-functions/fzl.md#limitations)); a real `fzr` run without +`--calculators` is the reliable check. + +## Publishing your own + +Template repositories: [fz-Model](https://github.com/Funz/fz-Model), +[fz-Algorithm](https://github.com/Funz/fz-Algorithm), +[fz-AlgorithmR](https://github.com/Funz/fz-AlgorithmR). Use the default branch `main` +(the installer downloads `archive/refs/heads/main.zip`). See also +[Writing Algorithms](design/algorithms.md) and the +[wrapper guide](https://github.com/Funz/fz/blob/main/skills/fz/code-wrapper.md). diff --git a/docs/user-guide/model-definition.md b/docs/user-guide/model-definition.md deleted file mode 100644 index 1d08c19..0000000 --- a/docs/user-guide/model-definition.md +++ /dev/null @@ -1,38 +0,0 @@ -# Model Definition - -A **model** tells FZ how to read variables out of your input templates and how to pull -results out of the output files. It is a plain `dict` (or a JSON alias in -`.fz/models/`). - -## Common Fields - -| Field | Default | Purpose | -|-------|---------|---------| -| `varprefix` | `$` | Marks a variable in input templates (`$x`, `${x}`, `${x~default}`) | -| `formulaprefix` | `@` | Marks a formula (`@{expr}`) | -| `delim` | `{}` | Delimiters around variable names / formula expressions | -| `commentline` | `#` | Comment marker introducing a formula-context line (`#@ ...`) | -| `interpreter` | `python` | Formula interpreter โ€” `python` or `R` | -| `output` | โ€” | Map of result column โ†’ extraction spec (see below) | -| `timeout` | โ€” | Per-model run timeout in seconds; overrides `FZ_RUN_TIMEOUT`. `None`/`0` disables it. (New in 1.2) | - -## Output Extraction (updated in 1.2) - -Each `output` entry says how to pull one result from a case's files. The spec can be: - -- a **shell command** (implicit default, or explicit `bash://`) โ€” `"grep 'P:' out.txt | awk '{print $2}'"` -- `python://` โ€” native Python, no shell required -- `jq:// ` โ€” JSON via [jq](https://jqlang.org/) -- `yq:// ` โ€” YAML/JSON/XML/TOML via [mikefarah/yq](https://github.com/mikefarah/yq) -- `xpath:// ` โ€” XML via `xmllint --xpath` -- a **Python callable** (Python API only) receiving the case directory as a `pathlib.Path` - -Any of these may resolve to a **list** for vector-valued outputs (time series, profiles, -spectra). Full details and helper reference: [fzo โ€” Output Command Forms](core-functions/fzo.md#output-command-forms). - -## See Also - -- [fzi](core-functions/fzi.md) / [fzc](core-functions/fzc.md) โ€” variable parsing and substitution -- [fzo โ€” Output Command Forms](core-functions/fzo.md#output-command-forms) -- [Formula Evaluation](advanced/formulas.md) โ€” `@{...}` expressions, interpreters, number formatting -- [Model syntax reference in the FZ repo](https://github.com/Funz/fz/blob/main/doc/model-definition.md) diff --git a/docs/user-guide/models/definition.md b/docs/user-guide/models/definition.md new file mode 100644 index 0000000..9d61bd3 --- /dev/null +++ b/docs/user-guide/models/definition.md @@ -0,0 +1,103 @@ +# Model Definition + +A **model** describes a simulation code for fz: how variables and formulas are marked in +its input files, and how to extract results from its output files. It is a Python `dict`, +a JSON string, a JSON file, or an alias stored in `.fz/models/.json`. + +!!! note "A model never says how to run the code" + There is no `run` or `command` field. Where and how the code runs is given by the + [calculator](../calculators/overview.md). Forgetting the calculator makes fz fall back + to `sh://` with no command, which tries to execute the input file itself; the symptom + is `Permission denied ... ./input.txt`. + +## Example + +```json title=".fz/models/perfectgas.json" +{ + "id": "perfectgas", + "varprefix": "$", + "formulaprefix": "@", + "delim": "{}", + "commentline": "#", + "interpreter": "python", + "timeout": 1800, + "output": { + "pressure": "python://grep(r'pressure = (\\S+)', 'output.txt')" + } +} +``` + +```python +fz.fzr("input.txt", variables, "perfectgas", calculators="sh://bash calc.sh") +``` + +## Fields + +| Field | Default | Role | +|-------|---------|------| +| `varprefix` | `$` | Variable marker: `$x` | +| `formulaprefix` | `@` | Formula marker: `@{expr}` | +| `delim` | see below | Two characters (or `""`) delimiting variables *and* formulas | +| `var_delim` / `formula_delim` | `()` / `{}` | Delimiters of variables / formulas separately; take precedence over `delim` | +| `commentline` | `#` | Comment marker; `commentline` + `formulaprefix` (`#@`) starts a context line | +| `interpreter` | `FZ_INTERPRETER` (`python`) | `python` or `R` for formulas | +| `output` | โ€” | Map *column name* โ†’ extractor; required to get results ([Output Extraction](outputs.md)) | +| `timeout` | โ€” | Per-case run timeout in seconds; `null` or `0` disables it ([Timeouts](../running/timeouts.md)) | +| `id` | โ€” | Identifier matched against calculator aliases' `models` keys, for auto-discovery | + +Accepted aliases of the key names (first found wins): `var_prefix`, `varprefix`, +`var_char`, `varchar`; `formula_prefix`, `formulaprefix`, `form_prefix`, `formprefix`, +`formula_char`, `form_char`; `commentline`, `comment_line`, `comment_char`, +`commentchar`, `comment`. + +### Default delimiters + +| Model contains | Variables | Formulas | +|----------------|-----------|----------| +| `"delim": "{}"` | `$x`, `${x}` | `@{...}` | +| `"delim": "()"` | `$x`, `$(x)` | `@(...)` | +| no `delim`, no `var_delim`/`formula_delim` | `$x`, `$(x)` โ€” **not** `${x}` | `@{...}` | +| CLI without `--model` | `$x`, `${x}` | `@{...}` | + +`delim` must be empty or exactly two characters (validated). Setting it explicitly +avoids the difference between the Python default and the CLI default. + +## Where a model can come from + +| Form | Example | +|------|---------| +| `dict` (Python) | `model={"delim": "{}", "output": {...}}` | +| JSON string | `--model '{"delim": "{}", "output": {...}}'` | +| JSON file (path ending in `.json`) | `--model models/perfectgas.json` | +| Alias | `--model perfectgas` โ†’ `./.fz/models/perfectgas.json`, then `~/.fz/models/perfectgas.json` | + +An alias file's name does **not** set its `id`: add `"id"` in the JSON for calculator +auto-discovery to work (installed wrappers do). + +## Inline model on the CLI + +Model fields can be given as flags instead of, or on top of, `--model`: + +```bash +fzo results/ --delim '{}' --output-cmd 'pressure=grep "P =" output.txt | cut -d= -f2' +``` + +`--varprefix`, `--formulaprefix`, `--delim`, `--commentline`, `--interpreter` and +repeatable `--output-cmd NAME=COMMAND` are available on `fzi`, `fzc`, `fzo`, `fzr`. + +## Checking a model + +```bash +fzi input.txt --model perfectgas --format json # exactly the expected variables? +fz list --models perfectgas --check # alias found and valid? +``` + +Unexpected variables in `fzi` usually mean `varprefix` collides with the code's own +syntax. + +## See also + +- [Input Template Syntax](../templates/syntax.md) ยท [Formulas](../templates/formulas.md) +- [Output Extraction](outputs.md) +- [Installing Models](../installing.md) โ€” ready-made models for known codes +- [.fz Directory & Aliases](../../reference/configuration.md) diff --git a/docs/user-guide/models/outputs.md b/docs/user-guide/models/outputs.md new file mode 100644 index 0000000..f262f1a --- /dev/null +++ b/docs/user-guide/models/outputs.md @@ -0,0 +1,108 @@ +# Output Extraction + +The model's `output` field maps each result **column name** to an **extractor**. After a +case has run, every extractor is evaluated **inside the case's result directory**, and +its value becomes the column's value for that case. The same extractors are used by +`fzo` on existing directories. + +## Extractor forms + +Forms can be mixed within one model. + +| Form | Example | Requires | +|------|---------|----------| +| Shell command (default, or `bash://` prefix) | `"grep 'P:' output.txt \| awk '{print $2}'"` | bash and the tools used | +| `python://` expression | `"python://grep(r'P: (\\S+)', 'output.txt')"` | nothing | +| `jq://filter file` | `"jq://.energy results.json"` | `jq` | +| `yq://filter file` | `"yq://.metadata.version config.yaml"` | [mikefarah/yq](https://github.com/mikefarah/yq) | +| `xpath://expr file` | `"xpath://'//result/T/text()' out.xml"` | `xmllint` | +| Python callable (Python API only) | `lambda d: float((d / "final.txt").read_text())` | nothing | + +`python://`, `jq://`, `yq://` and `xpath://` do not use bash and work on Windows without +MSYS2. A callable receives the case directory as a `pathlib.Path`. + +### `python://` helpers + +Relative paths are resolved against the case directory. + +| Helper | Returns | +|--------|---------| +| `read(path)` | File content as a string | +| `lines(path)` | List of lines | +| `line(path, n)` | Line `n` | +| `grep(pattern, path, group=None, all=False, cast=True)` | First capture group (or whole match) of the first match, cast to int/float when possible; `all=True` returns the list of all matches | +| `json_file(path)` | Parsed JSON | +| `csv_file(path, column=None)` | A column as a list, or the table | +| `hdf5_file(path, dataset=None)` | A dataset (needs `h5py`) | + +Also available in expressions: `re`, `json`, `math`, `statistics`, `np`, `pd`, `Path`, +`base_dir`. + +```python +model = { + "delim": "{}", + "output": { + "pressure": "python://grep(r'pressure = (\\S+)', 'output.txt')", + "T_max": "python://max(csv_file('temps.csv', column='T'))", + "residuals": "python://grep(r'res=(\\S+)', 'solver.log', all=True)", + "energy": "jq://.energy results.json", + }, +} +``` + +## Value conversion + +The text produced by a shell/`jq`/`yq`/`xpath` extractor is converted, in this order: +JSON (`[1, 2]`, `{"a": 1}`), Python literal, `int`/`float`, otherwise kept as a string. +An empty result gives `None`. + +| Extractor output | Value | +|------------------|-------| +| `1.5` | `1.5` | +| `[1, 2, 3]` | `[1, 2, 3]` | +| `[7]` from a **shell** command | `7` (single-element lists are unwrapped) | +| `[7]` from `python://`/`jq://`/`yq://`/`xpath://` | `[7]` (kept as a list) | +| `{"min": 1, "max": 4}` | expanded by `fzr` into columns `name_min`, `name_max` | +| *(empty)* | `None` | + +## Vector outputs + +An extractor may return a list (time series, profile, spectrum). `fzr` and `fzo` store +the whole list in the cell, without flattening or padding; cases may have different +lengths. `fzd` needs a scalar objective: reduce vectors in `output_expression` +(`mean(T_series)`, `T_series[-1]`, ... see [fzd](../core-functions/fzd.md)). + +!!! warning "Persisting vectors" + CSV turns lists into strings (`"[1, 2, 3]"`). Use `--format json`, `to_pickle()` or + `to_parquet()` to keep them as lists. + +## Failures + +When an extractor fails (missing file, empty output), the value is `None` and: + +- `fzo` adds a column `_output_error` with the reason; +- `fzr` keeps the case `status` and writes `Missing output: ` in the `error` + column. + +A `cache://` hit is only accepted when all outputs, re-parsed with the current model, +are non-`None`. + +## Pitfalls + +- **Reserved file names.** Do not parse results from `out.txt`, `err.txt`, `log.txt`, + `info.txt` or `history.txt` unless you mean fz's own files: `out.txt` *is* the + captured stdout of the command, which is a valid source when the code prints its + results. +- **Locale.** Shell tools may use a comma decimal separator. Prefix numeric pipelines + with `LC_ALL=C`, or use `python://`. +- **`python` vs `python3`.** Shell extractors calling `python` use whatever is first on + `PATH`. +- **Quoting awk fields.** In JSON files write `$3` normally; inside a single-quoted + inline `--model '...'` it is safe; never inside double quotes on the shell command + line. + +## See also + +- [Model Definition](definition.md) +- [fzo](../core-functions/fzo.md) +- [Results & Traceability](../running/results.md) diff --git a/docs/user-guide/running/caching.md b/docs/user-guide/running/caching.md new file mode 100644 index 0000000..31853d6 --- /dev/null +++ b/docs/user-guide/running/caching.md @@ -0,0 +1,76 @@ +# Caching + +fz does not recompute a case whose inputs were already computed, provided a +[`cache://`](../calculators/cache.md) entry points to the earlier results. `fzd` adds +automatic caching across its iterations. + +## Cache key + +Every case directory holds a `.fz_hash` file: + +```text title=".fz_hash" +# fz-hash v2 +# code_id: telemac@v8p5 +98752ee28d5484bdc2814fb70adb6a0b2fb31f6a9b8ee7ae81fd2fc9cf300b3b input.txt +``` + +- SHA-256 of each compiled input file and of each `input_static` file; +- optional `code_id` of the calculator that produced the results. + +A cached case matches when the hashes are equal, the code identities are compatible, +and all outputs re-parsed with the current model are non-`None`. + +**Not in the key:** the calculator command, the script content, the model's output +extractors, the directory name. + +## Cache identity (`code_id`) + +The same code is often launched differently on each calculator (`bash run.sh` locally, +`/opt/telemac/v8p5/run.sh` over SSH), so the command is not part of the key. To +distinguish code versions, calculator aliases can declare their identity: + +```json title=".fz/calculators/cluster.json" +{"uri": "ssh://u@hpc/bash /opt/telemac/v8p5/run.sh", "code_id": "telemac@v8p5"} +``` + +```json title=".fz/calculators/local.json" +{"uri": "sh://bash ./run.sh", "version_cmd": "./run.sh --version"} +``` + +| Situation | Result | +|-----------|--------| +| Same `code_id` on both sides | Match, whatever the commands | +| Different `code_id` | Never matches | +| No `code_id` on one or both sides | Match with a one-time warning; refused with `FZ_CACHE_STRICT=1` | +| Cache written in the old MD5 format | Ignored; considered with `FZ_CACHE_ACCEPT_LEGACY=1` (then as "no `code_id`") | + +`version_cmd` is run once per calculator per session; its output becomes the `code_id`. + +## Patterns + +```python +# Extend a study: only new cases are computed +fz.fzr("input.txt", {"T": [10, 20, 30, 40, 50]}, model, + calculators=["cache://study1", "sh://bash calc.sh"], results_dir="study2") + +# Resume in place: cache://_ is the previous content of results_dir +fz.fzr("input.txt", variables, model, + calculators=["cache://_", "sh://bash calc.sh"], results_dir="study1") + +# Several cache sources, then compute +calculators = ["cache://latest", "cache://archive/*", "sh://bash calc.sh"] +``` + +To force recomputation after changing the code, declare a new `code_id`, or run into a +new `results_dir` without `cache://`. + +## fzd + +- Points already evaluated in an earlier iteration are not re-run. +- An existing `analysis_dir` is renamed with a timestamp and its iterations are used as + cache by the new run. + +## See also + +[Cache calculator](../calculators/cache.md) ยท [Interrupt & Resume](interrupts.md) ยท +[Results & Traceability](results.md) diff --git a/docs/user-guide/running/interrupts.md b/docs/user-guide/running/interrupts.md new file mode 100644 index 0000000..f0a120d --- /dev/null +++ b/docs/user-guide/running/interrupts.md @@ -0,0 +1,43 @@ +# Interrupt & Resume + +## Ctrl+C during `fzr` / `fzd` + +| | Effect | +|--|--------| +| First Ctrl+C | No new case starts. Running local processes are terminated (killed after 5 s); remote and SLURM jobs are cancelled and remote temporary directories cleaned. Interrupted cases get `status="interrupted"`. `fzr` **returns** the DataFrame; the manifest records the interruption | +| Second Ctrl+C | `KeyboardInterrupt` is raised immediately | + +So a script usually does not need `try/except KeyboardInterrupt`: after the first +Ctrl+C the call returns normally with partial results. + +```python +results = fz.fzr("input.txt", variables, model, + calculators="sh://bash calc.sh", results_dir="run1") +print(results["status"].value_counts()) # done / interrupted +``` + +## Resuming + +Completed cases are on disk with their `.fz_hash`. Run again with a cache entry: + +```python +# in place: cache://_ = previous content of results_dir (renamed with a timestamp) +fz.fzr("input.txt", variables, model, + calculators=["cache://_", "sh://bash calc.sh"], results_dir="run1") + +# or into a new directory +fz.fzr("input.txt", variables, model, + calculators=["cache://run1", "sh://bash calc.sh"], results_dir="run1_resumed") +``` + +Interrupted cases have no valid outputs, so they are recomputed. + +## Calls from a non-main thread + +Python only allows signal handlers in the main thread. When `fzr`/`fzd` run in another +thread (Streamlit, a thread pool, a web server), fz skips the handler: everything works +except the graceful Ctrl+C handling. + +## See also + +[Caching](caching.md) ยท [Cache calculator](../calculators/cache.md) diff --git a/docs/user-guide/running/parallel.md b/docs/user-guide/running/parallel.md new file mode 100644 index 0000000..ccd88cb --- /dev/null +++ b/docs/user-guide/running/parallel.md @@ -0,0 +1,82 @@ +# Parallelism & Retries + +## Number of concurrent cases + +Each **non-cache calculator entry** runs one case at a time. The number of cases running +concurrently is: + +```text +min(number of non-cache calculator entries, number of cases, FZ_MAX_WORKERS if set) +``` + +```python +calculators = "sh://bash calc.sh" # 1: sequential +calculators = ["sh://bash calc.sh"] * 4 # 4 local cases at a time +calculators = ["cache://old"] + ["sh://bash calc.sh"] * 4 # cache entries do not count +calculators = [ # 3 at a time, on 3 machines + "sh://bash calc.sh", + "ssh://u@node1/bash /opt/run.sh", + "ssh://u@node2/bash /opt/run.sh", +] +``` + +An alias counts as one entry: `calculators=["localhost_Moret"] * 4` runs 4 cases at a +time, while omitting `calculators` (auto-discovery of one alias) runs them one by one. + +`FZ_MAX_WORKERS` only **lowers** that number; setting it never adds workers. Exception: +`slurm-array://` uses one waiting thread per case (capped by `FZ_MAX_WORKERS`) so that +all cases can be batched into one job array. + +For `fzd` with a Python function model, `calculators=N` is the number of threads. + +## Assignment of cases + +Case *i* first tries entry *i mod n*; if that entry is busy, the first free entry takes +it. With equal durations this is a round-robin distribution; with unequal durations, +faster entries take more cases. + +## Retries + +When a run fails (non-zero exit, timeout, transfer error), the case is tried again on +the calculators, preferring another entry. After `FZ_MAX_RETRIES` failures (default 5) +the case is marked `failed` with the last error. A case whose command succeeds but whose +outputs cannot be parsed is **not** retried: it keeps its status and the reason goes to +`error` (`Missing output: ...`). + +```python +calculators = [ + "sh://bash fast_but_fragile.sh", + "sh://bash robust.sh", # tried when the first one fails +] +``` + +## Setting the limits + +```bash +export FZ_MAX_WORKERS=8 # before starting Python +export FZ_MAX_RETRIES=3 +``` + +```python +import os, fz +os.environ["FZ_MAX_WORKERS"] = "8" +fz.reload_config() # FZ_* variables are read at import time +# or: fz.get_config().max_workers = 8 +``` + +## Choosing the number of entries + +- CPU-bound local code: about the number of cores divided by the cores used per case. +- Memory-bound: available memory / memory per case. +- Remote calculators: limited by the remote resources and your quota, not by the local + machine (fz threads mostly wait). + +## Progress + +A progress line with ETA is written to stderr (disabled when stderr is not a +terminal). For programmatic progress, use `fzr(..., callbacks={...})` +([fzr callbacks](../core-functions/fzr.md#callbacks)). + +## See also + +[Timeouts](timeouts.md) ยท [Caching](caching.md) ยท [Calculators](../calculators/overview.md) diff --git a/docs/user-guide/running/results.md b/docs/user-guide/running/results.md new file mode 100644 index 0000000..d122607 --- /dev/null +++ b/docs/user-guide/running/results.md @@ -0,0 +1,101 @@ +# Results & Traceability + +## Layout + +```text +results/ # results_dir (renamed with a timestamp if it exists) +โ”œโ”€โ”€ manifest.json # campaign record +โ”œโ”€โ”€ ro-crate-metadata.json # same, as RO-Crate 1.1 (FZ_RO_CRATE=0 disables) +โ”œโ”€โ”€ cases.csv # only with case_naming "hash" / "index" +โ”œโ”€โ”€ T=10,P=1/ # one directory per case +โ”‚ โ”œโ”€โ”€ input.txt # compiled inputs +โ”‚ โ”œโ”€โ”€ output.txt ... # files written by the code +โ”‚ โ”œโ”€โ”€ out.txt err.txt # stdout / stderr of the command +โ”‚ โ”œโ”€โ”€ log.txt # command, exit code, times, user, host, platform +โ”‚ โ”œโ”€โ”€ info.txt # state, calculator, inputs, outputs (key=value) +โ”‚ โ”œโ”€โ”€ history.txt # timeline of the case (attempts, errors) +โ”‚ โ””โ”€โ”€ .fz_hash # cache key (SHA-256 of inputs, code_id) +โ””โ”€โ”€ ... +``` + +!!! warning "Reserved names" + `out.txt`, `err.txt`, `log.txt`, `info.txt`, `history.txt`, `.fz_hash` are written by + fz in every case directory and overwrite files of the same name produced by the + code. `manifest.json`, `ro-crate-metadata.json`, `cases.csv` are reserved at the + results root. + +When the template has no variables (single non-parametric run), the files are written +directly in `results_dir`. + +## Case directory naming + +`case_naming` (argument, `--case_naming`, or `FZ_CASE_NAMING`): + +| Value | Example | Notes | +|-------|---------|-------| +| `"path"` (default) | `T=10,P=1,V=a%2Fb` | Readable. Characters `/ \ : * ? " < > \| %` and control characters are percent-encoded; `.`/`..` values are encoded. Can exceed the ~255-character name limit with many variables or long values | +| `"hash"` | `case_c39cc51bad10` | Short hash of the variable combination; stable across runs | +| `"index"` | `case_0` | Position in the design | + +With `"hash"`/`"index"`, `cases.csv` maps each directory to its variables +(`case,T,P,...`). The values are also in each case's `info.txt` (`input.T=10`), which +`fzo` reads when a directory name cannot be parsed. `fzd` always uses `"index"`. + +fz refuses to create a case directory that would resolve outside `results_dir`. + +## Campaign manifest + +`manifest.json` is written at the end of every `fzr` (and, per campaign, by `fzd` in +`analysis_dir`, linking the iteration manifests): + +| Key | Content | +|-----|---------| +| `schema` | `fz-manifest/1` | +| `fz_version`, `python_version`, `platform`, `dependencies` | Software environment | +| `start_time`, `end_time` | UTC timestamps | +| `interrupted` | Whether Ctrl+C was pressed | +| `input_path`, `model`, `model_sha256` | What was run | +| `calculators`, `hosts` | Where (passwords in URIs masked) | +| `n_cases`, `n_done` | Counts | +| `cases` | Per case: `path`, `status`, `calculator`, `inputs`, `.fz_hash` SHA-256, `code_id` when known | + +`ro-crate-metadata.json` describes the same campaign as an +[RO-Crate](https://www.researchobject.org/ro-crate/) (`Dataset`, `File`, +`SoftwareApplication`, `CreateAction`). Failure to write either file logs a warning and +never fails the run. + +## Shared static files (`input_static`) + +Files identical for every case (a large mesh, a weather series, a cross-section +library) should not be in `input_path`: they would be copied into every case and +re-hashed each time. Pass them with `input_static` (`fzi`, `fzc`, `fzr`, `fzd`; CLI +`--input_static`, repeatable or a JSON list): + +```python +fz.fzr("input.txt", variables, model, + calculators="sh://bash calc.sh", + input_static=["reference_data.csv", "/shared/big_mesh.msh"]) +``` + +| Entry | Behavior | +|-------|----------| +| Relative path | Resolved against the current directory, identified by its base name, symlinked into each case (copied where symlinks are not allowed), uploaded to `ssh://`, remote `slurm://` and `funz://` calculators | +| Absolute path | Must exist at the same path on the calculator (shared storage); never copied or transferred, only hashed | + +Static files are never templated and are excluded from `fzi`. They are hashed once per +call and included in `.fz_hash`, so the cache reacts to their content. `fzr` warns once +per file when a variable-free `input_path` file is at least +`FZ_STATIC_CANDIDATE_MIN_SIZE` bytes (default 1 MiB; `0` disables). + +## Reading results later + +```python +import pandas as pd, json +df = fz.fzo("results/*", model) # re-parse, possibly with a new model +manifest = json.load(open("results/manifest.json")) +``` + +## See also + +[fzr](../core-functions/fzr.md) ยท [Caching](caching.md) ยท +[Output Extraction](../models/outputs.md) diff --git a/docs/user-guide/running/timeouts.md b/docs/user-guide/running/timeouts.md new file mode 100644 index 0000000..94e9d0e --- /dev/null +++ b/docs/user-guide/running/timeouts.md @@ -0,0 +1,47 @@ +# Timeouts + +A timeout bounds the duration of **one run of one case** (for SLURM, including the time +spent in the queue). A case that exceeds it is stopped and gets `status="timeout"`. + +## Resolution order + +1. `timeout=` argument of `fzr()` (seconds); +2. the model's `"timeout"` entry; +3. `FZ_RUN_TIMEOUT`. + +| Calculator | Default when none of the above is set | +|------------|----------------------------------------| +| `sh://`, `funz://` | 3600 s | +| `ssh://`, `slurm://`, `slurm-array://` | **no timeout** (a warning is logged) | + +Setting `FZ_RUN_TIMEOUT` explicitly applies it to every calculator type. + +## Disabling + +| Setting | Effect | +|---------|--------| +| model `"timeout": null` or `"timeout": 0` | no timeout for this model | +| `FZ_RUN_TIMEOUT=0` | **every case times out immediately** | +| `timeout=0` argument | **every case times out immediately** | + +To run without limit, use the model entry, or set a large value. + +## Examples + +```json title=".fz/models/longcode.json" +{"delim": "{}", "timeout": 86400, "output": {"...": "..."}} +``` + +```python +fz.fzr("input.txt", variables, model, calculators="sh://bash calc.sh", timeout=600) +``` + +```bash +export FZ_RUN_TIMEOUT=7200 # before starting Python (or fz.reload_config()) +``` + +There is no CLI option for the timeout: use the model entry or `FZ_RUN_TIMEOUT`. + +## See also + +[Parallelism & Retries](parallel.md) ยท [Environment Variables](../../reference/environment.md) diff --git a/docs/user-guide/advanced/formulas.md b/docs/user-guide/templates/formulas.md similarity index 65% rename from docs/user-guide/advanced/formulas.md rename to docs/user-guide/templates/formulas.md index 00e6c17..8fe7948 100644 --- a/docs/user-guide/advanced/formulas.md +++ b/docs/user-guide/templates/formulas.md @@ -14,7 +14,8 @@ area: @{math.pi * $r ** 2} ``` The formula prefix (`@`), delimiters (`{}`), variable prefix (`$`) and comment marker -(`#`) are all configurable per model. +(`#`) are all configurable per model. Formulas use `{}` by default even when the model +has no `delim`; variables then use `()` ([defaults](../models/definition.md#default-delimiters)). ## Number Formatting (updated in 1.2) @@ -50,12 +51,20 @@ sci=1.23E05 ## R Interpreter -Set `model["interpreter"] = "R"` (or `FZ_INTERPRETER=R`) to evaluate formulas with R โ€” -`mean()`, `sd()`, `rnorm()`, and multi-line function definitions in `#@` context lines. -Requires the `rpy2` package and an R installation. +Set `model["interpreter"] = "R"` (or `FZ_INTERPRETER=R`, or `fz.set_interpreter("R")`) +to evaluate formulas with R โ€” `mean()`, `sd()`, `rnorm()`, and multi-line function +definitions in `#@` context lines. Requires R and `pip install 'funz-fz[r]'` (rpy2). + +```text +#@ samples <- rnorm(100, mean=$mu, sd=$sigma) +#@ ci <- function(x) mean(x) + c(-1, 1) * 1.96 * sd(x) / sqrt(length(x)) +lower = @{ci(samples)[1] | 0.000} +``` + +If `import rpy2.robjects` fails (R/rpy2 version mismatch), R is reported unavailable. ## See Also -- [Model Definition](../model-definition.md) โ€” `varprefix` / `formulaprefix` / `delim` / `commentline` +- [Input Template Syntax](syntax.md) ยท [Model Definition](../models/definition.md) โ€” `varprefix` / `formulaprefix` / `delim` / `commentline` - [fzc](../core-functions/fzc.md) โ€” where formulas are evaluated - [Formulas & interpreters reference in the FZ repo](https://github.com/Funz/fz/blob/main/doc/formulas-and-interpreters.md) diff --git a/docs/user-guide/templates/syntax.md b/docs/user-guide/templates/syntax.md new file mode 100644 index 0000000..b14b738 --- /dev/null +++ b/docs/user-guide/templates/syntax.md @@ -0,0 +1,123 @@ +# Input Template Syntax + +A template is the simulation code's normal input file (or directory of files) in which +some values are replaced by **variables** and **formulas**. The markers are configured +in the [model](../models/definition.md); this page uses `varprefix="$"`, +`formulaprefix="@"`, `delim="{}"`, `commentline="#"`. + +!!! warning "Set `delim` explicitly" + A model **without** a `delim` key delimits variables with `()` and formulas with `{}` + (the Java Funz convention). With such a model `${x}` is **not** a variable โ€” only `$x` + and `$(x)` are. The CLI used without `--model` applies `delim: "{}"`, so the same + template can behave differently from Python and from the CLI. + +## Variables + +| Syntax | Meaning | +|--------|---------| +| `$name` | Variable. The name is `[A-Za-z_][A-Za-z0-9_]*`, case-sensitive, and ends at the first other character. | +| `${name}` | Same, delimited: `${T}_celsius` is the variable `T` followed by `_celsius`. | +| `${name~default}` | Variable with a default value, used when `name` is not given (a warning is logged). | + +```text title="input.txt" +mesh_size = ${mesh~100} +time_step = $dt +output = run_${case_id}.dat +``` + +A variable that is neither provided nor defaulted is **left unchanged** in the compiled +file (no error from `fzc`). `fzr` refuses to run when `input_variables` is omitted but +the template declares variables. + +Values are inserted as text (`str(value)`): `1.0` stays `1.0`, `1` stays `1`. + +## Formulas + +`@{expression}` is replaced by the value of the expression, evaluated when the case is +compiled. Variables used inside a formula keep their prefix: + +```text +T_kelvin = @{$T_celsius + 273.15} +area = @{math.pi * $r ** 2} +ratio = @{$a / $b | 0.000} # formatted with a DecimalFormat pattern +``` + +The interpreter is Python by default, R with `"interpreter": "R"` (or +`FZ_INTERPRETER=R`). Number formats (`| 0.00`, `| #.###`, `| 0.00E00`) are described in +[Formulas](formulas.md). + +## Context lines (`#@`) + +Lines starting with `commentline` + `formulaprefix` (`#@`) hold code executed before the +formulas: imports, constants, functions. They may reference variables. + +```text +#@ import math +#@ R = 8.314 +#@ def L_to_m3(L): +#@ return L / 1000 +V_m3 = @{L_to_m3($V_L)} +P = @{$n_mol * R * ($T_celsius + 273.15) / L_to_m3($V_L)} +``` + +- `#@: code` (colon) declares a **static** object: it is also reported by `fzi` with its + value (e.g. `#@: K = 10` โ†’ `'K': '10'`). +- `#@? ...` lines are Java Funz unit tests and are skipped. +- Context lines stay in the compiled file with variables substituted; the code must + accept them as comments. Change `commentline` if `#` is not a comment for the code. + +## What `fzi` reports + +`fzi` returns a dict whose keys are the variables, the static objects and the formula +expressions found: + +```text +a=${a~3} +b=$b +#@: K = 10 +c=@{$a*2} +``` + +```python +fz.fzi("input.txt", {"delim": "{}"}) +# {'K': '10', 'a': 3, 'b': None, 'a*2': 6} +``` + +Variables map to `None` or their default; formulas map to their value when it can be +computed from defaults, `None` otherwise. The inputs to provide are the variable names, +not the formula keys. + +## Directories of input files + +`input_path` may be a directory: every file is scanned and compiled, the tree structure +is kept, and the calculator runs inside the per-case copy of the tree (e.g. an OpenFOAM +case with `system/`, `constant/`, `0/`). Large files identical for every case should be +passed with [`input_static`](../running/results.md#shared-static-files-input_static) +instead. + +## Choosing markers + +Change the markers when they collide with the code's own syntax: a `$` used by the +code's macros makes `fzi` report unexpected variables. + +| Code syntax conflict | Model change | +|----------------------|--------------| +| `$` used by the code | `"varprefix": "%"` | +| `#` is not a comment | `"commentline": "//"` (or `"!"`, `"*"`, `"C "`...) | +| `{}` used by the code | `"delim": "()"` or `"[]"` | + +Real wrappers illustrate the range: MCNP uses `%(...)` with `C ` comments, Scale +`&(...)` with `'` comments, Telemac `$(...)` with `/` comments ([Plugins](../../plugins/index.md)). + +## Java Funz templates + +Java Funz templates use `$(var)` and `@{expr}` โ€” the defaults when the model has no +`delim`. `$(var~default;comment;bounds)` is accepted (only the default is used). A +template marking variables with `?var` needs `"varprefix": "?"`; there is no automatic +`?var` โ†’ `$var` conversion. + +## See also + +- [Formulas](formulas.md) โ€” interpreters, R, number formatting +- [Model Definition](../models/definition.md) โ€” all model fields and defaults +- [fzi](../core-functions/fzi.md), [fzc](../core-functions/fzc.md) diff --git a/mkdocs.yml b/mkdocs.yml index 4ff29ee..c0ef590 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -29,6 +29,9 @@ theme: - navigation.sections - navigation.top - navigation.tracking + - navigation.indexes + - navigation.footer + - toc.follow - search.suggest - search.highlight - content.tabs.link @@ -40,6 +43,13 @@ theme: plugins: - search + - redirects: + redirect_maps: + user-guide/model-definition.md: user-guide/models/definition.md + user-guide/advanced/formulas.md: user-guide/templates/formulas.md + user-guide/advanced/parallel.md: user-guide/running/parallel.md + user-guide/advanced/caching.md: user-guide/running/caching.md + user-guide/advanced/interrupts.md: user-guide/running/interrupts.md markdown_extensions: - pymdownx.highlight: @@ -48,7 +58,11 @@ markdown_extensions: pygments_lang_class: true - pymdownx.inlinehilite - pymdownx.snippets - - pymdownx.superfences + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format - pymdownx.tabbed: alternate_style: true - admonition @@ -74,9 +88,7 @@ extra: - icon: fontawesome/brands/github link: https://github.com/Funz/fz - icon: fontawesome/brands/python - link: https://pypi.org/project/funz/ - version: - provider: mike + link: https://pypi.org/project/funz-fz/ nav: - Home: index.md @@ -85,6 +97,11 @@ nav: - Quick Start: getting-started/quickstart.md - Core Concepts: getting-started/concepts.md - User Guide: + - Templates & Models: + - Input Template Syntax: user-guide/templates/syntax.md + - Formulas: user-guide/templates/formulas.md + - Model Definition: user-guide/models/definition.md + - Output Extraction: user-guide/models/outputs.md - Core Functions: - fzi - Parse Input: user-guide/core-functions/fzi.md - fzc - Compile: user-guide/core-functions/fzc.md @@ -92,19 +109,23 @@ nav: - fzr - Run Study: user-guide/core-functions/fzr.md - fzd - Design of Experiments: user-guide/core-functions/fzd.md - fzl - List Models/Calculators: user-guide/core-functions/fzl.md - - Model Definition: user-guide/model-definition.md - Calculators: - - Overview: user-guide/calculators/overview.md - - Local Shell: user-guide/calculators/shell.md - - SSH Remote: user-guide/calculators/ssh.md - - SLURM: user-guide/calculators/slurm.md - - Funz Server: user-guide/calculators/funz.md - - Cache: user-guide/calculators/cache.md - - Advanced Features: - - Parallel Execution: user-guide/advanced/parallel.md - - Caching Strategy: user-guide/advanced/caching.md - - Interrupt Handling: user-guide/advanced/interrupts.md - - Formulas: user-guide/advanced/formulas.md + - Overview & Aliases: user-guide/calculators/overview.md + - Local Shell (sh://): user-guide/calculators/shell.md + - SSH (ssh://): user-guide/calculators/ssh.md + - SLURM (slurm://, slurm-array://): user-guide/calculators/slurm.md + - Funz Server (funz://): user-guide/calculators/funz.md + - Cache (cache://): user-guide/calculators/cache.md + - Running Studies: + - Parallelism & Retries: user-guide/running/parallel.md + - Timeouts: user-guide/running/timeouts.md + - Caching: user-guide/running/caching.md + - Results & Traceability: user-guide/running/results.md + - Interrupt & Resume: user-guide/running/interrupts.md + - Design of Experiments: + - Writing Algorithms: user-guide/design/algorithms.md + - Installing Models & Algorithms: user-guide/installing.md + - AI Agents (Claude Code, MCP): user-guide/ai-agents.md - Plugins: - Overview: plugins/index.md - FZ-Moret: plugins/moret.md @@ -119,11 +140,20 @@ nav: - Remote HPC: examples/hpc.md - Google Colab Notebooks: examples/colab.md - Reference: - - Configuration: reference/configuration.md + - CLI: reference/cli.md + - Python API: reference/api.md + - .fz Directory & Aliases: reference/configuration.md - Environment Variables: reference/environment.md - - Release Notes: reference/releases.md + - Constraints & Limits: reference/limitations.md + - Security Model: reference/security.md - Troubleshooting: reference/troubleshooting.md - - API Reference: reference/api.md + - Release Notes: reference/releases.md - Contributing: - Development: contributing/development.md - Testing: contributing/testing.md + +validation: + omitted_files: warn + absolute_links: warn + unrecognized_links: warn + anchors: warn diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b18173e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +# MkDocs 2.0 drops the plugin/theme system used here: stay on 1.x +mkdocs>=1.6,<2 +mkdocs-material>=9.5,<10 +mkdocs-redirects>=1.2,<2 +pymdown-extensions>=10 diff --git a/test_site_structure.py b/test_site_structure.py index 9cd2827..d83da3d 100644 --- a/test_site_structure.py +++ b/test_site_structure.py @@ -1,31 +1,49 @@ -import os +"""Check that the built site (mkdocs build) contains every page of the navigation.""" +import sys from pathlib import Path -# Check key pages exist -key_pages = [ - 'site/index.html', - 'site/getting-started/installation/index.html', - 'site/getting-started/quickstart/index.html', - 'site/user-guide/core-functions/fzr/index.html', - 'site/plugins/index.html', - 'site/examples/perfectgas/index.html', - 'site/examples/colab/index.html' -] - -print("Checking documentation structure...") -all_exist = True -for page in key_pages: - exists = os.path.exists(page) - status = "โœ“" if exists else "โœ—" - print(f"{status} {page}") - if not exists: - all_exist = False - -if all_exist: - print("\nโœ“ All key pages built successfully!") -else: - print("\nโœ— Some pages are missing") - -# Count total pages -html_files = list(Path('site').rglob('*.html')) -print(f"\nTotal HTML pages: {len(html_files)}") +import yaml + + +class _Loader(yaml.SafeLoader): + pass + + +# mkdocs.yml uses !!python/name tags (emoji, mermaid fences): accept them as strings +_Loader.add_multi_constructor("tag:yaml.org,2002:python/", lambda loader, suffix, node: None) + + +def nav_pages(nav): + for item in nav: + if isinstance(item, str): + yield item + elif isinstance(item, dict): + for value in item.values(): + if isinstance(value, str): + yield value + else: + yield from nav_pages(value) + + +def main(): + config = yaml.load(Path("mkdocs.yml").read_text(), Loader=_Loader) + missing = [] + for page in nav_pages(config["nav"]): + html = Path("site") / (page[:-3] + "/index.html" if not page.endswith("index.md") else page[:-3] + ".html") + if not html.exists(): + missing.append(str(html)) + redirects = next(p["redirects"]["redirect_maps"] for p in config["plugins"] + if isinstance(p, dict) and "redirects" in p) + for old in redirects: + html = Path("site") / (old[:-3] + "/index.html") + if not html.exists(): + missing.append(str(html)) + if missing: + print("Missing pages:\n " + "\n ".join(missing)) + return 1 + print(f"OK: {len(list(nav_pages(config['nav'])))} navigation pages and {len(redirects)} redirects built") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From 6dc7345fe5cdbce64c6ccd4809addf5d32b283fd Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 21:30:24 +0000 Subject: [PATCH 2/3] Update site for fz main after #98 and P0-8 - sh:// file-name resolution (P0-8): only words existing in the launch directory and absent from the case are resolved; appended input names remain a trap; old behavior noted for result re-checks - Python 3.14 tested; legacy-cache warning; failing version_cmd; security table lists version_cmd and quoted remote interrupt command - constraints page re-synced with fz doc/limitations.md; --global note - perfect gas example: replace dead links to examples/perfectgas/ Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_012GxLbauyVHBQPCeSow8hdh --- docs/contributing/development.md | 2 +- docs/examples/perfectgas.md | 16 +++------------ docs/getting-started/installation.md | 2 +- docs/index.md | 2 +- docs/reference/limitations.md | 21 ++++++++++--------- docs/reference/releases.md | 8 ++++++++ docs/reference/security.md | 6 +++++- docs/reference/troubleshooting.md | 3 ++- docs/user-guide/calculators/shell.md | 30 +++++++++++++++------------- docs/user-guide/installing.md | 6 +++--- docs/user-guide/running/caching.md | 6 ++++-- 11 files changed, 54 insertions(+), 48 deletions(-) diff --git a/docs/contributing/development.md b/docs/contributing/development.md index 982140d..f52a3f3 100644 --- a/docs/contributing/development.md +++ b/docs/contributing/development.md @@ -36,7 +36,7 @@ algorithms, extra `[r]`), `mcp` (extra `[mcp]`, Python โ‰ฅ 3.10), `h5py`, `jq`, | `fz/config.py`, `fz/logging.py`, `fz/shell.py` | Configuration (`FZ_*`), logs, bash / `FZ_SHELL_PATH` resolution | Documentation lives in three places that must stay consistent when the API or the CLI -changes: `README.md`, `doc/`, and the agent skill `skills/fz/` (tested by +changes: `README.md` (overview, under 300 lines), `doc/` (one file per topic), and the agent skill `skills/fz/` (tested by `tests/test_skill_static.py`). This website is a separate repository, [Funz/fz.github.io](https://github.com/Funz/fz.github.io). diff --git a/docs/examples/perfectgas.md b/docs/examples/perfectgas.md index 94a7775..7f5194d 100644 --- a/docs/examples/perfectgas.md +++ b/docs/examples/perfectgas.md @@ -321,19 +321,9 @@ for _, row in results.head().iterrows(): ## Complete Working Example -Download all files: - -- [input.txt](https://github.com/Funz/fz/blob/main/examples/perfectgas/input.txt) -- [calculate.sh](https://github.com/Funz/fz/blob/main/examples/perfectgas/calculate.sh) -- [run_study.py](https://github.com/Funz/fz/blob/main/examples/perfectgas/run_study.py) - -Or clone the examples: - -```bash -git clone https://github.com/Funz/fz.git -cd fz/examples/perfectgas -python run_study.py -``` +The files above are complete. The same study, runnable end to end, is the notebook +[`examples/01_getting_started.ipynb`](https://github.com/Funz/fz/blob/main/examples/01_getting_started.ipynb) +of the fz repository (also on [Colab](colab.md)). ## Next Steps diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 212a5cc..2a0716e 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -4,7 +4,7 @@ | Item | Requirement | |------|-------------| -| Python | โ‰ฅ 3.9 (CI tests 3.9 to 3.13; 3.14 as pre-release only) | +| Python | โ‰ฅ 3.9 (CI tests 3.9 to 3.14 on Linux, macOS and Windows; 3.9 not on Windows) | | Required packages | `paramiko`, `pandas`, `charset-normalizer` (installed automatically) | | bash | Needed by `sh://` calculators and shell output commands. Native on Linux/macOS; MSYS2 or Git Bash on Windows | | Operating system | Linux, macOS, Windows | diff --git a/docs/index.md b/docs/index.md index 3480f91..05ceee3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -121,7 +121,7 @@ Each function exists in Python (`fz.fzr(...)`) and as a command (`fzr ...` or ## Requirements -- Python โ‰ฅ 3.9 (tested 3.9โ€“3.13); dependencies `paramiko`, `pandas`, `charset-normalizer`. +- Python โ‰ฅ 3.9 (tested 3.9โ€“3.14); dependencies `paramiko`, `pandas`, `charset-normalizer`. - **bash** for shell calculators and shell output commands (MSYS2 or Git Bash on Windows, located with `FZ_SHELL_PATH`). - Optional: `rpy2` + R (R formulas), `mcp` on Python โ‰ฅ 3.10 (`fz-mcp`), `h5py`, `jq`, diff --git a/docs/reference/limitations.md b/docs/reference/limitations.md index 663ec29..41d0461 100644 --- a/docs/reference/limitations.md +++ b/docs/reference/limitations.md @@ -102,14 +102,13 @@ rule, the consequence, and what to do instead. Items were checked against the co - **The command runs inside a per-case temporary directory**, and the case's input file names are **appended to the end of the whole command line** (`.` when there are none). With pipes or redirections, they are appended after the last element. -- **Relative paths in the command are made absolute against the directory `fzr` was - launched from**, not the case directory. Every token that looks like a file name is - rewritten (e.g. `run.sh`, `data/mesh.msh`, `result.txt`; a fixed list of common tools - such as `bash`, `python`, `cat`, `cp`, `grep` is left alone). Consequence: - `sh://cat input.txt > res.txt` reads the *uncompiled* template of the launch directory - and writes `res.txt` outside the case. **Put the work in a script** and launch it with - `sh://bash run.sh`: inside the script, relative paths refer to the case directory and - `$1`, `$2`, ... are the compiled input files. +- **File names in the command**: a bare word (`run.sh`, `data.txt`) is made absolute in + the launch directory only if it exists there **and not** in the case directory; + redirection targets stay in the case directory. Consequence of the appended arguments: + `sh://cat input.txt > res.txt` runs `cat input.txt > res.txt input.txt` and `res.txt` + holds the input twice. **Put the work in a script** (`sh://bash run.sh`): inside it, + relative paths refer to the case directory and `$1`, `$2`, ... are the compiled input + files. - `ssh://` and `slurm://` commands run on the remote side: use absolute remote paths. ## Files and directories @@ -130,9 +129,9 @@ rule, the consequence, and what to do instead. Items were checked against the co `fzo` inside that sub-directory (or on the glob `output_dir/*`); `fzo output_dir` itself returns a row of `None`. - **Global wrapper installs:** `fz install model --global` copies the wrapper to `~/.fz/`, but its calculator - alias keeps the command `bash .fz/calculators/.sh`, which `sh://` resolves against the - **launch directory**: runs from any other directory fail (`Command not found locally: - '/.fz/calculators/.sh'`). Prefer project-local installs, or edit + alias keeps the relative command `bash .fz/calculators/.sh`, looked up in the + launch directory, then the case directory, never in `~/.fz/`: runs from any other + directory fail (`Command not found locally: '.fz/calculators/.sh'`). Prefer project-local installs, or edit `~/.fz/calculators/localhost_.json` to use the absolute path of the script (`~` is not expanded). - **Existing results directories are not overwritten in place**: an existing diff --git a/docs/reference/releases.md b/docs/reference/releases.md index 9de5c73..3dbcbe7 100644 --- a/docs/reference/releases.md +++ b/docs/reference/releases.md @@ -28,6 +28,14 @@ in the fz repository. - **Security**: threat model documented; remote commands built by fz are shell-quoted; remote cleanup restricted to fz's directories; passwords in URIs masked everywhere ([Security Model](security.md)). +- **Fix (P0-8)**: `sh://` no longer rewrites words to non-existent files of the launch + directory, nor redirection targets. Before, `sh://cat in.txt > out.txt` read the + uncompiled template and wrote outside the case: re-check results of such commands. +- Python 3.14 in the stable CI matrix (Linux, macOS, Windows) and declared. +- Ignored legacy caches are reported by a warning; a failing `version_cmd` gives "no + identity" instead of its error text; the "no timeout" warning is logged once per + scheme and campaign. +- fz documentation reorganized: short README, one file per topic in `doc/`. - `fz install model` installs every model of a repository. - `fzc`/`fzr` evaluate formulas using inline variable defaults, as `fzi` does. - Error reports no longer blame the command for a "not found" printed by the code. diff --git a/docs/reference/security.md b/docs/reference/security.md index ac12fad..ce34ace 100644 --- a/docs/reference/security.md +++ b/docs/reference/security.md @@ -12,6 +12,7 @@ calculators). | `#@` context lines and `@{...}` formulas | Input templates | Python `exec`/`eval`, or R | | Output extractors (shell, `python://`, callables) | Model `output` | bash / Python | | Calculator commands | Calculator URIs and aliases, installed wrappers' scripts | bash, locally or remotely | +| `version_cmd` of calculator aliases | Calculator aliases | bash, locally (`sh://`) or on the remote host (`ssh://`) | | Algorithm classes | `fzd` algorithm files | Python / R | | `#require:` lines of algorithms | Algorithm files | `pip install` of the listed packages | @@ -29,7 +30,10 @@ reminder of this. remote heredoc. - Passwords embedded in `ssh://` / `slurm://` URIs are masked in the DataFrame, `info.txt`, `history.txt`, logs and `manifest.json`. -- SLURM resource values in URIs are validated against a whitelist. +- SLURM resource values in URIs are validated against a whitelist; the remote + interrupt command (`pkill`/`pgrep` pattern) is shell-quoted. +- `sh://` no longer rewrites words to files of the launch directory that do not exist + there, nor redirection targets (P0-8). These measures protect fz's own command construction. They do not restrict the user's commands, formulas or extractors. diff --git a/docs/reference/troubleshooting.md b/docs/reference/troubleshooting.md index 3f32cae..6d3c7df 100644 --- a/docs/reference/troubleshooting.md +++ b/docs/reference/troubleshooting.md @@ -17,7 +17,8 @@ fz.set_log_level("DEBUG") # or FZ_LOG_LEVEL=DEBUG before starting Python | `fzi` reports unexpected variables | `varprefix` collides with the code's syntax | Change `varprefix` (e.g. `%`) | | `status` `done` but output `None`, `error` = `Missing output: ...` | Extractor found nothing: wrong file/pattern, file written elsewhere, locale | Test the extractor with `fzo` on the case directory | | Output value equals the program's stdout | The code writes `out.txt` (reserved, overwritten by stdout) | Rename the code's output file | -| Result file appears in the launch directory, not in the case | File names in the `sh://` command line are made absolute against the launch directory | Move file handling into a script run as `sh://bash script.sh` | +| Result file contains the input twice, or the command gets unexpected arguments | The compiled input names are appended to the end of the `sh://` command line | Move file handling into a script run as `sh://bash script.sh` | +| fz โ‰ค 1.2: result file in the launch directory, template read uncompiled | Old `sh://` path rewriting (fixed after 1.2, P0-8) | Upgrade; re-check results of such commands | | Every case `timeout` immediately | `FZ_RUN_TIMEOUT=0` or `timeout=0` | Use model `"timeout": null`, or a positive value | | `ssh://`/`slurm://` run never ends | No default timeout for these calculators | Set `FZ_RUN_TIMEOUT` or the model's `timeout` | | Changing `os.environ["FZ_..."]` has no effect | Configuration is read at import | `fz.reload_config()` | diff --git a/docs/user-guide/calculators/shell.md b/docs/user-guide/calculators/shell.md index a329364..2f323e1 100644 --- a/docs/user-guide/calculators/shell.md +++ b/docs/user-guide/calculators/shell.md @@ -25,18 +25,22 @@ calculators = ["sh://bash calculate.sh"] * 4 # 4 cases at a time Exit status 0 means the run succeeded; the case is then `done` if outputs can be parsed. A non-zero status is a failure and the case is retried. -## Path rewriting in the command line - -!!! warning "Relative file names are resolved against the launch directory" - Before running, every token of the command that looks like a file name - (`calculate.sh`, `data/mesh.msh`, `result.txt`) is made **absolute relative to the - directory from which `fzr` was called**, not the case directory. Common tools - (`bash`, `sh`, `python`, `python3`, `cat`, `cp`, `mv`, `rm`, `grep`, `awk`, `sed`, - ...), flags (`-x`, `--opt=v`) and numbers are left unchanged. - - `sh://cat input.txt > res.txt` therefore reads the **uncompiled template** of the - launch directory and writes `res.txt` outside the case; the case ends `done` with - empty outputs. +## File names in the command line + +- A bare word naming a file (`calculate.sh`, `data.txt`) is made absolute in the + **launch directory** only if it exists there **and not** in the case directory: a + helper script next to your Python script works (`sh://bash calculate.sh`), while the + compiled inputs of the case always win. Targets of `>`, `>>`, `2>` are never rewritten. + Each rewritten word is logged at INFO level; the `command` column shows the result. +- The compiled input file names are appended to the end of the **whole** command line, + after pipes and redirections: `sh://cat input.txt > res.txt` runs + `cat input.txt > res.txt input.txt`, so `res.txt` contains the input twice. + +!!! note "Before this fix (fz โ‰ค 1.2)" + Every file-looking word was rewritten to the launch directory, even when absent: + `sh://cat input.txt > res.txt` read the **uncompiled template** and wrote `res.txt` + outside the case, without error. Results obtained with such commands should be + re-checked. **Rule:** put the work in a script and launch it with `sh://bash script.sh`. Inside the script, relative paths are the case directory, and `$1`, `$2`, ... are the compiled @@ -49,8 +53,6 @@ source "$1" ./solver --in "$1" --out result.dat > solver.log 2>&1 || exit 1 ``` -The rewritten command is logged at INFO level and stored in the `command` column. - ## Windows `sh://` needs bash (MSYS2 or Git Bash). Set `FZ_SHELL_PATH` to their `bin` directories diff --git a/docs/user-guide/installing.md b/docs/user-guide/installing.md index dc7e6e1..6385d8a 100644 --- a/docs/user-guide/installing.md +++ b/docs/user-guide/installing.md @@ -59,9 +59,9 @@ README states what it expects (path, environment variables). !!! warning "`--global` installs and runner scripts" `fz install model --global` copies the wrapper to `~/.fz/`, but its calculator - alias keeps the command `bash .fz/calculators/.sh`, which `sh://` resolves against the - **launch directory**: runs from any other directory fail (`Command not found locally: - '/.fz/calculators/.sh'`). Prefer project-local installs, or edit + alias keeps the relative command `bash .fz/calculators/.sh`, looked up in the + launch directory, then the case directory, never in `~/.fz/`: runs from any other + directory fail (`Command not found locally: '.fz/calculators/.sh'`). Prefer project-local installs, or edit `~/.fz/calculators/localhost_.json` to use the absolute path of the script (`~` is not expanded). diff --git a/docs/user-guide/running/caching.md b/docs/user-guide/running/caching.md index 31853d6..46ff1fa 100644 --- a/docs/user-guide/running/caching.md +++ b/docs/user-guide/running/caching.md @@ -42,9 +42,11 @@ distinguish code versions, calculator aliases can declare their identity: | Same `code_id` on both sides | Match, whatever the commands | | Different `code_id` | Never matches | | No `code_id` on one or both sides | Match with a one-time warning; refused with `FZ_CACHE_STRICT=1` | -| Cache written in the old MD5 format | Ignored; considered with `FZ_CACHE_ACCEPT_LEGACY=1` (then as "no `code_id`") | +| Cache written in the old MD5 format | Ignored, with a one-time warning; considered with `FZ_CACHE_ACCEPT_LEGACY=1` (then as "no `code_id`") | -`version_cmd` is run once per calculator per session; its output becomes the `code_id`. +`version_cmd` is run once per calculator per session (locally for `sh://`, on the remote +host for `ssh://`); its output becomes the `code_id`. If it exits with a non-zero status, +the calculator is treated as having no declared identity, with a warning. ## Patterns From de9f8f5f48e325d9d46eb9b9cd87c986510c91e0 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 1 Oct 2026 05:40:02 +0000 Subject: [PATCH 3/3] Document the usability fixes of Funz/fz#99 timeout 0, default delimiters, fzr results_dir guard, fz list output and checks, global-install runner paths, .fz/tmp cleanup; fz <= 1.2 behavior noted where users of the released version are affected. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_012GxLbauyVHBQPCeSow8hdh --- docs/getting-started/quickstart.md | 5 +-- docs/plugins/cathare.md | 3 +- docs/plugins/cristal.md | 3 +- docs/plugins/mcnp.md | 3 +- docs/plugins/moret.md | 3 +- docs/plugins/scale.md | 3 +- docs/plugins/telemac.md | 3 +- docs/reference/api.md | 3 +- docs/reference/cli.md | 4 +-- docs/reference/configuration.md | 5 ++- docs/reference/environment.md | 2 +- docs/reference/limitations.md | 44 ++++++++++++++------------- docs/reference/releases.md | 5 +++ docs/reference/troubleshooting.md | 10 +++--- docs/user-guide/core-functions/fzi.md | 3 +- docs/user-guide/core-functions/fzl.md | 26 +++++++++------- docs/user-guide/core-functions/fzr.md | 8 ++--- docs/user-guide/installing.md | 21 ++++++------- docs/user-guide/models/definition.md | 9 +++--- docs/user-guide/models/outputs.md | 4 +-- docs/user-guide/running/parallel.md | 4 +-- docs/user-guide/running/timeouts.md | 12 ++++---- docs/user-guide/templates/formulas.md | 2 +- docs/user-guide/templates/syntax.md | 11 ++++--- 24 files changed, 100 insertions(+), 96 deletions(-) diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md index 4e13cc3..d1b175f 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -76,8 +76,9 @@ print(results[["T_celsius", "V_L", "n_mol", "pressure", "status"]].head()) !!! danger "Pass `calculators=` and `results_dir=` by keyword" The 4th positional parameter of `fz.fzr` is `results_dir`, not `calculators`. - `fz.fzr("input.txt", vars, model, "sh://bash calculate.sh")` creates a directory - named `sh:/bash calculate.sh` and runs without calculator: every case fails. + `fz.fzr("input.txt", vars, model, "sh://bash calculate.sh")` raises + `ValueError: results_dir looks like a calculator URI` (fz โ‰ค 1.2 silently created a + directory named `sh:/bash calculate.sh` and ran every case without calculator). ## 4. What was produced diff --git a/docs/plugins/cathare.md b/docs/plugins/cathare.md index a2173fd..4b733a6 100644 --- a/docs/plugins/cathare.md +++ b/docs/plugins/cathare.md @@ -12,8 +12,7 @@ pip install funz-fz fz install model Cathare ``` -Installs into `./.fz/` (`--global`: `~/.fz/`, see the -[warning on runner paths](../user-guide/installing.md)). **Requirements of the code +Installs into `./.fz/` (`--global`: `~/.fz/`). **Requirements of the code itself**: CATHARE installation. ## Models diff --git a/docs/plugins/cristal.md b/docs/plugins/cristal.md index 615c3da..19bcd7e 100644 --- a/docs/plugins/cristal.md +++ b/docs/plugins/cristal.md @@ -12,8 +12,7 @@ pip install funz-fz fz install model Cristal ``` -Installs into `./.fz/` (`--global`: `~/.fz/`, see the -[warning on runner paths](../user-guide/installing.md)). **Requirements of the code +Installs into `./.fz/` (`--global`: `~/.fz/`). **Requirements of the code itself**: Set `CRISTAL_HOME` and `CRISTAL_VERSION`. ## Models diff --git a/docs/plugins/mcnp.md b/docs/plugins/mcnp.md index e8a27c0..75f85d4 100644 --- a/docs/plugins/mcnp.md +++ b/docs/plugins/mcnp.md @@ -12,8 +12,7 @@ pip install funz-fz fz install model MCNP ``` -Installs into `./.fz/` (`--global`: `~/.fz/`, see the -[warning on runner paths](../user-guide/installing.md)). **Requirements of the code +Installs into `./.fz/` (`--global`: `~/.fz/`). **Requirements of the code itself**: Set `MCNP_PATH` environment variable. ## Models diff --git a/docs/plugins/moret.md b/docs/plugins/moret.md index 5a2cc72..8b37b11 100644 --- a/docs/plugins/moret.md +++ b/docs/plugins/moret.md @@ -12,8 +12,7 @@ pip install funz-fz fz install model Moret ``` -Installs into `./.fz/` (`--global`: `~/.fz/`, see the -[warning on runner paths](../user-guide/installing.md)). **Requirements of the code +Installs into `./.fz/` (`--global`: `~/.fz/`). **Requirements of the code itself**: MORET at `/opt/MORET/scripts/moret.py`. ## Models diff --git a/docs/plugins/scale.md b/docs/plugins/scale.md index 4994c31..2192923 100644 --- a/docs/plugins/scale.md +++ b/docs/plugins/scale.md @@ -12,8 +12,7 @@ pip install funz-fz fz install model Scale ``` -Installs into `./.fz/` (`--global`: `~/.fz/`, see the -[warning on runner paths](../user-guide/installing.md)). **Requirements of the code +Installs into `./.fz/` (`--global`: `~/.fz/`). **Requirements of the code itself**: SCALE 6.2+ at `/SCALE/scale6.2` or set `SCALE_HOME`. ## Models diff --git a/docs/plugins/telemac.md b/docs/plugins/telemac.md index 05a559a..afd8f67 100644 --- a/docs/plugins/telemac.md +++ b/docs/plugins/telemac.md @@ -12,8 +12,7 @@ pip install funz-fz fz install model Telemac ``` -Installs into `./.fz/` (`--global`: `~/.fz/`, see the -[warning on runner paths](../user-guide/installing.md)). **Requirements of the code +Installs into `./.fz/` (`--global`: `~/.fz/`). **Requirements of the code itself**: `pip install PyTelTools` + Telemac (or Docker). ## Models diff --git a/docs/reference/api.md b/docs/reference/api.md index f1cf127..6f17360 100644 --- a/docs/reference/api.md +++ b/docs/reference/api.md @@ -21,7 +21,8 @@ import fz Invalid argument types raise `TypeError`; invalid values (unknown `case_naming`, bad `delim`, unknown callback name, duplicate DataFrame rows, missing `input_variables` for a -template with variables) raise `ValueError`; a missing `input_path` raises +template with variables, negative `timeout`, a `results_dir` that looks like a +calculator URI) raise `ValueError`; a missing `input_path` raises `FileNotFoundError`. ## Installation of models and algorithms diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 0703d96..ffc489b 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -51,7 +51,8 @@ fzl [-m PATTERN] [-c PATTERN] [--check] [-f {json,markdown,table}] **Model fields** (fzi, fzc, fzo, fzr) define or override the model inline: `--varprefix`, `--formulaprefix`, `--delim`, `--commentline`, `--interpreter`, and repeatable `--output-cmd NAME=COMMAND`. Without `--model`, these start from -`{"varprefix": "$", "formulaprefix": "@", "delim": "{}", "commentline": "#"}`. +`{"varprefix": "$", "formulaprefix": "@", "commentline": "#"}` (no `delim`: variables +accept `$(x)` and `${x}`), the same default as a Python model. ## Conventions @@ -72,7 +73,6 @@ repeatable `--output-cmd NAME=COMMAND`. Without `--model`, these start from | `timeout` per call | `timeout=` | no option: model `timeout` or `FZ_RUN_TIMEOUT` | | `fzd` default directory | `analysis` | `results_fzd` | | `fzd` output format | dict | printed summary, no `--format` | -| Model without `delim` | variables use `()` | `{}` when `--model` is absent; `()` when `--model` gives a model without `delim` | ## Shell completion diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 9f1e99a..6903af1 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -51,9 +51,8 @@ omitted, every alias supporting the model's `id` is used. ## `.fz/tmp` Each run creates temporary case directories under `./.fz/tmp/fz_temp_*` (and, for -`ssh://`, under `.fz/tmp/fz_calc_*` in the remote home). Their content is removed after -the run, but empty `fz_temp_*` directories remain and accumulate; `.fz/tmp/` can be -deleted when no run is active. Remote cleanup is restricted to fz's own directories. +`ssh://`, under `.fz/tmp/fz_calc_*` in the remote home). Empty directories are removed +after the run; files left behind are kept for inspection. Remote cleanup is restricted to fz's own directories. ## Inspecting diff --git a/docs/reference/environment.md b/docs/reference/environment.md index 0f0538a..abad762 100644 --- a/docs/reference/environment.md +++ b/docs/reference/environment.md @@ -20,7 +20,7 @@ variables are read when the first `slurm-array://` case is submitted, MCP variab |----------|---------|---------| | `FZ_MAX_WORKERS` | unset | Upper bound on concurrent cases; never above the number of non-cache calculator entries (except `slurm-array://`) | | `FZ_MAX_RETRIES` | `5` | Calculator failures tolerated per case before `failed` | -| `FZ_RUN_TIMEOUT` | `3600` for `sh://`/`funz://`, none for `ssh://`/`slurm://` | Per-case timeout in seconds; when set, applies to all calculators. **`0` is not "unlimited"**: cases time out immediately ([Timeouts](../user-guide/running/timeouts.md)) | +| `FZ_RUN_TIMEOUT` | `3600` for `sh://`/`funz://`, none for `ssh://`/`slurm://` | Per-case timeout in seconds; when set, applies to all calculators; `0` = no timeout ([Timeouts](../user-guide/running/timeouts.md)) | | `FZ_CASE_NAMING` | `path` | Case directory naming: `path`, `hash`, `index` (invalid values fall back to `path`) | | `FZ_STATIC_CANDIDATE_MIN_SIZE` | `1048576` | Size (bytes) above which a variable-free input file triggers the `input_static` suggestion; `0` disables | | `FZ_RO_CRATE` | `1` | `0` disables `ro-crate-metadata.json` (`manifest.json` is always written) | diff --git a/docs/reference/limitations.md b/docs/reference/limitations.md index 41d0461..cf0b63a 100644 --- a/docs/reference/limitations.md +++ b/docs/reference/limitations.md @@ -2,7 +2,9 @@ This page lists the behaviors of fz that most often surprise users. Each item states the rule, the consequence, and what to do instead. Items were checked against the code of fz (`fz/core.py`, -`fz/helpers.py`, `fz/runners/`, `fz/config.py`, `fz/cli.py`) and by running it. Source: +`fz/helpers.py`, `fz/runners/`, `fz/config.py`, `fz/cli.py`) and by running it. They +describe fz after Funz/fz#99; fz โ‰ค 1.2 differs on timeouts `0`, default delimiters, +`fz list` and global installs. Source: [`doc/limitations.md`](https://github.com/Funz/fz/blob/main/doc/limitations.md). ## Platform and dependencies @@ -21,9 +23,10 @@ rule, the consequence, and what to do instead. Items were checked against the co ## Templates and models -- **Default delimiters differ for variables and formulas.** A model without `delim` - (nor `var_delim`) delimits variables with `()` and formulas with `{}`: `${x}` is then - not a variable. The CLI without `--model` uses `delim: "{}"`. Set `delim` explicitly. +- **Default delimiters.** A model without `delim` (nor `var_delim`) accepts both `$(x)` + and `${x}` for variables and uses `@{...}` for formulas; the CLI without `--model` uses + the same default. Setting `"delim": "{}"` or `"()"` restricts variables to one form. + Templates that contain other `${...}` text (shell snippets) should set `delim`. - **No automatic `?var` conversion.** `?var` is a variable only with `"varprefix": "?"`. - A variable absent from `input_variables` and without `~default` is left as-is in the compiled file (no error from `fzc`). @@ -33,10 +36,9 @@ rule, the consequence, and what to do instead. Items were checked against the co - **`fzr` argument order is `(input_path, input_variables, model, results_dir, calculators, ...)`.** `results_dir` comes *before* `calculators`. A call such as - `fz.fzr("input.txt", variables, model, "sh://bash calc.sh")` puts the calculator URI into - `results_dir` (a directory literally named `sh:/bash calc.sh` is created) and runs with - no calculator, so every case fails. **Always pass `calculators=` and `results_dir=` by - keyword.** + `fz.fzr("input.txt", variables, model, "sh://bash calc.sh")` is refused with a + `ValueError` (a `results_dir` that looks like a URI is rejected). **Always pass + `calculators=` and `results_dir=` by keyword.** - **A DataFrame design must not contain duplicate rows** (`ValueError`): each row is one case. Variables given but absent from the templates only trigger a warning. - **`FZ_*` environment variables are read once, at `import fz`.** Setting @@ -65,10 +67,9 @@ rule, the consequence, and what to do instead. Items were checked against the co (inline or file) or the short form `'a=1,b=[4,5,6]'`, always crossed as a full factorial; a list of cases requires a pandas DataFrame from Python. - **No `--timeout` flag.** Use the model's `"timeout"` entry or `FZ_RUN_TIMEOUT`. -- **`fz list` / `fzl`** shows calculator aliases by their `uri`, not their file name, and - `--check` reports an alias whose command sits in its `models` map - (`{"uri": "sh://", "models": {...}}`, the layout of installed wrappers) as failed - (`Empty sh:// command`) although `fzr` uses it correctly. Algorithms are not listed. +- **`fz list` / `fzl`** shows calculator aliases by file name with their `uri`; `--check` + validates the command of each entry of an alias's `models` map. Algorithms are not + listed (`fz.list_installed_algorithms()`). - **`fzr` exits with status 1 when no case succeeds**; data goes to stdout, logs and progress to stderr. Use `--format json` for machine-readable output. @@ -86,6 +87,9 @@ rule, the consequence, and what to do instead. Items were checked against the co attempt moves to another calculator. - **Case `status` values:** `done`, `failed`, `error`, `timeout`, `interrupted`. A cache hit is `done` with a `calculator` value starting with `cache://`. +- **`done` does not mean "outputs found"**: a run that exits normally stays `done` even + when no output can be parsed (by design: the calculation ran); the outputs are `None` + and `error` holds `Missing output: ...`. Filter on the output columns or on `error`. ## Timeouts @@ -93,9 +97,8 @@ rule, the consequence, and what to do instead. Items were checked against the co `FZ_RUN_TIMEOUT`. - **Default:** 3600 s for `sh://` and `funz://`; **no timeout** for `ssh://` and `slurm://` unless `FZ_RUN_TIMEOUT` is set explicitly (a warning is logged). -- **Only a model `"timeout"` of `null`/`None` or `0` disables the timeout.** - `FZ_RUN_TIMEOUT=0` or `timeout=0` does *not* disable it: every case times out - immediately. +- **`0` means "no timeout"** at every level (`timeout=0`, model `"timeout": 0` or + `null`, `FZ_RUN_TIMEOUT=0`). A negative value is refused. ## `sh://` command line @@ -128,12 +131,11 @@ rule, the consequence, and what to do instead. Items were checked against the co the input declares variables, even when all values are scalars. Run the code and `fzo` inside that sub-directory (or on the glob `output_dir/*`); `fzo output_dir` itself returns a row of `None`. -- **Global wrapper installs:** `fz install model --global` copies the wrapper to `~/.fz/`, but its calculator - alias keeps the relative command `bash .fz/calculators/.sh`, looked up in the - launch directory, then the case directory, never in `~/.fz/`: runs from any other - directory fail (`Command not found locally: '.fz/calculators/.sh'`). Prefer project-local installs, or edit - `~/.fz/calculators/localhost_.json` to use the absolute path of the script (`~` is - not expanded). +- **Installed calculator aliases**: `.fz/...` paths in an alias (`bash + .fz/calculators/.sh`) are resolved against the `.fz/` directory the alias was loaded + from, so `fz install --global` wrappers work from any directory. +- **Temporary directories**: `.fz/tmp/fz_temp_*` directories are removed after a run + when empty; leftover files are kept for inspection. - **Existing results directories are not overwritten in place**: an existing `results_dir` is renamed with a timestamp suffix before the new run (and can be reused via `cache://`). diff --git a/docs/reference/releases.md b/docs/reference/releases.md index 3dbcbe7..a456d67 100644 --- a/docs/reference/releases.md +++ b/docs/reference/releases.md @@ -15,6 +15,11 @@ in the fz repository. 3600 s. - `fz-mcp`: `FZ_MCP_TRUSTED=0` restricted mode now works at the MCP layer only. +- **Usability fixes** (Funz/fz#99): `timeout=0` / `FZ_RUN_TIMEOUT=0` mean no timeout; + a model without `delim` accepts both `$(x)` and `${x}` (CLI and Python alike); + `fzr()` rejects a `results_dir` that looks like a calculator URI; `fz list` lists aliases by name and checks their + `models` commands; `.fz/...` paths of aliases resolve against their own `.fz/` (global + installs work anywhere); empty `.fz/tmp/fz_temp_*` directories are removed. - **`slurm-array://`**: all cases batched in one `sbatch --array` job, one shared `sacct` monitor; resources in the URI (`?cores=4&mem=8G&time=01:00:00&maxrunning=M`), also accepted by `slurm://` ([SLURM](../user-guide/calculators/slurm.md)). diff --git a/docs/reference/troubleshooting.md b/docs/reference/troubleshooting.md index 6d3c7df..2430ec0 100644 --- a/docs/reference/troubleshooting.md +++ b/docs/reference/troubleshooting.md @@ -11,15 +11,15 @@ fz.set_log_level("DEBUG") # or FZ_LOG_LEVEL=DEBUG before starting Python | Symptom | Likely cause | Fix | |---------|--------------|-----| -| A directory named `sh:/...` appears; every case fails | Calculator passed as 4th positional argument of `fzr` (that slot is `results_dir`) | `calculators=...` by keyword | +| `ValueError: results_dir looks like a calculator URI` (fz โ‰ค 1.2: a directory named `sh:/...` appears and every case fails) | Calculator passed as 4th positional argument of `fzr` (that slot is `results_dir`) | `calculators=...` by keyword | | `Permission denied ... ./input.txt` | No calculator: fz fell back to an empty `sh://` | Give `calculators`, or install a calculator alias matching the model `id` | -| `fzi` misses `${x}` variables; `${x}` left in compiled files | Model has no `delim`: variables use `()` | Add `"delim": "{}"` | +| `fzi` misses `${x}` (fz โ‰ค 1.2) or `$(x)` variables | Model sets `delim` to the other pair (or fz โ‰ค 1.2 default `()`) | Fix `delim`, or remove it to accept both forms | | `fzi` reports unexpected variables | `varprefix` collides with the code's syntax | Change `varprefix` (e.g. `%`) | -| `status` `done` but output `None`, `error` = `Missing output: ...` | Extractor found nothing: wrong file/pattern, file written elsewhere, locale | Test the extractor with `fzo` on the case directory | +| Case `done` but output `None` and `error` = `Missing output: ...` | Extractor found nothing: wrong file/pattern, file written elsewhere, locale | Test the extractor with `fzo` on the case directory | | Output value equals the program's stdout | The code writes `out.txt` (reserved, overwritten by stdout) | Rename the code's output file | | Result file contains the input twice, or the command gets unexpected arguments | The compiled input names are appended to the end of the `sh://` command line | Move file handling into a script run as `sh://bash script.sh` | | fz โ‰ค 1.2: result file in the launch directory, template read uncompiled | Old `sh://` path rewriting (fixed after 1.2, P0-8) | Upgrade; re-check results of such commands | -| Every case `timeout` immediately | `FZ_RUN_TIMEOUT=0` or `timeout=0` | Use model `"timeout": null`, or a positive value | +| fz โ‰ค 1.2: every case `timeout` immediately | `FZ_RUN_TIMEOUT=0` or `timeout=0` (now "no timeout") | Upgrade, or use model `"timeout": null` | | `ssh://`/`slurm://` run never ends | No default timeout for these calculators | Set `FZ_RUN_TIMEOUT` or the model's `timeout` | | Changing `os.environ["FZ_..."]` has no effect | Configuration is read at import | `fz.reload_config()` | | `FZ_MAX_WORKERS=8` but cases still run one at a time | Parallelism = number of calculator entries | `calculators=["sh://bash calc.sh"] * 8` | @@ -27,7 +27,7 @@ fz.set_log_level("DEBUG") # or FZ_LOG_LEVEL=DEBUG before starting Python | Cache hit although the code changed | The command/script is not part of the cache key | Declare `code_id` in calculator aliases, or use a fresh `results_dir` | | `fzo results/` returns one row of `None` | `fzo` targets the directory itself | `fzo "results/*"` | | After `fzc`, `compiled/input.txt` does not exist | `fzc` writes `compiled//input.txt` | Look into the case sub-directory | -| `fz list --check` marks an installed calculator `failed` (`Empty sh:// command`) | Known `fz list` limitation for `{"uri": "sh://", "models": {...}}` aliases | Check with a real `fzr` run | +| fz โ‰ค 1.2: `fz list --check` marks an installed calculator `failed` (`Empty sh:// command`) | Old `fz list` check of `{"uri": "sh://", "models": {...}}` aliases | Upgrade; check with a real `fzr` run | | SSH run blocks at start | Host-key prompt (password in URI, unknown host) | Add the host to `known_hosts` or `FZ_SSH_AUTO_ACCEPT_HOSTKEYS=1` | | `bash: not found` / shell errors on Windows | bash not found | Install MSYS2/Git Bash, set `FZ_SHELL_PATH` | | `ValueError: signal only works in main thread` | fz < 1.2 called from a thread | Upgrade; since 1.2 the handler is skipped outside the main thread | diff --git a/docs/user-guide/core-functions/fzi.md b/docs/user-guide/core-functions/fzi.md index 49c9664..f891a9d 100644 --- a/docs/user-guide/core-functions/fzi.md +++ b/docs/user-guide/core-functions/fzi.md @@ -48,7 +48,8 @@ fzi case_dir/ --delim '{}' --format json # inline model fields, no alias ## Use it to - check that the model's markers match the template: stray variables mean `varprefix` - collides with the code's syntax; missing `${x}` variables mean `delim` is not `{}` + collides with the code's syntax; missing `${x}` or `$(x)` variables mean `delim` is set + to the other pair ([defaults](../models/definition.md#default-delimiters)); - list the parameters of an existing input deck. diff --git a/docs/user-guide/core-functions/fzl.md b/docs/user-guide/core-functions/fzl.md index 0dff967..f0a1f41 100644 --- a/docs/user-guide/core-functions/fzl.md +++ b/docs/user-guide/core-functions/fzl.md @@ -27,28 +27,32 @@ fz list ... # same "perfectgas": { "path": "/project/.fz/models/perfectgas.json", "properties": {"id": "perfectgas", "delim": "{}", "output": {"pressure": "..."}}, - "supported_calculators": ["sh://"], + "supported_calculators": ["localhost_perfectgas"], "check_status": "passed" } }, "calculators": { - "sh://": {"supports_models": "all", "check_status": "failed", - "check_error": "Empty sh:// command"} + "localhost_perfectgas": { + "path": "/project/.fz/calculators/localhost_perfectgas.json", + "uri": "sh://", + "supports_models": ["perfectgas"], + "check_status": "passed" + } } } ``` -## Limitations - -- Calculators are listed by their `uri`, not by alias file name. -- An alias whose command is in its `models` map โ€” `{"uri": "sh://", "models": - {"perfectgas": "bash calculate.sh"}}`, the layout used by installed wrappers โ€” is - reported `failed` / `Empty sh:// command` by `--check`, although `fzr` runs it - correctly. Check such an alias with a real run: `fzr ... --model perfectgas` without - `--calculators`. +- Calculator aliases are keyed by file name; a project alias shadows a global one with + the same name. With no alias installed, the default `sh://` is listed. +- `--check` validates each command of an alias's `models` map (`uri` + command). - Algorithms are not listed: use `ls .fz/algorithms ~/.fz/algorithms` or `fz.list_installed_algorithms()`. +!!! note "fz โ‰ค 1.2" + Calculators were listed by `uri` (`sh://`) and aliases of the form + `{"uri": "sh://", "models": {...}}` โ€” the layout of installed wrappers โ€” were + reported `failed` / `Empty sh:// command` by `--check`. + ## See also [.fz Directory & Aliases](../../reference/configuration.md) ยท diff --git a/docs/user-guide/core-functions/fzr.md b/docs/user-guide/core-functions/fzr.md index 4565662..1977cf1 100644 --- a/docs/user-guide/core-functions/fzr.md +++ b/docs/user-guide/core-functions/fzr.md @@ -19,8 +19,8 @@ fz.fzr( !!! danger "Pass `calculators` and `results_dir` by keyword" `results_dir` is the **4th** positional parameter. `fz.fzr("in.txt", vars, model, - "sh://bash run.sh")` uses the URI as a directory name, runs without calculator and - every case fails. + "sh://bash run.sh")` raises `ValueError` because the value looks like a calculator + URI (fz โ‰ค 1.2 used it as a directory name and ran without calculator). ## Parameters @@ -32,7 +32,7 @@ fz.fzr( | `results_dir` | Results root (default `results`); an existing one is renamed with a timestamp | | `calculators` | URI, alias, dict, or list of them. Omitted: installed aliases matching the model `id`, else `sh://` ([Calculators](../calculators/overview.md)) | | `callbacks` | Dict of progress callbacks (below) | -| `timeout` | Seconds per case; overrides the model's `timeout` and `FZ_RUN_TIMEOUT` ([Timeouts](../running/timeouts.md)) | +| `timeout` | Seconds per case (`0` = no timeout); overrides the model's `timeout` and `FZ_RUN_TIMEOUT` ([Timeouts](../running/timeouts.md)) | | `case_naming` | `"path"` (default, or `FZ_CASE_NAMING`), `"hash"`, `"index"` ([Results](../running/results.md#case-directory-naming)) | | `input_static` | Files identical for every case, never templated ([Results](../running/results.md#shared-static-files-input_static)) | @@ -62,7 +62,7 @@ One row per case, in design order: | variables | The case's values | | outputs | One column per `output` entry (dict outputs expand to `name_key` columns) | | `path` | Case result directory | -| `status` | `done`, `failed`, `error`, `timeout` or `interrupted` | +| `status` | `done`, `failed`, `error`, `timeout` or `interrupted`; `done` only means the run ended: check the outputs or `error` | | `calculator` | Calculator used (`cache://...` for a cache hit), with a short id suffix | | `error` | Error message, including `Missing output: ...` when an extractor failed | | `command` | Command actually executed (paths made absolute) | diff --git a/docs/user-guide/installing.md b/docs/user-guide/installing.md index 6385d8a..1b41fe4 100644 --- a/docs/user-guide/installing.md +++ b/docs/user-guide/installing.md @@ -11,7 +11,7 @@ into a `.fz/` directory. fz install model Moret # -> https://github.com/Funz/fz-Moret fz install model https://github.com/you/fz-mycode fz install model ./fz-mycode.zip # local archive -fz install model Moret --global # into ~/.fz/ instead of ./.fz/ (see warning below) +fz install model Moret --global # into ~/.fz/ instead of ./.fz/ fz install algorithm brent # -> https://github.com/Funz/fz-brent fz uninstall model Moret @@ -57,13 +57,11 @@ fzr input.inp --model Moret --input_variables '{"e": [1, 2]}' --format json The simulation code itself (MORET, MCNP, ...) is **not** installed: each wrapper's README states what it expects (path, environment variables). -!!! warning "`--global` installs and runner scripts" - `fz install model --global` copies the wrapper to `~/.fz/`, but its calculator - alias keeps the relative command `bash .fz/calculators/.sh`, looked up in the - launch directory, then the case directory, never in `~/.fz/`: runs from any other - directory fail (`Command not found locally: '.fz/calculators/.sh'`). Prefer project-local installs, or edit - `~/.fz/calculators/localhost_.json` to use the absolute path of the script (`~` is - not expanded). +!!! note "Runner paths" + Installed aliases run `bash .fz/calculators/.sh`. Such `.fz/...` paths are + resolved against the `.fz/` directory the alias was loaded from, so a `--global` + install works from any directory. In fz โ‰ค 1.2 they were resolved against the launch + directory, and global installs failed elsewhere (`Command not found locally`). ## Available packages @@ -79,10 +77,9 @@ README states what it expects (path, environment variables). fz list --models Moret --check --format json ``` -The model must report `check_status: passed`. The calculator line shows the alias's -`uri` (`sh://`) and may report `Empty sh:// command` for this alias layout โ€” a known -`fz list` limitation ([fzl](core-functions/fzl.md#limitations)); a real `fzr` run without -`--calculators` is the reliable check. +The model and its `localhost_Moret` calculator alias must both report +`check_status: passed` (fz โ‰ค 1.2 listed the alias as `sh://` and reported it failed with +`Empty sh:// command`; a real `fzr` run without `--calculators` was then the only check). ## Publishing your own diff --git a/docs/user-guide/models/definition.md b/docs/user-guide/models/definition.md index 9d61bd3..a230a88 100644 --- a/docs/user-guide/models/definition.md +++ b/docs/user-guide/models/definition.md @@ -37,7 +37,7 @@ fz.fzr("input.txt", variables, "perfectgas", calculators="sh://bash calc.sh") |-------|---------|------| | `varprefix` | `$` | Variable marker: `$x` | | `formulaprefix` | `@` | Formula marker: `@{expr}` | -| `delim` | see below | Two characters (or `""`) delimiting variables *and* formulas | +| `delim` | see below | Two characters (or `""`) delimiting variables *and* formulas; without it variables accept `$(x)` and `${x}` | | `var_delim` / `formula_delim` | `()` / `{}` | Delimiters of variables / formulas separately; take precedence over `delim` | | `commentline` | `#` | Comment marker; `commentline` + `formulaprefix` (`#@`) starts a context line | | `interpreter` | `FZ_INTERPRETER` (`python`) | `python` or `R` for formulas | @@ -56,11 +56,10 @@ Accepted aliases of the key names (first found wins): `var_prefix`, `varprefix`, |----------------|-----------|----------| | `"delim": "{}"` | `$x`, `${x}` | `@{...}` | | `"delim": "()"` | `$x`, `$(x)` | `@(...)` | -| no `delim`, no `var_delim`/`formula_delim` | `$x`, `$(x)` โ€” **not** `${x}` | `@{...}` | -| CLI without `--model` | `$x`, `${x}` | `@{...}` | +| no `delim`, no `var_delim`/`formula_delim` (also the CLI without `--model`) | `$x`, `$(x)`, `${x}` | `@{...}` | -`delim` must be empty or exactly two characters (validated). Setting it explicitly -avoids the difference between the Python default and the CLI default. +`delim` must be empty or exactly two characters (validated). In fz โ‰ค 1.2 the default +recognized only `$(x)` in Python and only `${x}` in the CLI. ## Where a model can come from diff --git a/docs/user-guide/models/outputs.md b/docs/user-guide/models/outputs.md index f262f1a..349d0e5 100644 --- a/docs/user-guide/models/outputs.md +++ b/docs/user-guide/models/outputs.md @@ -81,8 +81,8 @@ lengths. `fzd` needs a scalar objective: reduce vectors in `output_expression` When an extractor fails (missing file, empty output), the value is `None` and: - `fzo` adds a column `_output_error` with the reason; -- `fzr` keeps the case `status` and writes `Missing output: ` in the `error` - column. +- `fzr` keeps the case `status` (`done` if the command ended normally) and writes + `Missing output: ` in the `error` column. A `cache://` hit is only accepted when all outputs, re-parsed with the current model, are non-`None`. diff --git a/docs/user-guide/running/parallel.md b/docs/user-guide/running/parallel.md index ccd88cb..790ce7e 100644 --- a/docs/user-guide/running/parallel.md +++ b/docs/user-guide/running/parallel.md @@ -40,8 +40,8 @@ faster entries take more cases. When a run fails (non-zero exit, timeout, transfer error), the case is tried again on the calculators, preferring another entry. After `FZ_MAX_RETRIES` failures (default 5) the case is marked `failed` with the last error. A case whose command succeeds but whose -outputs cannot be parsed is **not** retried: it keeps its status and the reason goes to -`error` (`Missing output: ...`). +outputs cannot be parsed is **not** retried: the reason goes to `error` +(`Missing output: ...`) and the case stays `done`. ```python calculators = [ diff --git a/docs/user-guide/running/timeouts.md b/docs/user-guide/running/timeouts.md index 94e9d0e..7ecdd69 100644 --- a/docs/user-guide/running/timeouts.md +++ b/docs/user-guide/running/timeouts.md @@ -18,13 +18,13 @@ Setting `FZ_RUN_TIMEOUT` explicitly applies it to every calculator type. ## Disabling -| Setting | Effect | -|---------|--------| -| model `"timeout": null` or `"timeout": 0` | no timeout for this model | -| `FZ_RUN_TIMEOUT=0` | **every case times out immediately** | -| `timeout=0` argument | **every case times out immediately** | +`0` means **no timeout** at every level: `timeout=0`, model `"timeout": 0` or `null`, +`FZ_RUN_TIMEOUT=0` (which then also covers `ssh://`/`slurm://`). A negative `timeout=` +raises `ValueError`. -To run without limit, use the model entry, or set a large value. +!!! note "fz โ‰ค 1.2" + `timeout=0` and `FZ_RUN_TIMEOUT=0` made every case time out immediately; only the + model entry disabled the timeout. ## Examples diff --git a/docs/user-guide/templates/formulas.md b/docs/user-guide/templates/formulas.md index 8fe7948..00adf5e 100644 --- a/docs/user-guide/templates/formulas.md +++ b/docs/user-guide/templates/formulas.md @@ -15,7 +15,7 @@ area: @{math.pi * $r ** 2} The formula prefix (`@`), delimiters (`{}`), variable prefix (`$`) and comment marker (`#`) are all configurable per model. Formulas use `{}` by default even when the model -has no `delim`; variables then use `()` ([defaults](../models/definition.md#default-delimiters)). +has no `delim` ([defaults](../models/definition.md#default-delimiters)). ## Number Formatting (updated in 1.2) diff --git a/docs/user-guide/templates/syntax.md b/docs/user-guide/templates/syntax.md index b14b738..1641979 100644 --- a/docs/user-guide/templates/syntax.md +++ b/docs/user-guide/templates/syntax.md @@ -5,11 +5,12 @@ some values are replaced by **variables** and **formulas**. The markers are conf in the [model](../models/definition.md); this page uses `varprefix="$"`, `formulaprefix="@"`, `delim="{}"`, `commentline="#"`. -!!! warning "Set `delim` explicitly" - A model **without** a `delim` key delimits variables with `()` and formulas with `{}` - (the Java Funz convention). With such a model `${x}` is **not** a variable โ€” only `$x` - and `$(x)` are. The CLI used without `--model` applies `delim: "{}"`, so the same - template can behave differently from Python and from the CLI. +!!! note "Default delimiters" + A model **without** `delim` accepts both `$(x)` (Java Funz convention) and `${x}` for + variables, and `@{...}` for formulas; the CLI without `--model` does the same. Set + `delim` when the code's own text contains `${...}` or `$(...)` that must not be read + as variables. In fz โ‰ค 1.2, a model without `delim` only recognized `$(x)` and the CLI + only `${x}`. ## Variables