> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/StakeEngine/math-sdk/llms.txt
> Use this file to discover all available pages before exploring further.

# Setting Up Optimization

> Configure OptimizationSetup with conditions, scaling, and parameters, then run the Rust optimizer against your simulation lookup tables.

## Prerequisites

* **Rust and Cargo** installed (used to build and run the optimizer binary).
* A completed simulation run with lookup tables already generated. For production games, **100,000+ simulations per mode** are recommended to ensure a diverse payout distribution and reduce the chance of repeated round results.

## Overview

`OptimizationSetup` is a per-game class (defined in `game_optimization.py`) that takes a `GameConfig` instance and builds the `opt_params` dictionary. `OptimizationExecution` then reads those params and invokes the Rust binary for each mode.

## The opt\_params structure

For each bet mode, `opt_params` requires three keys:

```python theme={null}
opt_params = {
    "<mode_name>": {
        "conditions": { ... },
        "scaling":    [ ... ],
        "parameters": { ... },
    }
}
```

Each key has a corresponding construction class in `optimization_program/optimization_config.py`.

## Setup steps

<Steps>
  <Step title="Define conditions with ConstructConditions">
    `ConstructConditions` partitions simulations by win type and sets the RTP target for each partition.

    Each condition requires **2 of the 3 variables**: `rtp`, `av_win` (average win), and `hr` (hit-rate). The third is derived automatically.

    The **0-win condition** is a special case: its hit-rate can be left as a free variable (pass `"x"` or omit it) because all hit-rates across all conditions must sum to exactly 1, so the 0-win hit-rate can be deduced from the remainder.

    ```python theme={null}
    from optimization_program.optimization_config import ConstructConditions

    "conditions": {
        # wincap must come first — these sims are also freegame sims
        "wincap": ConstructConditions(
            rtp=0.01,
            av_win=wincaps["base"],
            search_conditions=wincaps["base"],
        ).return_dict(),
        "freegame": ConstructConditions(
            rtp=0.37,
            hr=200,
            search_conditions={"symbol": "scatter"},
        ).return_dict(),
        # 0-win: hit-rate is a free variable
        "0": ConstructConditions(
            rtp=0,
            av_win=0,
            search_conditions=0,
        ).return_dict(),
        "basegame": ConstructConditions(
            hr=3.5,
            rtp=0.59,
        ).return_dict(),
    }
    ```

    The `search_conditions` argument tells the optimizer which simulation IDs belong to this condition:

    * A **number** matches simulations with that exact payout.
    * A **tuple `(min, max)`** matches simulations within that payout range.
    * A **dict** (e.g., `{"symbol": "scatter"}`) matches by recorded event data in the force records.
  </Step>

  <Step title="Configure scaling with ConstructScaling">
    `ConstructScaling` biases specific win ranges during trial distribution generation. Each entry requires:

    | Key            | Type    | Description                                                           |
    | -------------- | ------- | --------------------------------------------------------------------- |
    | `criteria`     | `str`   | Which condition to apply the bias to                                  |
    | `win_range`    | `tuple` | `(min, max)` payout range to bias                                     |
    | `scale_factor` | `float` | Multiplier applied to Gaussian weights in this range                  |
    | `probability`  | `float` | Probability (0–1) that this scaling is applied per trial distribution |

    ```python theme={null}
    from optimization_program.optimization_config import ConstructScaling

    "scaling": ConstructScaling(
        [
            {"criteria": "basegame", "scale_factor": 1.2, "win_range": (1, 2),     "probability": 1.0},
            {"criteria": "basegame", "scale_factor": 1.5, "win_range": (10, 20),   "probability": 1.0},
            {"criteria": "freegame", "scale_factor": 0.8, "win_range": (1000, 2000), "probability": 1.0},
            {"criteria": "freegame", "scale_factor": 1.2, "win_range": (3000, 4000), "probability": 1.0},
        ]
    ).return_dict()
    ```

    Large scale factors significantly reduce the probability that a randomly generated trial distribution will be accepted, so use them conservatively.
  </Step>

  <Step title="Set run parameters with ConstructParameters">
    `ConstructParameters` defines the optimizer's computational budget and volatility bounds.

    ```python theme={null}
    from optimization_program.optimization_config import ConstructParameters

    "parameters": ConstructParameters(
        num_show=5000,           # number of final candidate distributions to retain
        num_per_fence=10000,     # number of trial distributions to generate per batch
        min_m2m=4,               # minimum mean-to-median ratio (lower bound on volatility)
        max_m2m=8,               # maximum mean-to-median ratio (upper bound on volatility)
        pmb_rtp=1.0,             # RTP weighting for scoring
        sim_trials=5000,         # number of distributions evaluated before combination
        test_spins=[50, 100, 200],      # spin counts used to rank viable distributions
        test_weights=[0.3, 0.4, 0.3],  # sampling weights for each test-spin count
        score_type="rtp",        # ranking metric
    ).return_dict()
    ```

    The `min_m2m` / `max_m2m` bounds control volatility: a higher mean-to-median ratio means the distribution has a heavier tail relative to its median, producing a more volatile game.
  </Step>

  <Step title="Instantiate OptimizationSetup">
    Create the `OptimizationSetup` class in your `run.py`. It attaches `opt_params` to the `GameConfig` and validates that the conditions match the configured bet modes.

    ```python theme={null}
    from game_optimization import OptimizationSetup

    config = GameConfig()
    optimization_setup_class = OptimizationSetup(config)
    # config.opt_params is now populated and validated
    ```
  </Step>

  <Step title="Run the optimizer with OptimizationExecution">
    Pass the configured `GameConfig` and the list of modes you want to optimize to `OptimizationExecution.run_all_modes()`.

    ```python theme={null}
    from optimization_program.run_script import OptimizationExecution

    optimization_modes_to_run = ["base", "bonus"]
    OptimizationExecution().run_all_modes(config, optimization_modes_to_run, rust_threads)
    ```

    This generates a `setup.toml` for each mode and invokes the Rust binary via `cargo run --release`. Optimized lookup tables are written to `library/lookup_tables/lookUpTable_<mode>_0.csv`.
  </Step>
