diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml new file mode 100644 index 0000000..dfddada --- /dev/null +++ b/.github/workflows/gh-pages.yml @@ -0,0 +1,68 @@ +name: Documentation + +on: + push: + workflow_dispatch: + pull_request: + repository_dispatch: + types: [ release ] + +permissions: + id-token: write + contents: write + pull-requests: write + actions: read + +jobs: + build: + runs-on: ubuntu-latest + name: "Build docs" + env: + # `uv pip ...` requires venv by default. This skips that requirement. + UV_SYSTEM_PYTHON: 1 + steps: + - uses: actions/checkout@v4 + - name: Install uv + uses: astral-sh/setup-uv@v5 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install dependencies + run: | + git clone https://github.com/xopt-org/xopt.git + cd xopt + uv pip install ".[doc]" + - name: Build Docs + shell: bash -l {0} + run: mkdocs build + - name: Extract examples + shell: bash -l {0} + run: | + zip -r xopt-examples.zip docs/examples/ + mv xopt-examples.zip ./site/assets/ + - name: Upload docs artifact + uses: actions/upload-pages-artifact@v3 + with: + path: ./site + deploy: + if: ${{ github.repository_owner == 'xopt-org' && (github.ref == 'refs/heads/main') }} + needs: build + name: "Deploy docs" + + # Grant GITHUB_TOKEN the permissions required to make a Pages deployment + permissions: + pages: write # to deploy to Pages + id-token: write # to verify the deployment originates from an appropriate source + + # Deploy to the github-pages environment # TODO: set environment and url +# environment: +# name: github-pages +# url: ${{ steps.deployment.outputs.page_url }} + + # Specify runner + deployment step + runs-on: ubuntu-latest + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/README.md b/README.md index bff8613..3671f1f 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ -# Xopt-docs +# Xopt Docs + +New repository for Xopt documentation. Under development. diff --git a/docs/algorithms.md b/docs/algorithms.md new file mode 100644 index 0000000..5f1e1d8 --- /dev/null +++ b/docs/algorithms.md @@ -0,0 +1,76 @@ +Pre-Configured Generators in Xopt +=============== +A number of algorithms are implemented in Xopt using the ```Generator``` class for +off-the-shelf usage. +Below is a +description of the different generators that are available in Xopt and their target +use cases. + +RandomGenerator +=============== +Generates random points in the input space according to ```VOCS```. + +Bayesian Generators +== +All of the generators here use Bayesian optimization (BO) type methods to solve single +objective, multi objective and characterization problems. Bayesian generators +incorperate unknown constrianing functions into optimization based on what is +specified in ```VOCS``` + +- ```ExpectedImprovementGenerator```: implements Expected Improvement single + objective BO. Automatically balances trade-offs between exploration and + exploitation and is thus useful for general purpose optimization. +- ```UpperConfidenceBoundGenerator```: implements Upper Confidence Bound single + objective BO. Requires a hyperparameter ```beta``` that explicitly sets the tradeoff + between exploration and exploitation. Default value of ```beta=2``` is a good + starting point. Increase $\beta$ to prioritize exploration and decrease ```beta``` to + prioritize exploitation. +- ```BayesianExplorationGenerator```: implements the Bayesian Exploration algorithm + for function characterization. This algorithm selects observation points that + maximize model uncertainty, thus picking points that maximize the information gain + about the target function at each iteration. If the target function is found to be + more sensative to one parameter this generator will adjust sampling frequency to + adapt. Note: specifying ```vocs.objective[1]``` + to ```MAXIMIZE``` or ```MINIMIZE``` does not change the behavior of this generator. +- ```MOBOGenerator```: implements Multi-Objective BO using the + Expected Hypervolume Improvement (EHVI) acquisition function. This is an ideal + general purpose multi-objective optimizer when objective evaluations cannot be + massively parallelized (< 10 parallel evaluations). +- ```MGGPOGenerator```: implements Multi-Generation Gaussian Process Optimization using + the + Expected Hypervolume Improvement (EHVI) acquisition function. This is an ideal + general purpose multi-objective optimizer when objective evaluations can be + massively parallelized (> 10 parallel evaluations) . +- ```MultiFidelityGenerator```: implements Multi-Fidelity BO which can take + advantage of lower fidelity evaluations of objectives and constraints to reduce + the computational cost of solving single or multi-objective optimization problems + in sequential or small scale parallel (< 10 parallel evaluations) + contexts. + +Evolutionary Generators +===== +- ```CNSGAGenerator```: implements Continuous Non-dominated Sorted Genetic Algorithm + which as a good general purpose evolutionary algorithm used for solving + multi-objective optimization problems where evaluating the objective is relatively + cheap and massively parallelizable (above 5-10 parallel evaluations). + +Extremum Seeking Generators +=== +- ```ExtremumSeekingGenerator```: implements the Extremum Seeking algorithm which is + ideal for solving optimization problems that are suceptable to drifts. + +Scipy Generators +=== +These generators serve as wrappers for algorithms implemented in scipy. +- ```NelderMeadGenerator```: implements Nelder-Mead (simplex) optimization. + +RCDS Generators +=== +- ```RCDSGenerator```: implements the RCDS algorithm. RCDS could be applied in noisy + online optimization scenarios + +Custom Generators +==== +Any general algorithm can be implemented by subclassing the abstract ```Generator``` +class and used in the Xopt framework. If you implement a generator for your use case +please consider opening a pull request so that we can add it to Xopt! diff --git a/docs/api/evaluator.md b/docs/api/evaluator.md new file mode 100644 index 0000000..75bf269 --- /dev/null +++ b/docs/api/evaluator.md @@ -0,0 +1 @@ +::: xopt.evaluator.Evaluator diff --git a/docs/api/generators.md b/docs/api/generators.md new file mode 100644 index 0000000..d1e108a --- /dev/null +++ b/docs/api/generators.md @@ -0,0 +1 @@ +::: xopt.generator diff --git a/docs/api/generators/bayesian.md b/docs/api/generators/bayesian.md new file mode 100644 index 0000000..c2c3b59 --- /dev/null +++ b/docs/api/generators/bayesian.md @@ -0,0 +1,18 @@ +::: xopt.generators.bayesian.bayesian_generator.BayesianGenerator +::: xopt.generators.bayesian.bayesian_exploration.BayesianExplorationGenerator +::: xopt.generators.bayesian.expected_improvement.ExpectedImprovementGenerator +::: xopt.generators.bayesian.expected_improvement.TDExpectedImprovementGenerator +::: xopt.generators.bayesian.mobo.MOBOGenerator +::: xopt.generators.bayesian.upper_confidence_bound.UpperConfidenceBoundGenerator +::: xopt.generators.bayesian.upper_confidence_bound.TDUpperConfidenceBoundGenerator +::: xopt.generators.bayesian.multi_fidelity.MultiFidelityGenerator +::: xopt.generators.bayesian.turbo.TurboController +- get_trust_region +- update_trust_region +- get_data_in_trust_region +- update_state +- reset +::: xopt.generators.bayesian.turbo.OptimizeTurboController +- minimize +::: xopt.generators.bayesian.turbo.SafetyTurboController +::: xopt.generators.bayesian.turbo.EntropyTurboController diff --git a/docs/api/generators/genetic.md b/docs/api/generators/genetic.md new file mode 100644 index 0000000..baeeb9d --- /dev/null +++ b/docs/api/generators/genetic.md @@ -0,0 +1 @@ +::: xopt.generators.ga.cnsga.CNSGAGenerator diff --git a/docs/api/generators/scipy.md b/docs/api/generators/scipy.md new file mode 100644 index 0000000..7bbf3c0 --- /dev/null +++ b/docs/api/generators/scipy.md @@ -0,0 +1,3 @@ +::: xopt.generators.sequential.neldermead.NelderMeadGenerator +::: xopt.generators.sequential.rcds.RCDSGenerator +::: xopt.generators.sequential.extremumseeking.ExtremumSeekingGenerator diff --git a/docs/api/vocs.md b/docs/api/vocs.md new file mode 100644 index 0000000..bc03d4d --- /dev/null +++ b/docs/api/vocs.md @@ -0,0 +1 @@ +::: xopt.vocs.VOCS diff --git a/docs/api/xopt.md b/docs/api/xopt.md new file mode 100644 index 0000000..c389fa2 --- /dev/null +++ b/docs/api/xopt.md @@ -0,0 +1 @@ +::: xopt.Xopt diff --git a/docs/assets/Xopt-logo.png b/docs/assets/Xopt-logo.png new file mode 100644 index 0000000..ec2b2cf Binary files /dev/null and b/docs/assets/Xopt-logo.png differ diff --git a/docs/assets/xopt_overview.pdf b/docs/assets/xopt_overview.pdf new file mode 100644 index 0000000..27996c3 Binary files /dev/null and b/docs/assets/xopt_overview.pdf differ diff --git a/docs/examples/basic/checkpointing_and_restarts.ipynb b/docs/examples/basic/checkpointing_and_restarts.ipynb new file mode 100644 index 0000000..64fe026 --- /dev/null +++ b/docs/examples/basic/checkpointing_and_restarts.ipynb @@ -0,0 +1,180 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "# Checkpointing and Restarts\n", + "If `dump_file` is provided Xopt will save the data and the Xopt configuration in a\n", + "yaml file. This can be used directly to create a new Xopt object." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-03T19:55:02.557814300Z", + "start_time": "2024-05-03T19:54:59.954542500Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:15.718571Z", + "iopub.status.busy": "2024-09-13T15:54:15.718173Z", + "iopub.status.idle": "2024-09-13T15:54:16.708491Z", + "shell.execute_reply": "2024-09-13T15:54:16.708212Z" + } + }, + "outputs": [], + "source": [ + "# Import the class\n", + "from xopt import Xopt\n", + "\n", + "# Make a proper input file.\n", + "YAML = \"\"\"\n", + "dump_file: dump.yml\n", + "generator:\n", + " name: random\n", + "\n", + "evaluator:\n", + " function: xopt.resources.test_functions.tnk.evaluate_TNK\n", + " function_kwargs:\n", + " a: 999\n", + "\n", + "vocs:\n", + " variables:\n", + " x1: [0, 3.14159]\n", + " x2: [0, 3.14159]\n", + " objectives: {y1: MINIMIZE, y2: MINIMIZE}\n", + " constraints:\n", + " c1: [GREATER_THAN, 0]\n", + " c2: [LESS_THAN, 0.5]\n", + " constants: {a: dummy_constant}\n", + "\n", + "\"\"\"" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Checkpoints\n", + "Since we specified a dump file Xopt will dump the data and all of the options\n", + "required to create a new Xopt object that continues the run." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-03T19:55:02.741844900Z", + "start_time": "2024-05-03T19:55:02.558814400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:16.710304Z", + "iopub.status.busy": "2024-09-13T15:54:16.710154Z", + "iopub.status.idle": "2024-09-13T15:54:16.754153Z", + "shell.execute_reply": "2024-09-13T15:54:16.753777Z" + } + }, + "outputs": [], + "source": [ + "# create Xopt object.\n", + "X = Xopt.from_yaml(YAML)\n", + "\n", + "# take 10 steps and view data\n", + "for _ in range(10):\n", + " X.step()\n", + "\n", + "X.data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Create Xopt object from dump file" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-03T19:55:02.813814500Z", + "start_time": "2024-05-03T19:55:02.740817500Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:16.775975Z", + "iopub.status.busy": "2024-09-13T15:54:16.775763Z", + "iopub.status.idle": "2024-09-13T15:54:16.785072Z", + "shell.execute_reply": "2024-09-13T15:54:16.784799Z" + } + }, + "outputs": [], + "source": [ + "X2 = Xopt.from_file(\"dump.yml\")\n", + "X2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-03T19:55:02.946844800Z", + "start_time": "2024-05-03T19:55:02.782814900Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:16.786507Z", + "iopub.status.busy": "2024-09-13T15:54:16.786330Z", + "iopub.status.idle": "2024-09-13T15:54:16.842388Z", + "shell.execute_reply": "2024-09-13T15:54:16.842111Z" + } + }, + "outputs": [], + "source": [ + "for _ in range(10):\n", + " X2.step()\n", + "\n", + "X2.data" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/basic/dump.yml b/docs/examples/basic/dump.yml new file mode 100644 index 0000000..5259234 --- /dev/null +++ b/docs/examples/basic/dump.yml @@ -0,0 +1,229 @@ +data: + a: + '0': dummy_constant + '1': dummy_constant + '10': dummy_constant + '11': dummy_constant + '12': dummy_constant + '13': dummy_constant + '14': dummy_constant + '15': dummy_constant + '16': dummy_constant + '17': dummy_constant + '18': dummy_constant + '19': dummy_constant + '2': dummy_constant + '3': dummy_constant + '4': dummy_constant + '5': dummy_constant + '6': dummy_constant + '7': dummy_constant + '8': dummy_constant + '9': dummy_constant + c1: + '0': 16.8687531669 + '1': -0.7162468842 + '10': 1.2808872726 + '11': 4.0729849484 + '12': 9.8696135232 + '13': 15.1328894688 + '14': 9.7784036149 + '15': -0.1751019073 + '16': 7.708008318 + '17': 5.6624589991 + '18': 9.8800266769 + '19': 7.7109908429 + '2': 8.8343541105 + '3': 2.5626716002 + '4': 0.628348862 + '5': 5.1854360775 + '6': 2.8638942934 + '7': 4.6390051108 + '8': 9.5898268302 + '9': 7.6409845263 + c2: + '0': 12.4734054036 + '1': 0.2251586888 + '10': 1.1897073502 + '11': 2.8430234481 + '12': 6.9262635811 + '13': 10.9817265145 + '14': 6.6544000963 + '15': 0.3858353762 + '16': 5.0374618482 + '17': 3.7465822315 + '18': 6.7156194909 + '19': 5.7405521063 + '2': 6.4051478253 + '3': 1.7799217633 + '4': 0.5385157033 + '5': 3.2382660295 + '6': 1.9302493449 + '7': 2.8077426537 + '8': 6.9502231 + '9': 4.9810435493 + x1: + '0': 3.0228010413 + '1': 0.5864469085 + '10': 1.5190863306 + '11': 2.1852293654 + '12': 1.5346072388 + '13': 3.0992987863 + '14': 2.0710547261 + '15': 0.936286057 + '16': 1.6332492033 + '17': 1.1422095497 + '18': 2.7042098474 + '19': 0.4859133143 + '2': 0.9795043472 + '3': 1.8263987544 + '4': 1.2151919773 + '5': 1.7985089476 + '6': 1.8891030742 + '7': 2.0017271965 + '8': 3.0444029498 + '9': 1.6456315297 + x2: + '0': 2.9716149194 + '1': 0.0334320834 + '10': 0.1111936201 + '11': 0.4449960536 + '12': 2.919886659 + '13': 2.5555710481 + '14': 2.546017386 + '15': 0.0578576556 + '16': 2.4373198216 + '17': 2.3259652587 + '18': 1.8627466528 + '19': 2.8959035188 + '2': 2.9849996793 + '3': 0.3565144342 + '4': 0.3356341308 + '5': 1.7458493258 + '6': 0.474662398 + '7': 1.2433425059 + '8': 1.1900990717 + '9': 2.4153516511 + xopt_error: + '0': false + '1': false + '10': false + '11': false + '12': false + '13': false + '14': false + '15': false + '16': false + '17': false + '18': false + '19': false + '2': false + '3': false + '4': false + '5': false + '6': false + '7': false + '8': false + '9': false + xopt_runtime: + '0': 3.0451e-05 + '1': 1.97641e-05 + '10': 0.000118121 + '11': 3.28682e-05 + '12': 2.32961e-05 + '13': 2.005e-05 + '14': 1.69589e-05 + '15': 1.8693e-05 + '16': 1.96053e-05 + '17': 2.0769e-05 + '18': 2.06972e-05 + '19': 1.72588e-05 + '2': 2.05259e-05 + '3': 2.30679e-05 + '4': 2.16002e-05 + '5': 2.22092e-05 + '6': 2.01198e-05 + '7': 1.98912e-05 + '8': 1.74772e-05 + '9': 1.52276e-05 + y1: + '0': 3.0228010413 + '1': 0.5864469085 + '10': 1.5190863306 + '11': 2.1852293654 + '12': 1.5346072388 + '13': 3.0992987863 + '14': 2.0710547261 + '15': 0.936286057 + '16': 1.6332492033 + '17': 1.1422095497 + '18': 2.7042098474 + '19': 0.4859133143 + '2': 0.9795043472 + '3': 1.8263987544 + '4': 1.2151919773 + '5': 1.7985089476 + '6': 1.8891030742 + '7': 2.0017271965 + '8': 3.0444029498 + '9': 1.6456315297 + y2: + '0': 2.9716149194 + '1': 0.0334320834 + '10': 0.1111936201 + '11': 0.4449960536 + '12': 2.919886659 + '13': 2.5555710481 + '14': 2.546017386 + '15': 0.0578576556 + '16': 2.4373198216 + '17': 2.3259652587 + '18': 1.8627466528 + '19': 2.8959035188 + '2': 2.9849996793 + '3': 0.3565144342 + '4': 0.3356341308 + '5': 1.7458493258 + '6': 0.474662398 + '7': 1.2433425059 + '8': 1.1900990717 + '9': 2.4153516511 +dump_file: dump.yml +evaluator: + function: xopt.resources.test_functions.tnk.evaluate_TNK + function_kwargs: + a: 999 + raise_probability: 0 + random_sleep: 0 + sleep: 0 + max_workers: 1 + vectorized: false +generator: + name: random + supports_batch_generation: true + supports_multi_objective: true +max_evaluations: null +serialize_inline: false +serialize_torch: false +strict: true +vocs: + constants: + a: dummy_constant + constraints: + c1: + - GREATER_THAN + - 0.0 + c2: + - LESS_THAN + - 0.5 + objectives: + y1: MINIMIZE + y2: MINIMIZE + observables: [] + variables: + x1: + - 0.0 + - 3.14159 + x2: + - 0.0 + - 3.14159 diff --git a/docs/examples/basic/xopt_basic.ipynb b/docs/examples/basic/xopt_basic.ipynb new file mode 100644 index 0000000..de84738 --- /dev/null +++ b/docs/examples/basic/xopt_basic.ipynb @@ -0,0 +1,636 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Xopt basic example\n", + "\n", + "Xopt optimization problems can be defined via one of two methods:\n", + "- a yaml text file (for limiting the amount of python script writing and/or setting up simulation runs)\n", + "- a simple python script (for those who prefer to use python directly)\n", + "\n", + "Here we will demonstrate how both of these techniques can be used to solve a relatively simple constrained optimization problem.\n", + "\n", + "$n=2$ variables:\n", + "$x_i \\in [0, \\pi], i=1,2$\n", + "\n", + "Objective:\n", + "- $f(x) = \\sum_i x_i$\n", + "\n", + "Constraint:\n", + "- $g(x) = -x_1^2 -x_2^2 + 1 \\le 0$" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Xopt Components\n", + "The definition of the Xopt object requires 3 parts, listed below:\n", + "- The `Evaluator` object, which evaluates input points using the arbitrary function\n", + "specified by the `function` property.\n", + "- The `Generator` object, which, when given data that has been evaluated, generates\n", + "future points to evaluate using the evaluator.\n", + "- The `VOCS` (variables, objectives, constraints, statics) object, which specifies the\n", + "input domain, the objectives, constraints and constants passed to the evaluator\n", + "function.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Import " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:54:36.148851Z", + "iopub.status.busy": "2024-09-13T15:54:36.148739Z", + "iopub.status.idle": "2024-09-13T15:54:37.136112Z", + "shell.execute_reply": "2024-09-13T15:54:37.135806Z" + } + }, + "outputs": [], + "source": [ + "from xopt import Evaluator\n", + "from xopt import VOCS\n", + "from xopt import Xopt\n", + "from xopt.generators import list_available_generators\n", + "from xopt.generators import get_generator\n", + "import math" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Defining Xopt components using python\n", + "We first examine how one would create and configure and Xopt optimization run using\n", + "python. This can also be done via a YAML file (see the next section)." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "### Define the objective function and the evaluator\n", + "Note that the objective function takes in a dict of variable values and returns a dict of objective return values. The keys of the input and output dictionaries must contain the keys we will specify in VOCS (see below)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-03-13T01:32:42.498257800Z", + "start_time": "2024-03-13T01:32:40.155677200Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:37.138011Z", + "iopub.status.busy": "2024-09-13T15:54:37.137879Z", + "iopub.status.idle": "2024-09-13T15:54:37.142258Z", + "shell.execute_reply": "2024-09-13T15:54:37.142023Z" + } + }, + "outputs": [], + "source": [ + "def evaluate_function(inputs: dict) -> dict:\n", + " objective_value = inputs[\"x1\"] ** 2 + inputs[\"x2\"] ** 2\n", + " constraint_value = -(inputs[\"x1\"] ** 2) - inputs[\"x2\"] ** 2 + 1\n", + " return {\"f\": objective_value, \"g\": constraint_value}\n", + "\n", + "\n", + "evaluator = Evaluator(function=evaluate_function)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "### Define VOCS\n", + "Here we define the names and ranges of input parameters, the names and settings of\n", + "objectives, and the names and settings of constraints. Note that the keys here should\n", + " be referenced in the evaluate function above." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-03-13T01:32:42.513257500Z", + "start_time": "2024-03-13T01:32:42.502259Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:37.143615Z", + "iopub.status.busy": "2024-09-13T15:54:37.143539Z", + "iopub.status.idle": "2024-09-13T15:54:37.145367Z", + "shell.execute_reply": "2024-09-13T15:54:37.145161Z" + } + }, + "outputs": [], + "source": [ + "vocs = VOCS(\n", + " variables={\"x1\": [0, math.pi], \"x2\": [0, math.pi]},\n", + " objectives={\"f\": \"MINIMIZE\"},\n", + " constraints={\"g\": [\"LESS_THAN\", 0]},\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "### Define the Generator\n", + "First lets see which generators are available for use." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-03-13T01:32:43.608257600Z", + "start_time": "2024-03-13T01:32:42.515259Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:37.147015Z", + "iopub.status.busy": "2024-09-13T15:54:37.146935Z", + "iopub.status.idle": "2024-09-13T15:54:37.755022Z", + "shell.execute_reply": "2024-09-13T15:54:37.754727Z" + } + }, + "outputs": [], + "source": [ + "list_available_generators()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "Here we will use the simplest generator that is defined by Xopt, random number generation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-03-13T01:32:43.652266600Z", + "start_time": "2024-03-13T01:32:43.611259600Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:37.777252Z", + "iopub.status.busy": "2024-09-13T15:54:37.777024Z", + "iopub.status.idle": "2024-09-13T15:54:37.779234Z", + "shell.execute_reply": "2024-09-13T15:54:37.778989Z" + } + }, + "outputs": [], + "source": [ + "# get the docstring for the random generator\n", + "print(get_generator(\"random\").__doc__)\n", + "\n", + "# use the get generator method to get the random number generator\n", + "generator = get_generator(\"random\")(vocs=vocs)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "### Combine into Xopt object" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-03-13T01:32:43.652266600Z", + "start_time": "2024-03-13T01:32:43.626258200Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:37.780722Z", + "iopub.status.busy": "2024-09-13T15:54:37.780612Z", + "iopub.status.idle": "2024-09-13T15:54:37.782226Z", + "shell.execute_reply": "2024-09-13T15:54:37.781968Z" + } + }, + "outputs": [], + "source": [ + "X = Xopt(vocs=vocs, generator=generator, evaluator=evaluator)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Defining Xopt object from yaml file\n", + "Alternatively, it might be more useful to define the Xopt object from a text file or\n", + "YAML string. We replicate the code above with the YAML file below." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-03-13T01:32:43.660292300Z", + "start_time": "2024-03-13T01:32:43.641259200Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:37.783675Z", + "iopub.status.busy": "2024-09-13T15:54:37.783585Z", + "iopub.status.idle": "2024-09-13T15:54:37.785281Z", + "shell.execute_reply": "2024-09-13T15:54:37.785057Z" + } + }, + "outputs": [], + "source": [ + "# Make a proper input file.\n", + "YAML = \"\"\"\n", + "evaluator:\n", + " function: __main__.evaluate_function\n", + "\n", + "generator:\n", + " name: random\n", + "\n", + "vocs:\n", + " variables:\n", + " x1: [0, 3.14159]\n", + " x2: [0, 3.14159]\n", + " objectives: {f: MINIMIZE}\n", + " constraints:\n", + " g: [LESS_THAN, 0]\n", + "\n", + "\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-03-13T01:32:43.669259800Z", + "start_time": "2024-03-13T01:32:43.655257700Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:37.786740Z", + "iopub.status.busy": "2024-09-13T15:54:37.786655Z", + "iopub.status.idle": "2024-09-13T15:54:37.789636Z", + "shell.execute_reply": "2024-09-13T15:54:37.789373Z" + } + }, + "outputs": [], + "source": [ + "# create Xopt object.\n", + "X_from_yaml = Xopt.from_yaml(YAML)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Introspection\n", + "Objects in Xopt can be printed to a string or dumped to a text file for easy\n", + "introspection of attributes and current configuration." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-03-13T01:32:43.712296Z", + "start_time": "2024-03-13T01:32:43.671257800Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:37.791038Z", + "iopub.status.busy": "2024-09-13T15:54:37.790938Z", + "iopub.status.idle": "2024-09-13T15:54:37.794335Z", + "shell.execute_reply": "2024-09-13T15:54:37.794092Z" + } + }, + "outputs": [], + "source": [ + "# Convenient representation of the state.\n", + "X" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Evaluating randomly generated or fixed inputs.\n", + "The main Xopt object has a variety of means for evaluating random or fixed points.\n", + "This is often used to initialize optimization, but can be used independently of any\n", + "generator. Results from evaluations are stored in the `data` attribute. Data can also\n", + " be explictly added to the Xopt object (and by extension the generator attached to\n", + " the xopt object by calling `X.add_data()`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-03-13T01:32:43.722288700Z", + "start_time": "2024-03-13T01:32:43.687283700Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:37.795665Z", + "iopub.status.busy": "2024-09-13T15:54:37.795566Z", + "iopub.status.idle": "2024-09-13T15:54:37.802776Z", + "shell.execute_reply": "2024-09-13T15:54:37.802553Z" + } + }, + "outputs": [], + "source": [ + "# randomly evaluate some points and add data to Xopt object\n", + "X.random_evaluate(5)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-03-13T01:32:43.722288700Z", + "start_time": "2024-03-13T01:32:43.712296Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:37.804109Z", + "iopub.status.busy": "2024-09-13T15:54:37.804027Z", + "iopub.status.idle": "2024-09-13T15:54:37.808684Z", + "shell.execute_reply": "2024-09-13T15:54:37.808446Z" + } + }, + "outputs": [], + "source": [ + "# evaluate some points additionally\n", + "points = {\"x1\": [1.0, 0.5, 2.25], \"x2\": [0, 1.75, 0.6]}\n", + "X.evaluate_data(points)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-03-13T01:32:43.729258500Z", + "start_time": "2024-03-13T01:32:43.716261900Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:37.810013Z", + "iopub.status.busy": "2024-09-13T15:54:37.809931Z", + "iopub.status.idle": "2024-09-13T15:54:37.813719Z", + "shell.execute_reply": "2024-09-13T15:54:37.813492Z" + } + }, + "outputs": [], + "source": [ + "# examine the data stored in Xopt\n", + "X.data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Optimization\n", + "Xopt conducts a single iteration of optimization by calling `X.step()`. Inside this\n", + "function Xopt will generate a point (or set of points) using the generator object,\n", + "then send the point to be evaluated by the evaluator. Results will be stored in the\n", + "data attribute." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-03-13T01:32:43.772322900Z", + "start_time": "2024-03-13T01:32:43.730259200Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:37.815111Z", + "iopub.status.busy": "2024-09-13T15:54:37.815038Z", + "iopub.status.idle": "2024-09-13T15:54:37.817763Z", + "shell.execute_reply": "2024-09-13T15:54:37.817558Z" + } + }, + "outputs": [], + "source": [ + "# Take one step (generate a single point)\n", + "X.step()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-03-13T01:32:43.862089800Z", + "start_time": "2024-03-13T01:32:43.745260300Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:37.818983Z", + "iopub.status.busy": "2024-09-13T15:54:37.818903Z", + "iopub.status.idle": "2024-09-13T15:54:37.822785Z", + "shell.execute_reply": "2024-09-13T15:54:37.822555Z" + } + }, + "outputs": [], + "source": [ + "# examine the results\n", + "X.data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-03-13T01:32:43.897089900Z", + "start_time": "2024-03-13T01:32:43.772322900Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:37.824518Z", + "iopub.status.busy": "2024-09-13T15:54:37.824357Z", + "iopub.status.idle": "2024-09-13T15:54:37.839928Z", + "shell.execute_reply": "2024-09-13T15:54:37.839600Z" + } + }, + "outputs": [], + "source": [ + "# take a couple of steps and examine the results\n", + "for _ in range(10):\n", + " X.step()\n", + "X.data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Find and evaluate the best point from `X.data`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-03-13T01:32:43.937089900Z", + "start_time": "2024-03-13T01:32:43.832332500Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:37.841842Z", + "iopub.status.busy": "2024-09-13T15:54:37.841699Z", + "iopub.status.idle": "2024-09-13T15:54:37.849613Z", + "shell.execute_reply": "2024-09-13T15:54:37.849371Z" + } + }, + "outputs": [], + "source": [ + "idx, val, params = X.vocs.select_best(X.data)\n", + "print(f\"best objective value {val}\")\n", + "print(f\"best point {params}\")\n", + "\n", + "X.evaluate_data(params)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Visualization\n", + "Finally, we can visualize the objectives and variables to monitor optimization or\n", + "visualize the results" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-03-13T01:32:44.620735900Z", + "start_time": "2024-03-13T01:32:43.851332300Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:37.851221Z", + "iopub.status.busy": "2024-09-13T15:54:37.851110Z", + "iopub.status.idle": "2024-09-13T15:54:38.258640Z", + "shell.execute_reply": "2024-09-13T15:54:38.258327Z" + } + }, + "outputs": [], + "source": [ + "# view objective values\n", + "X.data.plot(y=X.vocs.objective_names)\n", + "\n", + "# view variables values\n", + "X.data.plot(*X.vocs.variable_names, kind=\"scatter\")\n", + "\n", + "# you can also normalize the variables\n", + "X.vocs.normalize_inputs(X.data).plot(*X.vocs.variable_names, kind=\"scatter\")" + ] + } + ], + "metadata": { + "interpreter": { + "hash": "72034539424920dfb606fe3b820b3f27dca0cbf1c69938110810ec4641e275b1" + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/basic/xopt_evaluator.ipynb b/docs/examples/basic/xopt_evaluator.ipynb new file mode 100644 index 0000000..95ec2a2 --- /dev/null +++ b/docs/examples/basic/xopt_evaluator.ipynb @@ -0,0 +1,576 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Xopt Evaluator Basic Usage \n", + "\n", + "The `Evaluator` handles the execution of the user-provided `function` with optional `function_kwags`, asyncrhonously and parallel, with exception handling. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-08T21:35:16.928260600Z", + "start_time": "2023-09-08T21:35:16.919590900Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:26.524566Z", + "iopub.status.busy": "2024-09-13T15:54:26.524282Z", + "iopub.status.idle": "2024-09-13T15:54:27.553451Z", + "shell.execute_reply": "2024-09-13T15:54:27.553023Z" + } + }, + "outputs": [], + "source": [ + "from xopt import Xopt, Evaluator, VOCS\n", + "from xopt.generators.random import RandomGenerator\n", + "\n", + "# Usage with a parallel executor.\n", + "from xopt import AsynchronousXopt\n", + "\n", + "import pandas as pd\n", + "\n", + "from time import sleep\n", + "from numpy.random import randint\n", + "\n", + "from typing import Dict\n", + "\n", + "import numpy as np\n", + "\n", + "from concurrent.futures import ProcessPoolExecutor\n", + "\n", + "# needed for macos\n", + "import platform\n", + "\n", + "if platform.system() == \"Darwin\":\n", + " import multiprocessing\n", + "\n", + " multiprocessing.set_start_method(\"fork\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-08T21:35:32.309828Z", + "start_time": "2023-09-08T21:35:16.928260600Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:27.555306Z", + "iopub.status.busy": "2024-09-13T15:54:27.555168Z", + "iopub.status.idle": "2024-09-13T15:54:27.556849Z", + "shell.execute_reply": "2024-09-13T15:54:27.556651Z" + } + }, + "outputs": [], + "source": [ + "np.random.seed(666) # for reproducibility" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define a custom function `f(inputs: Dict) -> outputs: Dict`. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-08T21:35:32.324561500Z", + "start_time": "2023-09-08T21:35:32.311827800Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:27.558136Z", + "iopub.status.busy": "2024-09-13T15:54:27.558062Z", + "iopub.status.idle": "2024-09-13T15:54:27.560087Z", + "shell.execute_reply": "2024-09-13T15:54:27.559863Z" + } + }, + "outputs": [], + "source": [ + "def f(inputs: Dict, enable_errors=True) -> Dict:\n", + " sleep(randint(1, 5) * 0.1) # simulate computation time\n", + " # Make some occasional errors\n", + " if enable_errors and np.any(inputs[\"x\"] > 0.8):\n", + " raise ValueError(\"x > 0.8\")\n", + "\n", + " return {\"f1\": inputs[\"x\"] ** 2 + inputs[\"y\"] ** 2}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Define variables, objectives, constraints, and other settings (VOCS)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-08T21:35:32.368050300Z", + "start_time": "2023-09-08T21:35:32.328590900Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:27.561752Z", + "iopub.status.busy": "2024-09-13T15:54:27.561657Z", + "iopub.status.idle": "2024-09-13T15:54:27.564957Z", + "shell.execute_reply": "2024-09-13T15:54:27.564762Z" + } + }, + "outputs": [], + "source": [ + "vocs = VOCS(variables={\"x\": [0, 1], \"y\": [0, 1]}, objectives={\"f1\": \"MINIMIZE\"})\n", + "vocs" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This can be used to make some random inputs for evaluating the function. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-08T21:35:32.676437500Z", + "start_time": "2023-09-08T21:35:32.340456300Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:27.586881Z", + "iopub.status.busy": "2024-09-13T15:54:27.586726Z", + "iopub.status.idle": "2024-09-13T15:54:27.893588Z", + "shell.execute_reply": "2024-09-13T15:54:27.893264Z" + } + }, + "outputs": [], + "source": [ + "in1 = vocs.random_inputs()[0]\n", + "\n", + "f(in1, enable_errors=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-08T21:35:33.005111700Z", + "start_time": "2023-09-08T21:35:32.681546900Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:27.895291Z", + "iopub.status.busy": "2024-09-13T15:54:27.895153Z", + "iopub.status.idle": "2024-09-13T15:54:28.206981Z", + "shell.execute_reply": "2024-09-13T15:54:28.206227Z" + } + }, + "outputs": [], + "source": [ + "# Add in occasional errors.\n", + "try:\n", + " f({\"x\": 1, \"y\": 0})\n", + "except Exception as ex:\n", + " print(f\"Caught error in f: {ex}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-08T21:35:33.049056200Z", + "start_time": "2023-09-08T21:35:33.006111Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:28.211066Z", + "iopub.status.busy": "2024-09-13T15:54:28.210530Z", + "iopub.status.idle": "2024-09-13T15:54:28.220673Z", + "shell.execute_reply": "2024-09-13T15:54:28.220185Z" + } + }, + "outputs": [], + "source": [ + "# Create Evaluator\n", + "ev = Evaluator(function=f)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-08T21:35:33.252318Z", + "start_time": "2023-09-08T21:35:33.021667400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:28.223163Z", + "iopub.status.busy": "2024-09-13T15:54:28.222977Z", + "iopub.status.idle": "2024-09-13T15:54:28.435669Z", + "shell.execute_reply": "2024-09-13T15:54:28.435086Z" + } + }, + "outputs": [], + "source": [ + "# Single input evaluation\n", + "ev.evaluate(in1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-08T21:35:35.961577400Z", + "start_time": "2023-09-08T21:35:33.250319900Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:28.438836Z", + "iopub.status.busy": "2024-09-13T15:54:28.438558Z", + "iopub.status.idle": "2024-09-13T15:54:31.093684Z", + "shell.execute_reply": "2024-09-13T15:54:31.093111Z" + } + }, + "outputs": [], + "source": [ + "# Dataframe evaluation\n", + "in10 = pd.DataFrame({\"x\": np.linspace(0, 1, 10), \"y\": np.linspace(0, 1, 10)})\n", + "ev.evaluate_data(in10)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-08T21:35:36.188682500Z", + "start_time": "2023-09-08T21:35:35.964575800Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:31.097149Z", + "iopub.status.busy": "2024-09-13T15:54:31.096920Z", + "iopub.status.idle": "2024-09-13T15:54:31.311399Z", + "shell.execute_reply": "2024-09-13T15:54:31.310884Z" + } + }, + "outputs": [], + "source": [ + "# Dataframe evaluation, vectorized\n", + "ev.vectorized = True\n", + "ev.evaluate_data(in10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Executors" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-08T21:35:36.210825800Z", + "start_time": "2023-09-08T21:35:36.189682300Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:31.314542Z", + "iopub.status.busy": "2024-09-13T15:54:31.314152Z", + "iopub.status.idle": "2024-09-13T15:54:31.316966Z", + "shell.execute_reply": "2024-09-13T15:54:31.316499Z" + } + }, + "outputs": [], + "source": [ + "MAX_WORKERS = 10" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-08T21:35:36.277827Z", + "start_time": "2023-09-08T21:35:36.205828Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:31.319258Z", + "iopub.status.busy": "2024-09-13T15:54:31.319060Z", + "iopub.status.idle": "2024-09-13T15:54:31.323973Z", + "shell.execute_reply": "2024-09-13T15:54:31.323589Z" + } + }, + "outputs": [], + "source": [ + "# Create Executor instance\n", + "executor = ProcessPoolExecutor(max_workers=MAX_WORKERS)\n", + "executor" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-08T21:35:36.336827600Z", + "start_time": "2023-09-08T21:35:36.219826200Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:31.326164Z", + "iopub.status.busy": "2024-09-13T15:54:31.326004Z", + "iopub.status.idle": "2024-09-13T15:54:31.327802Z", + "shell.execute_reply": "2024-09-13T15:54:31.327526Z" + } + }, + "outputs": [], + "source": [ + "# Dask (Optional)\n", + "# from dask.distributed import Client\n", + "# import logging\n", + "# client = Client( silence_logs=logging.ERROR)\n", + "# executor = client.get_executor()\n", + "# client" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-08T21:35:36.340830Z", + "start_time": "2023-09-08T21:35:36.246825700Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:31.329751Z", + "iopub.status.busy": "2024-09-13T15:54:31.329593Z", + "iopub.status.idle": "2024-09-13T15:54:31.336762Z", + "shell.execute_reply": "2024-09-13T15:54:31.336461Z" + } + }, + "outputs": [], + "source": [ + "# This calls `executor.map`\n", + "ev = Evaluator(function=f, executor=executor, max_workers=MAX_WORKERS)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-08T21:35:42.832826300Z", + "start_time": "2023-09-08T21:35:36.250828900Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:31.338377Z", + "iopub.status.busy": "2024-09-13T15:54:31.338268Z", + "iopub.status.idle": "2024-09-13T15:54:31.794548Z", + "shell.execute_reply": "2024-09-13T15:54:31.793918Z" + } + }, + "outputs": [], + "source": [ + "# This will run in parallel\n", + "ev.evaluate_data(in10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Evaluator in the Xopt object" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-08T21:35:49.453947100Z", + "start_time": "2023-09-08T21:35:48.973424600Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:31.797478Z", + "iopub.status.busy": "2024-09-13T15:54:31.797291Z", + "iopub.status.idle": "2024-09-13T15:54:32.225792Z", + "shell.execute_reply": "2024-09-13T15:54:32.225518Z" + } + }, + "outputs": [], + "source": [ + "X = Xopt(\n", + " generator=RandomGenerator(vocs=vocs), evaluator=Evaluator(function=f), vocs=vocs\n", + ")\n", + "X.strict = False\n", + "\n", + "# Evaluate to the evaluator some new inputs\n", + "X.evaluate_data(X.vocs.random_inputs(4))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Asynchronous Xopt\n", + "Instead of waiting for evaluations to be finished, AsynchronousXopt can be used to generate candidates while waiting for other evaluations to finish (requires parallel execution). In this case, calling ```X.step()``` generates and executes a number of candidates that are executed in parallel using python ```concurrent.futures``` formalism. Calling ```X.step()``` again will generate and evaluate new points based on finished futures asynchronously." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-08T21:35:50.776098900Z", + "start_time": "2023-09-08T21:35:50.753057600Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:32.227332Z", + "iopub.status.busy": "2024-09-13T15:54:32.227223Z", + "iopub.status.idle": "2024-09-13T15:54:32.231134Z", + "shell.execute_reply": "2024-09-13T15:54:32.230814Z" + } + }, + "outputs": [], + "source": [ + "executor = ProcessPoolExecutor(max_workers=MAX_WORKERS)\n", + "\n", + "X2 = AsynchronousXopt(\n", + " generator=RandomGenerator(vocs=vocs),\n", + " evaluator=Evaluator(function=f, executor=executor, max_workers=MAX_WORKERS),\n", + " vocs=vocs,\n", + ")\n", + "X2.strict = False" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-08T21:35:57.154830200Z", + "start_time": "2023-09-08T21:35:51.660788Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:32.232629Z", + "iopub.status.busy": "2024-09-13T15:54:32.232540Z", + "iopub.status.idle": "2024-09-13T15:54:32.477829Z", + "shell.execute_reply": "2024-09-13T15:54:32.477089Z" + } + }, + "outputs": [], + "source": [ + "X2.step()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:54:32.480558Z", + "iopub.status.busy": "2024-09-13T15:54:32.480378Z", + "iopub.status.idle": "2024-09-13T15:54:33.023925Z", + "shell.execute_reply": "2024-09-13T15:54:33.023594Z" + } + }, + "outputs": [], + "source": [ + "for _ in range(20):\n", + " X2.step()\n", + "\n", + "len(X2.data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:54:33.025554Z", + "iopub.status.busy": "2024-09-13T15:54:33.025427Z", + "iopub.status.idle": "2024-09-13T15:54:33.353435Z", + "shell.execute_reply": "2024-09-13T15:54:33.353146Z" + } + }, + "outputs": [], + "source": [ + "X2.data.plot.scatter(\"x\", \"y\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:54:33.355036Z", + "iopub.status.busy": "2024-09-13T15:54:33.354876Z", + "iopub.status.idle": "2024-09-13T15:54:33.977523Z", + "shell.execute_reply": "2024-09-13T15:54:33.976871Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "# Asynchronous, Vectorized\n", + "X2 = AsynchronousXopt(\n", + " generator=RandomGenerator(vocs=vocs),\n", + " evaluator=Evaluator(function=f, executor=executor, max_workers=MAX_WORKERS),\n", + " vocs=vocs,\n", + ")\n", + "X2.evaluator.vectorized = True\n", + "X2.strict = False\n", + "\n", + "# This takes fewer steps to achieve a similar number of evaluations\n", + "for _ in range(3):\n", + " X2.step()\n", + "\n", + "len(X2.data)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.6" + }, + "vscode": { + "interpreter": { + "hash": "4483d4964800812ebc77892a92dde3b54705ec8a224d63d9bb659e2cc457018b" + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/basic/xopt_generator.ipynb b/docs/examples/basic/xopt_generator.ipynb new file mode 100644 index 0000000..676104b --- /dev/null +++ b/docs/examples/basic/xopt_generator.ipynb @@ -0,0 +1,285 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Working with Xopt generators\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-04-02T14:38:03.984509500Z", + "start_time": "2024-04-02T14:37:58.291483Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:18.892761Z", + "iopub.status.busy": "2024-09-13T15:54:18.892631Z", + "iopub.status.idle": "2024-09-13T15:54:19.917312Z", + "shell.execute_reply": "2024-09-13T15:54:19.916980Z" + } + }, + "outputs": [], + "source": [ + "# Import the class\n", + "from xopt.generators import generators, get_generator\n", + "from xopt.vocs import VOCS\n", + "from xopt import Xopt, Evaluator, Generator\n", + "\n", + "import math\n", + "import numpy as np" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-04-02T14:38:04.059481Z", + "start_time": "2024-04-02T14:38:04.043479100Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:19.919265Z", + "iopub.status.busy": "2024-09-13T15:54:19.919104Z", + "iopub.status.idle": "2024-09-13T15:54:19.922532Z", + "shell.execute_reply": "2024-09-13T15:54:19.922173Z" + } + }, + "outputs": [], + "source": [ + "# named generators\n", + "generators.keys()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-04-02T14:38:06.698513600Z", + "start_time": "2024-04-02T14:38:04.061480600Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:19.944610Z", + "iopub.status.busy": "2024-09-13T15:54:19.944468Z", + "iopub.status.idle": "2024-09-13T15:54:20.524671Z", + "shell.execute_reply": "2024-09-13T15:54:20.524249Z" + } + }, + "outputs": [], + "source": [ + "# get default options for the upper confidence bound generator\n", + "generator_type = get_generator(\"upper_confidence_bound\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-04-02T14:38:06.713510Z", + "start_time": "2024-04-02T14:38:06.701481300Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:20.526871Z", + "iopub.status.busy": "2024-09-13T15:54:20.526495Z", + "iopub.status.idle": "2024-09-13T15:54:20.528754Z", + "shell.execute_reply": "2024-09-13T15:54:20.528499Z" + } + }, + "outputs": [], + "source": [ + "# define vocs for the problem\n", + "\n", + "vocs = VOCS(\n", + " variables={\"x\": [0, 2 * math.pi]},\n", + " objectives={\"f\": \"MINIMIZE\"},\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-04-02T14:38:06.732510500Z", + "start_time": "2024-04-02T14:38:06.716506100Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:20.530460Z", + "iopub.status.busy": "2024-09-13T15:54:20.530338Z", + "iopub.status.idle": "2024-09-13T15:54:20.532404Z", + "shell.execute_reply": "2024-09-13T15:54:20.532136Z" + } + }, + "outputs": [], + "source": [ + "# define a test function to optimize\n", + "\n", + "\n", + "def test_function(input_dict):\n", + " return {\"f\": np.sin(input_dict[\"x\"])}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-04-02T14:38:06.787509400Z", + "start_time": "2024-04-02T14:38:06.730483400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:20.533901Z", + "iopub.status.busy": "2024-09-13T15:54:20.533785Z", + "iopub.status.idle": "2024-09-13T15:54:20.593852Z", + "shell.execute_reply": "2024-09-13T15:54:20.593555Z" + } + }, + "outputs": [], + "source": [ + "evaluator = Evaluator(function=test_function)\n", + "generator = generator_type(vocs=vocs)\n", + "X = Xopt(generator=generator, evaluator=evaluator, vocs=vocs)\n", + "X" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-04-02T14:38:07.329478900Z", + "start_time": "2024-04-02T14:38:06.760517Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:20.595302Z", + "iopub.status.busy": "2024-09-13T15:54:20.595189Z", + "iopub.status.idle": "2024-09-13T15:54:21.114663Z", + "shell.execute_reply": "2024-09-13T15:54:21.114313Z" + } + }, + "outputs": [], + "source": [ + "# run the optimization for a couple of iterations (see bayes_opt folder for\n", + "# more examples of ucb)\n", + "X.random_evaluate(2)\n", + "for i in range(4):\n", + " X.step()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-04-02T14:38:07.343479300Z", + "start_time": "2024-04-02T14:38:07.330479700Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:21.116528Z", + "iopub.status.busy": "2024-09-13T15:54:21.116363Z", + "iopub.status.idle": "2024-09-13T15:54:21.121624Z", + "shell.execute_reply": "2024-09-13T15:54:21.121332Z" + } + }, + "outputs": [], + "source": [ + "X.data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Write your own generator\n", + "Here we write a generator that generates the same point every time." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-04-02T14:38:30.157799200Z", + "start_time": "2024-04-02T14:38:30.148798400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:21.123537Z", + "iopub.status.busy": "2024-09-13T15:54:21.123381Z", + "iopub.status.idle": "2024-09-13T15:54:21.130807Z", + "shell.execute_reply": "2024-09-13T15:54:21.130499Z" + } + }, + "outputs": [], + "source": [ + "class MyGenerator(Generator):\n", + " def generate(self, n_candidates) -> list[dict]:\n", + " points = [{\"x\": 1.0}] * n_candidates\n", + " return points\n", + "\n", + "\n", + "my_generator = MyGenerator(vocs=vocs)\n", + "X2 = Xopt(evaluator=evaluator, vocs=vocs, generator=my_generator)\n", + "\n", + "for i in range(4):\n", + " X2.step()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-04-02T14:38:31.306804300Z", + "start_time": "2024-04-02T14:38:31.288798900Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:21.132352Z", + "iopub.status.busy": "2024-09-13T15:54:21.132250Z", + "iopub.status.idle": "2024-09-13T15:54:21.135795Z", + "shell.execute_reply": "2024-09-13T15:54:21.135566Z" + } + }, + "outputs": [], + "source": [ + "X2.data" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.6" + }, + "vscode": { + "interpreter": { + "hash": "e3625c9a0de9d330a79359c7c8508d3b9f835fd7259469d66057de346104c5ec" + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/basic/xopt_parallel.ipynb b/docs/examples/basic/xopt_parallel.ipynb new file mode 100644 index 0000000..df0de3f --- /dev/null +++ b/docs/examples/basic/xopt_parallel.ipynb @@ -0,0 +1,596 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "# Xopt Parallel Examples\n", + "\n", + "Xopt provides methods to parallelize optimizations using Processes, Threads, MPI, and Dask using the `concurrent.futures` interface as defined in https://www.python.org/dev/peps/pep-3148/ ." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:51:11.807711Z", + "iopub.status.busy": "2024-09-13T15:51:11.807234Z", + "iopub.status.idle": "2024-09-13T15:51:12.733177Z", + "shell.execute_reply": "2024-09-13T15:51:12.732871Z" + } + }, + "outputs": [], + "source": [ + "from xopt import AsynchronousXopt as Xopt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:51:12.734939Z", + "iopub.status.busy": "2024-09-13T15:51:12.734819Z", + "iopub.status.idle": "2024-09-13T15:51:13.118957Z", + "shell.execute_reply": "2024-09-13T15:51:13.118649Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# Helpers for this notebook\n", + "import multiprocessing\n", + "from concurrent.futures import ProcessPoolExecutor\n", + "from dask.distributed import Client\n", + "\n", + "import matplotlib.pyplot as plt\n", + "\n", + "import pandas as pd\n", + "from concurrent.futures import ThreadPoolExecutor\n", + "\n", + "import os\n", + "\n", + "\n", + "# Notebook printing output\n", + "# from xopt import output_notebook\n", + "# output_notebook()\n", + "\n", + "N_CPUS = multiprocessing.cpu_count()\n", + "N_CPUS\n", + "\n", + "# directory for data.\n", + "os.makedirs(\"temp\", exist_ok=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "The `Xopt` object can be instantiated from a JSON or YAML file, or a dict, with the proper structure.\n", + "\n", + "Here we will make one" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:51:13.120880Z", + "iopub.status.busy": "2024-09-13T15:51:13.120673Z", + "iopub.status.idle": "2024-09-13T15:51:13.139851Z", + "shell.execute_reply": "2024-09-13T15:51:13.139601Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# Make a proper input file.\n", + "YAML = \"\"\"\n", + "\n", + "max_evaluations: 1000\n", + "\n", + "generator:\n", + " name: cnsga\n", + " output_path: temp\n", + " population_size: 64\n", + " \n", + "evaluator:\n", + " function: xopt.resources.test_functions.tnk.evaluate_TNK\n", + " function_kwargs:\n", + " sleep: 0\n", + " random_sleep: 0.1\n", + " \n", + "vocs:\n", + " variables:\n", + " x1: [0, 3.14159]\n", + " x2: [0, 3.14159]\n", + " objectives: {y1: MINIMIZE, y2: MINIMIZE}\n", + " constraints:\n", + " c1: [GREATER_THAN, 0]\n", + " c2: [LESS_THAN, 0.5]\n", + " constants: {a: dummy_constant}\n", + "\n", + "\"\"\"\n", + "X = Xopt(YAML)\n", + "X" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:51:13.160584Z", + "iopub.status.busy": "2024-09-13T15:51:13.160437Z", + "iopub.status.idle": "2024-09-13T15:51:21.136857Z", + "shell.execute_reply": "2024-09-13T15:51:21.135973Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "%%timeit\n", + "# Check that the average time is close to random_sleep\n", + "X.evaluator.function({\"x1\": 0.5, \"x2\": 0.5}, random_sleep=0.1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:51:21.140796Z", + "iopub.status.busy": "2024-09-13T15:51:21.140489Z", + "iopub.status.idle": "2024-09-13T15:53:14.801654Z", + "shell.execute_reply": "2024-09-13T15:53:14.801083Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "%%time\n", + "X.run()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "# Processes" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:53:14.805616Z", + "iopub.status.busy": "2024-09-13T15:53:14.805006Z", + "iopub.status.idle": "2024-09-13T15:53:28.075142Z", + "shell.execute_reply": "2024-09-13T15:53:28.074868Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "%%time\n", + "X = Xopt(YAML)\n", + "\n", + "with ProcessPoolExecutor(max_workers=N_CPUS) as executor:\n", + " X.evaluator.executor = executor\n", + " X.evaluator.max_workers = N_CPUS\n", + " X.run()\n", + "len(X.data)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "# Threads\n", + "\n", + "Continue running, this time with threads." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:53:28.076716Z", + "iopub.status.busy": "2024-09-13T15:53:28.076594Z", + "iopub.status.idle": "2024-09-13T15:53:38.985622Z", + "shell.execute_reply": "2024-09-13T15:53:38.984858Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "%%time\n", + "X = Xopt(YAML)\n", + "\n", + "with ThreadPoolExecutor(max_workers=N_CPUS) as executor:\n", + " X.evaluator.executor = executor\n", + " X.evaluator.max_workers = N_CPUS\n", + " X.run()\n", + "len(X.data)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "# MPI \n", + "\n", + "The `test.yaml` file completely defines the problem. We will also direct the logging to an `xopt.log` file. The following invocation recruits 4 MPI workers to solve this problem.\n", + "\n", + "We can also continue by calling `.save` with a JSON filename. This will write all of previous results into the file." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:53:38.989702Z", + "iopub.status.busy": "2024-09-13T15:53:38.989391Z", + "iopub.status.idle": "2024-09-13T15:53:39.159372Z", + "shell.execute_reply": "2024-09-13T15:53:39.158752Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "X = Xopt(YAML)\n", + "X.dump(\"test.yaml\") # Write this input to file\n", + "!cat test.yaml" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:53:39.162178Z", + "iopub.status.busy": "2024-09-13T15:53:39.161823Z", + "iopub.status.idle": "2024-09-13T15:53:57.496664Z", + "shell.execute_reply": "2024-09-13T15:53:57.496156Z" + } + }, + "outputs": [], + "source": [ + "%%time\n", + "!mpirun -n 8 python -m mpi4py.futures -m xopt.mpi.run -vv --logfile xopt.log test.yaml" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:53:57.499178Z", + "iopub.status.busy": "2024-09-13T15:53:57.498874Z", + "iopub.status.idle": "2024-09-13T15:53:57.624811Z", + "shell.execute_reply": "2024-09-13T15:53:57.624262Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "!tail xopt.log" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "# Dask\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:53:57.627616Z", + "iopub.status.busy": "2024-09-13T15:53:57.627365Z", + "iopub.status.idle": "2024-09-13T15:53:58.571020Z", + "shell.execute_reply": "2024-09-13T15:53:58.570726Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "client = Client()\n", + "executor = client.get_executor()\n", + "client" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:53:58.572623Z", + "iopub.status.busy": "2024-09-13T15:53:58.572506Z", + "iopub.status.idle": "2024-09-13T15:54:10.653783Z", + "shell.execute_reply": "2024-09-13T15:54:10.653398Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "%%time\n", + "X = Xopt(YAML)\n", + "X.evaluator.executor = executor\n", + "X.evaluator.max_workers = N_CPUS\n", + "X.run()\n", + "len(X.data)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "# Load output into Pandas\n", + "\n", + "This algorithm writes two types of files: `gen_{i}.json` with all of the new individuals evaluated in a generation, and `pop_{i}.json` with the latest best population. Xopt provides some functions to load these easily into a Pandas dataframe for further analysis." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:54:10.655270Z", + "iopub.status.busy": "2024-09-13T15:54:10.655159Z", + "iopub.status.idle": "2024-09-13T15:54:10.661233Z", + "shell.execute_reply": "2024-09-13T15:54:10.660968Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "X.data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:54:10.662656Z", + "iopub.status.busy": "2024-09-13T15:54:10.662565Z", + "iopub.status.idle": "2024-09-13T15:54:10.669875Z", + "shell.execute_reply": "2024-09-13T15:54:10.669649Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "df = pd.concat([X.data, X.vocs.feasibility_data(X.data)], axis=1)\n", + "df[df[\"feasible\"]]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:54:10.671305Z", + "iopub.status.busy": "2024-09-13T15:54:10.671199Z", + "iopub.status.idle": "2024-09-13T15:54:10.757506Z", + "shell.execute_reply": "2024-09-13T15:54:10.757195Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# Plot the feasible ones\n", + "feasible_df = df[df[\"feasible\"]]\n", + "feasible_df.plot(\"y1\", \"y2\", kind=\"scatter\").set_aspect(\"equal\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:54:10.759331Z", + "iopub.status.busy": "2024-09-13T15:54:10.759013Z", + "iopub.status.idle": "2024-09-13T15:54:10.836037Z", + "shell.execute_reply": "2024-09-13T15:54:10.835773Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# Plot the infeasible ones\n", + "infeasible_df = df[~df[\"feasible\"]]\n", + "infeasible_df.plot(\"y1\", \"y2\", kind=\"scatter\").set_aspect(\"equal\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:54:10.837514Z", + "iopub.status.busy": "2024-09-13T15:54:10.837401Z", + "iopub.status.idle": "2024-09-13T15:54:10.896641Z", + "shell.execute_reply": "2024-09-13T15:54:10.896380Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# This is the final population\n", + "df1 = X.generator.population\n", + "df1.plot(\"y1\", \"y2\", kind=\"scatter\").set_aspect(\"equal\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "# matplotlib plotting\n", + "\n", + "You can always use matplotlib for customizable plotting" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:54:10.898215Z", + "iopub.status.busy": "2024-09-13T15:54:10.898106Z", + "iopub.status.idle": "2024-09-13T15:54:11.030543Z", + "shell.execute_reply": "2024-09-13T15:54:11.030209Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# Extract objectives from output\n", + "k1, k2 = \"y1\", \"y2\"\n", + "\n", + "fig, ax = plt.subplots(figsize=(6, 6))\n", + "\n", + "ax.scatter(\n", + " infeasible_df[k1],\n", + " infeasible_df[k2],\n", + " color=\"blue\",\n", + " marker=\".\",\n", + " alpha=0.5,\n", + " label=\"infeasible\",\n", + ")\n", + "ax.scatter(\n", + " feasible_df[k1], feasible_df[k2], color=\"orange\", marker=\".\", label=\"feasible\"\n", + ")\n", + "ax.scatter(df1[k1], df1[k2], color=\"red\", marker=\".\", label=\"final population\")\n", + "ax.set_xlabel(k1)\n", + "ax.set_ylabel(k2)\n", + "ax.set_aspect(\"auto\")\n", + "ax.set_title(\"Xopt's CNSGA algorithm\")\n", + "plt.legend()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:54:11.032351Z", + "iopub.status.busy": "2024-09-13T15:54:11.032009Z", + "iopub.status.idle": "2024-09-13T15:54:11.405742Z", + "shell.execute_reply": "2024-09-13T15:54:11.405356Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# Cleanup\n", + "#!rm -r dask-worker-space\n", + "!rm -r temp\n", + "!rm xopt.log*\n", + "!rm test.yaml" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.6" + }, + "vscode": { + "interpreter": { + "hash": "4483d4964800812ebc77892a92dde3b54705ec8a224d63d9bb659e2cc457018b" + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/basic/xopt_vocs.ipynb b/docs/examples/basic/xopt_vocs.ipynb new file mode 100644 index 0000000..dc6cb3c --- /dev/null +++ b/docs/examples/basic/xopt_vocs.ipynb @@ -0,0 +1,662 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "9700f595", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "# VOCS data structure \n", + "\n", + "Variables, Objectives, Constraints, and other Settings (VOCS) helps define our optimization problems. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "32324ac0-b299-42ee-a6b9-e112dfaa5a45", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-27T16:46:04.425862200Z", + "start_time": "2023-10-27T16:46:01.206803200Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:23.378500Z", + "iopub.status.busy": "2024-09-13T15:54:23.378214Z", + "iopub.status.idle": "2024-09-13T15:54:24.383836Z", + "shell.execute_reply": "2024-09-13T15:54:24.383553Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "from xopt.vocs import VOCS\n", + "from xopt.vocs import form_objective_data\n", + "import pandas as pd\n", + "import numpy as np" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "31a15253-14cd-4547-ab01-09f4b56f5bf5", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-27T16:46:04.440220800Z", + "start_time": "2023-10-27T16:46:04.427862900Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:24.385474Z", + "iopub.status.busy": "2024-09-13T15:54:24.385365Z", + "iopub.status.idle": "2024-09-13T15:54:24.389440Z", + "shell.execute_reply": "2024-09-13T15:54:24.389205Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "Y = \"\"\"\n", + "variables:\n", + " a: [0, 1e3] # Note that 1e3 usually parses as a str with YAML. \n", + " b: [-1, 1]\n", + "objectives:\n", + " c: maximize\n", + " d: minimize \n", + "constraints:\n", + " e: ['Less_than', 2]\n", + " f: ['greater_than', 0]\n", + "constants:\n", + " g: 1234\n", + "\n", + "\"\"\"\n", + "\n", + "vocs = VOCS.from_yaml(Y)\n", + "vocs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "304bcc89", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-27T16:46:04.484822200Z", + "start_time": "2023-10-27T16:46:04.442248900Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:24.390834Z", + "iopub.status.busy": "2024-09-13T15:54:24.390728Z", + "iopub.status.idle": "2024-09-13T15:54:24.392869Z", + "shell.execute_reply": "2024-09-13T15:54:24.392654Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# as dict\n", + "dict(vocs)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b236bfb4", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-27T16:46:04.493762900Z", + "start_time": "2023-10-27T16:46:04.458284400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:24.394199Z", + "iopub.status.busy": "2024-09-13T15:54:24.394090Z", + "iopub.status.idle": "2024-09-13T15:54:24.395693Z", + "shell.execute_reply": "2024-09-13T15:54:24.395474Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# re-parse dict\n", + "vocs2 = VOCS.from_dict(dict(vocs))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "76c9a173", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-27T16:46:04.493762900Z", + "start_time": "2023-10-27T16:46:04.471285600Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:24.396935Z", + "iopub.status.busy": "2024-09-13T15:54:24.396858Z", + "iopub.status.idle": "2024-09-13T15:54:24.398815Z", + "shell.execute_reply": "2024-09-13T15:54:24.398561Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# Check that these are the same\n", + "vocs2 == vocs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6a846f4d", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-27T16:46:04.503771300Z", + "start_time": "2023-10-27T16:46:04.486737500Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:24.400062Z", + "iopub.status.busy": "2024-09-13T15:54:24.399983Z", + "iopub.status.idle": "2024-09-13T15:54:24.402060Z", + "shell.execute_reply": "2024-09-13T15:54:24.401833Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# This replaces the old vocs[\"variables\"]\n", + "getattr(vocs, \"variables\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6fa2afc8", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-27T16:46:04.544770800Z", + "start_time": "2023-10-27T16:46:04.504768500Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:24.403350Z", + "iopub.status.busy": "2024-09-13T15:54:24.403277Z", + "iopub.status.idle": "2024-09-13T15:54:24.405301Z", + "shell.execute_reply": "2024-09-13T15:54:24.405056Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "vocs.objectives[\"c\"] == \"MAXIMIZE\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "713272b7", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-27T16:46:04.544770800Z", + "start_time": "2023-10-27T16:46:04.518770200Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:24.406554Z", + "iopub.status.busy": "2024-09-13T15:54:24.406478Z", + "iopub.status.idle": "2024-09-13T15:54:24.408554Z", + "shell.execute_reply": "2024-09-13T15:54:24.408340Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# json\n", + "vocs.to_json()" + ] + }, + { + "cell_type": "markdown", + "id": "11167a85", + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, + "source": [ + "# Objective Evaluation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c4d150be", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-27T16:46:04.566771500Z", + "start_time": "2023-10-27T16:46:04.534772500Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:24.409883Z", + "iopub.status.busy": "2024-09-13T15:54:24.409801Z", + "iopub.status.idle": "2024-09-13T15:54:24.416749Z", + "shell.execute_reply": "2024-09-13T15:54:24.416537Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "data = pd.DataFrame(vocs.random_inputs(10))\n", + "# Add some outputs\n", + "data[\"c\"] = data[\"a\"] + data[\"b\"]\n", + "data[\"d\"] = data[\"a\"] - data[\"b\"]\n", + "data[\"e\"] = data[\"a\"] * 2 + data[\"b\"] * 2\n", + "data[\"f\"] = data[\"a\"] * 2 - data[\"b\"] * 2\n", + "data.index = np.arange(len(data)) + 5 # custom index\n", + "data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a695dc98", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-27T16:46:04.604993200Z", + "start_time": "2023-10-27T16:46:04.563770500Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:24.418098Z", + "iopub.status.busy": "2024-09-13T15:54:24.418016Z", + "iopub.status.idle": "2024-09-13T15:54:24.420079Z", + "shell.execute_reply": "2024-09-13T15:54:24.419867Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "vocs.objectives" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "43051b71", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-27T16:46:04.635082700Z", + "start_time": "2023-10-27T16:46:04.582806500Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:24.421354Z", + "iopub.status.busy": "2024-09-13T15:54:24.421273Z", + "iopub.status.idle": "2024-09-13T15:54:24.424690Z", + "shell.execute_reply": "2024-09-13T15:54:24.424477Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# These are in standard form for minimization\n", + "form_objective_data(vocs.objectives, data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2ed50815", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-27T16:46:04.635997800Z", + "start_time": "2023-10-27T16:46:04.592809200Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:24.425955Z", + "iopub.status.busy": "2024-09-13T15:54:24.425881Z", + "iopub.status.idle": "2024-09-13T15:54:24.429058Z", + "shell.execute_reply": "2024-09-13T15:54:24.428830Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# This is also available as a method\n", + "vocs.objective_data(data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9f4275d4", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-27T16:46:04.665088600Z", + "start_time": "2023-10-27T16:46:04.607996700Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:24.430318Z", + "iopub.status.busy": "2024-09-13T15:54:24.430228Z", + "iopub.status.idle": "2024-09-13T15:54:24.432664Z", + "shell.execute_reply": "2024-09-13T15:54:24.432450Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "# use the to_numpy() method to convert for low level use.\n", + "vocs.objective_data(data).to_numpy()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "29b73df4", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-27T16:46:04.702034Z", + "start_time": "2023-10-27T16:46:04.622998Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:24.433958Z", + "iopub.status.busy": "2024-09-13T15:54:24.433875Z", + "iopub.status.idle": "2024-09-13T15:54:24.437333Z", + "shell.execute_reply": "2024-09-13T15:54:24.437135Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "vocs.constraint_data(data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e46a5e83", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-27T16:46:04.703032900Z", + "start_time": "2023-10-27T16:46:04.642000500Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:24.438649Z", + "iopub.status.busy": "2024-09-13T15:54:24.438572Z", + "iopub.status.idle": "2024-09-13T15:54:24.442669Z", + "shell.execute_reply": "2024-09-13T15:54:24.442443Z" + }, + "pycharm": { + "name": "#%%\n" + } + }, + "outputs": [], + "source": [ + "vocs.feasibility_data(data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c42dfa48", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-27T16:46:04.703032900Z", + "start_time": "2023-10-27T16:46:04.665088600Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:24.443972Z", + "iopub.status.busy": "2024-09-13T15:54:24.443881Z", + "iopub.status.idle": "2024-09-13T15:54:24.447077Z", + "shell.execute_reply": "2024-09-13T15:54:24.446871Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# normalize inputs to unit domain [0,1]\n", + "normed_data = vocs.normalize_inputs(data)\n", + "normed_data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7364e86b", + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:54:24.448319Z", + "iopub.status.busy": "2024-09-13T15:54:24.448238Z", + "iopub.status.idle": "2024-09-13T15:54:24.451434Z", + "shell.execute_reply": "2024-09-13T15:54:24.451213Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# and denormalize\n", + "vocs.denormalize_inputs(normed_data)" + ] + }, + { + "cell_type": "markdown", + "id": "59041be7", + "metadata": {}, + "source": [ + "# Error handling" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f9171ede", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-27T16:46:04.719064600Z", + "start_time": "2023-10-27T16:46:04.668031Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:24.452750Z", + "iopub.status.busy": "2024-09-13T15:54:24.452676Z", + "iopub.status.idle": "2024-09-13T15:54:24.454842Z", + "shell.execute_reply": "2024-09-13T15:54:24.454623Z" + } + }, + "outputs": [], + "source": [ + "Y = \"\"\"\n", + "variables:\n", + " a: [0, 1e3] # Note that 1e3 usually parses as a str with YAML. \n", + " b: [-1, 1]\n", + "objectives:\n", + " c: maximize\n", + " d: minimize \n", + "constraints:\n", + " e: ['Less_than', 2]\n", + " f: ['greater_than', 0]\n", + "constants:\n", + " g: 1234\n", + "\n", + "\"\"\"\n", + "\n", + "vocs = VOCS.from_yaml(Y)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b5513539", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-27T16:46:04.763060700Z", + "start_time": "2023-10-27T16:46:04.686996500Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:24.455998Z", + "iopub.status.busy": "2024-09-13T15:54:24.455925Z", + "iopub.status.idle": "2024-09-13T15:54:24.458659Z", + "shell.execute_reply": "2024-09-13T15:54:24.458429Z" + } + }, + "outputs": [], + "source": [ + "d = {\"a\": [1, 2, 3]}\n", + "\n", + "df = pd.DataFrame(d)\n", + "df2 = pd.DataFrame(df).copy()\n", + "\n", + "df2[\"b\"] = np.nan\n", + "df2[\"b\"] - 1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30f3b3b4", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-27T16:46:04.800032400Z", + "start_time": "2023-10-27T16:46:04.698034700Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:24.459913Z", + "iopub.status.busy": "2024-09-13T15:54:24.459836Z", + "iopub.status.idle": "2024-09-13T15:54:24.461545Z", + "shell.execute_reply": "2024-09-13T15:54:24.461348Z" + } + }, + "outputs": [], + "source": [ + "data[\"a\"] = np.nan" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6393376b", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-27T16:46:04.877099700Z", + "start_time": "2023-10-27T16:46:04.715062Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:24.462904Z", + "iopub.status.busy": "2024-09-13T15:54:24.462822Z", + "iopub.status.idle": "2024-09-13T15:54:24.464843Z", + "shell.execute_reply": "2024-09-13T15:54:24.464636Z" + } + }, + "outputs": [], + "source": [ + "a = 2\n", + "\n", + "\n", + "def f(x=a):\n", + " return x\n", + "\n", + "\n", + "a = 99\n", + "f()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cb0ecd74", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-27T16:46:04.880100300Z", + "start_time": "2023-10-27T16:46:04.728063600Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:24.466048Z", + "iopub.status.busy": "2024-09-13T15:54:24.465972Z", + "iopub.status.idle": "2024-09-13T15:54:24.468950Z", + "shell.execute_reply": "2024-09-13T15:54:24.468730Z" + } + }, + "outputs": [], + "source": [ + "pd.DataFrame(6e66, index=[1, 2, 3], columns=[\"A\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "88bb8253", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-27T16:46:04.896129200Z", + "start_time": "2023-10-27T16:46:04.744036400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:24.470167Z", + "iopub.status.busy": "2024-09-13T15:54:24.470092Z", + "iopub.status.idle": "2024-09-13T15:54:24.473523Z", + "shell.execute_reply": "2024-09-13T15:54:24.473268Z" + } + }, + "outputs": [], + "source": [ + "# These are in standard form for minimization\n", + "\n", + "data = pd.DataFrame({\"c\": [1, 2, 3, 4]}, index=[9, 3, 4, 5])\n", + "\n", + "form_objective_data(vocs.objectives, data)" + ] + } + ], + "metadata": { + "interpreter": { + "hash": "60703f99bc9a1e465ed9e894ac1e8f256837fa55699b4912a7939def4ae95691" + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/examples/bayes_exp/bayesian_exploration.ipynb b/docs/examples/bayes_exp/bayesian_exploration.ipynb new file mode 100644 index 0000000..3c78a51 --- /dev/null +++ b/docs/examples/bayes_exp/bayesian_exploration.ipynb @@ -0,0 +1,315 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "# Bayesian Exploration\n", + "Here we demonstrate the use of Bayesian Exploration to characterize an unknown\n", + "function in the presence of constraints (see [here](https://www.nature.com/articles/s41467-021-25757-3)).\n", + "The function we wish to explore is the first objective of the TNK test problem." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Specifiying generator options\n", + "We start with the generator defaults and modify as needed for conservative\n", + "exploration, which should prevent any constraint violations." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:49:35.830514Z", + "iopub.status.busy": "2024-09-13T15:49:35.830266Z", + "iopub.status.idle": "2024-09-13T15:49:37.500740Z", + "shell.execute_reply": "2024-09-13T15:49:37.500445Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# set values if testing\n", + "import os\n", + "\n", + "from copy import deepcopy\n", + "from xopt import Xopt, Evaluator\n", + "from xopt.generators.bayesian import BayesianExplorationGenerator\n", + "from xopt.resources.test_functions.tnk import evaluate_TNK, tnk_vocs\n", + "\n", + "# Ignore all warnings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"ignore\")\n", + "\n", + "SMOKE_TEST = os.environ.get(\"SMOKE_TEST\")\n", + "NUM_MC_SAMPLES = 1 if SMOKE_TEST else 128\n", + "NUM_RESTARTS = 1 if SMOKE_TEST else 20\n", + "\n", + "vocs = deepcopy(tnk_vocs)\n", + "vocs.objectives = {}\n", + "vocs.observables = [\"y1\"]\n", + "\n", + "generator = BayesianExplorationGenerator(vocs=vocs)\n", + "generator.max_travel_distances = [0.25, 0.25]\n", + "generator.numerical_optimizer.n_restarts = NUM_RESTARTS\n", + "generator.n_monte_carlo_samples = NUM_MC_SAMPLES\n", + "\n", + "evaluator = Evaluator(function=evaluate_TNK)\n", + "\n", + "X = Xopt(generator=generator, evaluator=evaluator, vocs=vocs)\n", + "X" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Run exploration\n", + "We start with evaluating 2 points that we know satisfy the constraints. We then run\n", + "30 exploration steps." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:49:37.522962Z", + "iopub.status.busy": "2024-09-13T15:49:37.522764Z", + "iopub.status.idle": "2024-09-13T15:49:37.530412Z", + "shell.execute_reply": "2024-09-13T15:49:37.530171Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "X.evaluate_data({\"x1\": [1.0, 0.75], \"x2\": [0.7, 0.95]})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:49:37.531732Z", + "iopub.status.busy": "2024-09-13T15:49:37.531635Z", + "iopub.status.idle": "2024-09-13T15:49:39.055235Z", + "shell.execute_reply": "2024-09-13T15:49:39.054797Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "for i in range(2):\n", + " print(f\"step {i}\")\n", + " X.step()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:49:39.056947Z", + "iopub.status.busy": "2024-09-13T15:49:39.056826Z", + "iopub.status.idle": "2024-09-13T15:49:39.060928Z", + "shell.execute_reply": "2024-09-13T15:49:39.060709Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# view the data\n", + "X.data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:49:39.062334Z", + "iopub.status.busy": "2024-09-13T15:49:39.062246Z", + "iopub.status.idle": "2024-09-13T15:49:39.408101Z", + "shell.execute_reply": "2024-09-13T15:49:39.407782Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# plot results\n", + "ax = X.data.plot(\"x1\", \"x2\")\n", + "ax.set_aspect(\"equal\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Introspect models, acquisition function and feasibility prediction\n", + "During exploration we generate Gaussian Process models of each objective and\n", + "constraint. We demonstrate how they are viewed below." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:49:39.409688Z", + "iopub.status.busy": "2024-09-13T15:49:39.409537Z", + "iopub.status.idle": "2024-09-13T15:49:40.596506Z", + "shell.execute_reply": "2024-09-13T15:49:40.596197Z" + } + }, + "outputs": [], + "source": [ + "fig, ax = X.generator.visualize_model(show_feasibility=True, n_grid=100)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Generator model hyperparameters" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:49:40.599399Z", + "iopub.status.busy": "2024-09-13T15:49:40.599290Z", + "iopub.status.idle": "2024-09-13T15:49:40.604079Z", + "shell.execute_reply": "2024-09-13T15:49:40.603845Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# print generator model hyperparameters\n", + "for name, val in X.generator.model.named_parameters():\n", + " print(f\"{name}:{val}\")\n", + "\n", + "X.generator.model.models[2].covar_module.lengthscale" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Examine the number of constraint violations\n", + "Using the convience function provided by the vocs object we can evaluate which\n", + "samples violate either or both of our constraints." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:49:40.605375Z", + "iopub.status.busy": "2024-09-13T15:49:40.605292Z", + "iopub.status.idle": "2024-09-13T15:49:40.609674Z", + "shell.execute_reply": "2024-09-13T15:49:40.609461Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "X.vocs.feasibility_data(X.data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:49:40.611135Z", + "iopub.status.busy": "2024-09-13T15:49:40.611035Z", + "iopub.status.idle": "2024-09-13T15:49:41.732575Z", + "shell.execute_reply": "2024-09-13T15:49:41.732305Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# generate next point\n", + "X.generator.generate(1)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/bayes_exp/bayesian_exploration_from_yaml.ipynb b/docs/examples/bayes_exp/bayesian_exploration_from_yaml.ipynb new file mode 100644 index 0000000..b8f6b6b --- /dev/null +++ b/docs/examples/bayes_exp/bayesian_exploration_from_yaml.ipynb @@ -0,0 +1,208 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Bayesian exploration from YAML" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-06-27T15:40:58.222341700Z", + "start_time": "2023-06-27T15:40:49.783378500Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:50:02.293334Z", + "iopub.status.busy": "2024-09-13T15:50:02.293007Z", + "iopub.status.idle": "2024-09-13T15:50:03.279640Z", + "shell.execute_reply": "2024-09-13T15:50:03.279335Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "from xopt import Xopt\n", + "\n", + "# set values if testing\n", + "import os\n", + "\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"ignore\")\n", + "\n", + "\n", + "SMOKE_TEST = os.environ.get(\"SMOKE_TEST\")\n", + "\n", + "YAML = \"\"\"\n", + "generator:\n", + " name: bayesian_exploration\n", + "\n", + "evaluator:\n", + " function: xopt.resources.test_functions.tnk.evaluate_TNK\n", + "\n", + "vocs:\n", + " variables:\n", + " x1: [0, 3.14159]\n", + " x2: [0, 3.14159]\n", + " observables: [y1]\n", + " constraints:\n", + " c1: [GREATER_THAN, 0]\n", + " c2: [LESS_THAN, 0.5]\n", + " constants: {a: dummy_constant}\n", + "\n", + "\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-06-27T15:40:58.279371300Z", + "start_time": "2023-06-27T15:40:58.223342400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:50:03.281449Z", + "iopub.status.busy": "2024-09-13T15:50:03.281310Z", + "iopub.status.idle": "2024-09-13T15:50:03.870248Z", + "shell.execute_reply": "2024-09-13T15:50:03.869883Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "X = Xopt.from_yaml(YAML)\n", + "\n", + "# for testing purposes only\n", + "if SMOKE_TEST:\n", + " X.generator.numerical_optimizer.n_restarts = 1\n", + " X.generator.n_monte_carlo_samples = 1\n", + "\n", + "X" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-06-27T15:41:47.000398300Z", + "start_time": "2023-06-27T15:40:58.255341800Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:50:03.891525Z", + "iopub.status.busy": "2024-09-13T15:50:03.891330Z", + "iopub.status.idle": "2024-09-13T15:50:21.303719Z", + "shell.execute_reply": "2024-09-13T15:50:21.303365Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "X.random_evaluate(5)\n", + "\n", + "for i in range(5):\n", + " print(f\"step {i}\")\n", + " X.step()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-06-27T15:41:47.016401600Z", + "start_time": "2023-06-27T15:41:47.002399Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:50:21.305548Z", + "iopub.status.busy": "2024-09-13T15:50:21.305362Z", + "iopub.status.idle": "2024-09-13T15:50:21.309628Z", + "shell.execute_reply": "2024-09-13T15:50:21.309368Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "print(X.data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-06-27T15:41:47.949659600Z", + "start_time": "2023-06-27T15:41:47.017428500Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:50:21.311002Z", + "iopub.status.busy": "2024-09-13T15:50:21.310915Z", + "iopub.status.idle": "2024-09-13T15:50:21.641630Z", + "shell.execute_reply": "2024-09-13T15:50:21.641351Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# plot results\n", + "ax = X.data.plot(\"x1\", \"x2\")\n", + "ax.set_aspect(\"equal\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:50:21.643202Z", + "iopub.status.busy": "2024-09-13T15:50:21.643041Z", + "iopub.status.idle": "2024-09-13T15:50:22.876968Z", + "shell.execute_reply": "2024-09-13T15:50:22.876653Z" + } + }, + "outputs": [], + "source": [ + "fig, ax = X.generator.visualize_model(show_feasibility=True, n_grid=100)" + ] + } + ], + "metadata": { + "interpreter": { + "hash": "72034539424920dfb606fe3b820b3f27dca0cbf1c69938110810ec4641e275b1" + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/bayes_exp/bayesian_exploration_w_interpolation.ipynb b/docs/examples/bayes_exp/bayesian_exploration_w_interpolation.ipynb new file mode 100644 index 0000000..fa2231c --- /dev/null +++ b/docs/examples/bayes_exp/bayesian_exploration_w_interpolation.ipynb @@ -0,0 +1,353 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "# Bayesian Exploration\n", + "Here we demonstrate the use of Bayesian Exploration to characterize an unknown\n", + "function in the presence of constraints (see [here](https://www.nature.com/articles/s41467-021-25757-3)).\n", + "The function we wish to explore is the first objective of the TNK test problem." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Specifiying generator options\n", + "We start with the generator defaults and modify as needed for conservative\n", + "exploration, which should prevent any constraint violations." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-11-14T21:38:27.504548300Z", + "start_time": "2023-11-14T21:38:24.272273500Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:49:43.989564Z", + "iopub.status.busy": "2024-09-13T15:49:43.989239Z", + "iopub.status.idle": "2024-09-13T15:49:45.516807Z", + "shell.execute_reply": "2024-09-13T15:49:45.516507Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# set values if testing\n", + "import os\n", + "\n", + "from copy import deepcopy\n", + "from xopt import Xopt, Evaluator\n", + "from xopt.generators.bayesian import BayesianExplorationGenerator\n", + "from xopt.resources.test_functions.tnk import evaluate_TNK, tnk_vocs\n", + "\n", + "# Ignore all warnings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"ignore\")\n", + "\n", + "SMOKE_TEST = os.environ.get(\"SMOKE_TEST\")\n", + "NUM_MC_SAMPLES = 1 if SMOKE_TEST else 128\n", + "NUM_RESTARTS = 1 if SMOKE_TEST else 20\n", + "\n", + "\n", + "vocs = deepcopy(tnk_vocs)\n", + "vocs.objectives = {}\n", + "vocs.observables = [\"y1\"]\n", + "\n", + "generator = BayesianExplorationGenerator(vocs=vocs)\n", + "generator.numerical_optimizer.n_restarts = NUM_RESTARTS\n", + "generator.numerical_optimizer.max_iter = 100\n", + "generator.n_monte_carlo_samples = NUM_MC_SAMPLES\n", + "generator.n_interpolate_points = 5\n", + "\n", + "evaluator = Evaluator(function=evaluate_TNK)\n", + "\n", + "X = Xopt(generator=generator, evaluator=evaluator, vocs=vocs)\n", + "X" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Run exploration\n", + "We start with evaluating 2 points that we know satisfy the constraints. We then run\n", + "30 exploration steps." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-11-14T21:38:27.547546500Z", + "start_time": "2023-11-14T21:38:27.506549300Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:49:45.538126Z", + "iopub.status.busy": "2024-09-13T15:49:45.537933Z", + "iopub.status.idle": "2024-09-13T15:49:45.545152Z", + "shell.execute_reply": "2024-09-13T15:49:45.544914Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "X.evaluate_data({\"x1\": [1.0, 0.75], \"x2\": [0.7, 0.95]})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-11-14T21:38:58.405527100Z", + "start_time": "2023-11-14T21:38:27.520548400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:49:45.546556Z", + "iopub.status.busy": "2024-09-13T15:49:45.546482Z", + "iopub.status.idle": "2024-09-13T15:49:57.580498Z", + "shell.execute_reply": "2024-09-13T15:49:57.580185Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "for i in range(20):\n", + " print(f\"step {i}\")\n", + " X.step()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-11-14T21:38:58.420527100Z", + "start_time": "2023-11-14T21:38:58.414526900Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:49:57.582464Z", + "iopub.status.busy": "2024-09-13T15:49:57.582292Z", + "iopub.status.idle": "2024-09-13T15:49:57.587523Z", + "shell.execute_reply": "2024-09-13T15:49:57.587314Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# view the data\n", + "X.data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-11-14T21:38:58.961213100Z", + "start_time": "2023-11-14T21:38:58.421527Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:49:57.588870Z", + "iopub.status.busy": "2024-09-13T15:49:57.588789Z", + "iopub.status.idle": "2024-09-13T15:49:57.921151Z", + "shell.execute_reply": "2024-09-13T15:49:57.920906Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# plot results\n", + "ax = X.data.plot(\"x1\", \"x2\")\n", + "ax.set_aspect(\"equal\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Introspect models, acquisition function and feasibility prediction\n", + "During exploration we generate Gaussian Process models of each objective and\n", + "constraint. We demonstrate how they are viewed below." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-11-14T21:39:03.161691600Z", + "start_time": "2023-11-14T21:38:58.962214700Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:49:57.922739Z", + "iopub.status.busy": "2024-09-13T15:49:57.922598Z", + "iopub.status.idle": "2024-09-13T15:49:59.442036Z", + "shell.execute_reply": "2024-09-13T15:49:59.441725Z" + } + }, + "outputs": [], + "source": [ + "fig, ax = X.generator.visualize_model(show_feasibility=True, n_grid=100)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Generator model hyperparameters" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-11-14T21:39:03.220691400Z", + "start_time": "2023-11-14T21:39:03.162691600Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:49:59.445523Z", + "iopub.status.busy": "2024-09-13T15:49:59.445390Z", + "iopub.status.idle": "2024-09-13T15:49:59.450445Z", + "shell.execute_reply": "2024-09-13T15:49:59.450139Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# print generator model hyperparameters\n", + "for name, val in X.generator.model.named_parameters():\n", + " print(f\"{name}:{val}\")\n", + "\n", + "X.generator.model.models[2].covar_module.lengthscale" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Examine the number of constraint violations\n", + "Using the convience function provided by the vocs object we can evaluate which\n", + "samples violate either or both of our constraints." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-11-14T21:39:03.234692100Z", + "start_time": "2023-11-14T21:39:03.177693800Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:49:59.451848Z", + "iopub.status.busy": "2024-09-13T15:49:59.451765Z", + "iopub.status.idle": "2024-09-13T15:49:59.456690Z", + "shell.execute_reply": "2024-09-13T15:49:59.456447Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "X.vocs.feasibility_data(X.data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-11-14T21:39:04.225690600Z", + "start_time": "2023-11-14T21:39:03.192694800Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:49:59.457977Z", + "iopub.status.busy": "2024-09-13T15:49:59.457892Z", + "iopub.status.idle": "2024-09-13T15:50:00.022176Z", + "shell.execute_reply": "2024-09-13T15:50:00.021807Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# generate next point\n", + "X.generator.generate(1)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/bayes_exp/bayesian_exploration_with_nans.ipynb b/docs/examples/bayes_exp/bayesian_exploration_with_nans.ipynb new file mode 100644 index 0000000..131ef47 --- /dev/null +++ b/docs/examples/bayes_exp/bayesian_exploration_with_nans.ipynb @@ -0,0 +1,232 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Bayesian Exploration with NaNs\n", + "\n", + "As violations of constraints can lead to invalid values of the objective, the evaluate function may simply return NaNs. We demonstrate below how we can still perform Bayesian Exploration in that case." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-06-14T15:37:25.535638700Z", + "start_time": "2023-06-14T15:37:22.379218300Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:50:25.160005Z", + "iopub.status.busy": "2024-09-13T15:50:25.159647Z", + "iopub.status.idle": "2024-09-13T15:50:26.672325Z", + "shell.execute_reply": "2024-09-13T15:50:26.671836Z" + } + }, + "outputs": [], + "source": [ + "# set values if testing\n", + "import os\n", + "\n", + "import pandas as pd\n", + "import torch\n", + "from copy import deepcopy\n", + "from xopt import Xopt, Evaluator\n", + "from xopt.generators.bayesian import BayesianExplorationGenerator\n", + "from xopt.resources.test_functions.tnk import evaluate_TNK, tnk_vocs\n", + "\n", + "# Ignore all warnings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"ignore\")\n", + "\n", + "SMOKE_TEST = os.environ.get(\"SMOKE_TEST\")\n", + "NUM_MC_SAMPLES = 1 if SMOKE_TEST else 128\n", + "NUM_RESTARTS = 1 if SMOKE_TEST else 20\n", + "\n", + "vocs = deepcopy(tnk_vocs)\n", + "vocs.objectives = {}\n", + "vocs.observables = [\"y1\"]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-06-14T15:37:25.549637200Z", + "start_time": "2023-06-14T15:37:25.539666500Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:50:26.674126Z", + "iopub.status.busy": "2024-09-13T15:50:26.673966Z", + "iopub.status.idle": "2024-09-13T15:50:26.676329Z", + "shell.execute_reply": "2024-09-13T15:50:26.676082Z" + } + }, + "outputs": [], + "source": [ + "# modify the evaluate function to return NaNs if constraints are violated\n", + "def evaluate(input_dict):\n", + " output_dict = evaluate_TNK(input_dict)\n", + " del output_dict[\"y2\"]\n", + " for c in vocs.constraints.keys():\n", + " if (\n", + " vocs.constraints[c][0].upper() == \"GREATER_THAN\"\n", + " and output_dict[c] <= vocs.constraints[c][1]\n", + " ):\n", + " output_dict[\"y1\"] = torch.nan\n", + " elif (\n", + " vocs.constraints[c][0].upper() == \"LESS_THAN\"\n", + " and output_dict[c] >= vocs.constraints[c][1]\n", + " ):\n", + " output_dict[\"y1\"] = torch.nan\n", + " return output_dict" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-06-14T15:37:25.580538800Z", + "start_time": "2023-06-14T15:37:25.552636600Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:50:26.677735Z", + "iopub.status.busy": "2024-09-13T15:50:26.677632Z", + "iopub.status.idle": "2024-09-13T15:50:26.685490Z", + "shell.execute_reply": "2024-09-13T15:50:26.685270Z" + } + }, + "outputs": [], + "source": [ + "generator = BayesianExplorationGenerator(vocs=vocs)\n", + "generator.max_travel_distances = [0.25, 0.25]\n", + "generator.n_monte_carlo_samples = NUM_MC_SAMPLES\n", + "generator.numerical_optimizer.n_restarts = NUM_RESTARTS\n", + "\n", + "evaluator = Evaluator(function=evaluate)\n", + "X = Xopt(generator=generator, evaluator=evaluator, vocs=vocs)\n", + "X" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Run exploration\n", + "We start with evaluating 2 points that we know satisfy the constraints. We then run\n", + "30 exploration steps." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-06-14T15:37:25.638907100Z", + "start_time": "2023-06-14T15:37:25.582541Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:50:26.706489Z", + "iopub.status.busy": "2024-09-13T15:50:26.706373Z", + "iopub.status.idle": "2024-09-13T15:50:26.713499Z", + "shell.execute_reply": "2024-09-13T15:50:26.713266Z" + } + }, + "outputs": [], + "source": [ + "X.evaluate_data(pd.DataFrame({\"x1\": [1.0, 0.75], \"x2\": [0.7, 0.95]}))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-06-14T15:42:05.755461900Z", + "start_time": "2023-06-14T15:37:25.611873400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:50:26.714870Z", + "iopub.status.busy": "2024-09-13T15:50:26.714785Z", + "iopub.status.idle": "2024-09-13T15:51:09.297672Z", + "shell.execute_reply": "2024-09-13T15:51:09.297339Z" + } + }, + "outputs": [], + "source": [ + "N_STEPS = 1 if SMOKE_TEST else 30\n", + "for i in range(N_STEPS):\n", + " print(f\"step {i}\")\n", + " X.step()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-06-14T15:42:05.786611100Z", + "start_time": "2023-06-14T15:42:05.773463300Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:51:09.299490Z", + "iopub.status.busy": "2024-09-13T15:51:09.299336Z", + "iopub.status.idle": "2024-09-13T15:51:09.305496Z", + "shell.execute_reply": "2024-09-13T15:51:09.305264Z" + } + }, + "outputs": [], + "source": [ + "# view the data\n", + "X.data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-06-14T15:42:06.400652600Z", + "start_time": "2023-06-14T15:42:05.787612700Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:51:09.306861Z", + "iopub.status.busy": "2024-09-13T15:51:09.306776Z", + "iopub.status.idle": "2024-09-13T15:51:09.645048Z", + "shell.execute_reply": "2024-09-13T15:51:09.644777Z" + } + }, + "outputs": [], + "source": [ + "# plot results\n", + "ax = X.data.plot(\"x1\", \"x2\")\n", + "ax.set_aspect(\"equal\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/ga/cnsga_tnk.ipynb b/docs/examples/ga/cnsga_tnk.ipynb new file mode 100644 index 0000000..c931a2b --- /dev/null +++ b/docs/examples/ga/cnsga_tnk.ipynb @@ -0,0 +1,688 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Xopt CNSGA algorithm" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T21:51:52.453021600Z", + "start_time": "2023-09-06T21:51:49.273323Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T16:00:01.585855Z", + "iopub.status.busy": "2024-09-13T16:00:01.585522Z", + "iopub.status.idle": "2024-09-13T16:00:02.749698Z", + "shell.execute_reply": "2024-09-13T16:00:02.749434Z" + } + }, + "outputs": [], + "source": [ + "from xopt.generators.ga.cnsga import CNSGAGenerator\n", + "\n", + "from xopt.resources.test_functions.tnk import evaluate_TNK, tnk_vocs\n", + "\n", + "from xopt.utils import read_xopt_csv\n", + "\n", + "from xopt import Xopt, Evaluator\n", + "\n", + "import pandas as pd\n", + "\n", + "from glob import glob\n", + "\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T21:51:52.469030800Z", + "start_time": "2023-09-06T21:51:52.449951900Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T16:00:02.751723Z", + "iopub.status.busy": "2024-09-13T16:00:02.751521Z", + "iopub.status.idle": "2024-09-13T16:00:02.753525Z", + "shell.execute_reply": "2024-09-13T16:00:02.753086Z" + } + }, + "outputs": [], + "source": [ + "# Useful for debugging\n", + "# %load_ext autoreload\n", + "# %autoreload 2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T21:51:52.493030800Z", + "start_time": "2023-09-06T21:51:52.465030400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T16:00:02.755254Z", + "iopub.status.busy": "2024-09-13T16:00:02.755117Z", + "iopub.status.idle": "2024-09-13T16:00:02.759971Z", + "shell.execute_reply": "2024-09-13T16:00:02.759724Z" + } + }, + "outputs": [], + "source": [ + "ev = Evaluator(function=evaluate_TNK)\n", + "ev.function_kwargs = {\n", + " \"raise_probability\": 0.1\n", + "} # optional random crashing, to mimic real-world use." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T21:51:52.509029400Z", + "start_time": "2023-09-06T21:51:52.495029300Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T16:00:02.761413Z", + "iopub.status.busy": "2024-09-13T16:00:02.761328Z", + "iopub.status.idle": "2024-09-13T16:00:02.763167Z", + "shell.execute_reply": "2024-09-13T16:00:02.762928Z" + } + }, + "outputs": [], + "source": [ + "X = Xopt(\n", + " generator=CNSGAGenerator(vocs=tnk_vocs),\n", + " evaluator=ev,\n", + " vocs=tnk_vocs,\n", + ")\n", + "X.strict = False" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "Run 100 generations" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T21:51:57.548278900Z", + "start_time": "2023-09-06T21:51:52.510031300Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T16:00:02.764555Z", + "iopub.status.busy": "2024-09-13T16:00:02.764469Z", + "iopub.status.idle": "2024-09-13T16:00:04.262400Z", + "shell.execute_reply": "2024-09-13T16:00:04.262090Z" + } + }, + "outputs": [], + "source": [ + "%%time\n", + "for _ in range(64 * 20):\n", + " X.step()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Plot " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T21:51:58.011277600Z", + "start_time": "2023-09-06T21:51:57.986277600Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T16:00:04.283246Z", + "iopub.status.busy": "2024-09-13T16:00:04.283105Z", + "iopub.status.idle": "2024-09-13T16:00:04.285630Z", + "shell.execute_reply": "2024-09-13T16:00:04.285358Z" + } + }, + "outputs": [], + "source": [ + "def plot_population(X):\n", + " fig, ax = plt.subplots(figsize=(8, 8))\n", + "\n", + " fdata = tnk_vocs.feasibility_data(X.data)\n", + "\n", + " k1 = \"x1\"\n", + " k2 = \"x2\"\n", + "\n", + " X.data.plot.scatter(k1, k2, marker=\".\", alpha=0.1, color=\"black\", ax=ax)\n", + " X.data[fdata[\"feasible\"]].plot.scatter(\n", + " k1, k2, marker=\"x\", alpha=0.3, color=\"orange\", ax=ax\n", + " )\n", + " X.generator.population.plot.scatter(k1, k2, marker=\"o\", color=\"red\", alpha=1, ax=ax)\n", + " ax.set_xlabel(k1)\n", + " ax.set_ylabel(k2)\n", + " ax.set_xlim(0, 1.5)\n", + " ax.set_ylim(0, 1.5)\n", + " ax.set_title(\"TNK with Xopt's CNSGA\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T21:51:58.283280100Z", + "start_time": "2023-09-06T21:51:58.000279400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T16:00:04.287031Z", + "iopub.status.busy": "2024-09-13T16:00:04.286924Z", + "iopub.status.idle": "2024-09-13T16:00:04.428891Z", + "shell.execute_reply": "2024-09-13T16:00:04.428627Z" + } + }, + "outputs": [], + "source": [ + "plot_population(X)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Write the current population" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T21:51:58.316288Z", + "start_time": "2023-09-06T21:51:58.285309200Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T16:00:04.430764Z", + "iopub.status.busy": "2024-09-13T16:00:04.430654Z", + "iopub.status.idle": "2024-09-13T16:00:04.436574Z", + "shell.execute_reply": "2024-09-13T16:00:04.436346Z" + } + }, + "outputs": [], + "source": [ + "X.generator.write_population(\"test.csv\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# YAML method" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T21:51:58.373278300Z", + "start_time": "2023-09-06T21:51:58.316288Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T16:00:04.438007Z", + "iopub.status.busy": "2024-09-13T16:00:04.437902Z", + "iopub.status.idle": "2024-09-13T16:00:04.444599Z", + "shell.execute_reply": "2024-09-13T16:00:04.444366Z" + } + }, + "outputs": [], + "source": [ + "YAML = \"\"\"\n", + "max_evaluations: 6400\n", + "strict: False\n", + "generator:\n", + " name: cnsga\n", + " population_size: 32\n", + " population_file: test.csv\n", + " output_path: .\n", + "\n", + "evaluator:\n", + " function: xopt.resources.test_functions.tnk.evaluate_TNK\n", + " function_kwargs:\n", + " raise_probability: 0.1\n", + "\n", + "vocs:\n", + " variables:\n", + " x1: [0, 3.14159]\n", + " x2: [0, 3.14159]\n", + " objectives: {y1: MINIMIZE, y2: MINIMIZE}\n", + " constraints:\n", + " c1: [GREATER_THAN, 0]\n", + " c2: [LESS_THAN, 0.5]\n", + " constants: {a: dummy_constant}\n", + "\n", + "\"\"\"\n", + "\n", + "X = Xopt(YAML)\n", + "X" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This will have loaded children from the population file. These will need to be re-evaluated." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T21:51:58.416310600Z", + "start_time": "2023-09-06T21:51:58.376279Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T16:00:04.445940Z", + "iopub.status.busy": "2024-09-13T16:00:04.445855Z", + "iopub.status.idle": "2024-09-13T16:00:04.447821Z", + "shell.execute_reply": "2024-09-13T16:00:04.447595Z" + } + }, + "outputs": [], + "source": [ + "len(X.generator._children)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T21:52:00.028438300Z", + "start_time": "2023-09-06T21:51:58.389282400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T16:00:04.449135Z", + "iopub.status.busy": "2024-09-13T16:00:04.449059Z", + "iopub.status.idle": "2024-09-13T16:00:12.513591Z", + "shell.execute_reply": "2024-09-13T16:00:12.513316Z" + } + }, + "outputs": [], + "source": [ + "%%time\n", + "X.run()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T21:52:00.248435500Z", + "start_time": "2023-09-06T21:52:00.026439100Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T16:00:12.514949Z", + "iopub.status.busy": "2024-09-13T16:00:12.514843Z", + "iopub.status.idle": "2024-09-13T16:00:12.676122Z", + "shell.execute_reply": "2024-09-13T16:00:12.675844Z" + } + }, + "outputs": [], + "source": [ + "plot_population(X)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T21:52:00.283467500Z", + "start_time": "2023-09-06T21:52:00.250437600Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T16:00:12.679960Z", + "iopub.status.busy": "2024-09-13T16:00:12.679838Z", + "iopub.status.idle": "2024-09-13T16:00:12.682027Z", + "shell.execute_reply": "2024-09-13T16:00:12.681797Z" + } + }, + "outputs": [], + "source": [ + "len(X.data)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Setting `output_path` will write .csv files for each population, as well as the offspring considered in each generation" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T21:52:00.284436600Z", + "start_time": "2023-09-06T21:52:00.264435700Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T16:00:12.683493Z", + "iopub.status.busy": "2024-09-13T16:00:12.683383Z", + "iopub.status.idle": "2024-09-13T16:00:12.685782Z", + "shell.execute_reply": "2024-09-13T16:00:12.685583Z" + } + }, + "outputs": [], + "source": [ + "pop_files = sorted(glob(\"cnsga_population*\"))\n", + "pop_files[:10]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T21:52:00.295436300Z", + "start_time": "2023-09-06T21:52:00.280436100Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T16:00:12.687091Z", + "iopub.status.busy": "2024-09-13T16:00:12.686989Z", + "iopub.status.idle": "2024-09-13T16:00:12.689402Z", + "shell.execute_reply": "2024-09-13T16:00:12.689121Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "offspring_files = sorted(glob(\"cnsga_offspring*\"))\n", + "offspring_files[0:10]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T21:52:00.412434900Z", + "start_time": "2023-09-06T21:52:00.294436Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T16:00:12.690731Z", + "iopub.status.busy": "2024-09-13T16:00:12.690639Z", + "iopub.status.idle": "2024-09-13T16:00:12.756141Z", + "shell.execute_reply": "2024-09-13T16:00:12.755882Z" + } + }, + "outputs": [], + "source": [ + "pop_df = read_xopt_csv(pop_files[-1])\n", + "pop_df.plot.scatter(\"x1\", \"x2\", marker=\"o\", color=\"red\", alpha=1)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "Similarly, offsrping files can be loaded. This will load the last few:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T16:00:12.757854Z", + "iopub.status.busy": "2024-09-13T16:00:12.757734Z", + "iopub.status.idle": "2024-09-13T16:00:12.840341Z", + "shell.execute_reply": "2024-09-13T16:00:12.840075Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "offspring_df = read_xopt_csv(*offspring_files[-10:])\n", + "offspring_df.plot.scatter(\"x1\", \"x2\", marker=\".\", color=\"black\", alpha=0.1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Occationally there are duplicates in offspring" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T16:00:12.841961Z", + "iopub.status.busy": "2024-09-13T16:00:12.841848Z", + "iopub.status.idle": "2024-09-13T16:00:12.906879Z", + "shell.execute_reply": "2024-09-13T16:00:12.906594Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "all_offspring = read_xopt_csv(*offspring_files)\n", + "len(all_offspring), len(all_offspring.drop_duplicates())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T16:00:12.908464Z", + "iopub.status.busy": "2024-09-13T16:00:12.908347Z", + "iopub.status.idle": "2024-09-13T16:00:13.302556Z", + "shell.execute_reply": "2024-09-13T16:00:13.301941Z" + } + }, + "outputs": [], + "source": [ + "# Cleanup\n", + "!rm cnsga_population*\n", + "!rm cnsga_offspring*\n", + "!rm test.csv" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Examine generator" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T16:00:13.305396Z", + "iopub.status.busy": "2024-09-13T16:00:13.305190Z", + "iopub.status.idle": "2024-09-13T16:00:13.438995Z", + "shell.execute_reply": "2024-09-13T16:00:13.438672Z" + } + }, + "outputs": [], + "source": [ + "df = pd.DataFrame(X.generator.generate(1000))\n", + "\n", + "fig, ax = plt.subplots()\n", + "df.plot.scatter(\n", + " \"x1\", \"x2\", marker=\".\", color=\"green\", alpha=0.5, ax=ax, label=\"candidates\"\n", + ")\n", + "pop_df.plot.scatter(\n", + " \"x1\", \"x2\", marker=\"o\", color=\"red\", alpha=1, ax=ax, label=\"population\"\n", + ")\n", + "plt.legend()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Vectorized evaluation\n", + "\n", + "Some functions also allow vectorized inputs. This can often be very fast.\n", + "\n", + "However, vectorized evaluation has some restrictions. For example, the output dict cannot append additional arrays with odd lengths." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T16:00:13.440603Z", + "iopub.status.busy": "2024-09-13T16:00:13.440494Z", + "iopub.status.idle": "2024-09-13T16:00:13.442758Z", + "shell.execute_reply": "2024-09-13T16:00:13.442553Z" + } + }, + "outputs": [], + "source": [ + "# Notice that this returns `some_array`\n", + "evaluate_TNK({\"x1\": 1, \"x2\": 1})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T16:00:13.444045Z", + "iopub.status.busy": "2024-09-13T16:00:13.443955Z", + "iopub.status.idle": "2024-09-13T16:00:13.445644Z", + "shell.execute_reply": "2024-09-13T16:00:13.445440Z" + } + }, + "outputs": [], + "source": [ + "# Here we make a version that does not have this\n", + "def evaluate_TNK2(*args, **kwargs):\n", + " outputs = evaluate_TNK(*args, **kwargs)\n", + " outputs.pop(\"some_array\")\n", + " return outputs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T16:00:13.446869Z", + "iopub.status.busy": "2024-09-13T16:00:13.446796Z", + "iopub.status.idle": "2024-09-13T16:00:13.780954Z", + "shell.execute_reply": "2024-09-13T16:00:13.780631Z" + } + }, + "outputs": [], + "source": [ + "YAML = \"\"\"\n", + "max_evaluations: 6400\n", + "strict: False\n", + "generator:\n", + " name: cnsga\n", + " population_size: 32\n", + "\n", + "evaluator:\n", + " function: __main__.evaluate_TNK2\n", + " function_kwargs:\n", + " raise_probability: 0.1\n", + " vectorized: True\n", + " max_workers: 32\n", + "\n", + "vocs:\n", + " variables:\n", + " x1: [0, 3.14159]\n", + " x2: [0, 3.14159]\n", + " objectives: {y1: MINIMIZE, y2: MINIMIZE}\n", + " constraints:\n", + " c1: [GREATER_THAN, 0]\n", + " c2: [LESS_THAN, 0.5]\n", + " constants: {a: dummy_constant}\n", + "\n", + "\"\"\"\n", + "\n", + "\n", + "X2 = Xopt.from_yaml(YAML)\n", + "X2.evaluator.function = evaluate_TNK2\n", + "\n", + "X2.run()\n", + "\n", + "len(X2.data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T16:00:13.782675Z", + "iopub.status.busy": "2024-09-13T16:00:13.782545Z", + "iopub.status.idle": "2024-09-13T16:00:13.945257Z", + "shell.execute_reply": "2024-09-13T16:00:13.945001Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "plot_population(X)" + ] + } + ], + "metadata": { + "interpreter": { + "hash": "72034539424920dfb606fe3b820b3f27dca0cbf1c69938110810ec4641e275b1" + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/ga/nsga2.ipynb b/docs/examples/ga/nsga2.ipynb new file mode 100644 index 0000000..7cffbd6 --- /dev/null +++ b/docs/examples/ga/nsga2.ipynb @@ -0,0 +1,374 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# NSGA2 Generator\n", + "This notebook demonstrates the use of the generator `NSGA2Generator` which implements the NSGA-II algorithm from [1]. We show how to set up the optimizer object, use it to solve a test problem, and show off some of the generator's output features. Running this notebook will generate files in a temporary directory on your computer. These files will be cleaned up at the end." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "import logging\n", + "import matplotlib.pyplot as plt\n", + "import os\n", + "import pandas as pd\n", + "import shutil\n", + "import tempfile\n", + "\n", + "from xopt.generators.ga.nsga2 import (\n", + " NSGA2Generator,\n", + " PolynomialMutation,\n", + " SimulatedBinaryCrossover,\n", + ")\n", + "from xopt.resources.test_functions.zdt import construct_zdt\n", + "from xopt import Xopt, Evaluator, VOCS" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# The NSGA2Generator object emits logger messages\n", + "# Configure the logging module to output them to console\n", + "logging.basicConfig(\n", + " level=logging.INFO,\n", + " format=\"%(asctime)s - %(name)s - %(levelname)s - %(message)s\", # Format for log messages\n", + " datefmt=\"%Y-%m-%d %H:%M:%S\", # Date/time format\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Get the problem ZDT2 and create an Xopt evaluator\n", + "prob_vocs, prob_fun, _ = construct_zdt(30, 3)\n", + "ev = Evaluator(function=prob_fun)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Generator Setup and Use\n", + "First we create the `NSGA2Generator` object, demonstrate some of its settings, and then use it to solve the ZDT3 test problem." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create the NSGA2 generator with default settings\n", + "generator = NSGA2Generator(\n", + " vocs=prob_vocs, # Must provide the problem's details\n", + ")\n", + "\n", + "# Let's demonstrate controlling the generator's hyperparameters and settings\n", + "generator = NSGA2Generator(\n", + " vocs=prob_vocs, # Must provide the problem's details\n", + " population_size=50, # How many individuals in a population\n", + " mutation_operator=PolynomialMutation(\n", + " pm=1 / 30, eta_m=20\n", + " ), # Can manually specify mutation operator and hyperparameters\n", + " crossover_operator=SimulatedBinaryCrossover(\n", + " delta_1=0.5, delta_2=0.5, eta_c=20\n", + " ), # Similarly can specify crossover manually\n", + " deduplicate_output=True, # Performs deduplication of candidate individuals. Ensure unique outputs from generator.\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Run the optimizer for a few generations. Notice log output printed below this cell\n", + "ev.max_workers = generator.population_size\n", + "X = Xopt(generator=generator, evaluator=ev, vocs=prob_vocs)\n", + "\n", + "for _ in range(3):\n", + " X.step()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Exploring Optimizer Output\n", + "We now take a look at the metadata associated with the optimizer run as well as its output." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%time\n", + "# Run for longer with log output turned off\n", + "for handler in logging.root.handlers[:]:\n", + " logging.root.removeHandler(handler)\n", + "\n", + "for _ in range(47):\n", + " X.step()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Inspect generator properties\n", + "print(\n", + " f\"Saw {generator.fevals} function evaluations\"\n", + ") # Number of function evaluations returned to generator\n", + "print(\n", + " f\"Completed {generator.n_generations} generations\"\n", + ") # Number of generations finished\n", + "print(\n", + " f\"Generated {generator.n_candidates} candidate solutions\"\n", + ") # Number of individuals generated" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# All evaluations are stored in the following Dataframe. Specific to this generator, a `xopt_parent_generation` and\n", + "# `xopt_candidate_idx` columns which indicate from which generation the individual's parents belong to as well as providing a\n", + "# unique index for individuals.\n", + "#\n", + "# NOTE: The data DataFrame is not stored when serializing the generator. It must be saved on its own for later use.\n", + "generator.data.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Each population the optimizer has seen is stored by the unique indices of each individual.\n", + "print(generator.history_idx[-1][:16]) # Show the first few indices of last generation\n", + "\n", + "# If you have the data DataFrame you can extract all variables, objectives, constraints for each population\n", + "# Get a DataFrame of all information for the first population with every row being an individual\n", + "generator.data[\n", + " generator.data[\"xopt_candidate_idx\"].isin(generator.history_idx[0])\n", + "].head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Using the population records we can plot the final generation's objective functions\n", + "final_pop = generator.data[\n", + " generator.data[\"xopt_candidate_idx\"].isin(generator.history_idx[-1])\n", + "]\n", + "plt.scatter(final_pop[\"f1\"], final_pop[\"f2\"])\n", + "plt.xlabel(\"f1\")\n", + "plt.ylabel(\"f2\")\n", + "plt.title(f\"ZDT3, Generation {generator.n_generations}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# File Output from Generator\n", + "In this section, we will take a look at the files which can be automatically written by the optimizer. We will create a temporary directory and clean it up at the end of this notebook.\n", + "\n", + "The output files are the following.\n", + " - `data.csv`: All data evaluated during the optimization\n", + " - `vocs.txt`: The VOCS object so that the objectives, constraints, decision variables are retained alongside the data\n", + " - `populations.csv`: Each population is written here with a column `xopt_generation` to distinguish which generation the row belongs to\n", + " - `checkpoints`: This generator periodically saves its full state to timestamped files in this directory\n", + " - `log.txt`: Log output from the generator is recorded to this file\n", + "\n", + " Wile running each of the blocks, go ahead and open the temporary directory printed from the next cell and take a look at the files for yourself." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Setup our output directory\n", + "output_dir = tempfile.mkdtemp()\n", + "print(f'Will write data to \"{output_dir}\"')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Set up a generator configured to output data\n", + "generator = NSGA2Generator(\n", + " vocs=prob_vocs,\n", + " output_dir=output_dir, # Where will we write data\n", + " checkpoint_freq=8, # Turn on checkpoints and set ow often (in terms of generations) we save the optimizer state\n", + " log_level=logging.DEBUG, # Set the level of log messages output to our log file\n", + ")\n", + "\n", + "# Run it for a couple of generations\n", + "ev.max_workers = generator.population_size\n", + "X = Xopt(generator=generator, evaluator=ev, vocs=prob_vocs)\n", + "for _ in range(32):\n", + " X.step()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Check out the generated files\n", + "print(f'\"{output_dir}\": {os.listdir(output_dir)}')\n", + "checkpoint_dir = os.path.join(output_dir, \"checkpoints\")\n", + "print(f'\"checkpoints\": {os.listdir(checkpoint_dir)}')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# In the event data was already written to `output_dir` the generator will choose a new path with a numeric suffix\n", + "# to avoid overwriting anything.\n", + "X = Xopt(\n", + " generator=NSGA2Generator(vocs=prob_vocs, output_dir=output_dir),\n", + " evaluator=ev,\n", + " vocs=prob_vocs,\n", + ")\n", + "for _ in range(32):\n", + " X.step()\n", + "\n", + "# Compare the requested path and where the data ended up\n", + "print(f'Requested path: \"{output_dir}\"')\n", + "print(f'Path used: \"{X.generator.output_dir}\"')\n", + "\n", + "# Clean up the directory\n", + "X.generator.close_log_file()\n", + "shutil.rmtree(X.generator.output_dir)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Load all data back in\n", + "df = pd.read_csv(os.path.join(generator.output_dir, \"data.csv\"))\n", + "df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Read the VOCS object back in. This can be used for data analysis / restarting optimizations\n", + "with open(os.path.join(generator.output_dir, \"vocs.txt\")) as f:\n", + " vocs_from_file = VOCS.from_dict(json.load(f))\n", + "\n", + "# Show the objectives\n", + "vocs_from_file.objectives" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Load the populations and get just the last population\n", + "df = pd.read_csv(os.path.join(generator.output_dir, \"populations.csv\"))\n", + "last_pop = df[df[\"xopt_generation\"] == df[\"xopt_generation\"].max()]\n", + "last_pop.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Reload the optimizer from its last checkpoint\n", + "# NOTE: the vocs object is not retained with the checkpoint and should be added as in the following code\n", + "last_checkpoint = os.path.join(checkpoint_dir, os.listdir(checkpoint_dir)[-1])\n", + "with open(last_checkpoint) as f:\n", + " restored_generator = NSGA2Generator.from_dict({\"vocs\": prob_vocs, **json.load(f)})\n", + "print(f\"From file: {restored_generator}\")\n", + "\n", + "# Demonstrate using the generator after loading (starting optimization from its last saved point)\n", + "X = Xopt(generator=restored_generator, evaluator=ev, vocs=prob_vocs)\n", + "for _ in range(32):\n", + " X.step()\n", + "print(f\"Further optimization: {restored_generator}\")\n", + "\n", + "# Clean up the output\n", + "X.generator.close_log_file()\n", + "shutil.rmtree(X.generator.output_dir)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Clean up the original output\n", + "generator.close_log_file()\n", + "shutil.rmtree(output_dir)" + ] + } + ], + "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.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/examples/gp_model_creation/model_creation.ipynb b/docs/examples/gp_model_creation/model_creation.ipynb new file mode 100644 index 0000000..6a67f6c --- /dev/null +++ b/docs/examples/gp_model_creation/model_creation.ipynb @@ -0,0 +1,259 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Building GP Models from Scratch\n", + "Sometimes it is useful to build GP models outside the context of BO for data\n", + "visualization and sensitivity measurements, ie. learned hyperparameters. Here we\n", + "demonstrate how to build models from data outside of generators.\n", + "\n", + "For this we use the 3D rosenbrock function test function." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-07T21:01:37.888221500Z", + "start_time": "2025-01-07T21:01:33.895927300Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:57.465196Z", + "iopub.status.busy": "2024-09-13T15:56:57.464798Z", + "iopub.status.idle": "2024-09-13T15:56:59.020677Z", + "shell.execute_reply": "2024-09-13T15:56:59.020398Z" + } + }, + "outputs": [], + "source": [ + "# set values if testing\n", + "import os\n", + "\n", + "from xopt import Xopt, Evaluator\n", + "from xopt.generators import RandomGenerator\n", + "from xopt.resources.test_functions.rosenbrock import (\n", + " evaluate_rosenbrock,\n", + " make_rosenbrock_vocs,\n", + ")\n", + "\n", + "from xopt.generators.bayesian.visualize import visualize_model\n", + "from xopt.generators.bayesian.models.standard import StandardModelConstructor\n", + "\n", + "# Ignore all warnings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"ignore\")\n", + "\n", + "\n", + "SMOKE_TEST = os.environ.get(\"SMOKE_TEST\")\n", + "NUM_MC_SAMPLES = 1 if SMOKE_TEST else 128\n", + "NUM_RESTARTS = 1 if SMOKE_TEST else 20\n", + "\n", + "# make rosenbrock function vocs in 3D\n", + "vocs = make_rosenbrock_vocs(3)\n", + "\n", + "# collect some data using random sampling\n", + "evaluator = Evaluator(function=evaluate_rosenbrock)\n", + "generator = RandomGenerator(vocs=vocs)\n", + "X = Xopt(generator=generator, evaluator=evaluator, vocs=vocs)\n", + "X.random_evaluate(15)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Create GP model based on the data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-07T21:01:37.891750100Z", + "start_time": "2025-01-07T21:01:37.890225800Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:59.042358Z", + "iopub.status.busy": "2024-09-13T15:56:59.042170Z", + "iopub.status.idle": "2024-09-13T15:56:59.043905Z", + "shell.execute_reply": "2024-09-13T15:56:59.043681Z" + } + }, + "outputs": [], + "source": [ + "data = X.data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-07T21:01:38.729744700Z", + "start_time": "2025-01-07T21:01:37.892751300Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:59.045209Z", + "iopub.status.busy": "2024-09-13T15:56:59.045127Z", + "iopub.status.idle": "2024-09-13T15:56:59.301614Z", + "shell.execute_reply": "2024-09-13T15:56:59.301336Z" + } + }, + "outputs": [], + "source": [ + "model_constructor = StandardModelConstructor()\n", + "\n", + "# here we build a model from vocs\n", + "model = model_constructor.build_model_from_vocs(vocs=vocs, data=data)\n", + "\n", + "# here we build a model from info (more flexible)\n", + "model = model_constructor.build_model(\n", + " input_names=[\"x0\", \"x1\", \"x2\"], outcome_names=[\"y\"], data=data\n", + ")\n", + "help(model_constructor.build_model)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Examine GP model hyperparameters\n", + "Here we look at the GP hyperparameters for the objective function (the first model).\n", + "Note: the hyperparameters here are in raw_units (due to contraints on parameter\n", + "values, ie. lengthscales > 0)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-07T21:01:38.736745Z", + "start_time": "2025-01-07T21:01:38.731745300Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:59.303297Z", + "iopub.status.busy": "2024-09-13T15:56:59.303134Z", + "iopub.status.idle": "2024-09-13T15:56:59.306355Z", + "shell.execute_reply": "2024-09-13T15:56:59.306053Z" + } + }, + "outputs": [], + "source": [ + "objective_model = model.models[vocs.output_names.index(\"y\")]\n", + "\n", + "# print raw hyperparameter values\n", + "for name, val in objective_model.named_parameters():\n", + " print(name, val)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-07T21:06:12.904773200Z", + "start_time": "2025-01-07T21:06:12.899686500Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:59.307614Z", + "iopub.status.busy": "2024-09-13T15:56:59.307530Z", + "iopub.status.idle": "2024-09-13T15:56:59.310005Z", + "shell.execute_reply": "2024-09-13T15:56:59.309776Z" + } + }, + "outputs": [], + "source": [ + "# print real values - note that these are in normalized coordinates\n", + "print(\"prior mean:\", objective_model.mean_module.constant.data)\n", + "print(\"noise:\", objective_model.likelihood.noise_covar.noise.data)\n", + "print(\"lengthscales\", objective_model.covar_module.lengthscale.data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-07T21:02:08.637731400Z", + "start_time": "2025-01-07T21:02:08.632699900Z" + } + }, + "outputs": [], + "source": [ + "objective_model" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Visualize model predictions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:56:59.311380Z", + "iopub.status.busy": "2024-09-13T15:56:59.311290Z", + "iopub.status.idle": "2024-09-13T15:56:59.854969Z", + "shell.execute_reply": "2024-09-13T15:56:59.854557Z" + } + }, + "outputs": [], + "source": [ + "fig, ax = visualize_model(\n", + " model, vocs, data, variable_names=[\"x0\", \"x1\"], reference_point={\"x2\": 0.0}\n", + ")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/multi_objective_bayes_opt/mggpo.ipynb b/docs/examples/multi_objective_bayes_opt/mggpo.ipynb new file mode 100644 index 0000000..e5c10f0 --- /dev/null +++ b/docs/examples/multi_objective_bayes_opt/mggpo.ipynb @@ -0,0 +1,308 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "# Multi-objective Bayesian Optimization\n", + "\n", + "\n", + "TNK function\n", + "$n=2$ variables:\n", + "$x_i \\in [0, \\pi], i=1,2$\n", + "\n", + "Objectives:\n", + "- $f_i(x) = x_i$\n", + "\n", + "Constraints:\n", + "- $g_1(x) = -x_1^2 -x_2^2 + 1 + 0.1 \\cos\\left(16 \\arctan \\frac{x_1}{x_2}\\right) \\le 0$\n", + "- $g_2(x) = (x_1 - 1/2)^2 + (x_2-1/2)^2 \\le 0.5$" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-08-02T21:09:04.147494200Z", + "start_time": "2023-08-02T21:09:01.121464100Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:25.142407Z", + "iopub.status.busy": "2024-09-13T15:59:25.141916Z", + "iopub.status.idle": "2024-09-13T15:59:26.865350Z", + "shell.execute_reply": "2024-09-13T15:59:26.865058Z" + } + }, + "outputs": [], + "source": [ + "# set values if testing\n", + "import os\n", + "\n", + "from copy import deepcopy\n", + "\n", + "import pandas as pd\n", + "import numpy as np\n", + "import torch\n", + "\n", + "from xopt import Xopt, Evaluator\n", + "from xopt.generators.bayesian.mggpo import MGGPOGenerator\n", + "from xopt.resources.test_functions.tnk import evaluate_TNK, tnk_vocs\n", + "\n", + "from xopt.generators.bayesian.objectives import feasibility\n", + "\n", + "from matplotlib import pyplot as plt\n", + "\n", + "# Ignore all warnings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"ignore\")\n", + "\n", + "SMOKE_TEST = os.environ.get(\"SMOKE_TEST\")\n", + "N_MC_SAMPLES = 1 if SMOKE_TEST else 128\n", + "NUM_RESTARTS = 1 if SMOKE_TEST else 20\n", + "\n", + "evaluator = Evaluator(function=evaluate_TNK)\n", + "evaluator.max_workers = 10\n", + "\n", + "# test check options\n", + "vocs = deepcopy(tnk_vocs)\n", + "gen = MGGPOGenerator(vocs=vocs, reference_point={\"y1\": 1.5, \"y2\": 1.5})\n", + "gen.n_monte_carlo_samples = N_MC_SAMPLES\n", + "gen.numerical_optimizer.n_restarts = NUM_RESTARTS\n", + "X = Xopt(evaluator=evaluator, generator=gen, vocs=vocs)\n", + "X.evaluate_data(pd.DataFrame({\"x1\": [1.0, 0.75], \"x2\": [0.75, 1.0]}))\n", + "\n", + "X" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-08-02T21:09:13.737232800Z", + "start_time": "2023-08-02T21:09:04.149492900Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:26.886511Z", + "iopub.status.busy": "2024-09-13T15:59:26.886313Z", + "iopub.status.idle": "2024-09-13T15:59:30.099103Z", + "shell.execute_reply": "2024-09-13T15:59:30.098791Z" + } + }, + "outputs": [], + "source": [ + "for i in range(10):\n", + " print(i)\n", + " X.step()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-08-02T21:09:13.768374600Z", + "start_time": "2023-08-02T21:09:13.735233200Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:30.100858Z", + "iopub.status.busy": "2024-09-13T15:59:30.100700Z", + "iopub.status.idle": "2024-09-13T15:59:30.106910Z", + "shell.execute_reply": "2024-09-13T15:59:30.106694Z" + } + }, + "outputs": [], + "source": [ + "X.generator.data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## plot results\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-08-02T21:09:14.288220800Z", + "start_time": "2023-08-02T21:09:13.765373900Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:30.108409Z", + "iopub.status.busy": "2024-09-13T15:59:30.108317Z", + "iopub.status.idle": "2024-09-13T15:59:30.185242Z", + "shell.execute_reply": "2024-09-13T15:59:30.184988Z" + } + }, + "outputs": [], + "source": [ + "fig, ax = plt.subplots()\n", + "\n", + "theta = np.linspace(0, np.pi / 2)\n", + "r = np.sqrt(1 + 0.1 * np.cos(16 * theta))\n", + "x_1 = r * np.sin(theta)\n", + "x_2_lower = r * np.cos(theta)\n", + "x_2_upper = (0.5 - (x_1 - 0.5) ** 2) ** 0.5 + 0.5\n", + "\n", + "z = np.zeros_like(x_1)\n", + "\n", + "# ax2.plot(x_1, x_2_lower,'r')\n", + "ax.fill_between(x_1, z, x_2_lower, fc=\"white\")\n", + "circle = plt.Circle(\n", + " (0.5, 0.5), 0.5**0.5, color=\"r\", alpha=0.25, zorder=0, label=\"Valid Region\"\n", + ")\n", + "ax.add_patch(circle)\n", + "history = pd.concat(\n", + " [X.data, tnk_vocs.feasibility_data(X.data)], axis=1, ignore_index=False\n", + ")\n", + "\n", + "ax.plot(*history[[\"x1\", \"x2\"]][history[\"feasible\"]].to_numpy().T, \".C1\")\n", + "ax.plot(*history[[\"x1\", \"x2\"]][~history[\"feasible\"]].to_numpy().T, \".C2\")\n", + "\n", + "ax.set_xlim(0, 3.14)\n", + "ax.set_ylim(0, 3.14)\n", + "ax.set_xlabel(\"x1\")\n", + "ax.set_ylabel(\"x2\")\n", + "ax.set_aspect(\"equal\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "start_time": "2023-08-02T21:09:14.297221Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:30.186678Z", + "iopub.status.busy": "2024-09-13T15:59:30.186581Z", + "iopub.status.idle": "2024-09-13T15:59:31.128132Z", + "shell.execute_reply": "2024-09-13T15:59:31.127827Z" + }, + "is_executing": true + }, + "outputs": [], + "source": [ + "# plot model predictions\n", + "\n", + "data = X.data\n", + "\n", + "bounds = X.generator.vocs.bounds\n", + "model = X.generator.train_model(X.generator.data)\n", + "\n", + "# create mesh\n", + "n = 50\n", + "x = torch.linspace(*bounds.T[0], n)\n", + "y = torch.linspace(*bounds.T[1], n)\n", + "xx, yy = torch.meshgrid(x, y)\n", + "pts = torch.hstack([ele.reshape(-1, 1) for ele in (xx, yy)]).double()\n", + "\n", + "xx, yy = xx.numpy(), yy.numpy()\n", + "\n", + "outputs = X.generator.vocs.output_names\n", + "with torch.no_grad():\n", + " post = model.posterior(pts)\n", + "\n", + " for i in range(len(vocs.output_names)):\n", + " mean = post.mean[..., i]\n", + " fig, ax = plt.subplots()\n", + " ax.plot(*data[[\"x1\", \"x2\"]].to_numpy().T, \"+C1\")\n", + " c = ax.pcolor(\n", + " xx, yy, mean.squeeze().reshape(n, n), cmap=\"seismic\", vmin=-10.0, vmax=10.0\n", + " )\n", + " fig.colorbar(c)\n", + " ax.set_title(f\"Posterior mean: {outputs[i]}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:59:31.129712Z", + "iopub.status.busy": "2024-09-13T15:59:31.129588Z", + "iopub.status.idle": "2024-09-13T15:59:32.372484Z", + "shell.execute_reply": "2024-09-13T15:59:32.372224Z" + }, + "is_executing": true + }, + "outputs": [], + "source": [ + "# plot the acquisition function\n", + "bounds = X.generator.vocs.bounds\n", + "model = X.generator.model\n", + "\n", + "# create mesh\n", + "n = 25\n", + "x = torch.linspace(*bounds.T[0], n)\n", + "y = torch.linspace(*bounds.T[1], n)\n", + "xx, yy = torch.meshgrid(x, y)\n", + "pts = torch.hstack([ele.reshape(-1, 1) for ele in (xx, yy)]).double()\n", + "\n", + "xx, yy = xx.numpy(), yy.numpy()\n", + "\n", + "acq_func = X.generator.get_acquisition(model)\n", + "with torch.no_grad():\n", + " acq_pts = pts.unsqueeze(1)\n", + " acq = acq_func(acq_pts)\n", + "\n", + " fig, ax = plt.subplots()\n", + " c = ax.pcolor(xx, yy, acq.reshape(n, n), cmap=\"Blues\")\n", + " fig.colorbar(c)\n", + " ax.set_title(\"Acquisition function\")\n", + "\n", + " ax.plot(*history[[\"x1\", \"x2\"]][history[\"feasible\"]].to_numpy().T, \".C1\")\n", + " ax.plot(*history[[\"x1\", \"x2\"]][~history[\"feasible\"]].to_numpy().T, \".C2\")\n", + "\n", + " ax.plot(*history[[\"x1\", \"x2\"]].to_numpy()[-1].T, \"+\")\n", + "\n", + " feas = feasibility(pts.unsqueeze(1), model, tnk_vocs).flatten()\n", + "\n", + " fig2, ax2 = plt.subplots()\n", + " c = ax2.pcolor(xx, yy, feas.reshape(n, n))\n", + " fig2.colorbar(c)\n", + " ax2.set_title(\"Feasible Region\")\n", + "\n", + "candidate = pd.DataFrame(X.generator.generate(1), index=[0])\n", + "print(candidate[[\"x1\", \"x2\"]].to_numpy())\n", + "ax.plot(*candidate[[\"x1\", \"x2\"]].to_numpy()[0], \"o\")" + ] + } + ], + "metadata": { + "interpreter": { + "hash": "72034539424920dfb606fe3b820b3f27dca0cbf1c69938110810ec4641e275b1" + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/multi_objective_bayes_opt/mobo.ipynb b/docs/examples/multi_objective_bayes_opt/mobo.ipynb new file mode 100644 index 0000000..ba33f58 --- /dev/null +++ b/docs/examples/multi_objective_bayes_opt/mobo.ipynb @@ -0,0 +1,232 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "# Multi-objective Bayesian Optimization\n", + "\n", + "\n", + "TNK function\n", + "$n=2$ variables:\n", + "$x_i \\in [0, \\pi], i=1,2$\n", + "\n", + "Objectives:\n", + "- $f_i(x) = x_i$\n", + "\n", + "Constraints:\n", + "- $g_1(x) = -x_1^2 -x_2^2 + 1 + 0.1 \\cos\\left(16 \\arctan \\frac{x_1}{x_2}\\right) \\le 0$\n", + "- $g_2(x) = (x_1 - 1/2)^2 + (x_2-1/2)^2 \\le 0.5$" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:57:02.030381Z", + "iopub.status.busy": "2024-09-13T15:57:02.029788Z", + "iopub.status.idle": "2024-09-13T15:57:03.730252Z", + "shell.execute_reply": "2024-09-13T15:57:03.729920Z" + } + }, + "outputs": [], + "source": [ + "# set values if testing\n", + "import os\n", + "\n", + "import pandas as pd\n", + "import numpy as np\n", + "\n", + "from xopt import Xopt, Evaluator\n", + "from xopt.generators.bayesian import MOBOGenerator\n", + "from xopt.resources.test_functions.tnk import evaluate_TNK, tnk_vocs\n", + "\n", + "import matplotlib.pyplot as plt\n", + "\n", + "# Ignore all warnings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"ignore\")\n", + "\n", + "SMOKE_TEST = os.environ.get(\"SMOKE_TEST\")\n", + "N_MC_SAMPLES = 1 if SMOKE_TEST else 128\n", + "NUM_RESTARTS = 1 if SMOKE_TEST else 20\n", + "N_STEPS = 1 if SMOKE_TEST else 30\n", + "\n", + "evaluator = Evaluator(function=evaluate_TNK)\n", + "print(tnk_vocs.dict())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:57:03.751516Z", + "iopub.status.busy": "2024-09-13T15:57:03.751311Z", + "iopub.status.idle": "2024-09-13T15:57:29.768361Z", + "shell.execute_reply": "2024-09-13T15:57:29.768054Z" + } + }, + "outputs": [], + "source": [ + "generator = MOBOGenerator(vocs=tnk_vocs, reference_point={\"y1\": 1.5, \"y2\": 1.5})\n", + "generator.n_monte_carlo_samples = N_MC_SAMPLES\n", + "generator.numerical_optimizer.n_restarts = NUM_RESTARTS\n", + "\n", + "X = Xopt(generator=generator, evaluator=evaluator, vocs=tnk_vocs)\n", + "X.evaluate_data(pd.DataFrame({\"x1\": [1.0, 0.75], \"x2\": [0.75, 1.0]}))\n", + "\n", + "for i in range(N_STEPS):\n", + " print(i)\n", + " X.step()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:57:29.770136Z", + "iopub.status.busy": "2024-09-13T15:57:29.769968Z", + "iopub.status.idle": "2024-09-13T15:57:29.778804Z", + "shell.execute_reply": "2024-09-13T15:57:29.778491Z" + } + }, + "outputs": [], + "source": [ + "X.generator.data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## plot results\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:57:29.780374Z", + "iopub.status.busy": "2024-09-13T15:57:29.780251Z", + "iopub.status.idle": "2024-09-13T15:57:29.856479Z", + "shell.execute_reply": "2024-09-13T15:57:29.856193Z" + } + }, + "outputs": [], + "source": [ + "fig, ax = plt.subplots()\n", + "\n", + "theta = np.linspace(0, np.pi / 2)\n", + "r = np.sqrt(1 + 0.1 * np.cos(16 * theta))\n", + "x_1 = r * np.sin(theta)\n", + "x_2_lower = r * np.cos(theta)\n", + "x_2_upper = (0.5 - (x_1 - 0.5) ** 2) ** 0.5 + 0.5\n", + "\n", + "z = np.zeros_like(x_1)\n", + "\n", + "# ax2.plot(x_1, x_2_lower,'r')\n", + "ax.fill_between(x_1, z, x_2_lower, fc=\"white\")\n", + "circle = plt.Circle(\n", + " (0.5, 0.5), 0.5**0.5, color=\"r\", alpha=0.25, zorder=0, label=\"Valid Region\"\n", + ")\n", + "ax.add_patch(circle)\n", + "history = pd.concat(\n", + " [X.data, tnk_vocs.feasibility_data(X.data)], axis=1, ignore_index=False\n", + ")\n", + "\n", + "\n", + "ax.plot(*history[[\"x1\", \"x2\"]][history[\"feasible\"]].to_numpy().T, \".C1\")\n", + "ax.plot(*history[[\"x1\", \"x2\"]][~history[\"feasible\"]].to_numpy().T, \".C2\")\n", + "\n", + "ax.set_xlim(0, 3.14)\n", + "ax.set_ylim(0, 3.14)\n", + "ax.set_xlabel(\"x1\")\n", + "ax.set_ylabel(\"x2\")\n", + "ax.set_aspect(\"equal\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "### Plot path through input space" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:57:29.858123Z", + "iopub.status.busy": "2024-09-13T15:57:29.857992Z", + "iopub.status.idle": "2024-09-13T15:57:29.929182Z", + "shell.execute_reply": "2024-09-13T15:57:29.928898Z" + } + }, + "outputs": [], + "source": [ + "ax = history.plot(\"x1\", \"x2\")\n", + "ax.set_ylim(0, 3.14)\n", + "ax.set_xlim(0, 3.14)\n", + "ax.set_aspect(\"equal\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:57:29.930805Z", + "iopub.status.busy": "2024-09-13T15:57:29.930596Z", + "iopub.status.idle": "2024-09-13T15:57:37.626954Z", + "shell.execute_reply": "2024-09-13T15:57:37.626631Z" + } + }, + "outputs": [], + "source": [ + "## visualize model\n", + "X.generator.visualize_model()" + ] + } + ], + "metadata": { + "interpreter": { + "hash": "72034539424920dfb606fe3b820b3f27dca0cbf1c69938110810ec4641e275b1" + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/multi_objective_bayes_opt/mobo_from_yaml.ipynb b/docs/examples/multi_objective_bayes_opt/mobo_from_yaml.ipynb new file mode 100644 index 0000000..8d986eb --- /dev/null +++ b/docs/examples/multi_objective_bayes_opt/mobo_from_yaml.ipynb @@ -0,0 +1,243 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "# Multi-objective Bayesian Optimization\n", + "\n", + "\n", + "TNK function\n", + "$n=2$ variables:\n", + "$x_i \\in [0, \\pi], i=1,2$\n", + "\n", + "Objectives:\n", + "- $f_i(x) = x_i$\n", + "\n", + "Constraints:\n", + "- $g_1(x) = -x_1^2 -x_2^2 + 1 + 0.1 \\cos\\left(16 \\arctan \\frac{x_1}{x_2}\\right) \\le 0$\n", + "- $g_2(x) = (x_1 - 1/2)^2 + (x_2-1/2)^2 \\le 0.5$" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:58:42.418134Z", + "iopub.status.busy": "2024-09-13T15:58:42.417864Z", + "iopub.status.idle": "2024-09-13T15:58:44.142616Z", + "shell.execute_reply": "2024-09-13T15:58:44.142320Z" + } + }, + "outputs": [], + "source": [ + "import os\n", + "from xopt import Xopt\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "\n", + "# Ignore all warnings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"ignore\")\n", + "\n", + "SMOKE_TEST = os.environ.get(\"SMOKE_TEST\")\n", + "NUM_STEPS = 2 if SMOKE_TEST else 50\n", + "\n", + "YAML = \"\"\"\n", + "generator:\n", + " name: mobo\n", + " reference_point: {y1: 1.5, y2: 1.5}\n", + "\n", + "evaluator:\n", + " function: xopt.resources.test_functions.tnk.evaluate_TNK\n", + "\n", + "vocs:\n", + " variables:\n", + " x1: [0, 3.14159]\n", + " x2: [0, 3.14159]\n", + " objectives: {y1: MINIMIZE, y2: MINIMIZE}\n", + " constraints:\n", + " c1: [GREATER_THAN, 0]\n", + " c2: [LESS_THAN, 0.5]\n", + " constants: {a: dummy_constant}\n", + "\n", + "\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:58:44.144448Z", + "iopub.status.busy": "2024-09-13T15:58:44.144296Z", + "iopub.status.idle": "2024-09-13T15:59:19.006532Z", + "shell.execute_reply": "2024-09-13T15:59:19.006230Z" + } + }, + "outputs": [], + "source": [ + "X = Xopt.from_yaml(YAML)\n", + "\n", + "# for testing purposes only\n", + "if SMOKE_TEST:\n", + " X.generator.numerical_optimizer.n_restarts = 1\n", + " X.generator.n_monte_carlo_samples = 1\n", + "\n", + "X.random_evaluate(5)\n", + "for i in range(NUM_STEPS):\n", + " print(i)\n", + " X.step()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:59:19.008255Z", + "iopub.status.busy": "2024-09-13T15:59:19.008111Z", + "iopub.status.idle": "2024-09-13T15:59:19.018656Z", + "shell.execute_reply": "2024-09-13T15:59:19.018378Z" + } + }, + "outputs": [], + "source": [ + "X.generator.data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## plot results\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:59:19.020019Z", + "iopub.status.busy": "2024-09-13T15:59:19.019929Z", + "iopub.status.idle": "2024-09-13T15:59:19.094493Z", + "shell.execute_reply": "2024-09-13T15:59:19.094211Z" + } + }, + "outputs": [], + "source": [ + "fig, ax = plt.subplots()\n", + "\n", + "theta = np.linspace(0, np.pi / 2)\n", + "r = np.sqrt(1 + 0.1 * np.cos(16 * theta))\n", + "x_1 = r * np.sin(theta)\n", + "x_2_lower = r * np.cos(theta)\n", + "x_2_upper = (0.5 - (x_1 - 0.5) ** 2) ** 0.5 + 0.5\n", + "\n", + "z = np.zeros_like(x_1)\n", + "\n", + "# ax2.plot(x_1, x_2_lower,'r')\n", + "ax.fill_between(x_1, z, x_2_lower, fc=\"white\")\n", + "circle = plt.Circle(\n", + " (0.5, 0.5), 0.5**0.5, color=\"r\", alpha=0.25, zorder=0, label=\"Valid Region\"\n", + ")\n", + "ax.add_patch(circle)\n", + "history = pd.concat(\n", + " [X.data, X.vocs.feasibility_data(X.data)], axis=1, ignore_index=False\n", + ")\n", + "\n", + "\n", + "ax.plot(*history[[\"x1\", \"x2\"]][history[\"feasible\"]].to_numpy().T, \".C1\")\n", + "ax.plot(*history[[\"x1\", \"x2\"]][~history[\"feasible\"]].to_numpy().T, \".C2\")\n", + "\n", + "ax.set_xlim(0, 3.14)\n", + "ax.set_ylim(0, 3.14)\n", + "ax.set_xlabel(\"x1\")\n", + "ax.set_ylabel(\"x2\")\n", + "ax.set_aspect(\"equal\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "### Plot path through input space" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:59:19.096122Z", + "iopub.status.busy": "2024-09-13T15:59:19.096008Z", + "iopub.status.idle": "2024-09-13T15:59:19.165547Z", + "shell.execute_reply": "2024-09-13T15:59:19.165289Z" + } + }, + "outputs": [], + "source": [ + "ax = history.plot(\"x1\", \"x2\")\n", + "ax.set_ylim(0, 3.14)\n", + "ax.set_xlim(0, 3.14)\n", + "ax.set_aspect(\"equal\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:59:19.167032Z", + "iopub.status.busy": "2024-09-13T15:59:19.166920Z", + "iopub.status.idle": "2024-09-13T15:59:22.869490Z", + "shell.execute_reply": "2024-09-13T15:59:22.869219Z" + } + }, + "outputs": [], + "source": [ + "## visualize model\n", + "X.generator.visualize_model()" + ] + } + ], + "metadata": { + "interpreter": { + "hash": "72034539424920dfb606fe3b820b3f27dca0cbf1c69938110810ec4641e275b1" + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/multi_objective_bayes_opt/multi_fidelity_mobo.ipynb b/docs/examples/multi_objective_bayes_opt/multi_fidelity_mobo.ipynb new file mode 100644 index 0000000..43c564a --- /dev/null +++ b/docs/examples/multi_objective_bayes_opt/multi_fidelity_mobo.ipynb @@ -0,0 +1,430 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Multi-fidelity Multi-objective Bayesian Optimization\n", + "\n", + "Here we attempt to solve for the constrained Pareto front of the TNK multi-objective\n", + "optimization problem using Multi-Fidelity Multi-Objective Bayesian optimization. For\n", + "simplicity we assume that the objective and constraint functions at lower fidelities is\n", + "exactly equal to the functions at higher fidelities (this is obviously not a\n", + "requirement, although for the best results lower fidelity calculations should\n", + "correlate with higher fidelity ones). The algorithm should learn this relationship\n", + "and use information gathered at lower fidelities to gather samples to improve the\n", + "hypervolume of the Pareto front at the maximum fidelity.\n", + "\n", + "TNK function\n", + "$n=2$ variables:\n", + "$x_i \\in [0, \\pi], i=1,2$\n", + "\n", + "Objectives:\n", + "- $f_i(x) = x_i$\n", + "\n", + "Constraints:\n", + "- $g_1(x) = -x_1^2 -x_2^2 + 1 + 0.1 \\cos\\left(16 \\arctan \\frac{x_1}{x_2}\\right) \\le 0$\n", + "- $g_2(x) = (x_1 - 1/2)^2 + (x_2-1/2)^2 \\le 0.5$" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-20T14:37:08.616050600Z", + "start_time": "2023-09-20T14:37:04.101720300Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:57:40.581721Z", + "iopub.status.busy": "2024-09-13T15:57:40.581427Z", + "iopub.status.idle": "2024-09-13T15:57:42.766030Z", + "shell.execute_reply": "2024-09-13T15:57:42.765648Z" + } + }, + "outputs": [], + "source": [ + "# set values if testing\n", + "import os\n", + "from copy import deepcopy\n", + "\n", + "import pandas as pd\n", + "import numpy as np\n", + "import torch\n", + "\n", + "from xopt import Xopt, Evaluator\n", + "from xopt.generators.bayesian import MultiFidelityGenerator\n", + "from xopt.resources.test_functions.tnk import evaluate_TNK, tnk_vocs\n", + "\n", + "import matplotlib.pyplot as plt\n", + "\n", + "# Ignore all warnings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"ignore\")\n", + "\n", + "SMOKE_TEST = os.environ.get(\"SMOKE_TEST\")\n", + "N_MC_SAMPLES = 1 if SMOKE_TEST else 128\n", + "NUM_RESTARTS = 1 if SMOKE_TEST else 20\n", + "BUDGET = 0.02 if SMOKE_TEST else 10\n", + "\n", + "evaluator = Evaluator(function=evaluate_TNK)\n", + "print(tnk_vocs.dict())" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Set up the Multi-Fidelity Multi-objective optimization algorithm\n", + "Here we create the Multi-Fidelity generator object which can solve both single and\n", + "multi-objective optimization problems depending on the number of objectives in VOCS.\n", + "We specify a cost function as a function of fidelity parameter $s=[0,1]$ as $C(s) =\n", + "s^{3.5}$ as an example from a real life multi-fidelity simulation problem." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-20T14:37:08.660042Z", + "start_time": "2023-09-20T14:37:08.617041800Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:57:42.791139Z", + "iopub.status.busy": "2024-09-13T15:57:42.790899Z", + "iopub.status.idle": "2024-09-13T15:57:42.802241Z", + "shell.execute_reply": "2024-09-13T15:57:42.801931Z" + } + }, + "outputs": [], + "source": [ + "my_vocs = deepcopy(tnk_vocs)\n", + "my_vocs.constraints = {}\n", + "generator = MultiFidelityGenerator(vocs=my_vocs, reference_point={\"y1\": 1.5, \"y2\": 1.5})\n", + "\n", + "# set cost function according to approximate scaling of laser plasma accelerator\n", + "# problem, see https://journals.aps.org/prresearch/abstract/10.1103/PhysRevResearch.5.013063\n", + "generator.cost_function = lambda s: s**3.5\n", + "generator.numerical_optimizer.n_restarts = NUM_RESTARTS\n", + "generator.n_monte_carlo_samples = N_MC_SAMPLES\n", + "\n", + "X = Xopt(generator=generator, evaluator=evaluator, vocs=my_vocs)\n", + "\n", + "# evaluate at some explicit initial points\n", + "X.evaluate_data(pd.DataFrame({\"x1\": [1.0, 0.75], \"x2\": [0.75, 1.0], \"s\": [0.0, 0.1]}))\n", + "\n", + "X" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Run optimization routine\n", + "Instead of ending the optimization routine after an explict number of samples we end\n", + "optimization once a given optimization budget has been exceeded. WARNING: This will\n", + "slightly exceed the given budget" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-20T14:37:14.657062800Z", + "start_time": "2023-09-20T14:37:08.648042200Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:57:42.803987Z", + "iopub.status.busy": "2024-09-13T15:57:42.803801Z", + "iopub.status.idle": "2024-09-13T15:58:35.292838Z", + "shell.execute_reply": "2024-09-13T15:58:35.292503Z" + } + }, + "outputs": [], + "source": [ + "budget = BUDGET\n", + "while X.generator.calculate_total_cost() < budget:\n", + " X.step()\n", + " print(\n", + " f\"n_samples: {len(X.data)} \"\n", + " f\"budget used: {X.generator.calculate_total_cost():.4} \"\n", + " f\"hypervolume: {X.generator.calculate_hypervolume():.4}\"\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Show results" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-20T14:37:14.684015600Z", + "start_time": "2023-09-20T14:37:14.647063400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:58:35.294458Z", + "iopub.status.busy": "2024-09-13T15:58:35.294286Z", + "iopub.status.idle": "2024-09-13T15:58:35.304159Z", + "shell.execute_reply": "2024-09-13T15:58:35.303906Z" + } + }, + "outputs": [], + "source": [ + "X.data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Plot results\n", + "Here we plot the resulting observations in input space, colored by feasibility\n", + "(neglecting the fact that\n", + "these data points are at varying fidelities).\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-20T14:37:15.292777300Z", + "start_time": "2023-09-20T14:37:14.679967200Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:58:35.305628Z", + "iopub.status.busy": "2024-09-13T15:58:35.305543Z", + "iopub.status.idle": "2024-09-13T15:58:35.380005Z", + "shell.execute_reply": "2024-09-13T15:58:35.379735Z" + } + }, + "outputs": [], + "source": [ + "fig, ax = plt.subplots()\n", + "\n", + "theta = np.linspace(0, np.pi / 2)\n", + "r = np.sqrt(1 + 0.1 * np.cos(16 * theta))\n", + "x_1 = r * np.sin(theta)\n", + "x_2_lower = r * np.cos(theta)\n", + "x_2_upper = (0.5 - (x_1 - 0.5) ** 2) ** 0.5 + 0.5\n", + "\n", + "z = np.zeros_like(x_1)\n", + "\n", + "# ax2.plot(x_1, x_2_lower,'r')\n", + "ax.fill_between(x_1, z, x_2_lower, fc=\"white\")\n", + "circle = plt.Circle(\n", + " (0.5, 0.5), 0.5**0.5, color=\"r\", alpha=0.25, zorder=0, label=\"Valid Region\"\n", + ")\n", + "ax.add_patch(circle)\n", + "history = pd.concat(\n", + " [X.data, tnk_vocs.feasibility_data(X.data)], axis=1, ignore_index=False\n", + ")\n", + "\n", + "ax.plot(*history[[\"x1\", \"x2\"]][history[\"feasible\"]].to_numpy().T, \".C1\")\n", + "ax.plot(*history[[\"x1\", \"x2\"]][~history[\"feasible\"]].to_numpy().T, \".C2\")\n", + "\n", + "ax.set_xlim(0, 3.14)\n", + "ax.set_ylim(0, 3.14)\n", + "ax.set_xlabel(\"x1\")\n", + "ax.set_ylabel(\"x2\")\n", + "ax.set_aspect(\"equal\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "### Plot path through input space" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-20T14:37:15.607777300Z", + "start_time": "2023-09-20T14:37:15.288777300Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:58:35.381462Z", + "iopub.status.busy": "2024-09-13T15:58:35.381358Z", + "iopub.status.idle": "2024-09-13T15:58:35.609159Z", + "shell.execute_reply": "2024-09-13T15:58:35.608874Z" + } + }, + "outputs": [], + "source": [ + "ax = history.hist([\"x1\", \"x2\", \"s\"], bins=20)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-20T14:37:15.768777Z", + "start_time": "2023-09-20T14:37:15.606777300Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:58:35.610675Z", + "iopub.status.busy": "2024-09-13T15:58:35.610566Z", + "iopub.status.idle": "2024-09-13T15:58:35.700614Z", + "shell.execute_reply": "2024-09-13T15:58:35.700341Z" + } + }, + "outputs": [], + "source": [ + "history.plot(y=[\"x1\", \"x2\", \"s\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Plot the acqusisition function\n", + "Here we plot the acquisition function at a small set of fidelities $[0, 0.5, 1.0]$." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-20T14:37:45.334189800Z", + "start_time": "2023-09-20T14:37:41.203201700Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:58:35.702504Z", + "iopub.status.busy": "2024-09-13T15:58:35.702420Z", + "iopub.status.idle": "2024-09-13T15:58:40.147848Z", + "shell.execute_reply": "2024-09-13T15:58:40.147599Z" + } + }, + "outputs": [], + "source": [ + "# plot the acquisition function\n", + "\n", + "bounds = X.generator.vocs.bounds\n", + "model = X.generator.model\n", + "\n", + "# create mesh over non-fidelity parameters\n", + "n = 50\n", + "x = torch.linspace(*bounds.T[1], n)\n", + "y = torch.linspace(*bounds.T[2], n)\n", + "xx, yy = torch.meshgrid(x, y)\n", + "\n", + "# plot function(s) at a single fidelity parameter\n", + "fidelities = [0.0, 0.5, 1.0]\n", + "for fidelity in fidelities:\n", + " pts = torch.hstack([ele.reshape(-1, 1) for ele in (xx, yy)]).double()\n", + " pts = torch.cat((torch.ones(pts.shape[0], 1) * fidelity, pts), dim=-1)\n", + "\n", + " acq_func = X.generator.get_acquisition(model)\n", + " with torch.no_grad():\n", + " acq_pts = pts.unsqueeze(1)\n", + " acq = acq_func(acq_pts)\n", + "\n", + " fig, ax = plt.subplots()\n", + "\n", + " xxn, yyn = xx.numpy(), yy.numpy()\n", + "\n", + " c = ax.pcolor(xxn, yyn, acq.reshape(n, n), cmap=\"Blues\")\n", + " fig.colorbar(c)\n", + " ax.set_title(f\"Acquisition function - s: {fidelity}\")\n", + "\n", + " ax.plot(*history[[\"x1\", \"x2\"]][history[\"feasible\"]].to_numpy().T, \".C1\")\n", + " ax.plot(*history[[\"x1\", \"x2\"]][~history[\"feasible\"]].to_numpy().T, \".C2\")\n", + "\n", + " ax.plot(*history[[\"x1\", \"x2\"]].to_numpy()[-1].T, \"+\")\n", + "\n", + "\n", + "candidate = pd.DataFrame(X.generator.generate(1), index=[0])\n", + "print(candidate[[\"x1\", \"x2\"]].to_numpy())\n", + "ax.plot(*candidate[[\"x1\", \"x2\"]].to_numpy()[0], \"o\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "start_time": "2023-09-20T14:37:17.294215100Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:58:40.149385Z", + "iopub.status.busy": "2024-09-13T15:58:40.149271Z", + "iopub.status.idle": "2024-09-13T15:58:40.154183Z", + "shell.execute_reply": "2024-09-13T15:58:40.153956Z" + } + }, + "outputs": [], + "source": [ + "# examine lengthscale of the first objective\n", + "list(model.models[0].named_parameters())" + ] + } + ], + "metadata": { + "interpreter": { + "hash": "72034539424920dfb606fe3b820b3f27dca0cbf1c69938110810ec4641e275b1" + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/scipy/latin_hypercube.ipynb b/docs/examples/scipy/latin_hypercube.ipynb new file mode 100644 index 0000000..684161e --- /dev/null +++ b/docs/examples/scipy/latin_hypercube.ipynb @@ -0,0 +1,191 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Latin Hypercube Generator Example\n", + "This notebook demonstrates basic use of the latin hypercube generator. This generator is a wrapper for the scipy latin hypercube method and allows users to efficiently sample functions (eg for surrogate models). Because the distribution of points depends on the number of sample requested, internally the xopt routine stores a batch of samples. The batch size is specified as an argument to the object's constructor. All other parameters to the scipy function are broken out this way and for a detailed explanation of what they do, the scipy documentation should be consulted." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:59:48.342031Z", + "iopub.status.busy": "2024-09-13T15:59:48.341760Z", + "iopub.status.idle": "2024-09-13T15:59:49.822807Z", + "shell.execute_reply": "2024-09-13T15:59:49.822468Z" + } + }, + "outputs": [], + "source": [ + "from copy import deepcopy\n", + "from xopt import Xopt, Evaluator\n", + "from xopt.generators.scipy.latin_hypercube import LatinHypercubeGenerator\n", + "from xopt.resources.test_functions.tnk import evaluate_TNK, tnk_vocs\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:59:49.824577Z", + "iopub.status.busy": "2024-09-13T15:59:49.824438Z", + "iopub.status.idle": "2024-09-13T15:59:49.831947Z", + "shell.execute_reply": "2024-09-13T15:59:49.831721Z" + } + }, + "outputs": [], + "source": [ + "# Create the test problem\n", + "vocs = deepcopy(tnk_vocs)\n", + "vocs.objectives = {}\n", + "vocs.observables = [\"y1\"]\n", + "evaluator = Evaluator(function=evaluate_TNK)\n", + "\n", + "# Create the generator and xopt object. Note: the samples are generated in\n", + "# batches and the batch size determines the arrangement of points to cover\n", + "# the bounded region of the variables.\n", + "generator = LatinHypercubeGenerator(vocs=vocs, batch_size=1024)\n", + "X = Xopt(generator=generator, evaluator=evaluator, vocs=vocs)\n", + "X" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:59:49.853822Z", + "iopub.status.busy": "2024-09-13T15:59:49.853693Z", + "iopub.status.idle": "2024-09-13T15:59:52.771762Z", + "shell.execute_reply": "2024-09-13T15:59:52.771514Z" + } + }, + "outputs": [], + "source": [ + "# Sample the function a number of times using latin hypercube points\n", + "for _ in range(1024):\n", + " X.step()\n", + "X.data.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:59:52.773315Z", + "iopub.status.busy": "2024-09-13T15:59:52.773198Z", + "iopub.status.idle": "2024-09-13T15:59:52.954069Z", + "shell.execute_reply": "2024-09-13T15:59:52.953771Z" + } + }, + "outputs": [], + "source": [ + "# Plot the data\n", + "plt.scatter(X.data[\"x1\"], X.data[\"x2\"], c=X.data[\"y1\"])\n", + "plt.xlabel(\"x1\")\n", + "plt.ylabel(\"x2\")\n", + "plt.colorbar(label=\"y1\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Distribution of Latin Hypercube points\n", + "Points in latin hypercube sampling are arranged in a grid such that none occupy the same row or column. That is, in chess it is similar to having n rooks on the board which cannot take each other. We can demonstrate this in the sampler by turning off the \"scramble\" feature (turned on by default)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:59:52.956942Z", + "iopub.status.busy": "2024-09-13T15:59:52.956849Z", + "iopub.status.idle": "2024-09-13T15:59:53.101131Z", + "shell.execute_reply": "2024-09-13T15:59:53.100871Z" + } + }, + "outputs": [], + "source": [ + "n = 16\n", + "generator = LatinHypercubeGenerator(vocs=vocs, batch_size=n, scramble=False, seed=0)\n", + "X = Xopt(generator=generator, evaluator=evaluator, vocs=vocs)\n", + "for _ in range(n):\n", + " X.step()\n", + "\n", + "plt.scatter(X.data[\"x1\"], X.data[\"x2\"], c=X.data[\"y1\"])\n", + "plt.xlabel(\"x1\")\n", + "plt.ylabel(\"x2\")\n", + "plt.colorbar(label=\"y1\")\n", + "\n", + "plt.hlines(np.linspace(0, 3.1416, n + 1), 0, 3.1416, color=\"k\")\n", + "plt.vlines(np.linspace(0, 3.1416, n + 1), 0, 3.1416, color=\"k\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The scramble feature will randomize the location of the points within each square while still maintaining the latin hypercube style cells." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:59:53.102570Z", + "iopub.status.busy": "2024-09-13T15:59:53.102481Z", + "iopub.status.idle": "2024-09-13T15:59:53.242827Z", + "shell.execute_reply": "2024-09-13T15:59:53.242558Z" + } + }, + "outputs": [], + "source": [ + "n = 16\n", + "generator = LatinHypercubeGenerator(vocs=vocs, batch_size=n, scramble=True, seed=0)\n", + "X = Xopt(generator=generator, evaluator=evaluator, vocs=vocs)\n", + "for _ in range(n):\n", + " X.step()\n", + "\n", + "plt.scatter(X.data[\"x1\"], X.data[\"x2\"], c=X.data[\"y1\"])\n", + "plt.xlabel(\"x1\")\n", + "plt.ylabel(\"x2\")\n", + "plt.colorbar(label=\"y1\")\n", + "\n", + "plt.hlines(np.linspace(0, 3.1416, n + 1), 0, 3.1416, color=\"k\")\n", + "plt.vlines(np.linspace(0, 3.1416, n + 1), 0, 3.1416, color=\"k\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/sequential/extremum_seeking.ipynb b/docs/examples/sequential/extremum_seeking.ipynb new file mode 100644 index 0000000..d541de3 --- /dev/null +++ b/docs/examples/sequential/extremum_seeking.ipynb @@ -0,0 +1,952 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "6f4b8676-940b-4a69-b495-df62bbc6e2fa", + "metadata": {}, + "source": [ + "## Extremum Seeking Optimization\n", + "\n", + "In this example we demonstrate extremum seeking optimization. The optimum of the test evaluate function would drift around a center point and we would be trying to follow the trend by applying extremum seeking technique." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "78534aeb-5ab2-4a3c-964c-4e080c7e953f", + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T20:53:47.373776100Z", + "start_time": "2023-09-06T20:53:44.301943600Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:34.524501Z", + "iopub.status.busy": "2024-09-13T15:59:34.524221Z", + "iopub.status.idle": "2024-09-13T15:59:35.714970Z", + "shell.execute_reply": "2024-09-13T15:59:35.714690Z" + } + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "from xopt.generators.sequential.extremumseeking import ExtremumSeekingGenerator\n", + "from xopt.vocs import VOCS\n", + "from xopt.evaluator import Evaluator\n", + "from xopt import Xopt\n", + "from tqdm.auto import tqdm\n", + "import os\n", + "\n", + "import pandas as pd\n", + "\n", + "import matplotlib.pyplot as plt\n", + "\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"ignore\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7fb27b941602401d91542211134fc71a", + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T20:53:44.299944200Z", + "start_time": "2023-09-06T20:53:44.286945900Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:35.716692Z", + "iopub.status.busy": "2024-09-13T15:59:35.716547Z", + "iopub.status.idle": "2024-09-13T15:59:35.718401Z", + "shell.execute_reply": "2024-09-13T15:59:35.718133Z" + } + }, + "outputs": [], + "source": [ + "# set values if testing\n", + "SMOKE_TEST = os.environ.get(\"SMOKE_TEST\")\n", + "NUM_STEPS = 10 if SMOKE_TEST else 1000" + ] + }, + { + "cell_type": "markdown", + "id": "fe8297c8-90e2-4e40-ab0b-63a9fe655aed", + "metadata": {}, + "source": [ + "### Extremum seeking test problem\n", + "\n", + "This test problem is a 10-D quadratic function, with its optimum drifting around the initial position. We also add some noise to make the problem more realistic." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35171e8a-ea8f-4cbb-b1a0-d01213e0bbcc", + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T20:53:47.397804100Z", + "start_time": "2023-09-06T20:53:47.376774600Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:35.719793Z", + "iopub.status.busy": "2024-09-13T15:59:35.719685Z", + "iopub.status.idle": "2024-09-13T15:59:35.722556Z", + "shell.execute_reply": "2024-09-13T15:59:35.722342Z" + } + }, + "outputs": [], + "source": [ + "np.random.seed(42) # set deterministic run\n", + "\n", + "nES = 10\n", + "\n", + "# This global dict is used as a counter to emulate drifting\n", + "states = {\"count\": 0}\n", + "\n", + "# This is the unknown optimal point\n", + "p_opt = 1.5 * (2 * np.random.rand(nES) - 1)\n", + "\n", + "# Various frequencies for unknown points\n", + "w_opt = 0.25 + 2 * np.random.rand(nES)\n", + "\n", + "\n", + "def f_ES_minimize(input_dict):\n", + " p = []\n", + " for i in range(10):\n", + " p.append(input_dict[f\"p{i}\"])\n", + " p = np.array(p)\n", + "\n", + " # Vary the optimal point with time\n", + " p_opt_i = np.zeros(nES)\n", + " i = states[\"count\"]\n", + "\n", + " outcome_dict = {}\n", + " for n in np.arange(nES):\n", + " p_opt_i[n] = p_opt[n] * (1 + np.sin(2 * np.pi * w_opt[n] * i / 2000))\n", + " # This simple cost will be distance from the optimal point\n", + " f_val = np.sum((p - p_opt_i) ** 2) + 0.1 * np.random.randn()\n", + "\n", + " states[\"count\"] += 1\n", + " outcome_dict = {\"f\": f_val, \"p_opt\": pd.Series(p_opt_i)}\n", + "\n", + " return outcome_dict" + ] + }, + { + "cell_type": "markdown", + "id": "9638b784-fa2f-4a94-900c-c66c8b0ea58b", + "metadata": {}, + "source": [ + "### Run ES on the test problem (YAML method)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8fac03d4-64f7-4773-973c-850b977a888e", + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T20:53:47.422816800Z", + "start_time": "2023-09-06T20:53:47.393777300Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:35.724072Z", + "iopub.status.busy": "2024-09-13T15:59:35.723965Z", + "iopub.status.idle": "2024-09-13T15:59:35.732244Z", + "shell.execute_reply": "2024-09-13T15:59:35.732011Z" + } + }, + "outputs": [], + "source": [ + "YAML = \"\"\"\n", + "max_evaluations: 5000\n", + "generator:\n", + " name: extremum_seeking\n", + " k: 2.0\n", + " oscillation_size: 0.1\n", + " decay_rate: 1.0\n", + "evaluator:\n", + " function: __main__.f_ES_minimize\n", + "vocs:\n", + " variables:\n", + " p0: [-2, 2]\n", + " p1: [-2, 2]\n", + " p2: [-2, 2]\n", + " p3: [-2, 2]\n", + " p4: [-2, 2]\n", + " p5: [-2, 2]\n", + " p6: [-2, 2]\n", + " p7: [-2, 2]\n", + " p8: [-2, 2]\n", + " p9: [-2, 2]\n", + " objectives:\n", + " f: MINIMIZE\n", + "\"\"\"\n", + "\n", + "X = Xopt.from_yaml(YAML)\n", + "X.max_evaluations = NUM_STEPS\n", + "\n", + "X" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e16ab267-8b5e-4616-b552-2b0410c5b75f", + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T20:53:47.477800200Z", + "start_time": "2023-09-06T20:53:47.425774500Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:35.733578Z", + "iopub.status.busy": "2024-09-13T15:59:35.733491Z", + "iopub.status.idle": "2024-09-13T15:59:35.739902Z", + "shell.execute_reply": "2024-09-13T15:59:35.739607Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "# Reset global counter to guarantee deterministic optimization\n", + "states[\"count\"] = 0\n", + "\n", + "X.random_evaluate(1)\n", + "X.step()" + ] + }, + { + "cell_type": "markdown", + "id": "54acb14d-2b25-4f2e-a3e9-fecb61af552f", + "metadata": {}, + "source": [ + "Now you can go directly to the [Visualization](#Visualization) section and check out the results." + ] + }, + { + "cell_type": "markdown", + "id": "4d688915-bdca-4d0f-baaa-5c50cfd80d79", + "metadata": {}, + "source": [ + "### Run ES on the test problem (API method)" + ] + }, + { + "cell_type": "markdown", + "id": "8f0a6f75-7f92-47ee-828d-ad7435932cf2", + "metadata": {}, + "source": [ + "#### VOCS\n", + "\n", + "We'll set the bounds for all the variables pi to [-2, 2]." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bd028b25-f395-43ed-8473-2dc950ff72c3", + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T20:53:47.484799800Z", + "start_time": "2023-09-06T20:53:47.451773900Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:35.741812Z", + "iopub.status.busy": "2024-09-13T15:59:35.741688Z", + "iopub.status.idle": "2024-09-13T15:59:35.743467Z", + "shell.execute_reply": "2024-09-13T15:59:35.743235Z" + } + }, + "outputs": [], + "source": [ + "variables = {}\n", + "for i in range(nES):\n", + " variables[f\"p{i}\"] = [-2, 2]\n", + "\n", + "vocs = VOCS(\n", + " variables=variables,\n", + " objectives={\"f\": \"MINIMIZE\"},\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0af5b8c7-4b3a-4a62-b92b-539899d11109", + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T20:53:47.484799800Z", + "start_time": "2023-09-06T20:53:47.477800200Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:35.744764Z", + "iopub.status.busy": "2024-09-13T15:59:35.744679Z", + "iopub.status.idle": "2024-09-13T15:59:35.746792Z", + "shell.execute_reply": "2024-09-13T15:59:35.746571Z" + } + }, + "outputs": [], + "source": [ + "vocs" + ] + }, + { + "cell_type": "markdown", + "id": "5354659c-cc30-4d70-8659-0fefd43ef0f0", + "metadata": {}, + "source": [ + "#### Evaluator" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "efafda0b-7282-4027-a1f3-f96454c114bd", + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T20:53:47.495775300Z", + "start_time": "2023-09-06T20:53:47.482775600Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:35.748148Z", + "iopub.status.busy": "2024-09-13T15:59:35.748053Z", + "iopub.status.idle": "2024-09-13T15:59:35.750066Z", + "shell.execute_reply": "2024-09-13T15:59:35.749852Z" + } + }, + "outputs": [], + "source": [ + "evaluator = Evaluator(function=f_ES_minimize)" + ] + }, + { + "cell_type": "markdown", + "id": "d6c58dcc-0b45-4408-a428-1681098a6a4a", + "metadata": {}, + "source": [ + "#### Generator" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "445daa23-5f97-4a82-bd59-57d9cbae6ac1", + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T20:53:47.537805600Z", + "start_time": "2023-09-06T20:53:47.496774Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:35.751404Z", + "iopub.status.busy": "2024-09-13T15:59:35.751303Z", + "iopub.status.idle": "2024-09-13T15:59:35.753159Z", + "shell.execute_reply": "2024-09-13T15:59:35.752943Z" + } + }, + "outputs": [], + "source": [ + "generator = ExtremumSeekingGenerator(vocs=vocs)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2e679d3f", + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T20:53:47.546774400Z", + "start_time": "2023-09-06T20:53:47.511775100Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:35.754526Z", + "iopub.status.busy": "2024-09-13T15:59:35.754424Z", + "iopub.status.idle": "2024-09-13T15:59:35.756451Z", + "shell.execute_reply": "2024-09-13T15:59:35.756213Z" + } + }, + "outputs": [], + "source": [ + "generator.dict()" + ] + }, + { + "cell_type": "markdown", + "id": "f5d375ac-a629-4d5e-8af9-becd47475bda", + "metadata": {}, + "source": [ + "Note that ES has 3 hyper-parameters: `k`, `oscillation_size`, and `decay_rate`.\n", + "\n", + "- `k`: ES feedback gain (set `k < 0` for maximization instead of minimization)\n", + "- `oscillation_size`: ES dithering size\n", + "- `decay_rate`: This value is optional, it causes the oscillation sizes to naturally decay. If you want the parameters to persistently oscillate without decay, set `decay_rate = 1.0`" + ] + }, + { + "cell_type": "markdown", + "id": "f4981983-1e64-43a1-ac90-f88546f9650a", + "metadata": {}, + "source": [ + "#### Run the optimization" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10c77281-f49c-4cf7-bb2d-8a278cf00e23", + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T20:53:47.546774400Z", + "start_time": "2023-09-06T20:53:47.526775900Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:35.757866Z", + "iopub.status.busy": "2024-09-13T15:59:35.757761Z", + "iopub.status.idle": "2024-09-13T15:59:35.759395Z", + "shell.execute_reply": "2024-09-13T15:59:35.759184Z" + } + }, + "outputs": [], + "source": [ + "X = Xopt(vocs=vocs, evaluator=evaluator, generator=generator)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4f6ff00b-9abe-4bd1-9dac-7f5cb9293d0f", + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T20:53:47.560774400Z", + "start_time": "2023-09-06T20:53:47.541776400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:35.760702Z", + "iopub.status.busy": "2024-09-13T15:59:35.760605Z", + "iopub.status.idle": "2024-09-13T15:59:35.762182Z", + "shell.execute_reply": "2024-09-13T15:59:35.761972Z" + } + }, + "outputs": [], + "source": [ + "X.max_evaluations = NUM_STEPS" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d34576f0-8ce4-49a6-b360-a9155d777ae7", + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T20:54:42.396162600Z", + "start_time": "2023-09-06T20:53:47.558774900Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:35.763499Z", + "iopub.status.busy": "2024-09-13T15:59:35.763418Z", + "iopub.status.idle": "2024-09-13T15:59:37.381345Z", + "shell.execute_reply": "2024-09-13T15:59:37.381023Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "# Reset global counter to guarantee deterministic optimization\n", + "states[\"count\"] = 0\n", + "\n", + "X.random_evaluate(1)\n", + "for i in tqdm(range(NUM_STEPS - 1)):\n", + " X.step()" + ] + }, + { + "cell_type": "markdown", + "id": "9808d9af-a815-4995-911a-43bd9eb105da", + "metadata": {}, + "source": [ + "### Visualization" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "56859433-7310-4c27-be46-ad8ccad2ece8", + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T20:54:43.238169800Z", + "start_time": "2023-09-06T20:54:42.760171100Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:37.391614Z", + "iopub.status.busy": "2024-09-13T15:59:37.391496Z", + "iopub.status.idle": "2024-09-13T15:59:37.639540Z", + "shell.execute_reply": "2024-09-13T15:59:37.639274Z" + } + }, + "outputs": [], + "source": [ + "# Plot all results\n", + "plt.figure(1, figsize=(8, 10))\n", + "\n", + "plt.subplot(2, 1, 1)\n", + "plt.plot(X.data[\"f\"])\n", + "plt.ylabel(\"ES cost\")\n", + "plt.xticks([])\n", + "\n", + "\n", + "plt.subplot(2, 1, 2)\n", + "plt.plot(X.data[[f\"p{i}\" for i in range(10)]], alpha=0.25)\n", + "_p_opt = np.vstack(X.data[\"p_opt\"].values).astype(\n", + " float\n", + ") # do not use p_opt as var name!\n", + "plt.plot(_p_opt, \"k--\")\n", + "plt.plot(2 + np.zeros(NUM_STEPS), \"r\")\n", + "plt.plot(-2 + np.zeros(NUM_STEPS), \"r\")\n", + "plt.legend(frameon=False)\n", + "plt.ylabel(\"ES parameter\")\n", + "plt.xlabel(\"ES step\")\n", + "\n", + "plt.tight_layout()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5840b360-a66f-486e-9a4c-44307e67737b", + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T20:54:44.514233700Z", + "start_time": "2023-09-06T20:54:43.238169800Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:37.642318Z", + "iopub.status.busy": "2024-09-13T15:59:37.642208Z", + "iopub.status.idle": "2024-09-13T15:59:38.208674Z", + "shell.execute_reply": "2024-09-13T15:59:38.208399Z" + } + }, + "outputs": [], + "source": [ + "# Plot Individual Parameter Trajectories\n", + "plt.figure(2, figsize=(15, 8))\n", + "\n", + "for n in np.arange(nES):\n", + " plt.subplot(2, 5, n + 1)\n", + " plt.plot(X.data[f\"p{n}\"], label=f\"$p^{{ES}}_{n + 1}$\")\n", + " plt.plot(_p_opt[:, n], \"k--\", label=f\"$p^*_{n + 1}$\")\n", + " plt.plot(2 + np.zeros(NUM_STEPS), \"r--\")\n", + " plt.plot(-2 + np.zeros(NUM_STEPS), \"r--\")\n", + " plt.ylim([-3, 5])\n", + " plt.legend(frameon=False, loc=1)\n", + " if n == 0:\n", + " plt.ylabel(\"parameters\")\n", + " elif n == 5:\n", + " plt.ylabel(\"parameters\")\n", + " else:\n", + " plt.yticks([])\n", + " if n > 4:\n", + " plt.xlabel(\"ES step\")\n", + " else:\n", + " plt.xticks([])\n", + "\n", + "plt.tight_layout()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a5472d46", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "xopt-dev", + "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.11.0" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "state": { + "07e1db66e84a4190a4d349ec736dac8c": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "098870d535c146a586a2418e0ee1763c": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "StyleView", + "background": null, + "description_width": "", + "font_size": null, + "text_color": null + } + }, + "2131e8f12c5d4d38bb8f529ff2215785": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_allow_html": false, + "layout": "IPY_MODEL_8630e86c087d48bfbe30701d5c2182c1", + "max": 1000, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_a58d17d6a87b4ed4bda196c34d38ea7d", + "tabbable": null, + "tooltip": null, + "value": 1000 + } + }, + "538dc218e581495c881c62505a556378": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "5499f69bb496479a8d997e031a7b0bd2": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_e1fdb3bb19ba4c20a832bc4001793a7e", + "IPY_MODEL_2131e8f12c5d4d38bb8f529ff2215785", + "IPY_MODEL_7bff28483fb34267894b86e720997886" + ], + "layout": "IPY_MODEL_7aaeb02e4bba482191c038e0409eddf9", + "tabbable": null, + "tooltip": null + } + }, + "7aaeb02e4bba482191c038e0409eddf9": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "7bff28483fb34267894b86e720997886": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "HTMLView", + "description": "", + "description_allow_html": false, + "layout": "IPY_MODEL_07e1db66e84a4190a4d349ec736dac8c", + "placeholder": "​", + "style": "IPY_MODEL_e586742ec9ea4692b88b5af78219a35c", + "tabbable": null, + "tooltip": null, + "value": " 1000/1000 [00:01<00:00, 626.38it/s]" + } + }, + "8630e86c087d48bfbe30701d5c2182c1": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "a58d17d6a87b4ed4bda196c34d38ea7d": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "e1fdb3bb19ba4c20a832bc4001793a7e": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "HTMLView", + "description": "", + "description_allow_html": false, + "layout": "IPY_MODEL_538dc218e581495c881c62505a556378", + "placeholder": "​", + "style": "IPY_MODEL_098870d535c146a586a2418e0ee1763c", + "tabbable": null, + "tooltip": null, + "value": "100%" + } + }, + "e586742ec9ea4692b88b5af78219a35c": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "StyleView", + "background": null, + "description_width": "", + "font_size": null, + "text_color": null + } + } + }, + "version_major": 2, + "version_minor": 0 + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/examples/sequential/neldermead.ipynb b/docs/examples/sequential/neldermead.ipynb new file mode 100644 index 0000000..6b899f6 --- /dev/null +++ b/docs/examples/sequential/neldermead.ipynb @@ -0,0 +1,827 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Nelder-Mead Generator adapted from SciPy\n", + "\n", + "Most of the algorithms in scipy.optimize are self-contained functions that operate on the user-provided `func`. Xopt has adapted the Nelder-Mead directly from scipy.optimize to be in a generator form. This allows for the manual stepping through the algorithm.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-30T17:44:49.812021200Z", + "start_time": "2023-10-30T17:44:49.674945300Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:55.290406Z", + "iopub.status.busy": "2024-09-13T15:59:55.290130Z", + "iopub.status.idle": "2024-09-13T15:59:56.629085Z", + "shell.execute_reply": "2024-09-13T15:59:56.628652Z" + } + }, + "outputs": [], + "source": [ + "from xopt.generators.sequential.neldermead import NelderMeadGenerator\n", + "from xopt import Evaluator, VOCS\n", + "from xopt.resources.test_functions.rosenbrock import rosenbrock\n", + "\n", + "import pandas as pd\n", + "\n", + "from xopt import Xopt\n", + "import numpy as np\n", + "\n", + "from scipy.optimize import fmin\n", + "\n", + "# from xopt import output_notebook\n", + "# output_notebook()\n", + "\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Nelder-Mead optimization of the Rosenbrock function with Xopt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-30T17:44:49.892121Z", + "start_time": "2023-10-30T17:44:49.680946600Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:56.630921Z", + "iopub.status.busy": "2024-09-13T15:59:56.630754Z", + "iopub.status.idle": "2024-09-13T15:59:56.770318Z", + "shell.execute_reply": "2024-09-13T15:59:56.769954Z" + } + }, + "outputs": [], + "source": [ + "YAML = \"\"\"\n", + "max_evaluations: 500\n", + "generator:\n", + " name: neldermead\n", + " adaptive: true\n", + "evaluator:\n", + " function: xopt.resources.test_functions.rosenbrock.evaluate_rosenbrock\n", + "vocs:\n", + " variables:\n", + " x0: [-5, 5]\n", + " x1: [-5, 5]\n", + " objectives: {y: MINIMIZE}\n", + "\"\"\"\n", + "X = Xopt.from_yaml(YAML)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-30T17:44:49.974002700Z", + "start_time": "2023-10-30T17:44:49.689493800Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:56.772033Z", + "iopub.status.busy": "2024-09-13T15:59:56.771899Z", + "iopub.status.idle": "2024-09-13T15:59:56.773651Z", + "shell.execute_reply": "2024-09-13T15:59:56.773421Z" + } + }, + "outputs": [], + "source": [ + "XMIN = [1, 1] # True minimum" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-30T17:44:50.195902300Z", + "start_time": "2023-10-30T17:44:49.692491400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:56.774890Z", + "iopub.status.busy": "2024-09-13T15:59:56.774814Z", + "iopub.status.idle": "2024-09-13T15:59:56.900724Z", + "shell.execute_reply": "2024-09-13T15:59:56.900477Z" + } + }, + "outputs": [], + "source": [ + "X.random_evaluate(2)\n", + "X.run()\n", + "X.data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-30T17:44:50.365947900Z", + "start_time": "2023-10-30T17:44:50.143890300Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:56.921439Z", + "iopub.status.busy": "2024-09-13T15:59:56.921315Z", + "iopub.status.idle": "2024-09-13T15:59:57.126519Z", + "shell.execute_reply": "2024-09-13T15:59:57.126249Z" + } + }, + "outputs": [], + "source": [ + "# Evaluation progression\n", + "X.data[\"y\"].plot()\n", + "plt.yscale(\"log\")\n", + "plt.xlabel(\"iteration\")\n", + "plt.ylabel(\"Rosenbrock value\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-30T17:44:50.365947900Z", + "start_time": "2023-10-30T17:44:50.334810200Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:57.128025Z", + "iopub.status.busy": "2024-09-13T15:59:57.127898Z", + "iopub.status.idle": "2024-09-13T15:59:57.130442Z", + "shell.execute_reply": "2024-09-13T15:59:57.130194Z" + } + }, + "outputs": [], + "source": [ + "# Minimum\n", + "dict(X.data.iloc[X.data[\"y\"].argmin()])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Visualize" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-30T17:44:50.678806600Z", + "start_time": "2023-10-30T17:44:50.342942900Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:57.131800Z", + "iopub.status.busy": "2024-09-13T15:59:57.131700Z", + "iopub.status.idle": "2024-09-13T15:59:57.350241Z", + "shell.execute_reply": "2024-09-13T15:59:57.349942Z" + } + }, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(figsize=(8, 8))\n", + "\n", + "Xgrid, Ygrid = np.meshgrid(np.linspace(-2, 2, 201), np.linspace(-2, 2, 201))\n", + "\n", + "Zgrid = np.vectorize(lambda x, y: rosenbrock([x, y]))(Xgrid, Ygrid)\n", + "Zgrid = np.log(Zgrid + 1)\n", + "\n", + "ax.pcolormesh(Xgrid, Ygrid, Zgrid)\n", + "ax.contour(Xgrid, Ygrid, Zgrid, levels=10, colors=\"black\")\n", + "ax.set_xlabel(\"x0\")\n", + "ax.set_ylabel(\"x1\")\n", + "\n", + "\n", + "# Add all evaluations\n", + "ax.plot(X.data[\"x0\"], X.data[\"x1\"], color=\"red\", alpha=0.5, marker=\".\")\n", + "ax.scatter(XMIN[0], XMIN[1], 50, marker=\"o\", color=\"orange\", label=\"True minimum\")\n", + "ax.set_xlim(-2, 2)\n", + "ax.set_ylim(-2, 2)\n", + "# plt.legend()\n", + "ax.set_title(\"Xopt's Nelder-Mead progression\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-30T17:44:51.150914300Z", + "start_time": "2023-10-30T17:44:50.678806600Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:57.353700Z", + "iopub.status.busy": "2024-09-13T15:59:57.353586Z", + "iopub.status.idle": "2024-09-13T15:59:57.475442Z", + "shell.execute_reply": "2024-09-13T15:59:57.475164Z" + } + }, + "outputs": [], + "source": [ + "# Manually step the algorithm and collect simplexes\n", + "X = Xopt.from_yaml(YAML)\n", + "X.random_evaluate(1)\n", + "simplexes = []\n", + "for i in range(500):\n", + " X.step()\n", + " simplexes.append(X.generator.simplex)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-30T17:44:51.435380800Z", + "start_time": "2023-10-30T17:44:51.141912400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:57.476926Z", + "iopub.status.busy": "2024-09-13T15:59:57.476833Z", + "iopub.status.idle": "2024-09-13T15:59:57.642323Z", + "shell.execute_reply": "2024-09-13T15:59:57.642032Z" + } + }, + "outputs": [], + "source": [ + "def plot_simplex(simplex, ax=None):\n", + " x0 = simplex[\"x0\"]\n", + " x1 = simplex[\"x1\"]\n", + " x0 = np.append(x0, x0[0])\n", + " x1 = np.append(x1, x1[0])\n", + " ax.plot(x0, x1)\n", + "\n", + "\n", + "fig, ax = plt.subplots(figsize=(8, 8))\n", + "ax.pcolormesh(Xgrid, Ygrid, Zgrid)\n", + "# ax.contour(Xgrid, Ygrid, Zgrid, levels=10, colors='black')\n", + "ax.set_xlim(-2, 2)\n", + "ax.set_ylim(-2, 2)\n", + "ax.set_xlabel(\"x0\")\n", + "ax.set_ylabel(\"x1\")\n", + "ax.set_title(\"Nelder-Mead simplex progression\")\n", + "\n", + "ax.scatter(XMIN[0], XMIN[1], 50, marker=\"o\", color=\"orange\", label=\"True minimum\")\n", + "\n", + "for simplex in simplexes:\n", + " plot_simplex(simplex, ax)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Compare with scipy.optimize.fmin Nelder-Mead\n", + "\n", + "Notice that fmin is much faster here. This is because the function runs very fast, so the internal Xopt bookkeeping overhead dominates.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-30T17:44:51.449604Z", + "start_time": "2023-10-30T17:44:51.439896600Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:57.644248Z", + "iopub.status.busy": "2024-09-13T15:59:57.644134Z", + "iopub.status.idle": "2024-09-13T15:59:57.647915Z", + "shell.execute_reply": "2024-09-13T15:59:57.647663Z" + } + }, + "outputs": [], + "source": [ + "result = fmin(rosenbrock, [-1, -1])\n", + "result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-30T17:44:51.464852Z", + "start_time": "2023-10-30T17:44:51.449604Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:57.649286Z", + "iopub.status.busy": "2024-09-13T15:59:57.649209Z", + "iopub.status.idle": "2024-09-13T15:59:57.652369Z", + "shell.execute_reply": "2024-09-13T15:59:57.652131Z" + } + }, + "outputs": [], + "source": [ + "X = Xopt.from_yaml(YAML)\n", + "X.random_evaluate(1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-30T17:44:51.953260600Z", + "start_time": "2023-10-30T17:44:51.458863500Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:57.653687Z", + "iopub.status.busy": "2024-09-13T15:59:57.653609Z", + "iopub.status.idle": "2024-09-13T15:59:57.774912Z", + "shell.execute_reply": "2024-09-13T15:59:57.774652Z" + } + }, + "outputs": [], + "source": [ + "X.run()\n", + "# Almost exactly the same number evaluations.\n", + "len(X.data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-30T17:44:51.953260600Z", + "start_time": "2023-10-30T17:44:51.947939500Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:57.776356Z", + "iopub.status.busy": "2024-09-13T15:59:57.776254Z", + "iopub.status.idle": "2024-09-13T15:59:57.778516Z", + "shell.execute_reply": "2024-09-13T15:59:57.778285Z" + } + }, + "outputs": [], + "source": [ + "# results are the same\n", + "xbest = X.data.iloc[X.data[\"y\"].argmin()]\n", + "xbest[\"x0\"] == result[0], xbest[\"x1\"] == result[1]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# NelderMeadGenerator object" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-30T17:44:51.961646200Z", + "start_time": "2023-10-30T17:44:51.956765600Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:57.779845Z", + "iopub.status.busy": "2024-09-13T15:59:57.779744Z", + "iopub.status.idle": "2024-09-13T15:59:57.781773Z", + "shell.execute_reply": "2024-09-13T15:59:57.781561Z" + } + }, + "outputs": [], + "source": [ + "NelderMeadGenerator.model_fields" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-30T17:44:51.983064100Z", + "start_time": "2023-10-30T17:44:51.963646900Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:57.783087Z", + "iopub.status.busy": "2024-09-13T15:59:57.782990Z", + "iopub.status.idle": "2024-09-13T15:59:57.786326Z", + "shell.execute_reply": "2024-09-13T15:59:57.786079Z" + } + }, + "outputs": [], + "source": [ + "Xbest = [33, 44]\n", + "\n", + "\n", + "def f(inputs, verbose=False):\n", + " if verbose:\n", + " print(f\"evaluate f({inputs})\")\n", + " x0 = inputs[\"x0\"]\n", + " x1 = inputs[\"x1\"]\n", + "\n", + " # if x0 < 10:\n", + " # raise ValueError('test XXXX')\n", + "\n", + " y = (x0 - Xbest[0]) ** 2 + (x1 - Xbest[1]) ** 2\n", + "\n", + " return {\"y\": y}\n", + "\n", + "\n", + "ev = Evaluator(function=f)\n", + "vocs = VOCS(\n", + " variables={\"x0\": [-100, 100], \"x1\": [-100, 100]}, objectives={\"y\": \"MINIMIZE\"}\n", + ")\n", + "vocs.json()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-30T17:44:52.060007300Z", + "start_time": "2023-10-30T17:44:51.971425700Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:57.787580Z", + "iopub.status.busy": "2024-09-13T15:59:57.787501Z", + "iopub.status.idle": "2024-09-13T15:59:57.789464Z", + "shell.execute_reply": "2024-09-13T15:59:57.789240Z" + } + }, + "outputs": [], + "source": [ + "# check output\n", + "f(vocs.random_inputs()[0])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-30T17:44:52.061007300Z", + "start_time": "2023-10-30T17:44:51.975482700Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:57.790962Z", + "iopub.status.busy": "2024-09-13T15:59:57.790884Z", + "iopub.status.idle": "2024-09-13T15:59:57.793148Z", + "shell.execute_reply": "2024-09-13T15:59:57.792936Z" + } + }, + "outputs": [], + "source": [ + "G = NelderMeadGenerator(vocs=vocs, initial_point={\"x0\": 0, \"x1\": 0})\n", + "inputs = G.generate(1)\n", + "inputs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-30T17:44:52.063008Z", + "start_time": "2023-10-30T17:44:51.981063900Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:57.794425Z", + "iopub.status.busy": "2024-09-13T15:59:57.794345Z", + "iopub.status.idle": "2024-09-13T15:59:57.796438Z", + "shell.execute_reply": "2024-09-13T15:59:57.796201Z" + } + }, + "outputs": [], + "source": [ + "# Further generate calls will continue to produce same point, as with BO\n", + "G.generate(1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-30T17:44:52.064007900Z", + "start_time": "2023-10-30T17:44:51.987724400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:57.797783Z", + "iopub.status.busy": "2024-09-13T15:59:57.797707Z", + "iopub.status.idle": "2024-09-13T15:59:57.799517Z", + "shell.execute_reply": "2024-09-13T15:59:57.799279Z" + } + }, + "outputs": [], + "source": [ + "ev.evaluate(inputs[0])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-30T17:44:52.065008300Z", + "start_time": "2023-10-30T17:44:51.991721600Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:57.800848Z", + "iopub.status.busy": "2024-09-13T15:59:57.800768Z", + "iopub.status.idle": "2024-09-13T15:59:57.803515Z", + "shell.execute_reply": "2024-09-13T15:59:57.803286Z" + } + }, + "outputs": [], + "source": [ + "# Adding new data will advance state to next step, and next generate() will yield new point\n", + "G.add_data(pd.DataFrame(inputs[0] | ev.evaluate(inputs[0]), index=[0]))\n", + "G.generate(1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-30T17:44:52.065008300Z", + "start_time": "2023-10-30T17:44:52.000489700Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:57.804779Z", + "iopub.status.busy": "2024-09-13T15:59:57.804698Z", + "iopub.status.idle": "2024-09-13T15:59:57.806303Z", + "shell.execute_reply": "2024-09-13T15:59:57.806080Z" + } + }, + "outputs": [], + "source": [ + "# Create Xopt object\n", + "X = Xopt(\n", + " evaluator=ev,\n", + " vocs=vocs,\n", + " generator=NelderMeadGenerator(vocs=vocs),\n", + " max_evaluations=100,\n", + ")\n", + "\n", + "# Optional: give an initial pioint\n", + "X.generator.initial_point = {\"x0\": 0, \"x1\": 0}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-30T17:44:52.813583500Z", + "start_time": "2023-10-30T17:44:52.002994200Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:57.807568Z", + "iopub.status.busy": "2024-09-13T15:59:57.807488Z", + "iopub.status.idle": "2024-09-13T15:59:57.973465Z", + "shell.execute_reply": "2024-09-13T15:59:57.973221Z" + } + }, + "outputs": [], + "source": [ + "X.run()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-30T17:44:52.832798100Z", + "start_time": "2023-10-30T17:44:52.825250500Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:57.981477Z", + "iopub.status.busy": "2024-09-13T15:59:57.981399Z", + "iopub.status.idle": "2024-09-13T15:59:57.983453Z", + "shell.execute_reply": "2024-09-13T15:59:57.983225Z" + } + }, + "outputs": [], + "source": [ + "# This shows the latest simplex\n", + "X.generator.simplex" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-30T17:44:53.022138900Z", + "start_time": "2023-10-30T17:44:52.830797500Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:57.984820Z", + "iopub.status.busy": "2024-09-13T15:59:57.984744Z", + "iopub.status.idle": "2024-09-13T15:59:58.084711Z", + "shell.execute_reply": "2024-09-13T15:59:58.084426Z" + } + }, + "outputs": [], + "source": [ + "X.data[\"y\"].plot()\n", + "plt.yscale(\"log\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-30T17:44:53.162747600Z", + "start_time": "2023-10-30T17:44:53.024139500Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:58.086142Z", + "iopub.status.busy": "2024-09-13T15:59:58.086038Z", + "iopub.status.idle": "2024-09-13T15:59:58.156876Z", + "shell.execute_reply": "2024-09-13T15:59:58.156616Z" + } + }, + "outputs": [], + "source": [ + "fig, ax = plt.subplots()\n", + "X.data.plot(\"x0\", \"x1\", ax=ax, color=\"black\", alpha=0.5)\n", + "ax.scatter(Xbest[0], Xbest[1], marker=\"x\", color=\"red\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-30T17:44:53.162747600Z", + "start_time": "2023-10-30T17:44:53.152349900Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:58.158257Z", + "iopub.status.busy": "2024-09-13T15:59:58.158172Z", + "iopub.status.idle": "2024-09-13T15:59:58.160476Z", + "shell.execute_reply": "2024-09-13T15:59:58.160263Z" + } + }, + "outputs": [], + "source": [ + "# This is the raw internal state of the generator\n", + "a = X.generator.current_state\n", + "a" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5-dimensional Rosenbrock\n", + "\n", + "`evaluate_rosenbrock` works for arbitrary dimensions, so adding more variables to `vocs` transforms this problem." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-30T17:45:23.048760200Z", + "start_time": "2023-10-30T17:45:22.988273100Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:58.335288Z", + "iopub.status.busy": "2024-09-13T15:59:58.335179Z", + "iopub.status.idle": "2024-09-13T15:59:58.338386Z", + "shell.execute_reply": "2024-09-13T15:59:58.338158Z" + } + }, + "outputs": [], + "source": [ + "YAML = \"\"\"\n", + "max_evaluations: 500\n", + "generator:\n", + " name: neldermead\n", + "evaluator:\n", + " function: xopt.resources.test_functions.rosenbrock.evaluate_rosenbrock\n", + "vocs:\n", + " variables:\n", + " x1: [-5, 5]\n", + " x2: [-5, 5]\n", + " x3: [-5, 5]\n", + " x4: [-5, 5]\n", + " x5: [-5, 5]\n", + " objectives:\n", + " y: MINIMIZE\n", + "\"\"\"\n", + "X = Xopt.from_yaml(YAML)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-30T17:45:27.799562100Z", + "start_time": "2023-10-30T17:45:22.996241200Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:58.339678Z", + "iopub.status.busy": "2024-09-13T15:59:58.339596Z", + "iopub.status.idle": "2024-09-13T15:59:59.299922Z", + "shell.execute_reply": "2024-09-13T15:59:59.299618Z" + } + }, + "outputs": [], + "source": [ + "X.random_evaluate(1)\n", + "X.run()\n", + "X.data[\"y\"].plot()\n", + "plt.yscale(\"log\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-30T17:45:28.180395600Z", + "start_time": "2023-10-30T17:45:27.804562400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:59:59.301516Z", + "iopub.status.busy": "2024-09-13T15:59:59.301406Z", + "iopub.status.idle": "2024-09-13T15:59:59.512342Z", + "shell.execute_reply": "2024-09-13T15:59:59.512091Z" + } + }, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(figsize=(8, 8))\n", + "\n", + "Xgrid, Ygrid = np.meshgrid(np.linspace(-2, 2, 201), np.linspace(-2, 2, 201))\n", + "\n", + "Zgrid = np.vectorize(lambda x, y: rosenbrock([x, y, 1, 1, 1]))(\n", + " Xgrid, Ygrid\n", + ") # The minimum is at 1,1,1,1,1\n", + "Zgrid = np.log(Zgrid + 1)\n", + "\n", + "ax.pcolormesh(Xgrid, Ygrid, Zgrid)\n", + "ax.contour(Xgrid, Ygrid, Zgrid, levels=10, colors=\"black\")\n", + "ax.set_xlabel(\"x0\")\n", + "ax.set_ylabel(\"x1\")\n", + "\n", + "\n", + "# Add all evaluations\n", + "ax.plot(X.data[\"x1\"], X.data[\"x2\"], color=\"red\", alpha=0.5, marker=\".\")\n", + "ax.scatter(XMIN[0], XMIN[1], 50, marker=\"o\", color=\"orange\", label=\"True minimum\")\n", + "ax.set_xlim(-2, 2)\n", + "ax.set_ylim(-2, 2)\n", + "# plt.legend()\n", + "ax.set_title(\"Xopt's Nelder-Mead progression\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "xopt-dev", + "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.11.0" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/sequential/rcds.ipynb b/docs/examples/sequential/rcds.ipynb new file mode 100644 index 0000000..7e5005b --- /dev/null +++ b/docs/examples/sequential/rcds.ipynb @@ -0,0 +1,835 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "6f4b8676-940b-4a69-b495-df62bbc6e2fa", + "metadata": {}, + "source": [ + "## RCDS Optimization\n", + "\n", + "In this example we demonstrate RCDS optimization." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d05700a3-6853-4dae-bc87-3f9f37dea446", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-26T22:16:31.208636600Z", + "start_time": "2023-10-26T22:16:31.201806800Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "# If you encounter the \"Initializing libomp.dylib, but found libomp.dylib already initialized.\" error\n", + "# Please run this cell\n", + "import os" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "78534aeb-5ab2-4a3c-964c-4e080c7e953f", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-26T22:16:34.557495800Z", + "start_time": "2023-10-26T22:16:31.210637400Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "from xopt.generators.sequential import RCDSGenerator\n", + "from xopt.vocs import VOCS\n", + "from xopt.evaluator import Evaluator\n", + "from xopt import Xopt\n", + "from tqdm.auto import tqdm\n", + "import warnings\n", + "\n", + "\n", + "warnings.filterwarnings(\"ignore\")\n", + "\n", + "os.environ[\"KMP_DUPLICATE_LIB_OK\"] = \"True\"" + ] + }, + { + "cell_type": "markdown", + "id": "fe8297c8-90e2-4e40-ab0b-63a9fe655aed", + "metadata": {}, + "source": [ + "### RCDS test problem\n", + "\n", + "This test problem is a 2-D quadratic function." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35171e8a-ea8f-4cbb-b1a0-d01213e0bbcc", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-26T22:16:34.571463700Z", + "start_time": "2023-10-26T22:16:34.559463800Z" + } + }, + "outputs": [], + "source": [ + "def f_test(input_dict):\n", + " p = []\n", + " for i in range(2):\n", + " p.append(input_dict[f\"p{i}\"])\n", + "\n", + " obj = np.linalg.norm(p)\n", + " outcome_dict = {\"f\": obj}\n", + "\n", + " return outcome_dict" + ] + }, + { + "cell_type": "markdown", + "id": "9638b784-fa2f-4a94-900c-c66c8b0ea58b", + "metadata": {}, + "source": [ + "### Run RCDS on the test problem (YAML method)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8fac03d4-64f7-4773-973c-850b977a888e", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-26T22:16:34.602490200Z", + "start_time": "2023-10-26T22:16:34.576462200Z" + } + }, + "outputs": [], + "source": [ + "YAML = \"\"\"\n", + "max_evaluations: 100\n", + "generator:\n", + " name: rcds\n", + " x0: null\n", + " init_mat: null\n", + " noise: 0.00001\n", + " step: 0.01\n", + " tol: 0.00001\n", + "evaluator:\n", + " function: __main__.f_test\n", + "vocs:\n", + " variables:\n", + " p0: [0, 1]\n", + " p1: [0, 1]\n", + " objectives:\n", + " f: MINIMIZE\n", + "\"\"\"\n", + "\n", + "X = Xopt.from_yaml(YAML)\n", + "X" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e16ab267-8b5e-4616-b552-2b0410c5b75f", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-26T22:16:35.201462800Z", + "start_time": "2023-10-26T22:16:34.607463300Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "X.random_evaluate(1)\n", + "X.run()" + ] + }, + { + "cell_type": "markdown", + "id": "54acb14d-2b25-4f2e-a3e9-fecb61af552f", + "metadata": {}, + "source": [ + "Now you can go directly to the [Visualization](#Visualization) section and check out the results." + ] + }, + { + "cell_type": "markdown", + "id": "eadf6cd3-24af-4b8b-8df0-9d85d0b26757", + "metadata": {}, + "source": [ + "### Run RCDS on the maximization test problem (YAML method)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3e6c673c-4518-4db2-82ba-1ea9b5a11d53", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-26T22:16:34.602490200Z", + "start_time": "2023-10-26T22:16:34.576462200Z" + } + }, + "outputs": [], + "source": [ + "YAML = \"\"\"\n", + "max_evaluations: 400\n", + "generator:\n", + " name: rcds\n", + " x0: null\n", + " init_mat: null\n", + " noise: 1e-8\n", + " step: 0.01\n", + " tol: 0.00001\n", + "evaluator:\n", + " function: __main__.f_test\n", + "vocs:\n", + " variables:\n", + " p0: [1, 3]\n", + " p1: [-4, 2]\n", + " objectives:\n", + " f: MAXIMIZE\n", + "\"\"\"\n", + "\n", + "X = Xopt.from_yaml(YAML)\n", + "X" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1f738c00-4a57-41a4-a0a8-2abf2584667f", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-26T22:16:35.201462800Z", + "start_time": "2023-10-26T22:16:34.607463300Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "X.random_evaluate(1)\n", + "X.run()" + ] + }, + { + "cell_type": "markdown", + "id": "09a02bed-32f5-4d2a-945d-2fdcc4756af3", + "metadata": {}, + "source": [ + "Now you can go directly to the [Visualization](#Visualization) section and check out the results." + ] + }, + { + "cell_type": "markdown", + "id": "4d688915-bdca-4d0f-baaa-5c50cfd80d79", + "metadata": {}, + "source": [ + "### Run RCDS on the test problem (API method)" + ] + }, + { + "cell_type": "markdown", + "id": "8f0a6f75-7f92-47ee-828d-ad7435932cf2", + "metadata": {}, + "source": [ + "#### VOCS\n", + "\n", + "We'll set the bounds for all the variables pi to [0, 1]." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bd028b25-f395-43ed-8473-2dc950ff72c3", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-26T22:16:35.216462Z", + "start_time": "2023-10-26T22:16:35.204461300Z" + } + }, + "outputs": [], + "source": [ + "n_var = 2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c99026b0-f595-4247-9695-6d0b067ceeca", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-26T22:16:35.239487Z", + "start_time": "2023-10-26T22:16:35.218462900Z" + } + }, + "outputs": [], + "source": [ + "variables = {}\n", + "for i in range(n_var):\n", + " variables[f\"p{i}\"] = [0, 1]\n", + "\n", + "vocs = VOCS(\n", + " variables=variables,\n", + " objectives={\"f\": \"MINIMIZE\"},\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0af5b8c7-4b3a-4a62-b92b-539899d11109", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-26T22:16:35.247462100Z", + "start_time": "2023-10-26T22:16:35.233463200Z" + } + }, + "outputs": [], + "source": [ + "vocs" + ] + }, + { + "cell_type": "markdown", + "id": "5354659c-cc30-4d70-8659-0fefd43ef0f0", + "metadata": {}, + "source": [ + "#### Evaluator" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "efafda0b-7282-4027-a1f3-f96454c114bd", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-26T22:16:35.290491Z", + "start_time": "2023-10-26T22:16:35.249462300Z" + } + }, + "outputs": [], + "source": [ + "evaluator = Evaluator(function=f_test)" + ] + }, + { + "cell_type": "markdown", + "id": "d6c58dcc-0b45-4408-a428-1681098a6a4a", + "metadata": {}, + "source": [ + "#### Generator" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "445daa23-5f97-4a82-bd59-57d9cbae6ac1", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-26T22:16:35.296460300Z", + "start_time": "2023-10-26T22:16:35.263463700Z" + } + }, + "outputs": [], + "source": [ + "generator = RCDSGenerator(vocs=vocs)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "346fad29", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-26T22:16:35.297491800Z", + "start_time": "2023-10-26T22:16:35.290491Z" + } + }, + "outputs": [], + "source": [ + "generator.model_dump()" + ] + }, + { + "cell_type": "markdown", + "id": "f4981983-1e64-43a1-ac90-f88546f9650a", + "metadata": {}, + "source": [ + "#### Run the optimization" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10c77281-f49c-4cf7-bb2d-8a278cf00e23", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-26T22:16:35.307490500Z", + "start_time": "2023-10-26T22:16:35.296460300Z" + } + }, + "outputs": [], + "source": [ + "X = Xopt(vocs=vocs, evaluator=evaluator, generator=generator)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1632b55e-1dd0-49b2-9444-cfe32e30977b", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-26T22:16:35.907460800Z", + "start_time": "2023-10-26T22:16:35.308463400Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "X.evaluate_data({\"p0\": 0.5, \"p1\": 0.5})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9bb04077-1562-430d-a661-de8cb8836b7d", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-26T22:16:35.907460800Z", + "start_time": "2023-10-26T22:16:35.308463400Z" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "for i in tqdm(range(400)):\n", + " X.step()" + ] + }, + { + "cell_type": "markdown", + "id": "9808d9af-a815-4995-911a-43bd9eb105da", + "metadata": {}, + "source": [ + "### Visualization" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "651d6821-8069-4efb-8bad-56a87f7240e8", + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-26T22:16:36.402079200Z", + "start_time": "2023-10-26T22:16:35.911052600Z" + } + }, + "outputs": [], + "source": [ + "X.data.plot(y=\"f\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "096fa078-9cf9-43a0-8366-2ccd2280bcc0", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.9" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "state": { + "0464f9bccf1248feb2ae2081538d7b1d": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "3d977298874a42d1b7432d67c99d5395": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "523c02e7168e4463af9859b2a2b945f7": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "92816b6214464fbeacc423f505253df6": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "a8e34b28670e42518d5302ec3737a09e": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "HTMLView", + "description": "", + "description_allow_html": false, + "layout": "IPY_MODEL_3d977298874a42d1b7432d67c99d5395", + "placeholder": "​", + "style": "IPY_MODEL_e7afd5ff3dec48b993eec540f6f6ea3d", + "tabbable": null, + "tooltip": null, + "value": " 100/100 [00:00<00:00, 958.09it/s]" + } + }, + "b20b85efefdd48eea89d08c6f34c9996": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "StyleView", + "background": null, + "description_width": "", + "font_size": null, + "text_color": null + } + }, + "cdb8edf0dc034465a7770e52e980ced6": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "de683211fe2044c79f27449a3784392f": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_e6c7597d5cef42e2a8c179c23dc66bfa", + "IPY_MODEL_fce367fa747c48c4a66d47e11a0b64f1", + "IPY_MODEL_a8e34b28670e42518d5302ec3737a09e" + ], + "layout": "IPY_MODEL_523c02e7168e4463af9859b2a2b945f7", + "tabbable": null, + "tooltip": null + } + }, + "e6c7597d5cef42e2a8c179c23dc66bfa": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "HTMLView", + "description": "", + "description_allow_html": false, + "layout": "IPY_MODEL_0464f9bccf1248feb2ae2081538d7b1d", + "placeholder": "​", + "style": "IPY_MODEL_b20b85efefdd48eea89d08c6f34c9996", + "tabbable": null, + "tooltip": null, + "value": "100%" + } + }, + "e7afd5ff3dec48b993eec540f6f6ea3d": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "StyleView", + "background": null, + "description_width": "", + "font_size": null, + "text_color": null + } + }, + "fce367fa747c48c4a66d47e11a0b64f1": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_allow_html": false, + "layout": "IPY_MODEL_cdb8edf0dc034465a7770e52e980ced6", + "max": 100, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_92816b6214464fbeacc423f505253df6", + "tabbable": null, + "tooltip": null, + "value": 100 + } + } + }, + "version_major": 2, + "version_minor": 0 + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/examples/single_objective_bayes_opt/bax_tutorial.ipynb b/docs/examples/single_objective_bayes_opt/bax_tutorial.ipynb new file mode 100644 index 0000000..f0b795c --- /dev/null +++ b/docs/examples/single_objective_bayes_opt/bax_tutorial.ipynb @@ -0,0 +1,430 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Basic Optimization using BAX\n", + "In this notebook we demonstrate the use of Xopt to perform Bayesian Algorithm Execution (BAX) as a means of minimizing the output of a simple test function. BAX is a generalization of Bayesian Optimization that seeks to acquire observations that provide our model with maximal information about our property of interest. In this simple example, our property of interest is the minimum function output and its location in input-space. See https://arxiv.org/pdf/2209.04587.pdf for details." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Imports and random seeding for reproducibility" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-07-30T20:40:01.274627800Z", + "start_time": "2024-07-30T20:39:57.849796300Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:55:55.364961Z", + "iopub.status.busy": "2024-09-13T15:55:55.364610Z", + "iopub.status.idle": "2024-09-13T15:55:57.130703Z", + "shell.execute_reply": "2024-09-13T15:55:57.130276Z" + } + }, + "outputs": [], + "source": [ + "import torch\n", + "\n", + "from xopt import Xopt\n", + "from xopt.vocs import VOCS\n", + "from xopt.generators.bayesian.bax_generator import BaxGenerator\n", + "from xopt.generators.bayesian.bax.algorithms import GridOptimize\n", + "from xopt.evaluator import Evaluator\n", + "from xopt.generators.bayesian.bax.visualize import visualize_virtual_objective\n", + "\n", + "import numpy as np\n", + "import random\n", + "\n", + "import os\n", + "import math\n", + "import matplotlib.pyplot as plt\n", + "\n", + "# Ignore all warnings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"ignore\")\n", + "\n", + "\n", + "os.environ[\"KMP_DUPLICATE_LIB_OK\"] = \"True\"\n", + "\n", + "# random seeds for reproducibility\n", + "rand_seed = 2\n", + "\n", + "torch.manual_seed(rand_seed)\n", + "np.random.seed(rand_seed) # only affects initial random observations through Xopt\n", + "random.seed(rand_seed)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Define the test problem\n", + "Here we define a simple optimization problem, where we attempt to minimize the sin\n", + "function in the domian [0,2*pi]. Note that the function used to evaluate the\n", + "objective function takes a dictionary as input and returns a dictionary as the output." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-07-30T20:40:01.289628200Z", + "start_time": "2024-07-30T20:40:01.277626900Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:55:57.132763Z", + "iopub.status.busy": "2024-09-13T15:55:57.132536Z", + "iopub.status.idle": "2024-09-13T15:55:57.134879Z", + "shell.execute_reply": "2024-09-13T15:55:57.134579Z" + } + }, + "outputs": [], + "source": [ + "# define variables and function objectives\n", + "vocs = VOCS(\n", + " variables={\"x\": [0, 2 * math.pi]},\n", + " observables=[\"y1\"],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-07-30T20:40:01.318392200Z", + "start_time": "2024-07-30T20:40:01.291627800Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:55:57.136536Z", + "iopub.status.busy": "2024-09-13T15:55:57.136408Z", + "iopub.status.idle": "2024-09-13T15:55:57.138213Z", + "shell.execute_reply": "2024-09-13T15:55:57.137991Z" + } + }, + "outputs": [], + "source": [ + "# define a test function to optimize\n", + "def sin_function(input_dict):\n", + " return {\"y1\": np.sin(input_dict[\"x\"])}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Prepare BAX generator for Xopt\n", + "Create a generator that uses the ExpectedInformationGain (InfoBAX) acquisition\n", + "function to perform Bayesian Optimization. Note that we use minimization on a grid,\n", + "so specifying the number of mesh points can negatively impact decision making time\n", + "(especially in higher dimensional feature spaces)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-07-30T20:40:01.322392400Z", + "start_time": "2024-07-30T20:40:01.308880200Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:55:57.139600Z", + "iopub.status.busy": "2024-09-13T15:55:57.139487Z", + "iopub.status.idle": "2024-09-13T15:55:57.141211Z", + "shell.execute_reply": "2024-09-13T15:55:57.140968Z" + } + }, + "outputs": [], + "source": [ + "# Prepare BAX algorithm and generator options\n", + "algorithm = GridOptimize(n_mesh_points=50) # NOTE: default is to minimize\n", + "\n", + "# construct BAX generator\n", + "generator = BaxGenerator(vocs=vocs, algorithm=algorithm)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create Evaluator and Xopt objects\n", + "Create the Evaluator (which allows Xopt to interface with our test function) and finish constructing our Xopt object." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-07-30T20:40:01.336812700Z", + "start_time": "2024-07-30T20:40:01.323393100Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:55:57.142623Z", + "iopub.status.busy": "2024-09-13T15:55:57.142503Z", + "iopub.status.idle": "2024-09-13T15:55:57.147883Z", + "shell.execute_reply": "2024-09-13T15:55:57.147582Z" + } + }, + "outputs": [], + "source": [ + "# construct evaluator\n", + "evaluator = Evaluator(function=sin_function)\n", + "\n", + "# construct Xopt optimizer\n", + "X = Xopt(evaluator=evaluator, generator=generator, vocs=vocs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Generate and evaluate initial points\n", + "To begin optimization, we must generate some random initial data points. The first call\n", + "to `X.step()` will generate and evaluate a number of randomly points specified by the\n", + " generator. Note that if we add data to xopt before calling `X.step()` by assigning\n", + " the data to `X.data`, calls to `X.step()` will ignore the random generation and\n", + " proceed to generating points via Bayesian optimization." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-07-30T20:40:01.395027700Z", + "start_time": "2024-07-30T20:40:01.337812700Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:55:57.149353Z", + "iopub.status.busy": "2024-09-13T15:55:57.149264Z", + "iopub.status.idle": "2024-09-13T15:55:57.157170Z", + "shell.execute_reply": "2024-09-13T15:55:57.156926Z" + } + }, + "outputs": [], + "source": [ + "# evaluate initial points\n", + "X.random_evaluate(3)\n", + "\n", + "# inspect the gathered data\n", + "X.data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "### Define plotting utility\n", + "Define a plotting function that plots the GP model, samples from the GP model, and the\n", + "execution paths (red crosses)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-07-30T20:40:01.398993800Z", + "start_time": "2024-07-30T20:40:01.380025800Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:55:57.179682Z", + "iopub.status.busy": "2024-09-13T15:55:57.179513Z", + "iopub.status.idle": "2024-09-13T15:55:57.185064Z", + "shell.execute_reply": "2024-09-13T15:55:57.184714Z" + } + }, + "outputs": [], + "source": [ + "def plot_bax(X):\n", + " # get the Gaussian process model from the generator\n", + " model = X.generator.train_model()\n", + "\n", + " # get acquisition function from generator\n", + " acq = X.generator.get_acquisition(model)\n", + "\n", + " # calculate model posterior and acquisition function at each test point\n", + " # NOTE: need to add a dimension to the input tensor for evaluating the\n", + " # posterior and another for the acquisition function, see\n", + " # https://botorch.org/docs/batching for details\n", + " # NOTE: we use the `torch.no_grad()` environment to speed up computation by\n", + " # skipping calculations for backpropagation\n", + " with torch.no_grad():\n", + " posterior = model.posterior(test_x.unsqueeze(1))\n", + " acq_val = acq(test_x.reshape(-1, 1, 1))\n", + "\n", + " # get mean function and confidence regions\n", + " mean = posterior.mean\n", + " L, u = posterior.mvn.confidence_region()\n", + "\n", + " # plot model and acquisition function\n", + " fig, ax = plt.subplots(3, 1, sharex=\"all\")\n", + " fig.set_size_inches(8, 6)\n", + "\n", + " # plot model posterior\n", + " ax[0].plot(test_x, mean, label=\"Posterior mean\")\n", + " ax[0].fill_between(test_x, L, u, alpha=0.25, label=\"Posterior confidence region\")\n", + "\n", + " # add data to model plot\n", + " ax[0].plot(X.data[\"x\"], X.data[\"y1\"], \"C1o\", label=\"Training data\")\n", + "\n", + " # plot true function\n", + " true_f = sin_function({\"x\": test_x})[\"y1\"]\n", + " ax[0].plot(test_x, true_f, \"--\", label=\"Ground truth\")\n", + "\n", + " # plot the function samples and their optima found by BAX\n", + " test_points = X.generator.algorithm_results[\"test_points\"]\n", + " posterior_samples = X.generator.algorithm_results[\"posterior_samples\"]\n", + " execution_paths = X.generator.algorithm_results[\"execution_paths\"]\n", + "\n", + " label1 = \"Function Samples\"\n", + " label2 = \"Sample Optima\"\n", + " for i in range(X.generator.algorithm.n_samples):\n", + " (samples,) = ax[1].plot(\n", + " test_points, posterior_samples[i], c=\"C0\", alpha=0.3, label=label1\n", + " )\n", + " ax[1].scatter(\n", + " *execution_paths[i], c=\"r\", marker=\"x\", s=80, label=label2, zorder=10\n", + " )\n", + " label1 = None\n", + " label2 = None\n", + "\n", + " # plot acquisition function\n", + " ax[2].plot(test_x, acq_val.flatten())\n", + "\n", + " ax[0].set_ylabel(\"y1\")\n", + " ax[1].set_ylabel(\"y1\")\n", + " ax[2].set_ylabel(r\"$\\alpha(x)$\")\n", + " ax[2].set_xlabel(\"x\")\n", + "\n", + " return fig, ax" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Do bayesian optimization steps\n", + "To perform optimization we simply call `X.step()` in a loop. This allows us to do\n", + "intermediate tasks in between optimization steps, such as examining the model and\n", + "acquisition function at each step (as we demonstrate here)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-07-30T20:40:11.438033900Z", + "start_time": "2024-07-30T20:40:01.385024100Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:55:57.186686Z", + "iopub.status.busy": "2024-09-13T15:55:57.186547Z", + "iopub.status.idle": "2024-09-13T15:56:01.230192Z", + "shell.execute_reply": "2024-09-13T15:56:01.229853Z" + } + }, + "outputs": [], + "source": [ + "n_steps = 3\n", + "\n", + "# test points for plotting\n", + "test_x = torch.linspace(*X.vocs.bounds.flatten(), 50).double()\n", + "\n", + "for i in range(5):\n", + " # plot model and bax information\n", + " fig, ax = plot_bax(X)\n", + "\n", + " if i == 0:\n", + " ax[0].legend(ncols=2)\n", + " ax[1].legend()\n", + "\n", + " # do the optimization step\n", + " X.step()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-07-30T20:40:11.453033700Z", + "start_time": "2024-07-30T20:40:11.435033200Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:01.231866Z", + "iopub.status.busy": "2024-09-13T15:56:01.231695Z", + "iopub.status.idle": "2024-09-13T15:56:01.235374Z", + "shell.execute_reply": "2024-09-13T15:56:01.235137Z" + } + }, + "outputs": [], + "source": [ + "# access the collected data\n", + "X.data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-07-30T20:40:11.696033700Z", + "start_time": "2024-07-30T20:40:11.450033600Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:01.236731Z", + "iopub.status.busy": "2024-09-13T15:56:01.236643Z", + "iopub.status.idle": "2024-09-13T15:56:01.319340Z", + "shell.execute_reply": "2024-09-13T15:56:01.319019Z" + } + }, + "outputs": [], + "source": [ + "# plot the virtual objective (which in this case is simply the observable model for y1) via posterior sampling\n", + "\n", + "visualize_virtual_objective(X.generator, n_samples=1000)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/single_objective_bayes_opt/benchmarking.ipynb b/docs/examples/single_objective_bayes_opt/benchmarking.ipynb new file mode 100644 index 0000000..42d34fd --- /dev/null +++ b/docs/examples/single_objective_bayes_opt/benchmarking.ipynb @@ -0,0 +1,329 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Benchmarking" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-26T22:03:09.729462100Z", + "start_time": "2023-10-26T22:03:06.180990700Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:30.955726Z", + "iopub.status.busy": "2024-09-13T15:56:30.955156Z", + "iopub.status.idle": "2024-09-13T15:56:32.687466Z", + "shell.execute_reply": "2024-09-13T15:56:32.687060Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import pandas as pd\n", + "import torch\n", + "from copy import deepcopy\n", + "from xopt.generators.bayesian import ExpectedImprovementGenerator\n", + "from xopt.generators.bayesian import BayesianExplorationGenerator\n", + "\n", + "from xopt.vocs import VOCS\n", + "\n", + "# Ignore all warnings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"ignore\")\n", + "\n", + "vocs = VOCS(\n", + " variables={\"x\": [0, 1]},\n", + " objectives={\"y\": \"MAXIMIZE\"},\n", + " constraints={\"c\": [\"LESS_THAN\", 0]},\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-26T22:03:09.924488500Z", + "start_time": "2023-10-26T22:03:09.731462500Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:32.689311Z", + "iopub.status.busy": "2024-09-13T15:56:32.689143Z", + "iopub.status.idle": "2024-09-13T15:56:32.772273Z", + "shell.execute_reply": "2024-09-13T15:56:32.772026Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# define test functions\n", + "def y(x):\n", + " return torch.sin(2 * 3.14 * x)\n", + "\n", + "\n", + "def c(x):\n", + " return 10.0 * torch.cos(2 * 3.14 * x + 0.25)\n", + "\n", + "\n", + "test_x = torch.linspace(*torch.tensor(vocs.bounds.flatten()), 100)\n", + "\n", + "# define training data to pass to the generator\n", + "train_x = torch.tensor((0.2, 0.5, 0.6))\n", + "train_y = y(train_x)\n", + "train_c = c(train_x)\n", + "\n", + "data = pd.DataFrame({\"x\": train_x.numpy(), \"y\": train_y.numpy(), \"c\": train_c})\n", + "\n", + "\n", + "def plot_ground_truth():\n", + " fig, ax = plt.subplots()\n", + " ax.plot(test_x, y(test_x), \"--C0\")\n", + " ax.plot(test_x, c(test_x), \"--C1\")\n", + " ax.plot(train_x, train_y, \"oC0\")\n", + " ax.plot(train_x, train_c, \"oC1\")\n", + "\n", + " return ax\n", + "\n", + "\n", + "plot_ground_truth();" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "# Normal Model with Standard transforms and no constraints\n", + "- acquisition function is Expected Improvement" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-26T22:03:10.494462400Z", + "start_time": "2023-10-26T22:03:09.925461800Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:32.793612Z", + "iopub.status.busy": "2024-09-13T15:56:32.793479Z", + "iopub.status.idle": "2024-09-13T15:56:33.235778Z", + "shell.execute_reply": "2024-09-13T15:56:33.235506Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# plot the generator model and acquisition function\n", + "test_vocs = deepcopy(vocs)\n", + "test_vocs.constraints = {}\n", + "generator = ExpectedImprovementGenerator(vocs=test_vocs)\n", + "generator.add_data(data)\n", + "model = generator.train_model()\n", + "fig, ax = generator.visualize_model()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "# Normal Model with Standard transforms and constraints" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-26T22:03:11.274725200Z", + "start_time": "2023-10-26T22:03:10.496461Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:33.237405Z", + "iopub.status.busy": "2024-09-13T15:56:33.237212Z", + "iopub.status.idle": "2024-09-13T15:56:33.558680Z", + "shell.execute_reply": "2024-09-13T15:56:33.558427Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# plot the generator model and acquisition function\n", + "generator = ExpectedImprovementGenerator(vocs=deepcopy(vocs))\n", + "generator.add_data(data)\n", + "model = generator.train_model()\n", + "fig, ax = generator.visualize_model()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-26T22:03:12.144692900Z", + "start_time": "2023-10-26T22:03:11.276691300Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:33.560357Z", + "iopub.status.busy": "2024-09-13T15:56:33.560246Z", + "iopub.status.idle": "2024-09-13T15:56:33.889156Z", + "shell.execute_reply": "2024-09-13T15:56:33.888888Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# plot the generator model and acquisition function\n", + "tvocs = deepcopy(vocs)\n", + "tvocs.constraints = {\"c\": [\"GREATER_THAN\", 0]}\n", + "\n", + "generator = ExpectedImprovementGenerator(vocs=tvocs)\n", + "generator.add_data(data)\n", + "model = generator.train_model()\n", + "fig, ax = generator.visualize_model()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-26T22:03:38.264419300Z", + "start_time": "2023-10-26T22:03:37.355381700Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:33.891272Z", + "iopub.status.busy": "2024-09-13T15:56:33.891143Z", + "iopub.status.idle": "2024-09-13T15:56:34.212847Z", + "shell.execute_reply": "2024-09-13T15:56:34.212564Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# plot the generator model and acquisition function\n", + "test_vocs = deepcopy(vocs)\n", + "test_vocs.objectives = {}\n", + "test_vocs.observables = [\"y\"]\n", + "\n", + "generator = BayesianExplorationGenerator(vocs=test_vocs)\n", + "generator.add_data(data)\n", + "model = generator.train_model()\n", + "fig, ax = generator.visualize_model()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-26T22:04:02.301053Z", + "start_time": "2023-10-26T22:04:01.536716800Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:34.214620Z", + "iopub.status.busy": "2024-09-13T15:56:34.214499Z", + "iopub.status.idle": "2024-09-13T15:56:34.534405Z", + "shell.execute_reply": "2024-09-13T15:56:34.534129Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "generator = BayesianExplorationGenerator(vocs=test_vocs)\n", + "\n", + "data = pd.DataFrame(\n", + " {\"x\": train_x.numpy(), \"y\": train_y.numpy(), \"c\": torch.zeros_like(train_y).numpy()}\n", + ")\n", + "generator.add_data(data)\n", + "model = generator.train_model()\n", + "fig, ax = generator.visualize_model()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-10-26T22:04:02.767052200Z", + "start_time": "2023-10-26T22:04:02.303051200Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:34.536114Z", + "iopub.status.busy": "2024-09-13T15:56:34.536006Z", + "iopub.status.idle": "2024-09-13T15:56:34.711473Z", + "shell.execute_reply": "2024-09-13T15:56:34.711171Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "test_vocs = deepcopy(test_vocs)\n", + "test_vocs.constraints = {}\n", + "generator = BayesianExplorationGenerator(vocs=test_vocs)\n", + "\n", + "data = pd.DataFrame(\n", + " {\"x\": train_x.numpy(), \"y\": train_y.numpy(), \"c\": torch.zeros_like(train_y).numpy()}\n", + ")\n", + "generator.add_data(data)\n", + "model = generator.train_model()\n", + "fig, ax = generator.visualize_model()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/single_objective_bayes_opt/bo_tutorial.ipynb b/docs/examples/single_objective_bayes_opt/bo_tutorial.ipynb new file mode 100644 index 0000000..c4979cb --- /dev/null +++ b/docs/examples/single_objective_bayes_opt/bo_tutorial.ipynb @@ -0,0 +1,360 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Basic Bayesian Optimization\n", + "In this tutorial we demonstrate the use of Xopt to preform Bayesian Optimization on a\n", + " simple test problem." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Define the test problem\n", + "Here we define a simple optimization problem, where we attempt to minimize the sin\n", + "function in the domian [0,2*pi]. Note that the function used to evaluate the\n", + "objective function takes a dictionary as input and returns a dictionary as the output." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-04-21T14:39:38.756460Z", + "start_time": "2023-04-21T14:39:36.916454Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:40.504418Z", + "iopub.status.busy": "2024-09-13T15:54:40.503940Z", + "iopub.status.idle": "2024-09-13T15:54:42.299260Z", + "shell.execute_reply": "2024-09-13T15:54:42.298941Z" + } + }, + "outputs": [], + "source": [ + "from xopt.vocs import VOCS\n", + "from xopt.evaluator import Evaluator\n", + "from xopt.generators.bayesian import UpperConfidenceBoundGenerator\n", + "from xopt import Xopt\n", + "import torch\n", + "import matplotlib.pyplot as plt\n", + "import math\n", + "import numpy as np\n", + "\n", + "# define variables and function objectives\n", + "vocs = VOCS(\n", + " variables={\"x\": [0, 2 * math.pi]},\n", + " objectives={\"f\": \"MINIMIZE\"},\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-04-21T14:39:38.760199Z", + "start_time": "2023-04-21T14:39:38.757155Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:42.301322Z", + "iopub.status.busy": "2024-09-13T15:54:42.301113Z", + "iopub.status.idle": "2024-09-13T15:54:42.303076Z", + "shell.execute_reply": "2024-09-13T15:54:42.302830Z" + } + }, + "outputs": [], + "source": [ + "# define a test function to optimize\n", + "def sin_function(input_dict):\n", + " return {\"f\": np.sin(input_dict[\"x\"])}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Create Xopt objects\n", + "Create the evaluator to evaluate our test function and create a generator that uses\n", + "the Upper Confidence Bound acquisition function to perform Bayesian Optimization." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-04-21T14:39:38.769546Z", + "start_time": "2023-04-21T14:39:38.765875Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:42.304489Z", + "iopub.status.busy": "2024-09-13T15:54:42.304389Z", + "iopub.status.idle": "2024-09-13T15:54:42.309045Z", + "shell.execute_reply": "2024-09-13T15:54:42.308828Z" + } + }, + "outputs": [], + "source": [ + "evaluator = Evaluator(function=sin_function)\n", + "generator = UpperConfidenceBoundGenerator(vocs=vocs)\n", + "X = Xopt(evaluator=evaluator, generator=generator, vocs=vocs)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Generate and evaluate initial points\n", + "To begin optimization, we must generate some random initial data points. The first call\n", + "to `X.step()` will generate and evaluate a number of randomly points specified by the\n", + " generator. Note that if we add data to xopt before calling `X.step()` by assigning\n", + " the data to `X.data`, calls to `X.step()` will ignore the random generation and\n", + " proceed to generating points via Bayesian optimization." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-04-21T14:39:38.780529Z", + "start_time": "2023-04-21T14:39:38.770010Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:42.310443Z", + "iopub.status.busy": "2024-09-13T15:54:42.310351Z", + "iopub.status.idle": "2024-09-13T15:54:42.317339Z", + "shell.execute_reply": "2024-09-13T15:54:42.317108Z" + } + }, + "outputs": [], + "source": [ + "# call X.random_evaluate() to generate + evaluate 3 initial points\n", + "X.random_evaluate(2)\n", + "\n", + "# inspect the gathered data\n", + "X.data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Do bayesian optimization steps\n", + "To perform optimization we simply call `X.step()` in a loop. This allows us to do\n", + "intermediate tasks in between optimization steps, such as examining the model and\n", + "acquisition function at each step (as we demonstrate here)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-04-21T14:39:40.645385Z", + "start_time": "2023-04-21T14:39:38.783110Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:42.337828Z", + "iopub.status.busy": "2024-09-13T15:54:42.337698Z", + "iopub.status.idle": "2024-09-13T15:54:43.800699Z", + "shell.execute_reply": "2024-09-13T15:54:43.800325Z" + } + }, + "outputs": [], + "source": [ + "n_steps = 5\n", + "\n", + "# test points for plotting\n", + "test_x = torch.linspace(*X.vocs.bounds.flatten(), 50).double()\n", + "\n", + "for i in range(n_steps):\n", + " # get the Gaussian process model from the generator\n", + " model = X.generator.train_model()\n", + "\n", + " # get acquisition function from generator\n", + " acq = X.generator.get_acquisition(model)\n", + "\n", + " # calculate model posterior and acquisition function at each test point\n", + " # NOTE: need to add a dimension to the input tensor for evaluating the\n", + " # posterior and another for the acquisition function, see\n", + " # https://botorch.org/docs/batching for details\n", + " # NOTE: we use the `torch.no_grad()` environment to speed up computation by\n", + " # skipping calculations for backpropagation\n", + " with torch.no_grad():\n", + " posterior = model.posterior(test_x.unsqueeze(1))\n", + " acq_val = acq(test_x.reshape(-1, 1, 1))\n", + "\n", + " # get mean function and confidence regions\n", + " mean = posterior.mean\n", + " L, u = posterior.mvn.confidence_region()\n", + "\n", + " # plot model and acquisition function\n", + " fig, ax = plt.subplots(2, 1, sharex=\"all\")\n", + "\n", + " # plot model posterior\n", + " ax[0].plot(test_x, mean, label=\"Posterior mean\")\n", + " ax[0].fill_between(test_x, L, u, alpha=0.25, label=\"Posterior confidence region\")\n", + "\n", + " # add data to model plot\n", + " ax[0].plot(X.data[\"x\"], X.data[\"f\"], \"C1o\", label=\"Training data\")\n", + "\n", + " # plot true function\n", + " true_f = sin_function({\"x\": test_x})[\"f\"]\n", + " ax[0].plot(test_x, true_f, \"--\", label=\"Ground truth\")\n", + "\n", + " # add legend\n", + " ax[0].legend()\n", + "\n", + " # plot acquisition function\n", + " ax[1].plot(test_x, acq_val.flatten())\n", + "\n", + " ax[0].set_ylabel(\"f\")\n", + " ax[1].set_ylabel(r\"$\\alpha(x)$\")\n", + " ax[1].set_xlabel(\"x\")\n", + "\n", + " # do the optimization step\n", + " X.step()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-04-21T14:39:40.650703Z", + "start_time": "2023-04-21T14:39:40.648118Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:43.802317Z", + "iopub.status.busy": "2024-09-13T15:54:43.802139Z", + "iopub.status.idle": "2024-09-13T15:54:43.805912Z", + "shell.execute_reply": "2024-09-13T15:54:43.805655Z" + } + }, + "outputs": [], + "source": [ + "# access the collected data\n", + "X.data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Getting the optimization result\n", + "To get the best point (without evaluating it) we ask the generator to\n", + "predict the optimum based on the posterior mean." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-04-21T14:39:41.043509Z", + "start_time": "2023-04-21T14:39:40.651174Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:43.807331Z", + "iopub.status.busy": "2024-09-13T15:54:43.807236Z", + "iopub.status.idle": "2024-09-13T15:54:43.851867Z", + "shell.execute_reply": "2024-09-13T15:54:43.851631Z" + } + }, + "outputs": [], + "source": [ + "X.generator.get_optimum()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Customizing optimization\n", + "Each generator has a set of options that can be modified to effect optimization behavior" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-04-21T14:39:41.046912Z", + "start_time": "2023-04-21T14:39:41.044438Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:43.853410Z", + "iopub.status.busy": "2024-09-13T15:54:43.853319Z", + "iopub.status.idle": "2024-09-13T15:54:43.856576Z", + "shell.execute_reply": "2024-09-13T15:54:43.856264Z" + } + }, + "outputs": [], + "source": [ + "X.generator.dict()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/single_objective_bayes_opt/constrained_bo_tutorial.ipynb b/docs/examples/single_objective_bayes_opt/constrained_bo_tutorial.ipynb new file mode 100644 index 0000000..57f8406 --- /dev/null +++ b/docs/examples/single_objective_bayes_opt/constrained_bo_tutorial.ipynb @@ -0,0 +1,279 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Constrained Bayesian Optimization\n", + "In this tutorial we demonstrate the use of Xopt to perform Bayesian Optimization on a simple test problem subject to a single constraint." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Define the test problem\n", + "Here we define a simple optimization problem, where we attempt to minimize the sin\n", + "function in the domian [0,2*pi], subject to a cos constraining function." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-07T20:55:57.751261500Z", + "start_time": "2025-01-07T20:55:53.793271Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:22.206398Z", + "iopub.status.busy": "2024-09-13T15:56:22.205993Z", + "iopub.status.idle": "2024-09-13T15:56:23.774772Z", + "shell.execute_reply": "2024-09-13T15:56:23.774246Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "from xopt.evaluator import Evaluator\n", + "from xopt.generators.bayesian import ExpectedImprovementGenerator\n", + "from xopt import Xopt\n", + "from xopt.vocs import VOCS\n", + "\n", + "import time\n", + "import math\n", + "import numpy as np\n", + "\n", + "# Ignore all warnings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"ignore\")\n", + "\n", + "# define variables, function objective and constraining function\n", + "vocs = VOCS(\n", + " variables={\"x\": [0, 2 * math.pi]},\n", + " objectives={\"f\": \"MINIMIZE\"},\n", + " constraints={\"c\": [\"LESS_THAN\", 0]},\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-07T20:55:57.766139300Z", + "start_time": "2025-01-07T20:55:57.738794400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:23.776855Z", + "iopub.status.busy": "2024-09-13T15:56:23.776613Z", + "iopub.status.idle": "2024-09-13T15:56:23.778792Z", + "shell.execute_reply": "2024-09-13T15:56:23.778518Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# define a test function to optimize\n", + "\n", + "\n", + "def test_function(input_dict):\n", + " return {\"f\": np.sin(input_dict[\"x\"]), \"c\": np.cos(input_dict[\"x\"])}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Create Xopt objects\n", + "Create the evaluator to evaluate our test function and create a generator that uses\n", + "the Expected Improvement acquisition function to perform Bayesian Optimization." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-07T20:55:57.766682400Z", + "start_time": "2025-01-07T20:55:57.751261500Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:23.780319Z", + "iopub.status.busy": "2024-09-13T15:56:23.780210Z", + "iopub.status.idle": "2024-09-13T15:56:23.785380Z", + "shell.execute_reply": "2024-09-13T15:56:23.785094Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "evaluator = Evaluator(function=test_function)\n", + "generator = ExpectedImprovementGenerator(vocs=vocs)\n", + "X = Xopt(evaluator=evaluator, generator=generator, vocs=vocs)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Generate and evaluate initial points\n", + "To begin optimization, we must generate some random initial data points. The first call\n", + "to `X.step()` will generate and evaluate a number of randomly points specified by the\n", + " generator. Note that if we add data to xopt before calling `X.step()` by assigning\n", + " the data to `X.data`, calls to `X.step()` will ignore the random generation and\n", + " proceed to generating points via Bayesian optimization." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-07T20:55:57.767710800Z", + "start_time": "2025-01-07T20:55:57.766139300Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:23.786867Z", + "iopub.status.busy": "2024-09-13T15:56:23.786774Z", + "iopub.status.idle": "2024-09-13T15:56:23.849752Z", + "shell.execute_reply": "2024-09-13T15:56:23.849497Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# call X.random_evaluate(n_samples) to generate + evaluate initial points\n", + "X.random_evaluate(n_samples=2)\n", + "\n", + "# inspect the gathered data\n", + "X.data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Do bayesian optimization steps\n", + "To perform optimization we simply call `X.step()` in a loop. This allows us to do\n", + "intermediate tasks in between optimization steps, such as examining the model and\n", + "acquisition function at each step (as we demonstrate here)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-07T20:57:47.398979200Z", + "start_time": "2025-01-07T20:57:37.742437900Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:23.870983Z", + "iopub.status.busy": "2024-09-13T15:56:23.870838Z", + "iopub.status.idle": "2024-09-13T15:56:28.798647Z", + "shell.execute_reply": "2024-09-13T15:56:28.798342Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "n_steps = 5\n", + "\n", + "# test points for plotting\n", + "test_x = np.linspace(*X.vocs.bounds.flatten(), 50)\n", + "\n", + "for i in range(n_steps):\n", + " start = time.perf_counter()\n", + " model = X.generator.train_model()\n", + " fig, ax = X.generator.visualize_model(n_grid=100)\n", + " print(time.perf_counter() - start)\n", + "\n", + " # add ground truth functions to plots\n", + " out = test_function({\"x\": test_x})\n", + " ax[0, 0].plot(test_x, out[\"f\"], \"C0-.\")\n", + " ax[1, 0].plot(test_x, out[\"c\"], \"C2-.\")\n", + "\n", + " # do the optimization step\n", + " X.step()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:56:28.800492Z", + "iopub.status.busy": "2024-09-13T15:56:28.800330Z", + "iopub.status.idle": "2024-09-13T15:56:28.804449Z", + "shell.execute_reply": "2024-09-13T15:56:28.804220Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# access the collected data\n", + "X.data" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/single_objective_bayes_opt/custom_model.ipynb b/docs/examples/single_objective_bayes_opt/custom_model.ipynb new file mode 100644 index 0000000..6e7dfd9 --- /dev/null +++ b/docs/examples/single_objective_bayes_opt/custom_model.ipynb @@ -0,0 +1,327 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "# Custom GP modeling for BO" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:55:44.392214Z", + "iopub.status.busy": "2024-09-13T15:55:44.391949Z", + "iopub.status.idle": "2024-09-13T15:55:46.132932Z", + "shell.execute_reply": "2024-09-13T15:55:46.132592Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "from xopt.generators.bayesian.expected_improvement import ExpectedImprovementGenerator\n", + "from xopt.generators.bayesian.models.standard import StandardModelConstructor\n", + "from gpytorch.kernels import PeriodicKernel, ScaleKernel\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import pandas as pd\n", + "import torch\n", + "from xopt.vocs import VOCS\n", + "\n", + "# Ignore all warnings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"ignore\")\n", + "\n", + "my_vocs = VOCS(\n", + " variables={\"x\": [0, 1]},\n", + " objectives={\"y\": \"MAXIMIZE\"},\n", + " constraints={\"c\": [\"LESS_THAN\", 0]},\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:55:46.134708Z", + "iopub.status.busy": "2024-09-13T15:55:46.134564Z", + "iopub.status.idle": "2024-09-13T15:55:46.211537Z", + "shell.execute_reply": "2024-09-13T15:55:46.211305Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# define test functions\n", + "def y(x):\n", + " return torch.sin(2 * 3.14 * x)\n", + "\n", + "\n", + "def c(x):\n", + " return 5.0 * torch.cos(2 * 3.14 * x + 0.25)\n", + "\n", + "\n", + "test_x = torch.linspace(*torch.tensor(my_vocs.bounds.flatten()), 100)\n", + "\n", + "# define training data to pass to the generator\n", + "train_x = torch.tensor((0.2, 0.5, 0.6))\n", + "train_y = y(train_x)\n", + "train_c = c(train_x)\n", + "\n", + "training_data = pd.DataFrame({\"x\": train_x.numpy(), \"y\": train_y.numpy(), \"c\": train_c})\n", + "\n", + "\n", + "def plot_ground_truth():\n", + " fig, ax = plt.subplots()\n", + " ax.plot(test_x, y(test_x), \"--C0\")\n", + " ax.plot(test_x, c(test_x), \"--C1\")\n", + " ax.plot(train_x, train_y, \"oC0\")\n", + " ax.plot(train_x, train_c, \"oC1\")\n", + "\n", + " return ax\n", + "\n", + "\n", + "plot_ground_truth()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "# Custom kernel definition\n", + "In this example we know that the target optimization function is periodic, so it\n", + "makes sense to use a periodic kernel for the GP model with no noise. Here we define a\n", + "function to create that model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:55:46.213082Z", + "iopub.status.busy": "2024-09-13T15:55:46.212973Z", + "iopub.status.idle": "2024-09-13T15:55:46.364491Z", + "shell.execute_reply": "2024-09-13T15:55:46.364170Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# note the creation of options beforehand\n", + "# specify a periodic kernel for each output (objectives and constraints)\n", + "covar_module = {\"y\": ScaleKernel(PeriodicKernel())}\n", + "gp_constructor = StandardModelConstructor(covar_modules=covar_module)\n", + "generator = ExpectedImprovementGenerator(vocs=my_vocs, gp_constructor=gp_constructor)\n", + "generator" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:55:46.366115Z", + "iopub.status.busy": "2024-09-13T15:55:46.365935Z", + "iopub.status.idle": "2024-09-13T15:55:47.115670Z", + "shell.execute_reply": "2024-09-13T15:55:47.115409Z" + } + }, + "outputs": [], + "source": [ + "# view custom model from data\n", + "generator.add_data(training_data)\n", + "model = generator.train_model()\n", + "\n", + "fig, ax = generator.visualize_model(n_grid=len(test_x))\n", + "# plot ground truth\n", + "ax[0, 0].plot(test_x, y(test_x), \"C0-.\")\n", + "ax[1, 0].plot(test_x, c(test_x), \"C2-.\");" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:55:47.117629Z", + "iopub.status.busy": "2024-09-13T15:55:47.117531Z", + "iopub.status.idle": "2024-09-13T15:55:47.120092Z", + "shell.execute_reply": "2024-09-13T15:55:47.119863Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:55:47.121444Z", + "iopub.status.busy": "2024-09-13T15:55:47.121358Z", + "iopub.status.idle": "2024-09-13T15:55:47.579026Z", + "shell.execute_reply": "2024-09-13T15:55:47.578738Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# get the next point from the generator\n", + "generator.generate(1)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Custom prior mean function\n", + "Here we assume we have some knowledge of the ground truth function, which we can take\n", + " advantage of to speed up optimization. This \"prior mean\" function is specified by a\n", + " pytorch module." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:55:47.580651Z", + "iopub.status.busy": "2024-09-13T15:55:47.580533Z", + "iopub.status.idle": "2024-09-13T15:55:47.582620Z", + "shell.execute_reply": "2024-09-13T15:55:47.582408Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "class ConstraintPrior(torch.nn.Module):\n", + " def forward(self, X):\n", + " return c(X).squeeze(dim=-1)\n", + "\n", + "\n", + "gp_constructor = StandardModelConstructor(mean_modules={\"c\": ConstraintPrior()})\n", + "generator = ExpectedImprovementGenerator(\n", + " vocs=my_vocs,\n", + " gp_constructor=gp_constructor,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:55:47.584014Z", + "iopub.status.busy": "2024-09-13T15:55:47.583912Z", + "iopub.status.idle": "2024-09-13T15:55:47.906696Z", + "shell.execute_reply": "2024-09-13T15:55:47.906433Z" + } + }, + "outputs": [], + "source": [ + "# view custom model from data\n", + "generator.add_data(training_data)\n", + "model = generator.train_model()\n", + "\n", + "fig, ax = generator.visualize_model(n_grid=len(test_x))\n", + "# plot ground truth\n", + "ax[0, 0].plot(test_x, y(test_x), \"C0-.\")\n", + "ax[1, 0].plot(test_x, c(test_x), \"C2-.\");" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:55:47.908422Z", + "iopub.status.busy": "2024-09-13T15:55:47.908299Z", + "iopub.status.idle": "2024-09-13T15:55:47.910794Z", + "shell.execute_reply": "2024-09-13T15:55:47.910537Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:55:47.912149Z", + "iopub.status.busy": "2024-09-13T15:55:47.912052Z", + "iopub.status.idle": "2024-09-13T15:55:47.916181Z", + "shell.execute_reply": "2024-09-13T15:55:47.915789Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "list(model.named_parameters())" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/single_objective_bayes_opt/custom_objective.ipynb b/docs/examples/single_objective_bayes_opt/custom_objective.ipynb new file mode 100644 index 0000000..ad48690 --- /dev/null +++ b/docs/examples/single_objective_bayes_opt/custom_objective.ipynb @@ -0,0 +1,278 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Bayesian Optimization with Custom Objectives\n", + "In this tutorial we demonstrate the use of Xopt to preform Bayesian Optimization on\n", + "custom objectives. In this case, we develop models of individual components of the\n", + "objective function and combine samples from these models to calculate predicted\n", + "objective values.\n", + "\n", + "In this example we try to maximize the objective function\n", + "$$f(g_1(x),g_2(x)) = \\min(g_1(x), g_2(x))$$ where $g_1(x) = (x-0.5)^2$ and $g_2(x) =\n", + "(x - 2)^2$.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Define the test problem" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-07T19:11:05.338818500Z", + "start_time": "2024-06-07T19:11:03.007948400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:42.362599Z", + "iopub.status.busy": "2024-09-13T15:56:42.362284Z", + "iopub.status.idle": "2024-09-13T15:56:43.963796Z", + "shell.execute_reply": "2024-09-13T15:56:43.963288Z" + } + }, + "outputs": [], + "source": [ + "from xopt.vocs import VOCS\n", + "\n", + "import torch\n", + "\n", + "from xopt.evaluator import Evaluator\n", + "from xopt.generators.bayesian import ExpectedImprovementGenerator\n", + "from xopt import Xopt\n", + "from xopt.generators.bayesian.objectives import CustomXoptObjective\n", + "from torch import Tensor\n", + "from typing import Optional\n", + "\n", + "\n", + "# define variables and function objectives\n", + "vocs = VOCS(variables={\"x\": [0.0, 2.0]}, observables=[\"g1\", \"g2\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-07T19:11:05.398577600Z", + "start_time": "2024-06-07T19:11:05.395358400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:43.966070Z", + "iopub.status.busy": "2024-09-13T15:56:43.965821Z", + "iopub.status.idle": "2024-09-13T15:56:43.968101Z", + "shell.execute_reply": "2024-09-13T15:56:43.967794Z" + } + }, + "outputs": [], + "source": [ + "# define a test function to optimize\n", + "\n", + "\n", + "def sin_function(input_dict):\n", + " return {\"g1\": (input_dict[\"x\"]) ** 2, \"g2\": (input_dict[\"x\"] - 2.0) ** 2}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Create Xopt objects\n", + "Create the evaluator to evaluate our test function and create a generator that uses\n", + "the Upper Confidence Bound acquisition function to perform Bayesian Optimization." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-07T19:11:13.023338300Z", + "start_time": "2024-06-07T19:11:05.403548700Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:43.969973Z", + "iopub.status.busy": "2024-09-13T15:56:43.969806Z", + "iopub.status.idle": "2024-09-13T15:56:47.066052Z", + "shell.execute_reply": "2024-09-13T15:56:47.065779Z" + } + }, + "outputs": [], + "source": [ + "class MyObjective(CustomXoptObjective):\n", + " def forward(self, samples: Tensor, X: Optional[Tensor] = None) -> Tensor:\n", + " return torch.min(\n", + " samples[..., self.vocs.output_names.index(\"g1\")],\n", + " samples[..., self.vocs.output_names.index(\"g2\")],\n", + " )\n", + "\n", + "\n", + "evaluator = Evaluator(function=sin_function)\n", + "generator = ExpectedImprovementGenerator(\n", + " vocs=vocs,\n", + " custom_objective=MyObjective(vocs),\n", + ")\n", + "X = Xopt(evaluator=evaluator, generator=generator, vocs=vocs)\n", + "print(X)\n", + "\n", + "X.random_evaluate(2)\n", + "\n", + "n_steps = 5\n", + "\n", + "# test points for plotting\n", + "test_x = torch.linspace(*X.vocs.bounds.flatten(), 50).double()\n", + "\n", + "for i in range(n_steps):\n", + " # get the Gaussian process model from the generator\n", + " model = X.generator.train_model()\n", + "\n", + " X.generator.visualize_model()\n", + "\n", + " # do the optimization step\n", + " X.step()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-07T19:11:13.065201200Z", + "start_time": "2024-06-07T19:11:13.025339800Z" + } + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-07T19:11:13.070201400Z", + "start_time": "2024-06-07T19:11:13.054349Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:47.067639Z", + "iopub.status.busy": "2024-09-13T15:56:47.067467Z", + "iopub.status.idle": "2024-09-13T15:56:47.072846Z", + "shell.execute_reply": "2024-09-13T15:56:47.072630Z" + } + }, + "outputs": [], + "source": [ + "# access the collected data\n", + "X.data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Getting the optimization result\n", + "To get the best point (without evaluating it) we ask the generator to\n", + "predict the optimum based on the posterior mean." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-07T19:11:13.894203Z", + "start_time": "2024-06-07T19:11:13.072204900Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:47.074184Z", + "iopub.status.busy": "2024-09-13T15:56:47.074098Z", + "iopub.status.idle": "2024-09-13T15:56:47.388879Z", + "shell.execute_reply": "2024-09-13T15:56:47.388566Z" + } + }, + "outputs": [], + "source": [ + "X.generator.get_optimum()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Customizing optimization\n", + "Each generator has a set of options that can be modified to effect optimization behavior" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-06-07T19:11:13.968067600Z", + "start_time": "2024-06-07T19:11:13.896722400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:47.390453Z", + "iopub.status.busy": "2024-09-13T15:56:47.390342Z", + "iopub.status.idle": "2024-09-13T15:56:47.393596Z", + "shell.execute_reply": "2024-09-13T15:56:47.393324Z" + } + }, + "outputs": [], + "source": [ + "X.generator.dict()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/single_objective_bayes_opt/fixed_features.ipynb b/docs/examples/single_objective_bayes_opt/fixed_features.ipynb new file mode 100644 index 0000000..01dc240 --- /dev/null +++ b/docs/examples/single_objective_bayes_opt/fixed_features.ipynb @@ -0,0 +1,361 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "# Bayesian optimization with fixed features\n", + "In some contexts a variable/feature needs to be fixed during optimization. However, we\n", + "can leverage previous measurements near the fixed variable value to potentially\n", + "jump-start optimization using observed model covariances established by the GP kernel\n", + ". In this example, we start with a number of random observations in 2D input space\n", + "and then proceed with BO at a fixed value for one of the variables. This notebook\n", + "uses the 2D Rosenbrock test function as an example." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-04-23T14:30:02.733046Z", + "start_time": "2024-04-23T14:29:59.284072800Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:55:10.932053Z", + "iopub.status.busy": "2024-09-13T15:55:10.931840Z", + "iopub.status.idle": "2024-09-13T15:55:12.675171Z", + "shell.execute_reply": "2024-09-13T15:55:12.674838Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# set values if testing\n", + "import os\n", + "\n", + "from xopt.generators.bayesian.visualize import plot_model_prediction\n", + "from xopt import Xopt, Evaluator\n", + "from xopt.generators.bayesian import UpperConfidenceBoundGenerator\n", + "from xopt.resources.test_functions.rosenbrock import (\n", + " evaluate_rosenbrock,\n", + " make_rosenbrock_vocs,\n", + ")\n", + "\n", + "# Ignore all warnings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"ignore\")\n", + "\n", + "\n", + "SMOKE_TEST = os.environ.get(\"SMOKE_TEST\")\n", + "NUM_MC_SAMPLES = 1 if SMOKE_TEST else 128\n", + "NUM_RESTARTS = 1 if SMOKE_TEST else 20\n", + "\n", + "# make rosenbrock function vocs in 2D\n", + "vocs = make_rosenbrock_vocs(2)\n", + "\n", + "# define a fixed value for the BO generator\n", + "fixed_features = {\"x0\": -1.0}\n", + "generator = UpperConfidenceBoundGenerator(vocs=vocs, fixed_features=fixed_features)\n", + "generator.numerical_optimizer.n_restarts = NUM_RESTARTS\n", + "generator.n_monte_carlo_samples = NUM_MC_SAMPLES\n", + "\n", + "evaluator = Evaluator(function=evaluate_rosenbrock)\n", + "\n", + "X = Xopt(generator=generator, evaluator=evaluator, vocs=vocs)\n", + "X" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Generate some initial random samples in 2D space" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-04-23T14:30:02.748083700Z", + "start_time": "2024-04-23T14:30:02.735488400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:55:12.696548Z", + "iopub.status.busy": "2024-09-13T15:55:12.696219Z", + "iopub.status.idle": "2024-09-13T15:55:12.704733Z", + "shell.execute_reply": "2024-09-13T15:55:12.704452Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "X.random_evaluate(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Run BO steps with fixed features" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-04-23T14:30:03.894045900Z", + "start_time": "2024-04-23T14:30:02.751046400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:55:12.706413Z", + "iopub.status.busy": "2024-09-13T15:55:12.706293Z", + "iopub.status.idle": "2024-09-13T15:55:13.268838Z", + "shell.execute_reply": "2024-09-13T15:55:13.268516Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "for i in range(5):\n", + " X.step()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-04-23T14:30:03.909044600Z", + "start_time": "2024-04-23T14:30:03.896046400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:55:13.270560Z", + "iopub.status.busy": "2024-09-13T15:55:13.270428Z", + "iopub.status.idle": "2024-09-13T15:55:13.274756Z", + "shell.execute_reply": "2024-09-13T15:55:13.274532Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "X.data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Visualize model and evaluations\n", + "Note that for the BO samples, they all are on the line $x_0=-1$" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-04-23T14:30:04.947068400Z", + "start_time": "2024-04-23T14:30:03.915044900Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:55:13.276113Z", + "iopub.status.busy": "2024-09-13T15:55:13.276022Z", + "iopub.status.idle": "2024-09-13T15:55:13.661335Z", + "shell.execute_reply": "2024-09-13T15:55:13.661053Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "ax = plot_model_prediction(\n", + " model=X.generator.model,\n", + " vocs=X.vocs,\n", + " data=X.data,\n", + " show_samples=False,\n", + " n_grid=100,\n", + ")\n", + "ax.plot(\n", + " *X.data[[\"x0\", \"x1\"]].to_numpy()[:10].T, \"+C1\", label=\"random samples\", zorder=10\n", + ")\n", + "ax.plot(*X.data[[\"x0\", \"x1\"]].to_numpy()[10:].T, \"+C3\", label=\"GP samples\", zorder=10)\n", + "ax.axvline(-1.0, ls=\"--\")\n", + "ax.legend();" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Run with fixed feature that is not in vocs\n", + "We can also run fixed features where the fixed variable is not listed in vocs, as\n", + "long as the generator data contains data corresponding to the fixed feature name. To\n", + "satisfy this requirements we add the data from the last optimization run." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-04-23T14:30:04.962067400Z", + "start_time": "2024-04-23T14:30:04.948069800Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:55:13.663054Z", + "iopub.status.busy": "2024-09-13T15:55:13.662884Z", + "iopub.status.idle": "2024-09-13T15:55:13.664825Z", + "shell.execute_reply": "2024-09-13T15:55:13.664561Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# make rosenbrock function vocs in 2-D but remove the `x0` name (set to a fixed\n", + "# feature in the next cell)\n", + "vocs = make_rosenbrock_vocs(2)\n", + "vocs.variables = {\"x1\": [-2.0, 2.0]}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-04-23T14:30:04.995067700Z", + "start_time": "2024-04-23T14:30:04.964067300Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:55:13.666096Z", + "iopub.status.busy": "2024-09-13T15:55:13.666007Z", + "iopub.status.idle": "2024-09-13T15:55:13.668895Z", + "shell.execute_reply": "2024-09-13T15:55:13.668681Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# define a fixed value for the BO generator\n", + "fixed_features = {\"x0\": -1.0}\n", + "generator = UpperConfidenceBoundGenerator(vocs=vocs, fixed_features=fixed_features)\n", + "generator.numerical_optimizer.n_restarts = NUM_RESTARTS\n", + "generator.n_monte_carlo_samples = NUM_MC_SAMPLES\n", + "\n", + "evaluator = Evaluator(function=evaluate_rosenbrock)\n", + "\n", + "X2 = Xopt(generator=generator, evaluator=evaluator, vocs=vocs)\n", + "X2.add_data(X.data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-04-23T14:30:05.217066800Z", + "start_time": "2024-04-23T14:30:04.978069700Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:55:13.670154Z", + "iopub.status.busy": "2024-09-13T15:55:13.670055Z", + "iopub.status.idle": "2024-09-13T15:55:13.751792Z", + "shell.execute_reply": "2024-09-13T15:55:13.751511Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# run an optimization step\n", + "X2.step()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-04-23T14:30:05.231068700Z", + "start_time": "2024-04-23T14:30:05.218066400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:55:13.753432Z", + "iopub.status.busy": "2024-09-13T15:55:13.753336Z", + "iopub.status.idle": "2024-09-13T15:55:13.757642Z", + "shell.execute_reply": "2024-09-13T15:55:13.757403Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "X2.data" + ] + } + ], + "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.12.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/single_objective_bayes_opt/hessian_kernel.ipynb b/docs/examples/single_objective_bayes_opt/hessian_kernel.ipynb new file mode 100644 index 0000000..7bbfba9 --- /dev/null +++ b/docs/examples/single_objective_bayes_opt/hessian_kernel.ipynb @@ -0,0 +1,174 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "# Bayesian Optimization with a Hessian\n", + "Here we demonstrate the use of a Hessian matrix to estimate the kernel." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Specifiying generator options\n", + "We start with the generator defaults and add a hessian kernel to the model. This also\n", + " requires specifying that we will not normalize inputs to the GP model. Note: this\n", + " can potentially mess up training of other hyperparameters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-01-30T18:13:12.173225200Z", + "start_time": "2024-01-30T18:13:08.389096400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:36.873445Z", + "iopub.status.busy": "2024-09-13T15:56:36.873193Z", + "iopub.status.idle": "2024-09-13T15:56:38.531203Z", + "shell.execute_reply": "2024-09-13T15:56:38.530904Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# set values if testing\n", + "import os\n", + "import torch\n", + "from copy import deepcopy\n", + "from xopt import Xopt, Evaluator\n", + "from xopt.generators.bayesian import UpperConfidenceBoundGenerator\n", + "from xopt.generators.bayesian.models.standard import StandardModelConstructor\n", + "from xopt.generators.bayesian.custom_botorch.hessian_kernel import HessianRBF\n", + "from gpytorch.kernels import ScaleKernel\n", + "from xopt.resources.test_functions.tnk import evaluate_TNK, tnk_vocs\n", + "\n", + "# Ignore all warnings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"ignore\")\n", + "\n", + "SMOKE_TEST = os.environ.get(\"SMOKE_TEST\")\n", + "NUM_MC_SAMPLES = 1 if SMOKE_TEST else 128\n", + "NUM_RESTARTS = 1 if SMOKE_TEST else 20\n", + "\n", + "vocs = deepcopy(tnk_vocs)\n", + "vocs.objectives = {\"y2\": \"MINIMIZE\"}\n", + "\n", + "# define a custom kernel and create the model constructor\n", + "hessian_matrix = torch.tensor([[1, -0.8], [-0.8, 1]]).double()\n", + "kernel = ScaleKernel(HessianRBF(hessian_matrix))\n", + "gp_constructor = StandardModelConstructor(covar_modules={\"y2\": kernel})\n", + "\n", + "\n", + "generator = UpperConfidenceBoundGenerator(vocs=vocs, gp_constructor=gp_constructor)\n", + "generator.numerical_optimizer.n_restarts = NUM_RESTARTS\n", + "generator.n_monte_carlo_samples = NUM_MC_SAMPLES\n", + "\n", + "evaluator = Evaluator(function=evaluate_TNK)\n", + "\n", + "X = Xopt(generator=generator, evaluator=evaluator, vocs=vocs)\n", + "X" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Evaluate explict points and view model\n", + "We start with evaluating 2 points that we know satisfy the constraints. Note the\n", + "cross correlations between x1 and x2 due to the Hessian kernel." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-01-30T18:13:47.228144100Z", + "start_time": "2024-01-30T18:13:44.084105500Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:38.552397Z", + "iopub.status.busy": "2024-09-13T15:56:38.552210Z", + "iopub.status.idle": "2024-09-13T15:56:40.162265Z", + "shell.execute_reply": "2024-09-13T15:56:40.161946Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "X.evaluate_data({\"x1\": [1.0, 0.75], \"x2\": [1.0, 2.0]})\n", + "X.generator.train_model()\n", + "fig, ax = X.generator.visualize_model(show_feasibility=True, n_grid=100)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-01-30T18:13:16.075775100Z", + "start_time": "2024-01-30T18:13:16.040771500Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:40.165128Z", + "iopub.status.busy": "2024-09-13T15:56:40.164983Z", + "iopub.status.idle": "2024-09-13T15:56:40.170771Z", + "shell.execute_reply": "2024-09-13T15:56:40.170544Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "X.data" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/single_objective_bayes_opt/heteroskedastic_noise_tutorial.ipynb b/docs/examples/single_objective_bayes_opt/heteroskedastic_noise_tutorial.ipynb new file mode 100644 index 0000000..b9fbcdc --- /dev/null +++ b/docs/examples/single_objective_bayes_opt/heteroskedastic_noise_tutorial.ipynb @@ -0,0 +1,362 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Bayesian Optimization with Heteroskedastic Noise GP Modeling\n", + "In this tutorial we demonstrate the use of Xopt to preform Bayesian Optimization on a\n", + " simple test problem. The problem exibits non-uniform (heteroskedastic) noise which\n", + " we account for in the GP model. This requires explicit specification of the\n", + " measurement variance." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Define the test problem\n", + "Here we define a simple optimization problem, where we attempt to minimize the sin\n", + "function in the domian [0,2*pi]. Note that the function used to evaluate the\n", + "objective function takes a dictionary as input and returns a dictionary as the output." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-07T21:28:39.921005100Z", + "start_time": "2023-09-07T21:28:24.293120800Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:49.730834Z", + "iopub.status.busy": "2024-09-13T15:56:49.730533Z", + "iopub.status.idle": "2024-09-13T15:56:51.293807Z", + "shell.execute_reply": "2024-09-13T15:56:51.293378Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "from xopt.vocs import VOCS\n", + "from xopt.evaluator import Evaluator\n", + "from xopt.generators.bayesian import UpperConfidenceBoundGenerator\n", + "from xopt import Xopt\n", + "import math\n", + "import numpy as np\n", + "import torch\n", + "\n", + "# define variables and function objectives\n", + "vocs = VOCS(\n", + " variables={\"x\": [0, 2 * math.pi]},\n", + " objectives={\"f\": \"MINIMIZE\"},\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Specifying measurement variance\n", + "We specify variance in the objective function by appending `_var` to it. This info\n", + "will collected by the model constructor to make a heteroskedastic model." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-07T21:28:39.935173300Z", + "start_time": "2023-09-07T21:28:39.924004400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:51.295804Z", + "iopub.status.busy": "2024-09-13T15:56:51.295584Z", + "iopub.status.idle": "2024-09-13T15:56:51.297605Z", + "shell.execute_reply": "2024-09-13T15:56:51.297393Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# define a test function to optimize\n", + "# the test function also returns an estimation of the variance, which is\n", + "# used to create a Heteroskedastic noise model for the gp\n", + "\n", + "\n", + "def sin_function(input_dict):\n", + " return {\"f\": np.sin(input_dict[\"x\"]), \"f_var\": 0.001 * input_dict[\"x\"]}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Create Xopt objects\n", + "Create the evaluator to evaluate our test function and create a generator that uses\n", + "the Upper Confidence Bound acquisition function to perform Bayesian Optimization." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-07T21:28:39.952164900Z", + "start_time": "2023-09-07T21:28:39.939194600Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:51.299041Z", + "iopub.status.busy": "2024-09-13T15:56:51.298940Z", + "iopub.status.idle": "2024-09-13T15:56:51.303791Z", + "shell.execute_reply": "2024-09-13T15:56:51.303548Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "evaluator = Evaluator(function=sin_function)\n", + "generator = UpperConfidenceBoundGenerator(vocs=vocs)\n", + "X = Xopt(evaluator=evaluator, generator=generator, vocs=vocs)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Generate and evaluate initial points\n", + "To begin optimization, we must generate some random initial data points. The first call\n", + "to `X.step()` will generate and evaluate a number of randomly points specified by the\n", + " generator. Note that if we add data to xopt before calling `X.step()` by assigning\n", + " the data to `X.data`, calls to `X.step()` will ignore the random generation and\n", + " proceed to generating points via Bayesian optimization." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-07T21:28:39.999187800Z", + "start_time": "2023-09-07T21:28:39.953166Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:51.305166Z", + "iopub.status.busy": "2024-09-13T15:56:51.305077Z", + "iopub.status.idle": "2024-09-13T15:56:51.372282Z", + "shell.execute_reply": "2024-09-13T15:56:51.371871Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# call X.random_evaluate() to generate + evaluate 3 initial points\n", + "X.random_evaluate(4)\n", + "\n", + "# inspect the gathered data\n", + "X.data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Do bayesian optimization steps\n", + "To perform optimization we simply call `X.step()` in a loop. This allows us to do\n", + "intermediate tasks in between optimization steps, such as examining the model and\n", + "acquisition function at each step (as we demonstrate here)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:56:51.394471Z", + "iopub.status.busy": "2024-09-13T15:56:51.394306Z", + "iopub.status.idle": "2024-09-13T15:56:55.128341Z", + "shell.execute_reply": "2024-09-13T15:56:55.128036Z" + } + }, + "outputs": [], + "source": [ + "n_steps = 5\n", + "\n", + "# test points for plotting\n", + "test_x = torch.linspace(*X.vocs.bounds.flatten(), 50).double()\n", + "\n", + "for i in range(n_steps):\n", + " # get the Gaussian process model from the generator\n", + " model = X.generator.train_model()\n", + "\n", + " # visualize model\n", + " fig, ax = X.generator.visualize_model(n_grid=len(test_x))\n", + "\n", + " # plot true function\n", + " true_f = sin_function({\"x\": test_x})[\"f\"]\n", + " ax[0, 0].plot(test_x, true_f, \"C1--\")\n", + "\n", + " # do the optimization step\n", + " X.step()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-07T21:28:51.486309800Z", + "start_time": "2023-09-07T21:28:51.468312900Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:55.129917Z", + "iopub.status.busy": "2024-09-13T15:56:55.129742Z", + "iopub.status.idle": "2024-09-13T15:56:55.134195Z", + "shell.execute_reply": "2024-09-13T15:56:55.133941Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# access the collected data\n", + "X.data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Getting the optimization result\n", + "To get the best point (without evaluating it) we ask the generator to\n", + "predict the optimum based on the posterior mean." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-07T21:28:51.561309500Z", + "start_time": "2023-09-07T21:28:51.483311900Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:55.135637Z", + "iopub.status.busy": "2024-09-13T15:56:55.135552Z", + "iopub.status.idle": "2024-09-13T15:56:55.162709Z", + "shell.execute_reply": "2024-09-13T15:56:55.162466Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "X.generator.get_optimum()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Customizing optimization\n", + "Each generator has a set of options that can be modified to effect optimization behavior" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-07T21:28:51.586309700Z", + "start_time": "2023-09-07T21:28:51.562311500Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:55.164214Z", + "iopub.status.busy": "2024-09-13T15:56:55.164125Z", + "iopub.status.idle": "2024-09-13T15:56:55.167266Z", + "shell.execute_reply": "2024-09-13T15:56:55.167035Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "X.generator.dict()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/single_objective_bayes_opt/interpolate_tutorial.ipynb b/docs/examples/single_objective_bayes_opt/interpolate_tutorial.ipynb new file mode 100644 index 0000000..3ec93a1 --- /dev/null +++ b/docs/examples/single_objective_bayes_opt/interpolate_tutorial.ipynb @@ -0,0 +1,340 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Bayesian Optimization w/ Interpolated Samples\n", + "In some situations, the process of evaluating objectives and constraints\n", + "consumes fewer resources compared to the computational demands associated\n", + "with Bayesian Optimization (BO) decision-making. Particularly, when\n", + "making subtle changes to parameters during optimization, the cost of\n", + "assessing objectives becomes notably more affordable.\n", + "\n", + "Consider a practical example: the optimization of magnet parameters in an\n", + "accelerator with the goal of either minimizing the beam spot size on a\n", + "screen or maximizing the Free Electron Laser (FEL) pulse energy. In such\n", + "cases, where adjustments to accelerator parameters are frequent, it proves\n", + "beneficial to augment the dataset by implementing multiple smaller changes\n", + "to the parameters. These adjustments are followed by quick measurements of\n", + "the objective in between the parameter changes guided by BO.\n", + "\n", + "This approach, although introducing a slight slowdown to the BO process\n", + "due to the addition of extra training points, it will expedite\n", + "convergence for most problems. It offers a more efficient alternative compared to the\n", + "strategy of measuring the same points multiple times in noisy environments.\n", + "The rationale here is that the exploration of a broader parameter space\n", + "through numerous smaller changes enhances the overall understanding of\n", + "the system's behavior, potentially leading to a more efficient and\n", + "effective optimization process.\n", + "\n", + "NOTE: This only works for serialized problems.\n", + "\n", + "WARNING: The interpolated points may violate constraints! Do not use this feature in\n", + "problems where safety is critical." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Define the test problem\n", + "Here we define a simple optimization problem, where we attempt to minimize the sphere\n", + " function." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-11-18T21:45:33.751558100Z", + "start_time": "2023-11-18T21:45:31.381879Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:46.023291Z", + "iopub.status.busy": "2024-09-13T15:54:46.023005Z", + "iopub.status.idle": "2024-09-13T15:54:47.479491Z", + "shell.execute_reply": "2024-09-13T15:54:47.479086Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "from xopt.vocs import VOCS\n", + "from xopt.evaluator import Evaluator\n", + "from xopt.generators.bayesian import ExpectedImprovementGenerator\n", + "from xopt import Xopt\n", + "\n", + "# define variables and function objectives\n", + "vocs = VOCS(\n", + " variables={\"x1\": [-1, 1], \"x2\": [-1, 1]},\n", + " objectives={\"f\": \"MINIMIZE\"},\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-11-18T21:45:33.765546400Z", + "start_time": "2023-11-18T21:45:33.753560100Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:47.481445Z", + "iopub.status.busy": "2024-09-13T15:54:47.481204Z", + "iopub.status.idle": "2024-09-13T15:54:47.483361Z", + "shell.execute_reply": "2024-09-13T15:54:47.483100Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# define a test function to optimize\n", + "\n", + "\n", + "def sphere_function(input_dict):\n", + " return {\"f\": input_dict[\"x1\"] ** 2 + input_dict[\"x2\"] ** 2}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Create Xopt objects\n", + "Create the evaluator to evaluate our test function and create a generator that uses\n", + "the Upper Confidence Bound acquisition function to perform Bayesian Optimization. We\n", + "additionally specify `n_interpolate_points` to be non-zero such that the generator\n", + "proposes interpolated points during generation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-11-18T21:45:34.728144600Z", + "start_time": "2023-11-18T21:45:33.767547200Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:47.484885Z", + "iopub.status.busy": "2024-09-13T15:54:47.484774Z", + "iopub.status.idle": "2024-09-13T15:54:47.489934Z", + "shell.execute_reply": "2024-09-13T15:54:47.489693Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# define a generator that uses 5 interpolation points during sampling\n", + "generator = ExpectedImprovementGenerator(vocs=vocs, n_interpolate_points=5)\n", + "generator.gp_constructor.use_low_noise_prior = True\n", + "\n", + "\n", + "evaluator = Evaluator(function=sphere_function)\n", + "X = Xopt(evaluator=evaluator, generator=generator, vocs=vocs)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Generate and evaluate initial points\n", + "To begin optimization, we must generate some random initial data points. The first call\n", + "to `X.step()` will generate and evaluate a number of randomly points specified by the\n", + " generator. Note that if we add data to xopt before calling `X.step()` by assigning\n", + " the data to `X.data`, calls to `X.step()` will ignore the random generation and\n", + " proceed to generating points via Bayesian optimization." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-11-18T21:45:34.771145400Z", + "start_time": "2023-11-18T21:45:34.729146100Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:47.491291Z", + "iopub.status.busy": "2024-09-13T15:54:47.491210Z", + "iopub.status.idle": "2024-09-13T15:54:47.550723Z", + "shell.execute_reply": "2024-09-13T15:54:47.550314Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# call X.random_evaluate() to generate + evaluate initial points\n", + "X.random_evaluate(2)\n", + "\n", + "# inspect the gathered data\n", + "X.data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Do bayesian optimization steps\n", + "To perform optimization we simply call `X.step()` in a loop. This allows us to do\n", + "intermediate tasks in between optimization steps, such as examining the model and\n", + "acquisition function at each step (as we demonstrate here)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-11-18T21:45:42.637209400Z", + "start_time": "2023-11-18T21:45:34.762146600Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:47.571375Z", + "iopub.status.busy": "2024-09-13T15:54:47.571234Z", + "iopub.status.idle": "2024-09-13T15:54:51.014558Z", + "shell.execute_reply": "2024-09-13T15:54:51.014298Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "X.generator.train_model()\n", + "X.generator.visualize_model(n_grid=50)\n", + "\n", + "n_steps = 5\n", + "for i in range(n_steps):\n", + " print(i)\n", + " # do the optimization step\n", + " X.step()\n", + "\n", + " # train the model and visualize\n", + " X.generator.train_model()\n", + " fig, ax = X.generator.visualize_model(n_grid=50)\n", + "\n", + " # add the ground truth minimum location\n", + " for a in ax.flatten()[:-1]:\n", + " a.plot(0, 0, \"x\", c=\"red\", ms=10)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-11-18T21:45:42.652179200Z", + "start_time": "2023-11-18T21:45:42.634209700Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:51.016157Z", + "iopub.status.busy": "2024-09-13T15:54:51.015990Z", + "iopub.status.idle": "2024-09-13T15:54:51.021195Z", + "shell.execute_reply": "2024-09-13T15:54:51.020958Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# access the collected data\n", + "X.data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Getting the optimization result\n", + "To get the best point (without evaluating it) we ask the generator to\n", + "predict the optimum based on the posterior mean." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-11-18T21:45:42.760178900Z", + "start_time": "2023-11-18T21:45:42.649210Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:51.022662Z", + "iopub.status.busy": "2024-09-13T15:54:51.022555Z", + "iopub.status.idle": "2024-09-13T15:54:51.038185Z", + "shell.execute_reply": "2024-09-13T15:54:51.037920Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "X.generator.get_optimum()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/single_objective_bayes_opt/multi_fidelity_simple.ipynb b/docs/examples/single_objective_bayes_opt/multi_fidelity_simple.ipynb new file mode 100644 index 0000000..d923cfe --- /dev/null +++ b/docs/examples/single_objective_bayes_opt/multi_fidelity_simple.ipynb @@ -0,0 +1,405 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "# Multi-fidelity BO\n", + "Here we demonstrate how Multi-Fidelity Bayesian Optimization can be used to reduce\n", + "the computational cost of optimization by using lower fidelity surrogate models. The\n", + "goal is to learn functional dependance of the objective on input variables at low\n", + "fidelities (which are cheap to compute) and use that information to quickly find the\n", + "best objective value at higher fidelities (which are more expensive to compute). This\n", + "assumes that there is some learnable correlation between the objective values at\n", + "different fidelities.\n", + "\n", + "Xopt implements the MOMF (https://botorch.org/tutorials/Multi_objective_multi_fidelity_BO)\n", + "algorithm which can be used to solve both single (this notebook) and multi-objective\n", + "(see multi-objective BO section) multi-fidelity problems. Under the hood this\n", + "algorithm attempts to solve a multi-objective optimization problem, where one\n", + "objective is the function objective and the other is a simple fidelity objective,\n", + "weighted by the ```cost_function``` of evaluating the objective at a given fidelity." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T20:56:37.715486400Z", + "start_time": "2023-09-06T20:56:34.287048500Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:53.299994Z", + "iopub.status.busy": "2024-09-13T15:54:53.299702Z", + "iopub.status.idle": "2024-09-13T15:54:55.104406Z", + "shell.execute_reply": "2024-09-13T15:54:55.104054Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "from xopt.generators.bayesian import MultiFidelityGenerator\n", + "from xopt import Evaluator, Xopt\n", + "from xopt import VOCS\n", + "import os\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import math\n", + "\n", + "import pandas as pd\n", + "\n", + "\n", + "# Ignore all warnings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"ignore\")\n", + "\n", + "SMOKE_TEST = os.environ.get(\"SMOKE_TEST\")\n", + "N_MC_SAMPLES = 1 if SMOKE_TEST else 128\n", + "N_RESTARTS = 1 if SMOKE_TEST else 20\n", + "\n", + "\n", + "def test_function(input_dict):\n", + " x = input_dict[\"x\"]\n", + " s = input_dict[\"s\"]\n", + " return {\"f\": np.sin(x + (1.0 - s)) * np.exp((-s + 1) / 2)}\n", + "\n", + "\n", + "# define vocs\n", + "\n", + "\n", + "vocs = VOCS(\n", + " variables={\n", + " \"x\": [0, 2 * math.pi],\n", + " },\n", + " objectives={\"f\": \"MINIMIZE\"},\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## plot the test function in input + fidelity space\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T20:56:37.926455800Z", + "start_time": "2023-09-06T20:56:37.717426100Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:55.106249Z", + "iopub.status.busy": "2024-09-13T15:54:55.106085Z", + "iopub.status.idle": "2024-09-13T15:54:55.204449Z", + "shell.execute_reply": "2024-09-13T15:54:55.204188Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "test_x = np.linspace(*vocs.bounds, 1000)\n", + "fidelities = [0.0, 0.5, 1.0]\n", + "\n", + "fig, ax = plt.subplots()\n", + "for ele in fidelities:\n", + " f = test_function({\"x\": test_x, \"s\": ele})[\"f\"]\n", + " ax.plot(test_x, f, label=f\"s:{ele}\")\n", + "\n", + "ax.legend()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T20:56:37.956437100Z", + "start_time": "2023-09-06T20:56:37.938740600Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:55.205929Z", + "iopub.status.busy": "2024-09-13T15:54:55.205819Z", + "iopub.status.idle": "2024-09-13T15:54:55.213771Z", + "shell.execute_reply": "2024-09-13T15:54:55.213524Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# create xopt object\n", + "# get and modify default generator options\n", + "generator = MultiFidelityGenerator(vocs=vocs)\n", + "\n", + "# specify a custom cost function based on the fidelity parameter\n", + "generator.cost_function = lambda s: s + 0.001\n", + "\n", + "generator.numerical_optimizer.n_restarts = N_RESTARTS\n", + "generator.n_monte_carlo_samples = N_MC_SAMPLES\n", + "\n", + "# pass options to the generator\n", + "evaluator = Evaluator(function=test_function)\n", + "\n", + "X = Xopt(vocs=vocs, generator=generator, evaluator=evaluator)\n", + "X" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T20:56:38.014457500Z", + "start_time": "2023-09-06T20:56:37.958428200Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:55.215114Z", + "iopub.status.busy": "2024-09-13T15:54:55.215029Z", + "iopub.status.idle": "2024-09-13T15:54:55.221521Z", + "shell.execute_reply": "2024-09-13T15:54:55.221288Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# evaluate initial points at mixed fidelities to seed optimization\n", + "X.evaluate_data(\n", + " pd.DataFrame({\"x\": [math.pi / 4, math.pi / 2.0, math.pi], \"s\": [0.0, 0.25, 0.0]})\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T20:56:38.044457900Z", + "start_time": "2023-09-06T20:56:37.988488800Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:55.222934Z", + "iopub.status.busy": "2024-09-13T15:54:55.222847Z", + "iopub.status.idle": "2024-09-13T15:54:55.226410Z", + "shell.execute_reply": "2024-09-13T15:54:55.226157Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# get the total cost of previous observations based on the cost function\n", + "X.generator.calculate_total_cost()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T20:57:03.359514100Z", + "start_time": "2023-09-06T20:56:38.003459300Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:54:55.227712Z", + "iopub.status.busy": "2024-09-13T15:54:55.227634Z", + "iopub.status.idle": "2024-09-13T15:55:07.587383Z", + "shell.execute_reply": "2024-09-13T15:55:07.587046Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# run optimization until the cost budget is exhausted\n", + "# we subtract one unit to make sure we don't go over our eval budget\n", + "budget = 10\n", + "while X.generator.calculate_total_cost() < budget - 1:\n", + " X.step()\n", + " print(\n", + " f\"n_samples: {len(X.data)} \"\n", + " f\"budget used: {X.generator.calculate_total_cost():.4} \"\n", + " f\"hypervolume: {X.generator.calculate_hypervolume():.4}\"\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T20:57:03.374517700Z", + "start_time": "2023-09-06T20:57:03.360514500Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:55:07.589480Z", + "iopub.status.busy": "2024-09-13T15:55:07.589294Z", + "iopub.status.idle": "2024-09-13T15:55:07.593868Z", + "shell.execute_reply": "2024-09-13T15:55:07.593600Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "X.data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Plot the model prediction and acquisition function inside the optimization space" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:55:07.595261Z", + "iopub.status.busy": "2024-09-13T15:55:07.595169Z", + "iopub.status.idle": "2024-09-13T15:55:08.375196Z", + "shell.execute_reply": "2024-09-13T15:55:08.374740Z" + } + }, + "outputs": [], + "source": [ + "fig, ax = X.generator.visualize_model()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Plot the Pareto front" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T20:57:04.728516600Z", + "start_time": "2023-09-06T20:57:04.588518700Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:55:08.377272Z", + "iopub.status.busy": "2024-09-13T15:55:08.377169Z", + "iopub.status.idle": "2024-09-13T15:55:08.456231Z", + "shell.execute_reply": "2024-09-13T15:55:08.455749Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "X.data.plot(x=\"f\", y=\"s\", style=\"o-\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T20:57:04.748515900Z", + "start_time": "2023-09-06T20:57:04.728516600Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:55:08.458039Z", + "iopub.status.busy": "2024-09-13T15:55:08.457920Z", + "iopub.status.idle": "2024-09-13T15:55:08.462314Z", + "shell.execute_reply": "2024-09-13T15:55:08.462030Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "X.data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2023-09-06T20:57:04.959513900Z", + "start_time": "2023-09-06T20:57:04.744515900Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:55:08.463848Z", + "iopub.status.busy": "2024-09-13T15:55:08.463748Z", + "iopub.status.idle": "2024-09-13T15:55:08.526992Z", + "shell.execute_reply": "2024-09-13T15:55:08.526656Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# get optimal value at max fidelity, note that the actual maximum is 4.71\n", + "X.generator.get_optimum().to_dict()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/single_objective_bayes_opt/noisy_bo_tutorial.ipynb b/docs/examples/single_objective_bayes_opt/noisy_bo_tutorial.ipynb new file mode 100644 index 0000000..846f826 --- /dev/null +++ b/docs/examples/single_objective_bayes_opt/noisy_bo_tutorial.ipynb @@ -0,0 +1,360 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Basic Bayesian Optimization\n", + "In this tutorial we demonstrate the use of Xopt to preform Bayesian Optimization on a\n", + " simple test problem." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Define the test problem\n", + "Here we define a simple optimization problem, where we attempt to minimize the sin\n", + "function in the domian [0,2*pi]. Note that the function used to evaluate the\n", + "objective function takes a dictionary as input and returns a dictionary as the output." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-07T20:49:46.118951500Z", + "start_time": "2025-01-07T20:49:42.123425200Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:55:50.083190Z", + "iopub.status.busy": "2024-09-13T15:55:50.082843Z", + "iopub.status.idle": "2024-09-13T15:55:51.596445Z", + "shell.execute_reply": "2024-09-13T15:55:51.596131Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "import torch\n", + "\n", + "from xopt.evaluator import Evaluator\n", + "from xopt.generators.bayesian import UpperConfidenceBoundGenerator\n", + "from xopt.generators.bayesian.models.standard import StandardModelConstructor\n", + "from xopt import Xopt\n", + "\n", + "from xopt.vocs import VOCS\n", + "\n", + "import numpy as np\n", + "import math\n", + "\n", + "# define variables and function objectives\n", + "vocs = VOCS(\n", + " variables={\"x\": [0, 2 * math.pi]},\n", + " objectives={\"f\": \"MINIMIZE\"},\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-07T20:49:46.123953400Z", + "start_time": "2025-01-07T20:49:46.121373300Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:55:51.598253Z", + "iopub.status.busy": "2024-09-13T15:55:51.598093Z", + "iopub.status.idle": "2024-09-13T15:55:51.600089Z", + "shell.execute_reply": "2024-09-13T15:55:51.599835Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# define a test function to optimize\n", + "def sin_function(input_dict):\n", + " # return multiple noisy measurements\n", + " return {\n", + " \"f\": np.sin(input_dict[\"x\"]) + 0.5 * np.random.randn(5),\n", + " \"dummy\": np.random.randn(5),\n", + " }" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Create Xopt objects\n", + "Create the evaluator to evaluate our test function and create a generator that uses\n", + "the Upper Confidence Bound acquisition function to perform Bayesian Optimization." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-07T20:49:46.137486900Z", + "start_time": "2025-01-07T20:49:46.125473400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:55:51.601542Z", + "iopub.status.busy": "2024-09-13T15:55:51.601443Z", + "iopub.status.idle": "2024-09-13T15:55:51.605824Z", + "shell.execute_reply": "2024-09-13T15:55:51.605620Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "evaluator = Evaluator(function=sin_function)\n", + "gp_constructor = StandardModelConstructor(use_low_noise_prior=False)\n", + "generator = UpperConfidenceBoundGenerator(vocs=vocs, gp_constructor=gp_constructor)\n", + "X = Xopt(evaluator=evaluator, generator=generator, vocs=vocs)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Generate and evaluate initial points\n", + "To begin optimization, we must generate some random initial data points. The first call\n", + "to `X.step()` will generate and evaluate a number of randomly points specified by the\n", + " generator. Note that if we add data to xopt before calling `X.step()` by assigning\n", + " the data to `X.data`, calls to `X.step()` will ignore the random generation and\n", + " proceed to generating points via Bayesian optimization." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-07T20:49:46.162503700Z", + "start_time": "2025-01-07T20:49:46.138488Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:55:51.607170Z", + "iopub.status.busy": "2024-09-13T15:55:51.607083Z", + "iopub.status.idle": "2024-09-13T15:55:51.616050Z", + "shell.execute_reply": "2024-09-13T15:55:51.615821Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# call X.random_evaluate() to generate + evaluate 3 initial points\n", + "X.random_evaluate(2)\n", + "\n", + "# inspect the gathered data\n", + "X.data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Do bayesian optimization steps\n", + "To perform optimization we simply call `X.step()` in a loop. This allows us to do\n", + "intermediate tasks in between optimization steps, such as examining the model and\n", + "acquisition function at each step (as we demonstrate here)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-07T20:53:34.584075700Z", + "start_time": "2025-01-07T20:53:31.873077Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:55:51.637303Z", + "iopub.status.busy": "2024-09-13T15:55:51.637164Z", + "iopub.status.idle": "2024-09-13T15:55:53.170688Z", + "shell.execute_reply": "2024-09-13T15:55:53.170435Z" + } + }, + "outputs": [], + "source": [ + "n_steps = 5\n", + "\n", + "# test points for plotting\n", + "test_x = torch.linspace(*X.vocs.bounds.flatten(), 50).double()\n", + "\n", + "for i in range(n_steps):\n", + " # get the Gaussian process model from the generator\n", + " model = X.generator.train_model()\n", + "\n", + " # visualize model\n", + " fig, ax = X.generator.visualize_model(n_grid=len(test_x))\n", + "\n", + " # plot true function\n", + " true_f = torch.sin(test_x)\n", + " ax[0, 0].plot(test_x, true_f, \"C1--\")\n", + "\n", + " # do the optimization step\n", + " X.step()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-07T20:54:35.973123Z", + "start_time": "2025-01-07T20:54:35.949124200Z" + } + }, + "outputs": [], + "source": [ + "ax[0]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "start_time": "2025-01-07T20:49:47.635533400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:55:53.172228Z", + "iopub.status.busy": "2024-09-13T15:55:53.172059Z", + "iopub.status.idle": "2024-09-13T15:55:53.176984Z", + "shell.execute_reply": "2024-09-13T15:55:53.176754Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "# access the collected data\n", + "X.data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Getting the optimization result\n", + "To get the best point (without evaluating it) we ask the generator to\n", + "predict the optimum based on the posterior mean." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:55:53.178345Z", + "iopub.status.busy": "2024-09-13T15:55:53.178263Z", + "iopub.status.idle": "2024-09-13T15:55:53.203298Z", + "shell.execute_reply": "2024-09-13T15:55:53.203027Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "X.generator.get_optimum()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Customizing optimization\n", + "Each generator has a set of options that can be modified to effect optimization behavior" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:55:53.204877Z", + "iopub.status.busy": "2024-09-13T15:55:53.204764Z", + "iopub.status.idle": "2024-09-13T15:55:53.207688Z", + "shell.execute_reply": "2024-09-13T15:55:53.207436Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "X.generator.dict()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/single_objective_bayes_opt/time_dependent_bo.ipynb b/docs/examples/single_objective_bayes_opt/time_dependent_bo.ipynb new file mode 100644 index 0000000..a7333d2 --- /dev/null +++ b/docs/examples/single_objective_bayes_opt/time_dependent_bo.ipynb @@ -0,0 +1,421 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "# Time dependent Bayesian Optimization\n", + "\n", + "In this example we demonstrate time dependent optimization. In this case we are not\n", + "only interested in finding an optimum point in input space, but also maintain the\n", + "ideal point over time." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# set values if testing\n", + "import os\n", + "import time\n", + "import warnings\n", + "import torch\n", + "from matplotlib import pyplot as plt\n", + "from tqdm import trange\n", + "from xopt.generators.bayesian import TDUpperConfidenceBoundGenerator\n", + "from xopt.vocs import VOCS\n", + "from xopt.evaluator import Evaluator\n", + "from xopt import Xopt\n", + "\n", + "SMOKE_TEST = os.environ.get(\"SMOKE_TEST\")\n", + "N_MC_SAMPLES = 1 if SMOKE_TEST else 128\n", + "NUM_RESTARTS = 1 if SMOKE_TEST else 20\n", + "N_STEPS = 1 if SMOKE_TEST else 250\n", + "warnings.filterwarnings(\"ignore\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "## Time dependent test problem\n", + "Optimization is carried out over a single variable `x`. The test function is a simple\n", + " quadratic, with a minimum location that drifts and changes as a function of time `t`." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "Define test functions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:55:17.650035Z", + "iopub.status.busy": "2024-09-13T15:55:17.649856Z", + "iopub.status.idle": "2024-09-13T15:55:17.659060Z", + "shell.execute_reply": "2024-09-13T15:55:17.658810Z" + } + }, + "outputs": [], + "source": [ + "# location of time dependent minimum\n", + "def k(t_):\n", + " return torch.where(\n", + " t_ < 50, 0.25 * torch.sin(t_ * 6 / 10.0) + 0.1e-2 * t_, -1.5e-2 * (t_ - 50.0)\n", + " )\n", + "\n", + "\n", + "# define function in time and position space\n", + "def g(x_, t_):\n", + " return (x_ - k(t_)) ** 2\n", + "\n", + "\n", + "# create callable function for Xopt\n", + "def f(inputs):\n", + " x_ = inputs[\"x\"]\n", + " current_time = time.time()\n", + " t_ = current_time - start_time\n", + " y_ = g(x_, torch.tensor(t_))\n", + "\n", + " return {\"y\": float(y_), \"time\": float(current_time)}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "## Define Xopt objects including optimization algorithm" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "variables = {\"x\": [-1, 1]}\n", + "objectives = {\"y\": \"MINIMIZE\"}\n", + "\n", + "vocs = VOCS(variables=variables, objectives=objectives)\n", + "\n", + "evaluator = Evaluator(function=f)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "## Run optimization" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:55:17.679779Z", + "iopub.status.busy": "2024-09-13T15:55:17.679658Z", + "iopub.status.idle": "2024-09-13T15:55:40.915698Z", + "shell.execute_reply": "2024-09-13T15:55:40.914547Z" + } + }, + "outputs": [], + "source": [ + "generator = TDUpperConfidenceBoundGenerator(\n", + " vocs=vocs,\n", + " beta=0.01,\n", + " added_time=0.1,\n", + " forgetting_time=10.0,\n", + ")\n", + "generator.n_monte_carlo_samples = N_MC_SAMPLES\n", + "generator.numerical_optimizer.n_restarts = NUM_RESTARTS\n", + "generator.max_travel_distances = [0.1]\n", + "\n", + "start_time = time.time()\n", + "X = Xopt(evaluator=evaluator, generator=generator, vocs=vocs)\n", + "X.random_evaluate(2)\n", + "\n", + "for _ in trange(N_STEPS):\n", + " # note that in this example we can ignore warnings if computation\n", + " # time is greater than added time\n", + " with warnings.catch_warnings():\n", + " warnings.filterwarnings(\"ignore\", category=RuntimeWarning)\n", + " X.step()\n", + " time.sleep(0.1)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "## Visualize GP model of objective function and plot trajectory" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = X.data\n", + "\n", + "xbounds = generator.vocs.bounds\n", + "tbounds = [data[\"time\"].min(), data[\"time\"].max()]\n", + "\n", + "model = X.generator.model\n", + "n = 100\n", + "t = torch.linspace(*tbounds, n, dtype=torch.double)\n", + "x = torch.linspace(*xbounds.flatten(), n, dtype=torch.double)\n", + "tt, xx = torch.meshgrid(t, x)\n", + "pts = torch.hstack([ele.reshape(-1, 1) for ele in (tt, xx)]).double()\n", + "\n", + "tt, xx = tt.numpy(), xx.numpy()\n", + "\n", + "# NOTE: the model inputs are such that t is the last dimension\n", + "gp_pts = torch.flip(pts, dims=[-1])\n", + "\n", + "gt_vals = g(gp_pts.T[0], gp_pts.T[1] - start_time)\n", + "\n", + "with torch.no_grad():\n", + " post = model.posterior(gp_pts)\n", + "\n", + " mean = post.mean\n", + " std = torch.sqrt(post.variance)\n", + "\n", + " fig, ax = plt.subplots()\n", + " ax.set_title(\"model mean\")\n", + " ax.set_xlabel(\"unix time\")\n", + " ax.set_ylabel(\"x\")\n", + " c = ax.pcolor(tt, xx, mean.reshape(n, n), rasterized=True)\n", + " ax.plot(data[\"time\"].to_numpy(), data[\"x\"].to_numpy(), \"oC1\", label=\"samples\")\n", + "\n", + " ax.plot(t, k(t - start_time), \"C3--\", label=\"ideal path\", zorder=10)\n", + " ax.legend()\n", + " fig.colorbar(c)\n", + "\n", + " fig2, ax2 = plt.subplots()\n", + " ax2.set_title(\"model uncertainty\")\n", + " ax2.set_xlabel(\"unix time\")\n", + " ax2.set_ylabel(\"x\")\n", + " c = ax2.pcolor(tt, xx, std.reshape(n, n))\n", + " fig2.colorbar(c)\n", + "\n", + " fig3, ax3 = plt.subplots()\n", + " ax3.set_title(\"ground truth value\")\n", + " ax3.set_xlabel(\"unix time\")\n", + " ax3.set_ylabel(\"x\")\n", + " c = ax3.pcolor(tt, xx, gt_vals.reshape(n, n))\n", + " fig3.colorbar(c)\n", + "\n", + " ax2.plot(data[\"time\"].to_numpy(), data[\"x\"].to_numpy(), \"oC1\")\n", + " ax3.plot(data[\"time\"].to_numpy(), data[\"x\"].to_numpy(), \"oC1\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "## plot the acquisition function" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:55:41.794609Z", + "iopub.status.busy": "2024-09-13T15:55:41.794517Z", + "iopub.status.idle": "2024-09-13T15:55:42.112062Z", + "shell.execute_reply": "2024-09-13T15:55:42.111804Z" + } + }, + "outputs": [], + "source": [ + "# note that target time is only updated during the generate call\n", + "target_time = X.generator.target_prediction_time\n", + "print(target_time - start_time)\n", + "my_acq_func = X.generator.get_acquisition(model)\n", + "\n", + "with torch.no_grad():\n", + " acq_pts = x.unsqueeze(-1).unsqueeze(-1)\n", + " full_acq = my_acq_func.acq_func(gp_pts.unsqueeze(1))\n", + " fixed_acq = my_acq_func(acq_pts)\n", + "\n", + " fig, ax = plt.subplots()\n", + " c = ax.pcolor(tt, xx, full_acq.reshape(n, n))\n", + " ax.set_xlabel(\"unix time\")\n", + " ax.set_ylabel(\"x\")\n", + " ax.set_title(\"acquisition function\")\n", + " fig.colorbar(c)\n", + "\n", + " fi2, ax2 = plt.subplots()\n", + " ax2.plot(x.flatten(), fixed_acq.flatten())\n", + " ax2.set_xlabel(\"x\")\n", + " ax2.set_ylabel(\"acquisition function\")\n", + " ax2.set_title(\"acquisition function at last time step\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false + }, + "source": [ + "## Run Time Dependent BO with Model Caching\n", + "Instead of retraining the GP model hyperparameters at every step, we can instead hold\n", + "on to previously determined model parameters by setting\n", + "`use_catched_hyperparameters=True` in the model constructor. This reduces the time\n", + "needed to make decisions, leading to faster feedback when addressing time-critical\n", + "optimization tasks. However, this can come at the cost of model accuracy when the\n", + "target function changes behavior (change in lengthscale for example)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "generator = TDUpperConfidenceBoundGenerator(\n", + " vocs=vocs,\n", + " beta=0.01,\n", + " added_time=0.1,\n", + " forgetting_time=20.0,\n", + ")\n", + "generator.n_monte_carlo_samples = N_MC_SAMPLES\n", + "generator.numerical_optimizer.n_restarts = NUM_RESTARTS\n", + "generator.max_travel_distances = [0.1]\n", + "\n", + "start_time = time.time()\n", + "\n", + "X = Xopt(evaluator=evaluator, generator=generator, vocs=vocs)\n", + "X.random_evaluate(2)\n", + "\n", + "for i in trange(N_STEPS):\n", + " # note that in this example we can ignore warnings if computation time is greater\n", + " # than added time\n", + " if i == 50:\n", + " X.generator.gp_constructor.use_cached_hyperparameters = True\n", + "\n", + " with warnings.catch_warnings():\n", + " warnings.filterwarnings(\"ignore\", category=RuntimeWarning)\n", + " X.step()\n", + " time.sleep(0.1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# plot total computation time\n", + "ax = X.generator.computation_time.sum(axis=1).plot()\n", + "ax.set_xlabel(\"Iteration\")\n", + "ax.set_ylabel(\"total BO computation time (s)\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = X.data\n", + "\n", + "xbounds = generator.vocs.bounds\n", + "tbounds = [data[\"time\"].min(), data[\"time\"].max()]\n", + "\n", + "model = X.generator.model\n", + "n = 100\n", + "t = torch.linspace(*tbounds, n, dtype=torch.double)\n", + "x = torch.linspace(*xbounds.flatten(), n, dtype=torch.double)\n", + "tt, xx = torch.meshgrid(t, x)\n", + "pts = torch.hstack([ele.reshape(-1, 1) for ele in (tt, xx)]).double()\n", + "\n", + "tt, xx = tt.numpy(), xx.numpy()\n", + "\n", + "# NOTE: the model inputs are such that t is the last dimension\n", + "gp_pts = torch.flip(pts, dims=[-1])\n", + "\n", + "gt_vals = g(gp_pts.T[0], gp_pts.T[1] - start_time)\n", + "\n", + "with torch.no_grad():\n", + " post = model.posterior(gp_pts)\n", + "\n", + " mean = post.mean\n", + " std = torch.sqrt(post.variance)\n", + "\n", + " fig, ax = plt.subplots()\n", + " ax.set_title(\"model mean\")\n", + " ax.set_xlabel(\"unix time\")\n", + " ax.set_ylabel(\"x\")\n", + " c = ax.pcolor(tt, xx, mean.reshape(n, n))\n", + " ax.plot(data[\"time\"].to_numpy(), data[\"x\"].to_numpy(), \"oC1\", label=\"samples\")\n", + "\n", + " ax.plot(t, k(t - start_time), \"C3--\", label=\"ideal path\", zorder=10)\n", + " ax.legend()\n", + " fig.colorbar(c)\n", + "\n", + " fig2, ax2 = plt.subplots()\n", + " ax2.set_title(\"model uncertainty\")\n", + " ax2.set_xlabel(\"unix time\")\n", + " ax2.set_ylabel(\"x\")\n", + " c = ax2.pcolor(tt, xx, std.reshape(n, n))\n", + " fig2.colorbar(c)\n", + "\n", + " fig3, ax3 = plt.subplots()\n", + " ax3.set_title(\"ground truth value\")\n", + " ax3.set_xlabel(\"unix time\")\n", + " ax3.set_ylabel(\"x\")\n", + " c = ax3.pcolor(tt, xx, gt_vals.reshape(n, n))\n", + " fig3.colorbar(c)\n", + "\n", + " ax2.plot(data[\"time\"].to_numpy(), data[\"x\"].to_numpy(), \"oC1\")\n", + " ax3.plot(data[\"time\"].to_numpy(), data[\"x\"].to_numpy(), \"oC1\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/single_objective_bayes_opt/turbo_tutorial.ipynb b/docs/examples/single_objective_bayes_opt/turbo_tutorial.ipynb new file mode 100644 index 0000000..ba9feb6 --- /dev/null +++ b/docs/examples/single_objective_bayes_opt/turbo_tutorial.ipynb @@ -0,0 +1,424 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "# TuRBO Bayesian Optimization\n", + "In this tutorial we demonstrate the use of Xopt to preform Trust Region Bayesian\n", + "Optimization (TuRBO) on a simple test problem. During optimization of high\n", + "dimensional input spaces off the shelf BO tends to over-emphasize exploration which\n", + "severely degrades optimization performance. TuRBO attempts to prevent this by\n", + "maintaining a surrogate model over a local (trust) region centered on the best\n", + "observation so far and restricting optimization inside that local region. The trust\n", + "region is expanded and contracted based on the number of `successful` (observations\n", + "that improve over the best observed point) or `unsuccessful` (no improvement)\n", + "observations in a row. See https://botorch.org/tutorials/turbo_1 for details." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Define the test problem\n", + "Here we define a simple optimization problem, where we attempt to minimize a\n", + "function in the domian [0,2*pi]. Note that the function used to evaluate the\n", + "objective function takes a dictionary as input and returns a dictionary as the output." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-08-07T01:59:27.474306300Z", + "start_time": "2024-08-07T01:59:25.101273800Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:03.477703Z", + "iopub.status.busy": "2024-09-13T15:56:03.477389Z", + "iopub.status.idle": "2024-09-13T15:56:05.180187Z", + "shell.execute_reply": "2024-09-13T15:56:05.179805Z" + } + }, + "outputs": [], + "source": [ + "from xopt.evaluator import Evaluator\n", + "from xopt.generators.bayesian import UpperConfidenceBoundGenerator\n", + "from xopt import Xopt\n", + "from xopt.vocs import VOCS\n", + "import math\n", + "import numpy as np\n", + "import pandas as pd\n", + "import torch\n", + "import matplotlib.pyplot as plt\n", + "\n", + "# define variables and function objectives\n", + "vocs = VOCS(\n", + " variables={\"x\": [0, 2 * math.pi]},\n", + " objectives={\"f\": \"MINIMIZE\"},\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-08-07T01:59:27.502331400Z", + "start_time": "2024-08-07T01:59:27.476306700Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:05.182105Z", + "iopub.status.busy": "2024-09-13T15:56:05.181910Z", + "iopub.status.idle": "2024-09-13T15:56:05.183934Z", + "shell.execute_reply": "2024-09-13T15:56:05.183678Z" + } + }, + "outputs": [], + "source": [ + "# define a test function to optimize\n", + "def sin_function(input_dict):\n", + " x = input_dict[\"x\"]\n", + " return {\"f\": -10 * np.exp(-((x - np.pi) ** 2) / 0.01) + 0.5 * np.sin(5 * x)}" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Create Xopt objects\n", + "Create the evaluator to evaluate our test function and create a generator that uses\n", + "the Upper Confidence Bound acquisition function to perform Bayesian Optimization." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-08-07T01:59:28.802460800Z", + "start_time": "2024-08-07T01:59:27.502331400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:05.185338Z", + "iopub.status.busy": "2024-09-13T15:56:05.185241Z", + "iopub.status.idle": "2024-09-13T15:56:05.189688Z", + "shell.execute_reply": "2024-09-13T15:56:05.189466Z" + } + }, + "outputs": [], + "source": [ + "evaluator = Evaluator(function=sin_function)\n", + "generator = UpperConfidenceBoundGenerator(vocs=vocs, turbo_controller=\"optimize\")\n", + "X = Xopt(evaluator=evaluator, generator=generator, vocs=vocs)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-08-07T01:59:28.814460100Z", + "start_time": "2024-08-07T01:59:28.803460700Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:05.191014Z", + "iopub.status.busy": "2024-09-13T15:56:05.190910Z", + "iopub.status.idle": "2024-09-13T15:56:05.196944Z", + "shell.execute_reply": "2024-09-13T15:56:05.196575Z" + } + }, + "outputs": [], + "source": [ + "X" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Generate and evaluate initial points\n", + "To begin optimization, we must generate some random initial data points. The first call\n", + "to `X.step()` will generate and evaluate a number of randomly points specified by the\n", + " generator. Note that if we add data to xopt before calling `X.step()` by assigning\n", + " the data to `X.data`, calls to `X.step()` will ignore the random generation and\n", + " proceed to generating points via Bayesian optimization." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-08-07T01:59:28.883458900Z", + "start_time": "2024-08-07T01:59:28.816461400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:05.219975Z", + "iopub.status.busy": "2024-09-13T15:56:05.219805Z", + "iopub.status.idle": "2024-09-13T15:56:05.227477Z", + "shell.execute_reply": "2024-09-13T15:56:05.227127Z" + } + }, + "outputs": [], + "source": [ + "X.evaluate_data(pd.DataFrame({\"x\": [3.0, 1.75, 2.0]}))\n", + "\n", + "# inspect the gathered data\n", + "X.data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-08-07T01:59:29.053969300Z", + "start_time": "2024-08-07T01:59:28.844461500Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:05.229360Z", + "iopub.status.busy": "2024-09-13T15:56:05.229229Z", + "iopub.status.idle": "2024-09-13T15:56:05.437052Z", + "shell.execute_reply": "2024-09-13T15:56:05.436790Z" + } + }, + "outputs": [], + "source": [ + "# determine trust region from gathered data\n", + "X.generator.train_model()\n", + "X.generator.turbo_controller.update_state(X.generator)\n", + "X.generator.turbo_controller.get_trust_region(X.generator)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Define plotting utility" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-08-07T01:59:29.069971400Z", + "start_time": "2024-08-07T01:59:29.059970500Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:05.438704Z", + "iopub.status.busy": "2024-09-13T15:56:05.438549Z", + "iopub.status.idle": "2024-09-13T15:56:05.442827Z", + "shell.execute_reply": "2024-09-13T15:56:05.442581Z" + } + }, + "outputs": [], + "source": [ + "def plot_turbo(X):\n", + " # get the Gaussian process model from the generator\n", + " model = X.generator.train_model()\n", + "\n", + " # get trust region\n", + " trust_region = X.generator.turbo_controller.get_trust_region(generator).squeeze()\n", + " scale_factor = X.generator.turbo_controller.length\n", + " region_width = trust_region[1] - trust_region[0]\n", + " best_value = X.generator.turbo_controller.best_value\n", + "\n", + " # get number of successes and failures\n", + " n_successes = X.generator.turbo_controller.success_counter\n", + " n_failures = X.generator.turbo_controller.failure_counter\n", + "\n", + " # get acquisition function from generator\n", + " acq = X.generator.get_acquisition(model)\n", + "\n", + " # calculate model posterior and acquisition function at each test point\n", + " # NOTE: need to add a dimension to the input tensor for evaluating the\n", + " # posterior and another for the acquisition function, see\n", + " # https://botorch.org/docs/batching for details\n", + " # NOTE: we use the `torch.no_grad()` environment to speed up computation by\n", + " # skipping calculations for backpropagation\n", + " with torch.no_grad():\n", + " posterior = model.posterior(test_x.unsqueeze(1))\n", + " acq_val = acq(test_x.reshape(-1, 1, 1))\n", + "\n", + " # get mean function and confidence regions\n", + " mean = posterior.mean\n", + " L, u = posterior.mvn.confidence_region()\n", + "\n", + " # plot model and acquisition function\n", + " fig, ax = plt.subplots(2, 1, sharex=\"all\")\n", + "\n", + " # add title for successes and failures\n", + " ax[0].set_title(\n", + " f\"n_successes: {n_successes}, n_failures: {n_failures}, \"\n", + " f\"scale_factor: {scale_factor}, region_width: {region_width:.2}, \"\n", + " f\"best_value: {best_value:.4}\"\n", + " )\n", + "\n", + " # plot model posterior\n", + " ax[0].plot(test_x, mean, label=\"Posterior mean\")\n", + " ax[0].fill_between(test_x, L, u, alpha=0.25, label=\"Confidence region\")\n", + "\n", + " # add data to model plot\n", + " ax[0].plot(X.data[\"x\"], X.data[\"f\"], \"C1o\", label=\"Training data\")\n", + "\n", + " # plot true function\n", + " true_f = sin_function({\"x\": test_x})[\"f\"]\n", + " ax[0].plot(test_x, true_f, \"--\", label=\"Ground truth\")\n", + "\n", + " # plot acquisition function\n", + " ax[1].plot(test_x, acq_val.flatten())\n", + "\n", + " ax[0].set_ylabel(\"f\")\n", + " ax[0].set_ylim(-12, 10)\n", + " ax[1].set_ylabel(r\"$\\alpha(x)$\")\n", + " ax[1].set_xlabel(\"x\")\n", + "\n", + " # plot trust region\n", + " for a in ax:\n", + " a.axvline(trust_region[0], c=\"r\", label=\"Trust region boundary\")\n", + " a.axvline(trust_region[1], c=\"r\")\n", + "\n", + " # add legend\n", + " ax[0].legend(fontsize=\"x-small\")\n", + "\n", + " fig.tight_layout()\n", + "\n", + " return fig, ax" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Do bayesian optimization steps\n", + "Notice that when the number of successive successes or failures reaches 2 the trust\n", + "region expands or contracts and counters are reset to zero. Counters are also reset\n", + "to zero during alternate successes/failures. Finally, the model is most accurate\n", + "inside the trust region, which supports our goal of local optimization." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-08-07T01:59:40.084279200Z", + "start_time": "2024-08-07T01:59:29.069971400Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:05.444248Z", + "iopub.status.busy": "2024-09-13T15:56:05.444168Z", + "iopub.status.idle": "2024-09-13T15:56:09.703297Z", + "shell.execute_reply": "2024-09-13T15:56:09.703018Z" + } + }, + "outputs": [], + "source": [ + "# test points for plotting\n", + "test_x = torch.linspace(*X.vocs.bounds.flatten(), 500).double()\n", + "\n", + "for i in range(15):\n", + " # plot trust region analysis\n", + " fig, ax = plot_turbo(X)\n", + "\n", + " # take optimization state\n", + " X.step()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-08-07T01:59:40.126280300Z", + "start_time": "2024-08-07T01:59:40.084279200Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:09.704882Z", + "iopub.status.busy": "2024-09-13T15:56:09.704770Z", + "iopub.status.idle": "2024-09-13T15:56:09.706953Z", + "shell.execute_reply": "2024-09-13T15:56:09.706701Z" + } + }, + "outputs": [], + "source": [ + "# access the collected data\n", + "X.generator.turbo_controller" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "ExecuteTime": { + "end_time": "2024-08-07T01:59:40.140291900Z", + "start_time": "2024-08-07T01:59:40.100281300Z" + }, + "execution": { + "iopub.execute_input": "2024-09-13T15:56:09.708312Z", + "iopub.status.busy": "2024-09-13T15:56:09.708214Z", + "iopub.status.idle": "2024-09-13T15:56:09.712062Z", + "shell.execute_reply": "2024-09-13T15:56:09.711826Z" + } + }, + "outputs": [], + "source": [ + "X.data" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/examples/single_objective_bayes_opt/upper_confidence_bound.ipynb b/docs/examples/single_objective_bayes_opt/upper_confidence_bound.ipynb new file mode 100644 index 0000000..3855cde --- /dev/null +++ b/docs/examples/single_objective_bayes_opt/upper_confidence_bound.ipynb @@ -0,0 +1,194 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Upper Confidence Bound BO\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:56:17.411738Z", + "iopub.status.busy": "2024-09-13T15:56:17.411199Z", + "iopub.status.idle": "2024-09-13T15:56:18.380269Z", + "shell.execute_reply": "2024-09-13T15:56:18.379962Z" + } + }, + "outputs": [], + "source": [ + "from xopt import Xopt\n", + "\n", + "# Ignore all warnings\n", + "import warnings\n", + "\n", + "warnings.filterwarnings(\"ignore\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `Xopt` object can be instantiated from a JSON or YAML file, or a dict, with the proper structure.\n", + "\n", + "Here we will make one" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:56:18.382048Z", + "iopub.status.busy": "2024-09-13T15:56:18.381940Z", + "iopub.status.idle": "2024-09-13T15:56:18.383540Z", + "shell.execute_reply": "2024-09-13T15:56:18.383336Z" + } + }, + "outputs": [], + "source": [ + "# Make a proper input file.\n", + "YAML = \"\"\"\n", + "generator:\n", + " name: upper_confidence_bound\n", + " beta: 0.1\n", + "\n", + "evaluator:\n", + " function: xopt.resources.test_functions.sinusoid_1d.evaluate_sinusoid\n", + "\n", + "vocs:\n", + " variables:\n", + " x1: [0, 6.28]\n", + " objectives:\n", + " y1: 'MINIMIZE'\n", + "\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:56:18.384797Z", + "iopub.status.busy": "2024-09-13T15:56:18.384722Z", + "iopub.status.idle": "2024-09-13T15:56:18.965528Z", + "shell.execute_reply": "2024-09-13T15:56:18.965274Z" + } + }, + "outputs": [], + "source": [ + "X = Xopt.from_yaml(YAML)\n", + "X" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Run Optimization" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:56:18.987017Z", + "iopub.status.busy": "2024-09-13T15:56:18.986820Z", + "iopub.status.idle": "2024-09-13T15:56:19.494115Z", + "shell.execute_reply": "2024-09-13T15:56:19.493854Z" + } + }, + "outputs": [], + "source": [ + "X.random_evaluate(3)\n", + "for i in range(5):\n", + " print(i)\n", + " X.step()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## View output data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:56:19.495618Z", + "iopub.status.busy": "2024-09-13T15:56:19.495487Z", + "iopub.status.idle": "2024-09-13T15:56:19.500405Z", + "shell.execute_reply": "2024-09-13T15:56:19.500188Z" + }, + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "X.data" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "## Visualize model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "execution": { + "iopub.execute_input": "2024-09-13T15:56:19.501788Z", + "iopub.status.busy": "2024-09-13T15:56:19.501679Z", + "iopub.status.idle": "2024-09-13T15:56:19.941144Z", + "shell.execute_reply": "2024-09-13T15:56:19.940870Z" + } + }, + "outputs": [], + "source": [ + "fig, ax = X.generator.visualize_model(n_grid=100)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..03bfd67 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,188 @@ +
+