Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
# FZ Release Notes

## Unreleased

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

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

## Unreleased (feat/vector-objectives-fzd)

### Multi-objective (vector) objectives in fzd
Expand Down
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,7 @@ fzr input.txt \
```
--calculator URI Calculator URI (can be specified multiple times)
--results DIR Results directory (default: results)
--case_naming SCHEME Case directory naming: path (default), hash, or index
```

### Complete CLI Examples
Expand Down Expand Up @@ -1025,6 +1026,12 @@ print(output)
# 2 T_celsius=30,V_L=1 2520.74 30.0 1.0
```

If subdirectories were instead named with `case_naming="hash"` or `"index"` (see below),
`fzo` recovers the variable columns from `cases.csv`, a single manifest `fzr` writes
at the results root mapping each case directory to its variables (falling back to
each case's own `info.txt`, which always has `input.<var>=<value>` lines, if the
manifest is missing or incomplete).

### fzr - Run Parametric Calculations

Execute complete parametric study with automatic parallelization:
Expand Down Expand Up @@ -1064,6 +1071,17 @@ print(results)
- `model`: Model definition (dict or alias)
- `calculators`: Calculator URI(s) - string or list
- `results_dir`: Results directory path
- `case_naming`: How each case's result/temp subdirectory is named (default `"path"`):
- `"path"`: `var1=val1,var2=val2,...` - human-readable, but can exceed filesystem
filename length limits (~255 chars) with many variables
- `"hash"`: short content hash of the variable combination - always short and stable
- `"index"`: `case_<i>` - shortest, order-dependent

With `"hash"`/`"index"`, a single `cases.csv` manifest is written at the results
root mapping each case directory to its variables, and `fzo()` reads it back when
the directory name isn't a `key=val,...` pattern (falling back to each case's own
`info.txt` if the manifest is missing or incomplete). Defaults to the
`FZ_CASE_NAMING` env var, or `"path"`.

**Returns**: pandas DataFrame with all results

Expand Down Expand Up @@ -2444,6 +2462,11 @@ export FZ_SHELL_PATH=/usr/local/bin:/usr/bin

# Run timeout in seconds (default: 600 = 10 minutes)
export FZ_RUN_TIMEOUT=3600

# Case directory naming scheme: "path" (var=val,... subdirs, default), "hash"
# (short content hash, avoids filesystem filename length limits with many
# variables), or "index" (case_<i>)
export FZ_CASE_NAMING=path
```

### Shell Path Configuration (FZ_SHELL_PATH)
Expand Down
4 changes: 4 additions & 0 deletions doc/INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,11 +217,13 @@ Quick reference index for finding specific topics in the FZ context documentatio
| Set up parallel execution | parallel-and-caching.md → "Basic Parallel Execution" |
| Use caching | calculators.md → "Cache Calculator" |
| Debug my calculation | quick-examples.md → "Troubleshooting Examples" |
| Avoid filename length limits with many variables | core-functions.md → "fzr" → `case_naming` |

## Configuration & Advanced Topics

| Topic | File | Section |
|-------|------|---------|
| Case directory naming (`case_naming`, `FZ_CASE_NAMING`) | core-functions.md | "fzr" |
| FZ_SHELL_PATH overview | shell-path.md | "Overview" |
| Shell path setup | shell-path.md | "Usage" |
| Windows path configuration | shell-path.md | "Common Configurations" → "Windows with MSYS2" |
Expand Down Expand Up @@ -267,3 +269,5 @@ Quick keyword search:
- **Interrupt**: parallel-and-caching.md → "Interrupt Handling"
- **Retry**: parallel-and-caching.md → "Retry Mechanism"
- **Performance**: parallel-and-caching.md → "Performance Optimization"
- **case_naming / FZ_CASE_NAMING**: core-functions.md → "fzr"
- **cases.csv manifest**: core-functions.md → "fzo" → "Automatic Variable Extraction"
14 changes: 14 additions & 0 deletions doc/core-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,13 @@ If subdirectory names follow the pattern `key1=val1,key2=val2,...`, variables ar
# Automatically creates columns: mesh=100, dt=0.01, solver="fast"
```

If the directory names don't follow that pattern (e.g. `fzr` was run with
`case_naming="hash"` or `"index"`), `fzo` recovers the variable columns from
`cases.csv`, a single manifest `fzr` writes at the results root mapping each
case directory to its variables (falling back to each case's own `info.txt`,
which always has `input.<var>=<value>` lines, if the manifest is missing or
incomplete).

### Output Type Casting

Values are automatically cast to appropriate types:
Expand Down Expand Up @@ -372,6 +379,13 @@ results_df = fz.fzr(
- `model` (dict or str): Model definition or alias
- `calculators` (str or list): Calculator URI(s)
- `results_dir` (str): Results directory path (default: "results")
- `case_naming` (str): How each case's result/temp subdirectory is named - `"path"`
(`var1=val1,var2=val2,...`, default; human-readable but can exceed filesystem
filename length limits with many variables), `"hash"` (short content hash, always
short and stable), or `"index"` (`case_<i>`, shortest). With `"hash"`/`"index"`, a
single `cases.csv` manifest is written at the results root mapping each case
directory to its variables (each case's own `info.txt` also has them, as a
fallback). Defaults to the `FZ_CASE_NAMING` env var, or `"path"`.

**Returns**: pandas DataFrame with all results and metadata

Expand Down
5 changes: 5 additions & 0 deletions doc/parallel-and-caching.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ f6e5d4c3b2a1... config.dat
3. If match found and outputs are valid → reuse results
4. If no match → run calculation

Matching is by `.fz_hash` content, not by directory name, so it's unaffected by
`case_naming` (see core-functions.md → "fzr") — a `cache://` calculator still finds
matches whether the cache directory was written with `case_naming="path"`,
`"hash"`, or `"index"`.

