The --formula
parameter: optimizing equations directly
What is --formula
?
Normally OmniOpt2 optimizes a black-box program: you write a run.sh
(or any executable) that prints a RESULT: …
line, OmniOpt2 sweeps it with different hyperparameter values. The --formula
flag lets you skip that step and describe the objective as a math formula instead. OmniOpt2 parses the formula with sympy (so sums, products, integrals, derivatives and limits all work), infers the free symbols and writes a tiny Python runner that evaluates the formula for each trial.This is useful when:
- You want to test OmniOpt2 on a known mathematical landscape before plugging in a real program.
- You have an analytical objective (loss surface, toy benchmark, kernel, …) and want to sweep it.
- You want a quick sanity check that a search space is well-shaped before investing in a full run.
Quick start (CLI)
A typical invocation looks like this (taken from the same set of flags a normal.tests/
run would use):./omniopt --partition=alpha --experiment_name=a --mem_gb=10 --time=60 --worker_timeout=60 \
--max_eval=500 --num_parallel_jobs=20 --gpus=1 --num_random_steps=20 --follow \
--send_anonymized_usage_stats --result_names 'RESULT=min' --cpus_per_task=1 \
--nodes_per_job=1 --revert_to_random_when_seemingly_exhausted \
--model=BOTORCH_MODULAR --n_estimators_randomforest=100 --optuna_pruner=none \
--optuna_n_startup_trials=10 --optuna_n_ei_candidates=0 \
--optuna_study_name=omniopt_study --optuna_extra_iters=1 --run_mode=local \
--occ_type=euclid --main_process_gb=8 --nr_evals_per_arm=1 \
--max_nr_of_zero_results=50 --slurm_signal_delay_s=0 --max_failed_jobs=0 \
--max_attempts_for_generation=20 --num_restarts=20 --raw_samples=1024 \
--max_abandoned_retrial=20 --max_num_of_parallel_sruns=16 \
--number_of_generators=1 --generate_all_jobs_at_once \
--formula="$(printf '%s' 'f(a,b) = a - b' | base64 -w0)" \
--parameter a range -1000 1000 float false \
--parameter b range -1000 1000 float false
When OmniOpt2 sees
--formula
(and no --run_program
):- It decodes the formula from base64.
- It parses the formula.
- It writes a tiny
run_with_formula.pyhelper into the run folder. - It replaces the (missing)
--run_programwith a call to that helper that exports each parameter as an env varOMNIOPT_PARAM_<name>=…and printsRESULT: …per trial.
--parameter a range -1000 1000 float false
and --parameter b range -1000 1000 float false
; without those, OmniOpt2 has nothing to sweep. The GUI is what suggests parameters for you; on the CLI you write them yourself.You don't have to base64-encode by hand on the CLI — the GUI does it for you — but on the CLI it's the safest way to get backslashes, spaces and quotes through bash. Both
--formula 'f(a,b) = a - b'
(raw) and --formula="$(… | base64 -w0)"
(encoded) are accepted.You will see something like this in the log (the last line is just informational — the free symbols are detected from the parsed formula, not from
--parameter
):[Formula]
a - b
[Formula LaTeX] a - b
[Formula hyperparameters] a, b
Quick start (GUI)
In the GUI, the Run program field has a small tab bar underneath it. Click the Formula tab and a math editor appears: a text input on the left with a live MathJax preview underneath, and a Suggested parameters panel on the right with an Apply → add as parameters button. Toggling the Formula mode select (Auto / LaTeX / Infix) switches the parser on the fly.When you click Apply, the suggested hyperparameters are pushed into the main parameter table and the Run program textarea is replaced by the auto-generated helper. The two are mutually exclusive — exactly one of the two must be filled. If you want to go back to a regular script, just click Clear formula and the Run program tab comes back.
Modes: --formula_mode
OmniOpt2 accepts three modes for parsing:| Mode | When to use |
|---|---|
auto (default) |
Picks latex if the formula contains a backslash, otherwise infix. Best for general use. |
latex
|
Forces LaTeX-style input. Use this when auto mis-detects (e.g. an infix expression with backslashes in string literals). |
infix
|
Forces Python-style infix. Use this when the formula is pure Python syntax without any LaTeX. |
Pick the mode in the GUI via the Formula mode select next to the editor, or on the CLI:
--formula_mode=latex
--formula_mode=infix
--formula_mode=auto # default
If parsing fails, OmniOpt2 prints the underlying sympy error. Common causes:
- Empty
\frac{}{},\int^{}_{}or unterminated\sum_{. - An
=inside a brace block that was not intended as an assignment. - A stray backslash followed by a space (
\ sin).
Supported LaTeX
The preprocessor in.formulas.py
understands a useful subset of LaTeX. Anything beyond that is passed through to sympy's parse_latex
(when antlr4-python3-runtime
is installed) or to parse_expr
as a last resort.
Operators
| LaTeX | Meaning |
|---|---|
+, -, *, /
|
usual arithmetic |
^ and the double-star operator |
power (both accepted) |
\cdot, \times, \ast
|
multiplication |
\frac{a}{b}, \dfrac{a}{b}, \tfrac{a}{b}
|
division a/b
|
\sqrt{x}, \sqrt[n]{x}
|
square / n-th root |
|x|
|
absolute value (also \|x\|) |
Functions
| LaTeX | Sympy |
|---|---|
\sin, \cos, \tan
|
sin, cos, tan
|
\asin, \acos, \atan
|
asin, acos, atan
|
\sinh, \cosh, \tanh
|
sinh, cosh, tanh
|
\exp, \log, \ln
|
exp, log, ln
|
\abs{x}
|
Abs(x)
|
Sums, products, integrals and limits
| LaTeX | Meaning |
|---|---|
\sum_{i=0}^{n} f
|
Sum(f, (i, 0, n))
|
\sum_{i} f
|
defaults to (i, 0, oo)
|
\prod_{i=0}^{n} f
|
Product(f, (i, 0, n))
|
\int_{a}^{b} f \,dx
|
Integral(f, (x, a, b))
|
\int f \,dx
|
defaults to (-oo, oo)
|
\lim_{x \to v} f
|
Limit(f, x, v)
|
\frac{d}{dx} f
|
Derivative(f, x)
|
The bound variable (
i
, j
, k
, …) is automatically excluded from the hyperparameter list.
Macros that are stripped (formatting only)
\text{…}
, \textit{…}
, \mathrm{…}
, \mathbf{…}
, \mathcal{…}
, \mathbb{…}
, \mathfrak{…}
, \mathsf{…}
, \mathtt{…}
, \operatorname{…}
, \mbox{…}
, \boldsymbol{…}
, \hat
, \tilde
, \bar
, \vec
, \dot
, \widehat
, \widetilde
, \overbrace
, \underbrace
, \overline
, \underline
, \left
, \right
, \,
, \;
, \!
, \quad
, \qquad
, \
, \displaystyle
.These don't change the math — they exist purely for typesetting.
Greek letters and special constants
\alpha
→ alpha
, \beta
→ beta
, …, \omega
→ omega
(same names in both upper and lower case). \pi
→ sympy's pi
, \infty
→ oo
. Bare e
and E
are treated as Euler's number when they appear in the body and are not turned into hyperparameters.
Supported infix
Infix mode accepts plain Python expressions that sympy'sparse_expr
can chew on. Highlights:sin(x) + cos(y) # trig
exp(-x**2) / sqrt(2*pi) # gaussian
Sum(i**2, (i, 0, n)) # sum with bound variable
Product((x - i), (i, 1, k)) # product
Integral(exp(-x**2), (x, -oo, oo)) # integral
Derivative(f, x) # derivative
Limit(sin(x)/x, x, 0) # limit
Min(a, b) Max(a, b) # min / max
abs(x - y) # absolute value
sign(x) floor(x) ceil(x) # rounding
2**x x**2 x**y # powers
Implicit multiplication is on by default:
2x
, xy
, (a)(b)
all work.
Worked examples
Every example below is exercised by an automated test in.tests/test_formulas
(the test_tutorial_*
functions). If a tutorial example starts failing, that test will fail too.
Sphere (quick start)
--formula="$(printf '%s' 'f(x, y) = x**2 + y**2' | base64 -w0)" \
--parameter x range -1000 1000 float false \
--parameter y range -1000 1000 float false
Quadratic loss
--formula="$(printf '%s' 'f(x) = a*x**2 + b*x + c' | base64 -w0)" \
--parameter x range -1000 1000 float false
a
, b
and c
appear only on the RHS. The GUI would classify them as constants; the CLI however treats every free symbol as a range parameter, so without explicit --parameter a/b/c …
lines OmniOpt2 would auto-fill them with range -1 1 float false
and try to optimize them too. The example above only lists x
— that means OmniOpt2 will auto-fill a
, b
, c
as additional range -1 1 float
parameters (look for the [Formula] Auto-filled --parameter: …
log line). If you actually want a
, b
, c
to stay at a fixed value, pass them as --parameter a fixed 3
etc. explicitly. Variables whose name ends in the suffix _int
are suggested as int
; here everything is float
.
Sigmoid
--formula="$(printf '%s' '\sigma(z) = \frac{1}{1 + e^{-z}}' | base64 -w0)" \
--formula_mode=latex \
--parameter z range -10 10 float false
Polynomial kernel
--formula="$(printf '%s' 'K(x, y) = (x \cdot y + c)^{d}' | base64 -w0)" \
--formula_mode=latex \
--parameter x range -10 10 float false \
--parameter y range -10 10 float false
Sigmoid cross-entropy
--formula="$(printf '%s' 'L = -1/n * Sum(y_i * log(sigmoid(w*x_i + b)) + (1 - y_i) * log(1 - sigmoid(w*x_i + b)), (i, 0, n))' | base64 -w0)" \
--parameter w range -5 5 float false \
--parameter b range -5 5 float false \
--parameter n range 1 100 int false
Definite integral
--formula="$(printf '%s' '\int_{0}^{1} x**2 \,dx' | base64 -w0)"
Note: a definite integral with constant bounds evaluates to a constant — there are no free symbols at all here, so the helper script has nothing to sweep and the run exits immediately. To get something to optimise, make at least one bound a hyperparameter, e.g.
\int_{0}^{n} x^2 \,dx
with --parameter n range 0 10 float false
.
Bare infix
--formula="$(printf '%s' 'sin(x)**2 + cos(x)**2' | base64 -w0)" \
--formula_mode=infix \
--parameter x range 0 6.283185 float false
This is the trigonometric identity
sin² + cos² = 1
and evaluates to 1
for every x
, so OmniOpt2 should report a flat objective.
Multi-objective
A formula whose body is a tuple(… , …)
is automatically treated as multi-objective. OmniOpt2 prints one RESULT_…
line per tuple component and matches them up against --result_names
(one entry per component, in order). Without --result_names
everything is minimized by default.--formula="$(printf '%s' 'f(x) = (x**2, (x - 3)**2)' | base64 -w0)" \
--result_names='OBJ1=min OBJ2=min' \
--parameter x range -10 10 float false
How parameters are inferred
The GUI and the CLI use slightly different but equivalent heuristics. Both look at the left-hand side of an optional=
assignment first:| Formula | Parameters | Constants |
|---|---|---|
f(x, y) = x^2 + y^2
|
x, y
|
— |
f(x) = a*x + b
|
x
|
a, b (RHS-only) |
f(x) = \sum_{i=0}^{n} i*x
|
x
|
n (RHS-only; i is bound, not suggested) |
a + sin(b) (no LHS) |
a, b
|
— (RHS-only fallback treats everything as a parameter) |
Once the LHS is stripped, the parser classifies the remaining free symbols:
- Anything on the LHS is a parameter (range by default).
- Anything that only appears on the RHS is a constant (fixed to its default value, but you can flip it to a range in the GUI).
- Variables bound by
\sum,\prod,\int,\limor\frac{d}{dx}are excluded. - The literal
e,E,pi,PIare excluded and substituted with their conventional values.
[-1, 1]
, name the variable accordingly (this only matters in the GUI's auto-suggest table — on the CLI you always pass --parameter
yourself):| Naming hint | Effect |
|---|---|
name ending in _int
|
suggested as int with [0, 10]
|
name starting with lr_ or log_, or ending in _log
|
suggested with log_scale=true and [1e-5, 1e-1]
|
| anything else |
[-1, 1], float, log_scale=false
|
For example, a variable named
lr_learning_rate
or epochs_int
is auto-tuned correctly without any further editing.
How --formula
works internally
Behind the scenes, OmniOpt2 generates a tiny per-run helper called run_with_formula.py
in the run folder, hands the parsed formula to it, and uses that helper as the --run_program
for every trial. The helper reads each trial's hyperparameter values from environment variables (OMNIOPT_PARAM_=…
), evaluates the formula with sympy, and prints a single RESULT: …
line (or one RESULT_0
, RESULT_1
, … line per component for the multi-objective case). You normally never look at or edit this script — it's just the mechanism OmniOpt2 uses to turn a math equation into a run_program
.
What gets written to the run folder
A formula-based run drops four extra files (formula.txt
, formula_pretty.txt
, formula_underbraces.txt
, formula_params.json
) plus the auto-generated run_with_formula.py
into the run folder. See the folder structure tutorial for what each one contains.
Continuing a formula-based job
--continue_previous_job
works as usual: the next run re-uses the previous run folder, picks up formula.txt
/ formula_params.json
and re-uses the existing run_with_formula.py
. No need to re-supply --formula
(and no need to worry about shell-quoting it again).omniopt --continue_previous_job=runs/my_experiment/42 … --follow
If you do want to override the formula on a continued run, just pass
--formula
(and --formula_mode
if needed); the new value wins.
Sharing a formula-based run
omniopt_share
(or the Share button in the GUI) automatically picks up the four formula files and renders the formula with clickable parameter overlays on the share page. Nothing extra to do.
Interaction with other options
--run_programand--formulaare mutually exclusive. If both are present, the explicit--run_programwins and the formula is silently dropped. If neither is present and--continue_previous_jobis unset, OmniOpt2 exits with code 19 ("--run_program was empty").--parameteris optional in principle, required in practice. If you omit it, OmniOpt2 auto-fills it from the formula's suggestions (withrangeorfixedand the unhelpful-1, 1]default). Always pass--parameterexplicitly with ranges that match your problem — see the [Quick start (CLI) section above.--formula_python_pathlets you point OmniOpt2 at a specific Python interpreter (defaults to the currentsys.executable). Useful when the auto-detected interpreter doesn't have sympy installed.--formula_modeand the GUI's Formula mode select map directly onto the same parser dispatch.- All other options (
--model,--num_parallel_jobs,--max_eval,--constraint …, etc.) work the same as for a regular run.
Troubleshooting
| Symptom | Likely cause |
|---|---|
Could not parse --formula …: …
|
Empty/unbalanced LaTeX group, stray \, or unsupported syntax. Fix the formula and retry, or supply --run_program as a fallback. |
| The GUI shows "Preview error: unbalanced braces" | A { without a matching } (or vice-versa). |
| Suggested parameters don't include a variable | It's bound by \sum / \prod / \int, or it's a reserved name (sin, pi, oo, …), or it's e / pi / E (treated as a fixed constant). |
Auto-suggested range is [-1, 1] when you wanted [0, 1]
|
Name the variable lr_…, log_… or …_log for log scale; otherwise set the range manually in the GUI. |
| The CLI call works but the GUI's curl fails | The base64 round-trip through printf '%s' … | base64 -w0 was missing — the GUI sends base64 and the shell needs to decode it. Use $(printf '%s' '…' | base64 -w0) exactly. |
OMNIOPT_PARAM_x is empty / nan
|
The parameter name was renamed (e.g. \lambda → lam_) by the LaTeX preprocessor. Use the renamed name in your formula. |
For unit-level coverage of the parser, suggestions and lambdify see
.tests/test_formulas
. The CLI self-test python3 .formulas.py
exercises a handful of representative cases from the command line. Every example in the Worked examples section above has its own test_tutorial_*
entry in test_formulas
, so a regression in any documented example will fail the smoke tests in CI.