</Steps>

## Complete example (run.py)

The following is the full `run.py` from the `0_0_lines` sample game, showing how all pieces connect:

```python theme={null}
"""Main file for generating results for sample lines-pay game."""

from gamestate import GameState
from game_config import GameConfig
from game_optimization import OptimizationSetup
from optimization_program.run_script import OptimizationExecution
from utils.game_analytics.run_analysis import create_stat_sheet
from utils.rgs_verification import execute_all_tests
from src.state.run_sims import create_books
from src.write_data.write_configs import generate_configs

if __name__ == "__main__":

    num_threads = 10
    rust_threads = 20
    batching_size = 5000
    compression = True
    profiling = False

    num_sim_args = {
        "base":  int(1e4),
        "bonus": int(1e4),
    }

    run_conditions = {
        "run_sims":          True,
        "run_optimization":  True,
        "run_analysis":      True,
        "run_format_checks": True,
    }
    target_modes = list(num_sim_args.keys())

    config = GameConfig()
    gamestate = GameState(config)
    if run_conditions["run_optimization"] or run_conditions["run_analysis"]:
        optimization_setup_class = OptimizationSetup(config)

    if run_conditions["run_sims"]:
        create_books(
            gamestate, config, num_sim_args, batching_size, num_threads, compression, profiling
        )

    generate_configs(gamestate)

    if run_conditions["run_optimization"]:
        OptimizationExecution().run_all_modes(config, target_modes, rust_threads)
        generate_configs(gamestate)

    if run_conditions["run_analysis"]:
        custom_keys = [{"symbol": "scatter"}]
        create_stat_sheet(gamestate, custom_keys=custom_keys)

    if run_conditions["run_format_checks"]:
        execute_all_tests(config)
```

Set `run_optimization: True` in `run_conditions` to enable the optimization step. Set it to `False` to skip optimization and only run simulations or analysis.

## Next steps

<CardGroup cols={2}>
  <Card title="Optimization algorithm" icon="gear" href="/optimization/algorithm">
    Understand how the iterative weighted sampling algorithm works under the hood.
  </Card>

  <Card title="Game analysis" icon="chart-bar" href="/optimization/analysis">
    Generate a PAR sheet and analyze the optimized win distribution.
  </Card>
</CardGroup>