### Strategy 1: Resume Interrupted Runs

```python
Expand Down
16 changes: 14 additions & 2 deletions fz/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,11 @@ def fzr_main():
_add_variables_arg(parser)
parser.add_argument("--results_dir", "--results", "-r", dest="results_dir", default="results",
help="Results directory (default: results)")
parser.add_argument("--case_naming", dest="case_naming", default=None,
choices=["path", "hash", "index"],
help="Case directory naming scheme: 'path' (var=val,... subdirs, default), "
"'hash' (short content hash, avoids filename length limits), or "
"'index' (case_<i>). Defaults to FZ_CASE_NAMING env var, or 'path'.")
_add_calculators_arg(parser)
_add_format_arg(parser)

Expand All @@ -620,7 +625,8 @@ def fzr_main():

result = fzr_func(input_path, variables, model,
results_dir=args.results_dir,
calculators=calculators)
calculators=calculators,
case_naming=args.case_naming)
print(format_output(result, args.format))
# Exit non-zero when no case succeeded, so shell scripts and agents
# can detect total failure without parsing the per-case status column
Expand Down Expand Up @@ -739,6 +745,11 @@ def main():
_add_variables_arg(parser_run)
parser_run.add_argument("--results_dir", "--results", "-r", dest="results_dir",
default="results", help="Results directory (default: results)")
parser_run.add_argument("--case_naming", dest="case_naming", default=None,
choices=["path", "hash", "index"],
help="Case directory naming scheme: 'path' (var=val,... subdirs, default), "
"'hash' (short content hash, avoids filename length limits), or "
"'index' (case_<i>). Defaults to FZ_CASE_NAMING env var, or 'path'.")
_add_calculators_arg(parser_run)
_add_format_arg(parser_run)

Expand Down Expand Up @@ -834,7 +845,8 @@ def main():

result = fzr_func(input_path, variables, model,
results_dir=args.results_dir,
calculators=calculators)
calculators=calculators,
case_naming=args.case_naming)
print(format_output(result, args.format))

elif args.command == "design":
Expand Down
16 changes: 15 additions & 1 deletion fz/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,16 @@ def _load_from_environment(self):
# Shell path configuration (overrides system PATH for binary resolution)
self.shell_path = os.getenv('FZ_SHELL_PATH', None)

# Case directory naming scheme: "path" (key=val,... subdirs, default),
# "hash" (short hash of the variable combination), or "index" (case_<i>).
# "hash"/"index" avoid exceeding filesystem filename length limits when
# there are many input variables; the variable values are still
# recoverable from each case's info.txt.
case_naming = os.getenv('FZ_CASE_NAMING', 'path').lower()
if case_naming not in ('path', 'hash', 'index'):
case_naming = 'path'
self.case_naming = case_naming

def _parse_int_env(self, key: str, default: Optional[int]) -> Optional[int]:
"""Parse integer environment variable"""
value = os.getenv(key)
Expand Down Expand Up @@ -129,7 +139,8 @@ def get_summary(self) -> dict:
'ssh_auto_accept_hostkeys': self.ssh_auto_accept_hostkeys,
'ssh_keepalive': self.ssh_keepalive,
'run_timeout': self.run_timeout,
'shell_path': self.shell_path
'shell_path': self.shell_path,
'case_naming': self.case_naming
}


Expand Down Expand Up @@ -222,6 +233,9 @@ def print_config():
print("\n🔍 SHELL PATH:")
print(f" FZ_SHELL_PATH = {summary['shell_path'] or '(not set, use system PATH)'}")

print("\n📁 CASE DIRECTORY NAMING:")
print(f" FZ_CASE_NAMING = {summary['case_naming']}")

print("\n" + "=" * 60)
print("Set environment variables to customize these defaults")
print("Example: export FZ_LOG_LEVEL=INFO FZ_MAX_RETRIES=3")
Expand Down
Loading
Loading