{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "\n\n# User-Defined Sampler\n\nThanks to user-defined samplers, you can:\n\n- experiment your own sampling algorithms,\n- implement task-specific algorithms to refine the optimization performance, or\n- wrap other optimization libraries to integrate them into Optuna pipelines (e.g., :class:`~optuna.integration.SkoptSampler`).\n\nThis section describes the internal behavior of sampler classes and shows an example of implementing a user-defined sampler.\n\n\n## Overview of Sampler\n\nA sampler has the responsibility to determine the parameter values to be evaluated in a trial.\nWhen a `suggest` API (e.g., :func:`~optuna.trial.Trial.suggest_float`) is called inside an objective function, the corresponding distribution object (e.g., :class:`~optuna.distributions.UniformDistribution`) is created internally. A sampler samples a parameter value from the distribution. The sampled value is returned to the caller of the `suggest` API and evaluated in the objective function.\n\nTo create a new sampler, you need to define a class that inherits :class:`~optuna.samplers.BaseSampler`.\nThe base class has three abstract methods;\n:meth:`~optuna.samplers.BaseSampler.infer_relative_search_space`,\n:meth:`~optuna.samplers.BaseSampler.sample_relative`, and\n:meth:`~optuna.samplers.BaseSampler.sample_independent`.\n\nAs the method names imply, Optuna supports two types of sampling: one is **relative sampling** that can consider the correlation of the parameters in a trial, and the other is **independent sampling** that samples each parameter independently.\n\nAt the beginning of a trial, :meth:`~optuna.samplers.BaseSampler.infer_relative_search_space` is called to provide the relative search space for the trial. Then, :meth:`~optuna.samplers.BaseSampler.sample_relative` is invoked to sample relative parameters from the search space. During the execution of the objective function, :meth:`~optuna.samplers.BaseSampler.sample_independent` is used to sample parameters that don't belong to the relative search space.\n\n<div class=\"alert alert-info\"><h4>Note</h4><p>Please refer to the document of :class:`~optuna.samplers.BaseSampler` for further details.</p></div>\n\n\n## An Example: Implementing SimulatedAnnealingSampler\n\nFor example, the following code defines a sampler based on\n[Simulated Annealing (SA)](https://en.wikipedia.org/wiki/Simulated_annealing):\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "import numpy as np\nimport optuna\n\n\nclass SimulatedAnnealingSampler(optuna.samplers.BaseSampler):\n    def __init__(self, temperature=100):\n        self._rng = np.random.RandomState()\n        self._temperature = temperature  # Current temperature.\n        self._current_trial = None  # Current state.\n\n    def sample_relative(self, study, trial, search_space):\n        if search_space == {}:\n            return {}\n\n        # Simulated Annealing algorithm.\n        # 1. Calculate transition probability.\n        prev_trial = study.trials[-2]\n        if self._current_trial is None or prev_trial.value <= self._current_trial.value:\n            probability = 1.0\n        else:\n            probability = np.exp(\n                (self._current_trial.value - prev_trial.value) / self._temperature\n            )\n        self._temperature *= 0.9  # Decrease temperature.\n\n        # 2. Transit the current state if the previous result is accepted.\n        if self._rng.uniform(0, 1) < probability:\n            self._current_trial = prev_trial\n\n        # 3. Sample parameters from the neighborhood of the current point.\n        # The sampled parameters will be used during the next execution of\n        # the objective function passed to the study.\n        params = {}\n        for param_name, param_distribution in search_space.items():\n            if not isinstance(param_distribution, optuna.distributions.UniformDistribution):\n                raise NotImplementedError(\"Only suggest_float() is supported\")\n\n            current_value = self._current_trial.params[param_name]\n            width = (param_distribution.high - param_distribution.low) * 0.1\n            neighbor_low = max(current_value - width, param_distribution.low)\n            neighbor_high = min(current_value + width, param_distribution.high)\n            params[param_name] = self._rng.uniform(neighbor_low, neighbor_high)\n\n        return params\n\n    # The rest are unrelated to SA algorithm: boilerplate\n    def infer_relative_search_space(self, study, trial):\n        return optuna.samplers.intersection_search_space(study)\n\n    def sample_independent(self, study, trial, param_name, param_distribution):\n        independent_sampler = optuna.samplers.RandomSampler()\n        return independent_sampler.sample_independent(study, trial, param_name, param_distribution)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "<div class=\"alert alert-info\"><h4>Note</h4><p>In favor of code simplicity, the above implementation doesn't support some features (e.g., maximization).\n   If you're interested in how to support those features, please see\n   [examples/samplers/simulated_annealing.py](https://github.com/optuna/optuna/blob/master/examples/samplers/simulated_annealing_sampler.py).</p></div>\n\n\nYou can use ``SimulatedAnnealingSampler`` in the same way as built-in samplers as follows:\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "def objective(trial):\n    x = trial.suggest_float(\"x\", -10, 10)\n    y = trial.suggest_float(\"y\", -5, 5)\n    return x ** 2 + y\n\n\nsampler = SimulatedAnnealingSampler()\nstudy = optuna.create_study(sampler=sampler)\nstudy.optimize(objective, n_trials=100)\n\nbest_trial = study.best_trial\nprint(\"Best value: \", best_trial.value)\nprint(\"Parameters that achieve the best value: \", best_trial.params)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "In this optimization, the values of ``x`` and ``y`` parameters are sampled by using\n``SimulatedAnnealingSampler.sample_relative`` method.\n\n<div class=\"alert alert-info\"><h4>Note</h4><p>Strictly speaking, in the first trial,\n    ``SimulatedAnnealingSampler.sample_independent`` method is used to sample parameter values.\n    Because :func:`~optuna.samplers.intersection_search_space` used in\n    ``SimulatedAnnealingSampler.infer_relative_search_space`` cannot infer the search space\n    if there are no complete trials.</p></div>\n\n"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.8.6"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}