ScaDS.ai-Logo

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:

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 ):
  1. It decodes the formula from base64.
  2. It parses the formula.
  3. It writes a tiny run_with_formula.py helper into the run folder.
  4. It replaces the (missing) --run_program with a call to that helper that exports each parameter as an env var OMNIOPT_PARAM_<name>=… and prints RESULT: … per trial.
That's it — there is no automatic detection of free symbols on the CLI. The example above explicitly passes --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:

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

\alphaalpha , \betabeta , …, \omegaomega (same names in both upper and lower case). \pi → sympy's pi , \inftyoo . 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's parse_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:
If you want to suggest a default range other than [-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

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. \lambdalam_) 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.