|
1 | 1 | # ParameterSpace.jl |
2 | 2 |
|
3 | | -General tuning tools for julia. Dive into the parameter space of functions or external programs. |
| 3 | +Parameter space exploration and tuning tools for Julia. Supports function tuning, program tuning, and various sampling strategies. |
4 | 4 |
|
5 | 5 | [](https://codecov.io/gh/JuliaAstroSim/ParameterSpace.jl) |
6 | 6 |
|
7 | | -## Install |
| 7 | +## Installation |
8 | 8 |
|
9 | 9 | ```julia |
10 | | -]add ParameterSpace |
| 10 | +using Pkg |
| 11 | +Pkg.add("ParameterSpace") |
11 | 12 | using ParameterSpace |
12 | 13 | ``` |
13 | 14 |
|
14 | | -## Usage |
| 15 | +## Quick Start |
15 | 16 |
|
16 | | -Examples could be found in folder `examples` |
| 17 | +### Basic Usage with ParamSpace |
17 | 18 |
|
18 | | -### Tuning a function |
| 19 | +```julia |
| 20 | +using ParameterSpace |
19 | 21 |
|
20 | | -Let's take a simple function for example: |
| 22 | +# Define parameter dimensions |
| 23 | +pspace = ParamSpace([ |
| 24 | + ParamDim("x", values=[1, 2, 3]), |
| 25 | + ParamDim("y", range=(0, 10, 2)) # 0, 2, 4, 6, 8, 10 |
| 26 | +]) |
21 | 27 |
|
22 | | -```julia |
23 | | -@inline g(x::Real, y::Real) = x * y |
| 28 | +# Iterate over all combinations |
| 29 | +for params in pspace |
| 30 | + println("x=$(params.x), y=$(params.y)") |
| 31 | +end |
| 32 | + |
| 33 | +# Analyze a function |
| 34 | +f(params) = params.x * params.y |
| 35 | +result = analyse_function(f, pspace) |
24 | 36 | ``` |
25 | 37 |
|
26 | | -First construct the parameter space: |
| 38 | +### Sampling Strategies |
| 39 | + |
| 40 | +For large parameter spaces, use sampling strategies: |
27 | 41 |
|
28 | 42 | ```julia |
29 | | -params = [Parameter("x", 1, 0:2), |
30 | | - Parameter("y", 2, 0:2)] |
31 | | -``` |
| 43 | +# Random sampling |
| 44 | +result = analyse_function(f, pspace; strategy=RandomSampling(seed=42), n_samples=100) |
32 | 45 |
|
33 | | -which means there would be $3 \times 3 = 9$ combination of parameters in total, and `ParameterSpace` would help you run tests over all of them by calling the target function iterately: |
| 46 | +# Latin Hypercube Sampling (better coverage) |
| 47 | +result = analyse_function(f, pspace; strategy=LatinHypercubeSampling(), n_samples=100) |
34 | 48 |
|
35 | | -```julia |
36 | | -tuning = analyse_function(g, params) |
| 49 | +# Sobol sequence (low discrepancy, deterministic) |
| 50 | +result = analyse_function(f, pspace; strategy=SobolSampling(), n_samples=100) |
| 51 | + |
| 52 | +# Grid sampling (full enumeration) |
| 53 | +result = analyse_function(f, pspace; strategy=GridSampling()) |
37 | 54 | ``` |
38 | 55 |
|
39 | | -The returns are stored by `DataFrames` to give you enough freedom in data processing |
| 56 | +### Configuration Files |
| 57 | + |
| 58 | +Define parameter spaces in YAML or TOML: |
| 59 | + |
| 60 | +```yaml |
| 61 | +# params.yaml |
| 62 | +parameters: |
| 63 | + x: |
| 64 | + values: [1, 2, 3] |
| 65 | + default: 2 |
| 66 | + y: |
| 67 | + range: [0, 10, 2] |
| 68 | + z: |
| 69 | + linspace: [0.0, 1.0, 11] |
| 70 | +``` |
40 | 71 |
|
41 | 72 | ```julia |
42 | | -julia> tuning = analyse_function(g, params) |
43 | | -9×3 DataFrame |
44 | | - Row │ x y result |
45 | | - │ Any Any Any |
46 | | -─────┼────────────────── |
47 | | - 1 │ 0 0 0 |
48 | | - 2 │ 1 0 0 |
49 | | - 3 │ 2 0 0 |
50 | | - 4 │ 0 1 0 |
51 | | - 5 │ 1 1 1 |
52 | | - 6 │ 2 1 2 |
53 | | - 7 │ 0 2 0 |
54 | | - 8 │ 1 2 2 |
55 | | - 9 │ 2 2 4 |
| 73 | +pspace = load_yaml("params.yaml") |
56 | 74 | ``` |
57 | 75 |
|
58 | | -If only tuning the second parameter `y` of function `g`, the other parameters should be set in order: |
| 76 | +## Detailed Usage |
| 77 | + |
| 78 | +### ParamDim: Parameter Dimension |
| 79 | + |
| 80 | +`ParamDim` defines a single parameter dimension with multiple construction methods: |
| 81 | + |
59 | 82 | ```julia |
60 | | -params = [Parameter("y", 2, 0:0.1:0.5)] |
| 83 | +# Direct values |
| 84 | +ParamDim("x", values=[1, 2, 3, 4, 5]) |
| 85 | + |
| 86 | +# Arithmetic sequence: start, stop, step |
| 87 | +ParamDim("y", range=(0, 10, 2)) # [0, 2, 4, 6, 8, 10] |
| 88 | + |
| 89 | +# Linearly spaced: start, stop, n points |
| 90 | +ParamDim("z", linspace=(0.0, 1.0, 11)) # 11 points from 0 to 1 |
| 91 | + |
| 92 | +# Logarithmically spaced: 10^a to 10^b, n points |
| 93 | +ParamDim("w", logspace=(-1, 1, 5)) # [0.1, 1.0, 10.0] |
| 94 | + |
| 95 | +# With default value and mask |
| 96 | +ParamDim("x", values=[1,2,3,4,5], default=3, mask=[true, false, true, true, false]) |
61 | 97 | ``` |
62 | 98 |
|
| 99 | +### ParamSpace: Multi-dimensional Parameter Space |
| 100 | + |
63 | 101 | ```julia |
64 | | -julia> result = analyse_function(g, params, 1.0) |
65 | | -6×2 DataFrame |
66 | | - Row │ y result |
67 | | - │ Any Any |
68 | | -─────┼───────────── |
69 | | - 1 │ 0.0 0.0 |
70 | | - 2 │ 0.1 0.1 |
71 | | - 3 │ 0.2 0.2 |
72 | | - 4 │ 0.3 0.3 |
73 | | - 5 │ 0.4 0.4 |
74 | | - 6 │ 0.5 0.5 |
| 102 | +# From vector |
| 103 | +pspace = ParamSpace([ |
| 104 | + ParamDim("x", values=[1, 2, 3]), |
| 105 | + ParamDim("y", values=[1, 2]) |
| 106 | +]) |
| 107 | + |
| 108 | +# From dictionary |
| 109 | +pspace = ParamSpace(Dict( |
| 110 | + "x" => ParamDim("x", values=[1, 2, 3]), |
| 111 | + "y" => ParamDim("y", values=[1, 2]) |
| 112 | +)) |
| 113 | + |
| 114 | +# Properties |
| 115 | +volume(pspace) # Total combinations: 6 |
| 116 | +shape(pspace) # (3, 2) |
| 117 | +dim_names(pspace) # [:x, :y] |
| 118 | + |
| 119 | +# State management for resumable iteration |
| 120 | +set_state!(pspace, 10) # Jump to state 10 |
| 121 | +state_no(pspace) # Current state number |
| 122 | +reset!(pspace) # Reset to beginning |
75 | 123 | ``` |
76 | 124 |
|
77 | | -### Tuning a program |
| 125 | +### Masking and Subspace Selection |
78 | 126 |
|
79 | | -It is assumed that all of the parameters are passed through parameter file. First you need to tell `ParameterSpace` how to run your program, by define a `Cmd`: |
| 127 | +Filter parameter values using masks: |
80 | 128 |
|
81 | 129 | ```julia |
82 | | -command = `julia E:/ParameterSpace.jl/examples/simple_program/print.jl` |
| 130 | +pspace = ParamSpace([ |
| 131 | + ParamDim("x", values=[1, 2, 3, 4, 5]), |
| 132 | + ParamDim("y", values=[10, 20, 30]) |
| 133 | +]) |
| 134 | + |
| 135 | +# Mask specific values |
| 136 | +set_mask!(pspace, :x, [true, false, true, false, true]) # Only 1, 3, 5 |
| 137 | + |
| 138 | +# Activate a subspace by conditions |
| 139 | +activate_subspace!(pspace, Dict("x" => [1, 3, 5])) # Only odd x values |
| 140 | +activate_subspace!(pspace, Dict("x" => [1, 2], "y" => 10)) # x in [1,2] and y=10 |
| 141 | + |
| 142 | +# Clear masks |
| 143 | +clear_mask!(pspace, :x) |
| 144 | +clear_all_masks!(pspace) |
83 | 145 | ``` |
84 | 146 |
|
85 | | -It may cause issues if you do not run the program from an absolute path. |
| 147 | +### Coupled Parameters |
86 | 148 |
|
87 | | -Then write down the content of parameter file in formatted string: |
| 149 | +Synchronize parameters with coupled dimensions: |
88 | 150 |
|
89 | 151 | ```julia |
90 | | -content = "x = %d, y = %d" |
| 152 | +pspace = ParamSpace([ |
| 153 | + ParamDim("x", values=[1, 2, 3]) |
| 154 | +]) |
| 155 | + |
| 156 | +# Add coupled dimension: x_squared follows x |
| 157 | +add_coupled!(pspace, CoupledParamDim("x_squared", target=:x, values=[1, 4, 9])) |
| 158 | + |
| 159 | +for params in pspace |
| 160 | + # params.x and params.x_squared are synchronized |
| 161 | + # x=1 → x_squared=1, x=2 → x_squared=4, x=3 → x_squared=9 |
| 162 | +end |
91 | 163 | ``` |
92 | 164 |
|
93 | | -Construct parameter space and use the tuning tool: |
| 165 | +### Analyzing Functions |
94 | 166 |
|
95 | 167 | ```julia |
96 | | -params = [Parameter("x", 1, 0:2), |
97 | | - Parameter("y", 2, 0:2)] |
| 168 | +# New API: function receives NamedTuple |
| 169 | +f(params) = params.x * params.y + params.z |
98 | 170 |
|
99 | | -analyse_program(command, content, "param.txt", params) |
100 | | -``` |
| 171 | +pspace = ParamSpace([ |
| 172 | + ParamDim("x", values=[1, 2]), |
| 173 | + ParamDim("y", values=[1, 2]), |
| 174 | + ParamDim("z", values=[0, 1]) |
| 175 | +]) |
| 176 | + |
| 177 | +result = analyse_function(f, pspace; folder="output", filename="results.csv") |
101 | 178 |
|
102 | | -where `"param.txt"` defines the name of parameter file. |
| 179 | +# With parallel execution (multi-threaded) |
| 180 | +result = analyse_function(f, pspace; parallel=:threads) |
103 | 181 |
|
104 | | -Each set of parameters would be handled in a seperate sub-folder |
| 182 | +# With extra arguments and keyword arguments |
| 183 | +g(params, scale=1) = params.x * scale |
| 184 | +result = analyse_function(g, pspace; scale=2) |
| 185 | +``` |
105 | 186 |
|
106 | | -There is no general way to pass data from a program to Julia, however it's easy and convenient to analyse the output files automatically if you could provide an anlysis function. The procedure has no difference from tuning a function, and the parameters of analysis function could be set with keyword `args::Union{Tuple,Array}`: |
| 187 | +### Analyzing External Programs |
107 | 188 |
|
108 | 189 | ```julia |
109 | | -function analyse(args...) |
110 | | - ... |
111 | | - return ... |
| 190 | +# Define command to run |
| 191 | +command = `julia simulation.jl` |
| 192 | + |
| 193 | +# Parameter file format (printf-style) |
| 194 | +content = "x = %d, y = %d" |
| 195 | + |
| 196 | +# Analysis function to extract results |
| 197 | +function analyse() |
| 198 | + data = readlines("output.txt") |
| 199 | + return parse(Float64, data[1]) |
112 | 200 | end |
113 | 201 |
|
114 | | -analyse_program(command, content, "param.txt", params, analyse, args = []) |
| 202 | +pspace = ParamSpace([ |
| 203 | + ParamDim("x", values=[1, 10, 100]), |
| 204 | + ParamDim("y", values=[1000, 10000]) |
| 205 | +]) |
| 206 | + |
| 207 | +result = analyse_program(command, content, "param.txt", pspace, analyse; |
| 208 | + folder="output", filename="results.csv") |
115 | 209 | ``` |
116 | 210 |
|
117 | | -more details in `examples/simple_program/` |
| 211 | +## Legacy API (Backward Compatibility) |
| 212 | + |
| 213 | +The original `Parameter` type is still supported: |
| 214 | + |
| 215 | +```julia |
| 216 | +# Legacy parameter definition |
| 217 | +params = [ |
| 218 | + Parameter("x", 1, 0:2), # name, index, range |
| 219 | + Parameter("y", 2, 0:2) |
| 220 | +] |
| 221 | + |
| 222 | +# Function receives arguments in order specified by Parameter.Index |
| 223 | +g(x, y) = x * y |
| 224 | +result = analyse_function(g, params) |
| 225 | + |
| 226 | +# Partial tuning (fix some parameters) |
| 227 | +params = [Parameter("y", 2, 0:0.1:0.5)] |
| 228 | +result = analyse_function(g, params, 1.0) # x is fixed at 1.0 |
| 229 | +``` |
| 230 | + |
| 231 | +## Sampling Strategies Comparison |
| 232 | + |
| 233 | +| Strategy | Use Case | Pros | Cons | |
| 234 | +|----------|----------|------|------| |
| 235 | +| `GridSampling` | Small spaces, exhaustive search | Complete coverage | Exponential growth | |
| 236 | +| `RandomSampling` | Quick exploration | Simple, fast | Uneven coverage | |
| 237 | +| `LatinHypercubeSampling` | Medium-dimensional spaces | Good 1D projection | Not optimal for high dimensions | |
| 238 | +| `SobolSampling` | High-dimensional spaces | Low discrepancy, deterministic | Requires Sobol.jl | |
| 239 | +| `StratifiedGridSampling` | Large spaces, quick overview | Sparse but uniform | May miss important regions | |
| 240 | + |
| 241 | +## API Reference |
| 242 | + |
| 243 | +### Types |
| 244 | +- `ParamDim{T}`: Parameter dimension |
| 245 | +- `ParamSpace`: Multi-dimensional parameter space |
| 246 | +- `CoupledParamDim{T}`: Coupled parameter dimension |
| 247 | +- `Parameter{A}`: Legacy parameter type |
| 248 | + |
| 249 | +### Sampling Strategies |
| 250 | +- `AbstractSamplingStrategy`: Abstract base type |
| 251 | +- `RandomSampling(seed=nothing)`: Random uniform sampling |
| 252 | +- `LatinHypercubeSampling(seed=nothing)`: Latin Hypercube Sampling |
| 253 | +- `SobolSampling()`: Sobol quasi-random sequence |
| 254 | +- `GridSampling()`: Complete grid traversal |
| 255 | +- `StratifiedGridSampling(resolution)`: Sparse grid sampling |
| 256 | + |
| 257 | +### Functions |
| 258 | +- `analyse_function(f, pspace; strategy, n_samples, parallel, ...)`: Analyze function over parameter space |
| 259 | +- `analyse_program(cmd, content, paramfile, pspace, analyse; ...)`: Analyze external program |
| 260 | +- `sample(pspace, strategy; n)`: Sample points from parameter space |
| 261 | +- `load_yaml(filepath)`, `load_toml(filepath)`: Load from config files |
| 262 | +- `save_yaml(pspace, filepath)`, `save_toml(pspace, filepath)`: Save to config files |
| 263 | + |
| 264 | +## Examples |
| 265 | + |
| 266 | +See the `examples/` directory for more detailed examples: |
| 267 | +- `simple_function.jl`: Basic function tuning |
| 268 | +- `simple_program/`: External program tuning |
| 269 | + |
0 commit comments