fzr - Run 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
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) |
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
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)
Full Factorial Design
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"
)
Variable Handling
Scalar Variables
Fixed values for all cases:
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
List Variables
Creates Cartesian product:
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
Large Parameter Spaces
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
)
Calculator Options
Single Calculator
Multiple Calculators (Parallel)
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
)
Failover Chain
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
]
)
Remote Execution
results = fz.fzr(
"input.txt",
variables,
model,
calculators="ssh://user@server.com/bash /path/to/calculate.sh"
)
Model Options
Dictionary Model
model = {
"varprefix": "$",
"formulaprefix": "@",
"delim": "()",
"commentline": "#",
"output": {
"pressure": "grep 'P:' output.txt | awk '{print $2}'",
"temperature": "grep 'T:' output.txt | awk '{print $2}'"
}
}
results = fz.fzr("input.txt", variables, model, calculators)
Model Alias
Save model to .fz/models/mymodel.json:
Use by name:
Results Analysis
Basic Analysis
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
# Filter by condition
high_pressure = results[results['pressure'] > 1000]
# Filter by multiple conditions
subset = results[
(results['temperature'] > 300) &
(results['pressure'] < 2000)
]
# Filter by status
successful = results[results['status'] == 'done']
Visualization
import matplotlib.pyplot as plt
# 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()
Advanced Features
Progress Callbacks (New in 0.9.1)
Monitor execution progress in real-time with custom callback functions:
def progress_callback(event_type, case_info):
"""
Callback function for monitoring progress.
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')}")
results = fz.fzr(
"input.txt",
variables,
model,
calculators="sh://bash calc.sh",
callbacks=[progress_callback]
)
Multiple callbacks can be registered:
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
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
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
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
# 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"
)
Output Directory Structure
results/
├── param=1/
│ ├── 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
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
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
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
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
- Use caching for expensive calculations
- Parallelize with multiple calculators
- Batch similar cases for better locality
- Filter early to reduce data processing
- Save checkpoints for long runs
See Also
- fzi - Parse input variables
- fzc - Compile input files
- fzo - Parse output files
- Calculators - Calculator types
- Parallel Execution - Parallelization guide