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..f52a3f3 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` (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). ## 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..7f5194d 100644 --- a/docs/examples/perfectgas.md +++ b/docs/examples/perfectgas.md @@ -321,23 +321,13 @@ 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 - [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..2a0716e 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.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 | -```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..d1b175f 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -1,353 +1,165 @@ # 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")` 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). -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..05ceee3 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.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`, + `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..4b733a6 100644 --- a/docs/plugins/cathare.md +++ b/docs/plugins/cathare.md @@ -1,15 +1,51 @@ -# 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/`). **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..19bcd7e 100644 --- a/docs/plugins/cristal.md +++ b/docs/plugins/cristal.md @@ -1,15 +1,57 @@ -# 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/`). **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..75f85d4 100644 --- a/docs/plugins/mcnp.md +++ b/docs/plugins/mcnp.md @@ -1,15 +1,51 @@ -# 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/`). **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..8b37b11 100644 --- a/docs/plugins/moret.md +++ b/docs/plugins/moret.md @@ -1,15 +1,51 @@ -# 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/`). **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..2192923 100644 --- a/docs/plugins/scale.md +++ b/docs/plugins/scale.md @@ -1,15 +1,57 @@ -# 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/`). **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..afd8f67 100644 --- a/docs/plugins/telemac.md +++ b/docs/plugins/telemac.md @@ -1,15 +1,51 @@ -# 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/`). **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..6f17360 100644 --- a/docs/reference/api.md +++ b/docs/reference/api.md @@ -1,46 +1,62 @@ -# 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, negative `timeout`, a `results_dir` that looks like a +calculator URI) 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..ffc489b --- /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": "@", "commentline": "#"}` (no `delim`: variables +accept `$(x)` and `${x}`), the same default as a Python model. + +## 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` | + +## 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..6903af1 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -1,105 +1,66 @@ -# 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). Empty directories are removed +after the run; files left behind are kept for inspection. 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..abad762 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` = 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) | -**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..cf0b63a --- /dev/null +++ b/docs/reference/limitations.md @@ -0,0 +1,187 @@ +# 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. 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 + +- **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.** 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`). +- `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")` 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 + `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 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. + +## 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://`. +- **`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 + +- **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). +- **`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 + +- **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. +- **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 + +- **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`. +- **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://`). +- **`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..a456d67 100644 --- a/docs/reference/releases.md +++ b/docs/reference/releases.md @@ -1,5 +1,52 @@ # 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. + +- **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)). +- **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)). +- **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. +- `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 +329,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..ce34ace --- /dev/null +++ b/docs/reference/security.md @@ -0,0 +1,59 @@ +# 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 | +| `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 | + +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; 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. + +## 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..2430ec0 100644 --- a/docs/reference/troubleshooting.md +++ b/docs/reference/troubleshooting.md @@ -1,86 +1,47 @@ # 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 | +|---------|--------------|-----| +| `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}` (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. `%`) | +| 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 | +| 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` | +| `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 โ‰ค 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 | +| 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..2f323e1 100644 --- a/docs/user-guide/calculators/shell.md +++ b/docs/user-guide/calculators/shell.md @@ -1,86 +1,70 @@ -# 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 +## File names in the command line -# Read input variables written by the compiled template -source "$INPUT_FILE" +- 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. -echo "Running with temp=$temp, pressure=$pressure" -result=$(echo "scale=2; $temp * $pressure / 100" | bc) +!!! 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. -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 - -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..f891a9d 100644 --- a/docs/user-guide/core-functions/fzi.md +++ b/docs/user-guide/core-functions/fzi.md @@ -1,67 +1,58 @@ -# 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}` 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. + +## 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..f0a1f41 100644 --- a/docs/user-guide/core-functions/fzl.md +++ b/docs/user-guide/core-functions/fzl.md @@ -1,98 +1,60 @@ -# 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": ["localhost_perfectgas"], + "check_status": "passed" + } + }, + "calculators": { + "localhost_perfectgas": { + "path": "/project/.fz/calculators/localhost_perfectgas.json", + "uri": "sh://", + "supports_models": ["perfectgas"], + "check_status": "passed" + } + } +} +``` + +- 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) ยท +[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..1977cf1 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")` raises `ValueError` because the value looks like a calculator + URI (fz โ‰ค 1.2 used it as a directory name and ran without calculator). -## 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 (`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)) | -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`; `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) | ```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..1b41fe4 --- /dev/null +++ b/docs/user-guide/installing.md @@ -0,0 +1,91 @@ +# 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/ + +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). + +!!! 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 + +- 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 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 + +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..a230a88 --- /dev/null +++ b/docs/user-guide/models/definition.md @@ -0,0 +1,102 @@ +# 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; 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 | +| `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` (also the CLI without `--model`) | `$x`, `$(x)`, `${x}` | `@{...}` | + +`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 + +| 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..349d0e5 --- /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` (`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`. + +## 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..46ff1fa --- /dev/null +++ b/docs/user-guide/running/caching.md @@ -0,0 +1,78 @@ +# 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, 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 (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 + +```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..790ce7e --- /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: the reason goes to `error` +(`Missing output: ...`) and the case stays `done`. + +```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..7ecdd69 --- /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 + +`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`. + +!!! 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 + +```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..00adf5e 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` ([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..1641979 --- /dev/null +++ b/docs/user-guide/templates/syntax.md @@ -0,0 +1,124 @@ +# 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="#"`. + +!!! 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 + +| 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())